2019-09-11 09:55:43 +02:00
# include "update_manager.h"
2020-02-22 20:42:49 +01:00
# include "progress_dialog.h"
2020-03-22 16:15:14 +01:00
# include "localized.h"
2019-09-11 09:55:43 +02:00
# include "rpcs3_version.h"
2020-07-01 20:48:19 +02:00
# include "downloader.h"
2023-04-18 19:25:55 +02:00
# include "gui_settings.h"
2020-12-22 09:42:57 +01:00
# include "Utilities/File.h"
2019-09-11 09:55:43 +02:00
# include "Emu/System.h"
2022-06-06 22:06:42 +02:00
# include "Crypto/utils.h"
2020-12-22 09:42:57 +01:00
# include "util/logs.hpp"
2024-09-19 21:58:00 +02:00
# include "util/types.hpp"
2025-02-03 02:19:10 +01:00
# include "util/sysinfo.hpp"
2019-09-11 09:55:43 +02:00
2020-03-21 22:17:15 +01:00
# include <QApplication>
2024-12-22 15:35:16 +01:00
# include <QCheckBox>
2020-03-22 11:11:42 +01:00
# include <QDateTime>
2019-09-11 09:55:43 +02:00
# include <QMessageBox>
2021-09-16 05:34:06 +02:00
# include <QLabel>
# include <QJsonArray>
2020-03-13 18:34:08 +01:00
# include <QJsonObject>
# include <QJsonDocument>
# include <QThread>
2024-01-23 08:36:49 +01:00
# if defined(_WIN32) || defined(__APPLE__)
# include <7z.h>
# include <7zAlloc.h>
# include <7zCrc.h>
# include <7zFile.h>
# endif
2019-09-11 09:55:43 +02:00
# if defined(_WIN32)
2020-12-22 09:42:57 +01:00
# ifndef NOMINMAX
2020-03-13 18:34:08 +01:00
# define NOMINMAX
2020-12-22 09:42:57 +01:00
# endif
2019-09-11 09:55:43 +02:00
# include <windows.h>
# include <CpuArch.h>
2023-07-11 20:40:30 +02:00
# ifndef PATH_MAX
2019-09-11 09:55:43 +02:00
# define PATH_MAX MAX_PATH
2023-07-11 20:40:30 +02:00
# endif
2019-09-11 09:55:43 +02:00
2025-02-11 03:00:37 +01:00
# include "Utilities/StrUtil.h"
2019-09-11 09:55:43 +02:00
# else
# include <unistd.h>
# include <sys/stat.h>
# endif
2020-02-22 20:42:49 +01:00
LOG_CHANNEL ( update_log , " UPDATER " ) ;
2020-02-01 05:15:50 +01:00
2023-04-18 19:25:55 +02:00
update_manager : : update_manager ( QObject * parent , std : : shared_ptr < gui_settings > gui_settings )
: QObject ( parent ) , m_gui_settings ( std : : move ( gui_settings ) )
{
}
2021-07-22 23:38:26 +02:00
void update_manager : : check_for_updates ( bool automatic , bool check_only , bool auto_accept , QWidget * parent )
2019-09-11 09:55:43 +02:00
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Checking for updates: automatic=%d, check_only=%d, auto_accept=%d " , automatic , check_only , auto_accept ) ;
2020-07-02 23:29:26 +02:00
m_update_message . clear ( ) ;
2021-09-16 05:34:06 +02:00
m_changelog . clear ( ) ;
2020-07-02 23:29:26 +02:00
2024-02-12 17:30:40 +01:00
if ( automatic )
2019-11-04 17:08:27 +01:00
{
2024-02-12 17:30:40 +01:00
// Don't check for updates on local builds
if ( rpcs3 : : is_local_build ( ) )
2024-09-20 14:14:06 +02:00
{
update_log . notice ( " Skipped automatic update check: this is a local build " ) ;
2024-02-12 17:30:40 +01:00
return ;
2024-09-20 14:14:06 +02:00
}
2024-02-12 17:30:40 +01:00
# ifdef __linux__
2019-11-04 17:08:27 +01:00
// Don't check for updates on startup if RPCS3 is not running from an AppImage.
2024-02-12 17:30:40 +01:00
if ( ! : : getenv ( " APPIMAGE " ) )
2024-09-20 14:14:06 +02:00
{
update_log . notice ( " Skipped automatic update check: this is not an AppImage " ) ;
2024-02-12 17:30:40 +01:00
return ;
2024-09-20 14:14:06 +02:00
}
2019-11-04 17:08:27 +01:00
# endif
2024-02-12 17:30:40 +01:00
}
2019-11-04 17:08:27 +01:00
2020-07-01 20:48:19 +02:00
m_parent = parent ;
2020-09-06 11:47:45 +02:00
m_downloader = new downloader ( parent ) ;
2019-09-11 09:55:43 +02:00
2020-07-01 20:48:19 +02:00
connect ( m_downloader , & downloader : : signal_download_error , this , [ this , automatic ] ( const QString & /*error*/ )
2019-09-11 09:55:43 +02:00
{
2020-07-01 20:48:19 +02:00
if ( ! automatic )
2020-03-13 18:34:08 +01:00
{
2020-07-01 20:48:19 +02:00
QMessageBox : : warning ( m_parent , tr ( " Auto-updater " ) , tr ( " An error occurred during the auto-updating process. \n Check the log for more information. " ) ) ;
2020-03-13 18:34:08 +01:00
}
2020-03-21 22:17:15 +01:00
} ) ;
2020-07-01 20:48:19 +02:00
2021-07-22 23:38:26 +02:00
connect ( m_downloader , & downloader : : signal_download_finished , this , [ this , automatic , check_only , auto_accept ] ( const QByteArray & data )
2020-03-21 22:17:15 +01:00
{
2021-07-22 23:38:26 +02:00
const bool result_json = handle_json ( automatic , check_only , auto_accept , data ) ;
2019-09-11 09:55:43 +02:00
2020-07-01 20:48:19 +02:00
if ( ! result_json )
2020-03-13 18:34:08 +01:00
{
2020-07-01 20:48:19 +02:00
// The progress dialog is configured to stay open, so we need to close it manually if the download succeeds.
m_downloader - > close_progress_dialog ( ) ;
2020-03-21 22:17:15 +01:00
if ( ! automatic )
{
QMessageBox : : warning ( m_parent , tr ( " Auto-updater " ) , tr ( " An error occurred during the auto-updating process. \n Check the log for more information. " ) ) ;
}
2020-03-13 18:34:08 +01:00
}
2020-07-01 20:48:19 +02:00
2020-07-02 23:29:26 +02:00
Q_EMIT signal_update_available ( result_json & & ! m_update_message . isEmpty ( ) ) ;
2020-03-21 22:17:15 +01:00
} ) ;
2020-07-01 20:48:19 +02:00
2025-02-03 02:19:10 +01:00
const utils : : OS_version os = utils : : get_OS_version ( ) ;
const std : : string url = fmt : : format ( " https://update.rpcs3.net/?api=v3&c=%s&os_type=%s&os_arch=%s&os_version=%i.%i.%i " ,
rpcs3 : : get_commit_and_hash ( ) . second , os . type , os . arch , os . version_major , os . version_minor , os . version_patch ) ;
2020-07-01 20:48:19 +02:00
m_downloader - > start ( url , true , ! automatic , tr ( " Checking For Updates " ) , true ) ;
2019-09-11 09:55:43 +02:00
}
2021-07-22 23:38:26 +02:00
bool update_manager : : handle_json ( bool automatic , bool check_only , bool auto_accept , const QByteArray & data )
2019-09-11 09:55:43 +02:00
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Download of update info finished. automatic=%d, check_only=%d, auto_accept=%d " , automatic , check_only , auto_accept ) ;
2020-07-01 20:48:19 +02:00
const QJsonObject json_data = QJsonDocument : : fromJson ( data ) . object ( ) ;
2020-03-21 22:17:15 +01:00
const int return_code = json_data [ " return_code " ] . toInt ( - 255 ) ;
2019-09-11 09:55:43 +02:00
bool hash_found = true ;
if ( return_code < 0 )
{
std : : string error_message ;
switch ( return_code )
{
case - 1 : error_message = " Hash not found(Custom/PR build) " ; break ;
case - 2 : error_message = " Server Error - Maintenance Mode " ; break ;
case - 3 : error_message = " Server Error - Illegal Search " ; break ;
case - 255 : error_message = " Server Error - Return code not found " ; break ;
default : error_message = " Server Error - Unknown Error " ; break ;
}
if ( return_code ! = - 1 )
2020-03-02 07:53:30 +01:00
update_log . error ( " Update error: %s return code: %d " , error_message , return_code ) ;
2019-09-11 09:55:43 +02:00
else
2020-03-02 07:53:30 +01:00
update_log . warning ( " Update error: %s return code: %d " , error_message , return_code ) ;
2019-09-11 09:55:43 +02:00
// If a user clicks "Check for Updates" with a custom build ask him if he's sure he wants to update to latest version
if ( ! automatic & & return_code = = - 1 )
{
hash_found = false ;
}
else
{
return false ;
}
}
2020-03-22 15:09:37 +01:00
const auto & current = json_data [ " current_build " ] ;
2019-09-11 09:55:43 +02:00
const auto & latest = json_data [ " latest_build " ] ;
2020-03-22 15:09:37 +01:00
2019-09-11 09:55:43 +02:00
if ( ! latest . isObject ( ) )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " JSON doesn't contain latest_build section " ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
QString os ;
# ifdef _WIN32
os = " windows " ;
# elif defined(__linux__)
os = " linux " ;
2024-01-23 08:36:49 +01:00
# elif defined(__APPLE__)
os = " mac " ;
2019-09-11 09:55:43 +02:00
# else
2020-03-02 07:53:30 +01:00
update_log . error ( " Your OS isn't currently supported by the auto-updater " ) ;
2019-09-11 09:55:43 +02:00
return false ;
# endif
// Check that every bit of info we need is there
2025-02-03 02:19:10 +01:00
const auto check_json = [ ] ( bool cond , std : : string_view msg ) - > bool
{
if ( cond ) return true ;
update_log . error ( " %s " , msg ) ;
return false ;
} ;
if ( ! ( check_json ( latest [ os ] . isObject ( ) , fmt : : format ( " Node 'latest_build: %s' not found " , os ) ) & &
check_json ( latest [ os ] [ " download " ] . isString ( ) , fmt : : format ( " Node 'latest_build: %s: download' not found or not a string " , os ) ) & &
check_json ( latest [ os ] [ " size " ] . isDouble ( ) , fmt : : format ( " Node 'latest_build: %s: size' not found or not a double " , os ) ) & &
check_json ( latest [ os ] [ " checksum " ] . isString ( ) , fmt : : format ( " Node 'latest_build: %s: checksum' not found or not a string " , os ) ) & &
check_json ( latest [ " version " ] . isString ( ) , " Node 'latest_build: version' not found or not a string " ) & &
check_json ( latest [ " datetime " ] . isString ( ) , " Node 'latest_build: datetime' not found or not a string " )
) | |
( hash_found & & ! (
check_json ( current . isObject ( ) , " JSON doesn't contain current_build section " ) & &
check_json ( current [ " version " ] . isString ( ) , " Node 'current_build: datetime' not found or not a string " ) & &
check_json ( current [ " datetime " ] . isString ( ) , " Node 'current_build: version' not found or not a string " )
) ) )
2019-09-11 09:55:43 +02:00
{
return false ;
}
if ( hash_found & & return_code = = 0 )
{
2020-03-02 07:53:30 +01:00
update_log . success ( " RPCS3 is up to date! " ) ;
2020-07-01 20:48:19 +02:00
m_downloader - > close_progress_dialog ( ) ;
2019-09-11 09:55:43 +02:00
if ( ! automatic )
QMessageBox : : information ( m_parent , tr ( " Auto-updater " ) , tr ( " Your version is already up to date! " ) ) ;
return true ;
}
2020-03-22 11:11:42 +01:00
// Calculate how old the build is
const QString date_fmt = QStringLiteral ( " yyyy-MM-dd hh:mm:ss " ) ;
2019-09-11 09:55:43 +02:00
2020-03-22 15:09:37 +01:00
const QDateTime cur_date = hash_found ? QDateTime : : fromString ( current [ " datetime " ] . toString ( ) , date_fmt ) : QDateTime : : currentDateTimeUtc ( ) ;
2020-03-22 11:11:42 +01:00
const QDateTime lts_date = QDateTime : : fromString ( latest [ " datetime " ] . toString ( ) , date_fmt ) ;
2019-09-11 09:55:43 +02:00
2020-03-22 14:58:00 +01:00
const QString cur_str = cur_date . toString ( date_fmt ) ;
const QString lts_str = lts_date . toString ( date_fmt ) ;
2020-03-22 16:15:14 +01:00
const qint64 diff_msec = cur_date . msecsTo ( lts_date ) ;
2019-09-11 09:55:43 +02:00
2023-06-12 03:45:37 +02:00
update_log . notice ( " Current: %s, latest: %s, difference: %lld ms " , cur_str , lts_str , diff_msec ) ;
2020-03-22 16:15:14 +01:00
2021-04-07 23:05:18 +02:00
const Localized localized ;
2019-09-11 09:55:43 +02:00
2024-12-22 15:35:16 +01:00
const QString new_version = latest [ " version " ] . toString ( ) ;
m_new_version = new_version . toStdString ( ) ;
2024-04-04 02:32:44 +02:00
const QString support_message = tr ( " <br>You can empower our project at <a href= \" https://rpcs3.net/patreon \" >RPCS3 Patreon</a>.<br> " ) ;
2021-10-05 22:52:31 +02:00
2020-03-22 11:11:42 +01:00
if ( hash_found )
{
2024-12-22 15:35:16 +01:00
const QString old_version = current [ " version " ] . toString ( ) ;
m_old_version = old_version . toStdString ( ) ;
2021-10-05 22:52:31 +02:00
2020-10-26 23:50:47 +01:00
if ( diff_msec < 0 )
{
// This usually means that the current version was marked as broken and won't be shipped anymore, so we need to downgrade to avoid certain bugs.
2024-04-02 19:09:05 +02:00
m_update_message = tr ( " A better version of RPCS3 is available!<br><br>Current version: %0 (%1)<br>Better version: %2 (%3)<br>%4<br>Do you want to update? " )
2024-12-22 15:35:16 +01:00
. arg ( old_version )
2020-10-26 23:50:47 +01:00
. arg ( cur_str )
2024-12-22 15:35:16 +01:00
. arg ( new_version )
2024-04-04 02:32:44 +02:00
. arg ( lts_str )
. arg ( support_message ) ;
2020-10-26 23:50:47 +01:00
}
else
{
2024-04-04 02:32:44 +02:00
m_update_message = tr ( " A new version of RPCS3 is available!<br><br>Current version: %0 (%1)<br>Latest version: %2 (%3)<br>Your version is %4 behind.<br>%5<br>Do you want to update? " )
2024-12-22 15:35:16 +01:00
. arg ( old_version )
2020-10-26 23:50:47 +01:00
. arg ( cur_str )
2024-12-22 15:35:16 +01:00
. arg ( new_version )
2020-10-26 23:50:47 +01:00
. arg ( lts_str )
2024-04-04 02:32:44 +02:00
. arg ( localized . GetVerboseTimeByMs ( diff_msec , true ) )
. arg ( support_message ) ;
2020-10-26 23:50:47 +01:00
}
2020-03-22 11:11:42 +01:00
}
else
{
2021-10-05 22:52:31 +02:00
m_old_version = fmt : : format ( " %s-%s-%s " , rpcs3 : : get_full_branch ( ) , rpcs3 : : get_branch ( ) , rpcs3 : : get_version ( ) . to_string ( ) ) ;
2024-04-02 19:09:05 +02:00
m_update_message = tr ( " You're currently using a custom or PR build.<br><br>Latest version: %0 (%1)<br>The latest version is %2 old.<br>%3<br>Do you want to update to the latest official RPCS3 version? " )
2024-12-22 15:35:16 +01:00
. arg ( new_version )
2020-03-22 14:58:00 +01:00
. arg ( lts_str )
2024-04-04 02:32:44 +02:00
. arg ( localized . GetVerboseTimeByMs ( std : : abs ( diff_msec ) , true ) )
. arg ( support_message ) ;
2020-03-22 11:11:42 +01:00
}
2019-09-11 09:55:43 +02:00
2020-07-01 20:48:19 +02:00
m_request_url = latest [ os ] [ " download " ] . toString ( ) . toStdString ( ) ;
2019-09-11 09:55:43 +02:00
m_expected_hash = latest [ os ] [ " checksum " ] . toString ( ) . toStdString ( ) ;
m_expected_size = latest [ os ] [ " size " ] . toInt ( ) ;
2021-02-05 21:53:48 +01:00
if ( ! m_request_url . starts_with ( " https://github.com/RPCS3/rpcs3 " ) )
{
update_log . fatal ( " Bad url: %s " , m_request_url ) ;
return false ;
}
2021-03-06 15:23:26 +01:00
update_log . notice ( " Update found: %s " , m_request_url ) ;
2021-09-16 05:34:06 +02:00
if ( ! auto_accept )
{
2024-12-22 15:35:16 +01:00
if ( automatic & & m_gui_settings - > GetValue ( gui : : ib_skip_version ) . toString ( ) = = new_version )
{
update_log . notice ( " Skipping automatic update notification for version '%s' due to user preference " , new_version ) ;
m_downloader - > close_progress_dialog ( ) ;
return true ;
}
2021-09-16 05:34:06 +02:00
const auto & changelog = json_data [ " changelog " ] ;
if ( changelog . isArray ( ) )
{
for ( const QJsonValue & changelog_entry : changelog . toArray ( ) )
{
if ( changelog_entry . isObject ( ) )
{
changelog_data entry ;
if ( QJsonValue version = changelog_entry [ " version " ] ; version . isString ( ) )
{
entry . version = version . toString ( ) ;
}
else
{
entry . version = tr ( " N/A " ) ;
update_log . notice ( " JSON changelog entry does not contain a version string. " ) ;
}
if ( QJsonValue title = changelog_entry [ " title " ] ; title . isString ( ) )
{
entry . title = title . toString ( ) ;
}
else
{
entry . title = tr ( " N/A " ) ;
update_log . notice ( " JSON changelog entry does not contain a title string. " ) ;
}
m_changelog . push_back ( entry ) ;
}
else
{
update_log . error ( " JSON changelog entry is not an object. " ) ;
}
}
}
else if ( changelog . isObject ( ) )
{
update_log . error ( " JSON changelog is not an array. " ) ;
}
else
{
update_log . notice ( " JSON does not contain a changelog section. " ) ;
}
}
2022-06-12 09:00:30 +02:00
if ( check_only )
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Update postponed. Check only is active " ) ;
2022-06-12 09:00:30 +02:00
m_downloader - > close_progress_dialog ( ) ;
return true ;
}
2021-07-22 23:38:26 +02:00
update ( auto_accept ) ;
2020-07-01 20:48:19 +02:00
return true ;
}
2019-09-11 09:55:43 +02:00
2021-07-22 23:38:26 +02:00
void update_manager : : update ( bool auto_accept )
2020-07-01 20:48:19 +02:00
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Updating with auto_accept=%d " , auto_accept ) ;
2021-03-06 21:32:36 +01:00
ensure ( m_downloader ) ;
2021-09-16 05:34:06 +02:00
if ( ! auto_accept )
2020-07-01 20:48:19 +02:00
{
2021-09-16 05:34:06 +02:00
if ( m_update_message . isEmpty ( ) )
{
2024-03-08 18:50:50 +01:00
// This can happen if we abort the check_for_updates download. Just check again in this case.
2024-09-20 14:14:06 +02:00
update_log . notice ( " Aborting update: Update message is empty. Trying again... " ) ;
2021-09-16 05:34:06 +02:00
m_downloader - > close_progress_dialog ( ) ;
2024-03-08 18:50:50 +01:00
check_for_updates ( false , false , false , m_parent ) ;
2021-09-16 05:34:06 +02:00
return ;
}
QString changelog_content ;
for ( const changelog_data & entry : m_changelog )
{
if ( ! changelog_content . isEmpty ( ) )
changelog_content . append ( ' \n ' ) ;
changelog_content . append ( tr ( " • %0: %1 " ) . arg ( entry . version , entry . title ) ) ;
}
2022-04-09 20:54:35 +02:00
QMessageBox mb ( QMessageBox : : Icon : : Question , tr ( " Update Available " ) , m_update_message , QMessageBox : : Yes | QMessageBox : : No , m_downloader - > get_progress_dialog ( ) ? m_downloader - > get_progress_dialog ( ) : m_parent ) ;
2024-04-02 19:09:05 +02:00
mb . setTextFormat ( Qt : : RichText ) ;
2024-12-22 15:35:16 +01:00
mb . setCheckBox ( new QCheckBox ( tr ( " Don't show again for this version " ) ) ) ;
2021-09-16 05:34:06 +02:00
if ( ! changelog_content . isEmpty ( ) )
{
mb . setInformativeText ( tr ( " To see the changelog, please click \" Show Details \" . " ) ) ;
mb . setDetailedText ( tr ( " Changelog: \n \n %0 " ) . arg ( changelog_content ) ) ;
// Smartass hack to make the unresizeable message box wide enough for the changelog
const int changelog_width = QLabel ( changelog_content ) . sizeHint ( ) . width ( ) ;
2024-04-04 19:37:52 +02:00
if ( QLabel ( m_update_message ) . sizeHint ( ) . width ( ) < changelog_width )
2021-09-16 05:34:06 +02:00
{
2024-04-04 19:37:52 +02:00
m_update_message + = " " ;
while ( QLabel ( m_update_message ) . sizeHint ( ) . width ( ) < changelog_width )
{
m_update_message + = " " ;
}
2021-09-16 05:34:06 +02:00
}
2024-04-04 19:37:52 +02:00
2021-09-16 05:34:06 +02:00
mb . setText ( m_update_message ) ;
}
2024-09-20 14:14:06 +02:00
update_log . notice ( " Asking user for permission to update... " ) ;
2021-09-16 05:34:06 +02:00
if ( mb . exec ( ) = = QMessageBox : : No )
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Aborting update: User declined update " ) ;
2024-12-22 15:35:16 +01:00
if ( mb . checkBox ( ) - > isChecked ( ) )
{
update_log . notice ( " User requested to skip further automatic update notifications for version '%s' " , m_new_version ) ;
m_gui_settings - > SetValue ( gui : : ib_skip_version , QString : : fromStdString ( m_new_version ) ) ;
}
2021-09-16 05:34:06 +02:00
m_downloader - > close_progress_dialog ( ) ;
return ;
}
2020-07-01 20:48:19 +02:00
}
2019-09-11 09:55:43 +02:00
2021-03-06 21:32:36 +01:00
if ( ! Emu . IsStopped ( ) )
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Aborting update: Emulation is running... " ) ;
2021-03-06 21:32:36 +01:00
m_downloader - > close_progress_dialog ( ) ;
QMessageBox : : warning ( m_parent , tr ( " Auto-updater " ) , tr ( " Please stop the emulation before trying to update. " ) ) ;
return ;
}
2020-07-01 20:48:19 +02:00
m_downloader - > disconnect ( ) ;
2019-09-11 09:55:43 +02:00
2020-07-02 23:21:12 +02:00
connect ( m_downloader , & downloader : : signal_download_error , this , [ this ] ( const QString & /*error*/ )
2019-09-11 09:55:43 +02:00
{
2020-07-02 23:21:12 +02:00
QMessageBox : : warning ( m_parent , tr ( " Auto-updater " ) , tr ( " An error occurred during the auto-updating process. \n Check the log for more information. " ) ) ;
2020-03-21 22:17:15 +01:00
} ) ;
2020-07-01 20:48:19 +02:00
2021-07-22 23:38:26 +02:00
connect ( m_downloader , & downloader : : signal_download_finished , this , [ this , auto_accept ] ( const QByteArray & data )
2020-03-21 22:17:15 +01:00
{
2021-07-22 23:38:26 +02:00
const bool result_json = handle_rpcs3 ( data , auto_accept ) ;
2020-03-21 22:17:15 +01:00
2020-07-01 20:48:19 +02:00
if ( ! result_json )
2020-03-13 18:34:08 +01:00
{
2020-07-01 20:48:19 +02:00
// The progress dialog is configured to stay open, so we need to close it manually if the download succeeds.
m_downloader - > close_progress_dialog ( ) ;
2020-07-02 23:21:12 +02:00
QMessageBox : : warning ( m_parent , tr ( " Auto-updater " ) , tr ( " An error occurred during the auto-updating process. \n Check the log for more information. " ) ) ;
2020-03-13 18:34:08 +01:00
}
2020-07-01 20:48:19 +02:00
Q_EMIT signal_update_available ( false ) ;
2020-03-21 22:17:15 +01:00
} ) ;
2019-09-11 09:55:43 +02:00
2024-09-20 14:14:06 +02:00
update_log . notice ( " Downloading update... " ) ;
2020-07-01 20:48:19 +02:00
m_downloader - > start ( m_request_url , true , true , tr ( " Downloading Update " ) , true , m_expected_size ) ;
2019-09-11 09:55:43 +02:00
}
2021-07-22 23:38:26 +02:00
bool update_manager : : handle_rpcs3 ( const QByteArray & data , bool auto_accept )
2019-09-11 09:55:43 +02:00
{
2024-09-20 14:14:06 +02:00
update_log . notice ( " Download of update file finished. Updating rpcs3 with auto_accept=%d " , auto_accept ) ;
2020-07-01 20:48:19 +02:00
m_downloader - > update_progress_dialog ( tr ( " Updating RPCS3 " ) ) ;
2021-04-07 23:05:18 +02:00
if ( m_expected_size ! = static_cast < u64 > ( data . size ( ) ) )
2019-09-11 09:55:43 +02:00
{
2020-07-01 20:48:19 +02:00
update_log . error ( " Download size mismatch: %d expected: %d " , data . size ( ) , m_expected_size ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
2022-06-06 22:06:42 +02:00
if ( const std : : string res_hash_string = sha256_get_hash ( data . data ( ) , data . size ( ) , false ) ;
2020-09-07 14:10:57 +02:00
m_expected_hash ! = res_hash_string )
2019-09-11 09:55:43 +02:00
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Hash mismatch: %s expected: %s " , res_hash_string , m_expected_hash ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
2024-01-23 08:36:49 +01:00
# if defined(_WIN32) || defined(__APPLE__)
2020-07-01 20:48:19 +02:00
2020-03-08 18:40:23 +01:00
// Get executable path
2024-01-20 09:04:25 +01:00
const std : : string exe_dir = fs : : get_executable_dir ( ) ;
2024-01-23 08:36:49 +01:00
const std : : string orig_path = fs : : get_executable_path ( ) ;
# ifdef _WIN32
2023-01-07 11:57:00 +01:00
const std : : wstring wchar_orig_path = utf8_to_wchar ( orig_path ) ;
2024-01-13 15:34:32 +01:00
const std : : string tmpfile_path = fs : : get_temp_dir ( ) + " \\ rpcs3_update.7z " ;
2024-01-23 08:36:49 +01:00
# else
const std : : string tmpfile_path = fs : : get_temp_dir ( ) + " rpcs3_update.7z " ;
# endif
2019-09-11 09:55:43 +02:00
2024-09-20 14:14:06 +02:00
update_log . notice ( " Writing temporary update file: %s " , tmpfile_path ) ;
2019-09-11 09:55:43 +02:00
fs : : file tmpfile ( tmpfile_path , fs : : read + fs : : write + fs : : create + fs : : trunc ) ;
if ( ! tmpfile )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Failed to create temporary file: %s " , tmpfile_path ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
2021-04-07 23:05:18 +02:00
if ( tmpfile . write ( data . data ( ) , data . size ( ) ) ! = static_cast < u64 > ( data . size ( ) ) )
2019-09-11 09:55:43 +02:00
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Failed to write temporary file: %s " , tmpfile_path ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
tmpfile . close ( ) ;
2024-09-20 14:14:06 +02:00
update_log . notice ( " Unpacking update file: %s " , tmpfile_path ) ;
2019-09-11 09:55:43 +02:00
// 7z stuff (most of this stuff is from 7z Util sample and has been reworked to be more stl friendly)
2023-01-07 11:57:00 +01:00
CFileInStream archiveStream { } ;
2023-12-13 20:54:24 +01:00
CLookToRead2 lookStream { } ;
2019-09-11 09:55:43 +02:00
CSzArEx db ;
UInt16 temp_u16 [ PATH_MAX ] ;
u8 temp_u8 [ PATH_MAX ] ;
2020-12-18 08:39:54 +01:00
const usz kInputBufSize = static_cast < usz > ( 1u < < 18u ) ;
2025-03-30 12:07:28 +02:00
const ISzAlloc g_Alloc = { SzAlloc , SzFree } ;
2019-09-11 09:55:43 +02:00
2023-12-13 20:54:24 +01:00
ISzAlloc allocImp = g_Alloc ;
ISzAlloc allocTempImp = g_Alloc ;
2019-09-11 09:55:43 +02:00
2025-03-30 12:07:28 +02:00
if ( const WRes res = InFile_Open ( & archiveStream . file , tmpfile_path . c_str ( ) ) )
2019-09-11 09:55:43 +02:00
{
2025-03-30 12:07:28 +02:00
update_log . error ( " Failed to open temporary storage file: '%s' (error=%d) " , tmpfile_path , static_cast < u64 > ( res ) ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
FileInStream_CreateVTable ( & archiveStream ) ;
LookToRead2_CreateVTable ( & lookStream , False ) ;
2023-12-13 20:54:24 +01:00
SRes res = SZ_OK ;
2024-09-20 14:14:06 +02:00
lookStream . buf = static_cast < Byte * > ( ISzAlloc_Alloc ( & allocImp , kInputBufSize ) ) ;
if ( ! lookStream . buf )
2019-09-11 09:55:43 +02:00
{
2024-09-20 14:14:06 +02:00
res = SZ_ERROR_MEM ;
}
else
{
lookStream . bufSize = kInputBufSize ;
lookStream . realStream = & archiveStream . vt ;
2019-09-11 09:55:43 +02:00
}
CrcGenerateTable ( ) ;
SzArEx_Init ( & db ) ;
2020-03-21 22:17:15 +01:00
auto error_free7z = [ & ] ( )
{
2019-09-11 09:55:43 +02:00
SzArEx_Free ( & db , & allocImp ) ;
ISzAlloc_Free ( & allocImp , lookStream . buf ) ;
2025-03-30 12:07:28 +02:00
const WRes res2 = File_Close ( & archiveStream . file ) ;
if ( res2 ) update_log . warning ( " 7z failed to close file (error=%d) " , static_cast < u64 > ( res2 ) ) ;
2019-09-11 09:55:43 +02:00
switch ( res )
{
case SZ_OK : break ;
2020-03-02 07:53:30 +01:00
case SZ_ERROR_UNSUPPORTED : update_log . error ( " 7z decoder doesn't support this archive " ) ; break ;
case SZ_ERROR_MEM : update_log . error ( " 7z decoder failed to allocate memory " ) ; break ;
case SZ_ERROR_CRC : update_log . error ( " 7z decoder CRC error " ) ; break ;
default : update_log . error ( " 7z decoder error: %d " , static_cast < u64 > ( res ) ) ; break ;
2019-09-11 09:55:43 +02:00
}
} ;
if ( res ! = SZ_OK )
{
error_free7z ( ) ;
return false ;
}
res = SzArEx_Open ( & db , & lookStream . vt , & allocImp , & allocTempImp ) ;
if ( res ! = SZ_OK )
{
error_free7z ( ) ;
return false ;
}
2023-12-13 20:54:24 +01:00
UInt32 blockIndex = 0xFFFFFFFF ;
Byte * outBuffer = nullptr ;
2020-12-18 08:39:54 +01:00
usz outBufferSize = 0 ;
2019-09-11 09:55:43 +02:00
2024-01-23 08:36:49 +01:00
# ifdef _WIN32
// Create temp folder for moving active files
2022-06-29 23:19:46 +02:00
const std : : string tmp_folder = exe_dir + " rpcs3_old/ " ;
2024-01-23 08:36:49 +01:00
# else
// Create temp folder for extracting the new app
const std : : string tmp_folder = fs : : get_temp_dir ( ) + " rpcs3_new/ " ;
# endif
2019-09-11 09:55:43 +02:00
fs : : create_dir ( tmp_folder ) ;
for ( UInt32 i = 0 ; i < db . NumFiles ; i + + )
{
2020-12-18 08:39:54 +01:00
usz offset = 0 ;
usz outSizeProcessed = 0 ;
2024-01-23 08:36:49 +01:00
const bool isDir = SzArEx_IsDir ( & db , i ) ;
2024-01-27 08:56:26 +01:00
[[maybe_unused]] const DWORD attribs = SzBitWithVals_Check ( & db . Attribs , i ) ? db . Attribs . Vals [ i ] : 0 ;
2024-01-23 08:36:49 +01:00
# ifdef _WIN32
// This is commented out for now as we shouldn't need it and symlinks
// aren't well supported on Windows. Left in case it is needed in the future.
// const bool is_symlink = (attribs & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
const bool is_symlink = false ;
# else
const DWORD permissions = ( attribs > > 16 ) & ( S_IRWXU | S_IRWXG | S_IRWXO ) ;
const bool is_symlink = ( attribs & FILE_ATTRIBUTE_UNIX_EXTENSION ) ! = 0 & & S_ISLNK ( attribs > > 16 ) ;
# endif
const usz len = SzArEx_GetFileNameUtf16 ( & db , i , nullptr ) ;
2019-09-11 09:55:43 +02:00
if ( len > = PATH_MAX )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " 7z decoder error: filename longer or equal to PATH_MAX " ) ;
2019-09-11 09:55:43 +02:00
error_free7z ( ) ;
return false ;
}
SzArEx_GetFileNameUtf16 ( & db , i , temp_u16 ) ;
memset ( temp_u8 , 0 , sizeof ( temp_u8 ) ) ;
// Simplistic conversion to UTF-8
2020-12-18 08:39:54 +01:00
for ( usz index = 0 ; index < len ; index + + )
2019-09-11 09:55:43 +02:00
{
if ( temp_u16 [ index ] > 0xFF )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " 7z decoder error: Failed to convert UTF-16 to UTF-8 " ) ;
2019-09-11 09:55:43 +02:00
error_free7z ( ) ;
return false ;
}
2019-12-04 21:56:19 +01:00
temp_u8 [ index ] = static_cast < u8 > ( temp_u16 [ index ] ) ;
2019-09-11 09:55:43 +02:00
}
temp_u8 [ len ] = 0 ;
2024-01-23 08:36:49 +01:00
const std : : string archived_name = std : : string ( reinterpret_cast < char * > ( temp_u8 ) ) ;
# ifdef __APPLE__
const std : : string name = tmp_folder + archived_name ;
# else
const std : : string name = exe_dir + archived_name ;
# endif
2019-09-11 09:55:43 +02:00
if ( ! isDir )
{
res = SzArEx_Extract ( & db , & lookStream . vt , i , & blockIndex , & outBuffer , & outBufferSize , & offset , & outSizeProcessed , & allocImp , & allocTempImp ) ;
if ( res ! = SZ_OK )
break ;
}
2022-06-29 23:19:46 +02:00
if ( const usz pos = name . find_last_of ( fs : : delim ) ; pos ! = umax )
2019-09-11 09:55:43 +02:00
{
2020-03-02 07:53:30 +01:00
update_log . trace ( " Creating path: %s " , name . substr ( 0 , pos ) ) ;
2019-09-11 09:55:43 +02:00
fs : : create_path ( name . substr ( 0 , pos ) ) ;
}
if ( isDir )
{
2020-03-02 07:53:30 +01:00
update_log . trace ( " Creating dir: %s " , name ) ;
2019-09-11 09:55:43 +02:00
fs : : create_dir ( name ) ;
continue ;
}
2024-01-23 08:36:49 +01:00
if ( is_symlink )
{
const std : : string link_target ( reinterpret_cast < const char * > ( outBuffer + offset ) , outSizeProcessed ) ;
update_log . trace ( " Creating symbolic link: %s -> %s " , name , link_target ) ;
fs : : create_symlink ( name , link_target ) ;
continue ;
}
2019-09-11 09:55:43 +02:00
fs : : file outfile ( name , fs : : read + fs : : write + fs : : create + fs : : trunc ) ;
if ( ! outfile )
{
// File failed to open, probably because in use, rename existing file and try again
2022-06-29 23:19:46 +02:00
const auto pos = name . find_last_of ( fs : : delim ) ;
2019-09-11 09:55:43 +02:00
std : : string filename ;
2020-03-05 12:05:23 +01:00
if ( pos = = umax )
2019-09-11 09:55:43 +02:00
filename = name ;
else
filename = name . substr ( pos + 1 ) ;
// Moving to temp is not an option on windows as it will fail if the disk is different
// So we create a folder in config dir and move stuff there
const std : : string rename_target = tmp_folder + filename ;
2020-03-02 07:53:30 +01:00
update_log . trace ( " Renaming %s to %s " , name , rename_target ) ;
2019-09-11 09:55:43 +02:00
if ( ! fs : : rename ( name , rename_target , true ) )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Failed to rename %s to %s " , name , rename_target ) ;
2019-09-11 09:55:43 +02:00
res = SZ_ERROR_FAIL ;
break ;
}
outfile . open ( name , fs : : read + fs : : write + fs : : create + fs : : trunc ) ;
if ( ! outfile )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Can not open output file %s " , name ) ;
2019-09-11 09:55:43 +02:00
res = SZ_ERROR_FAIL ;
break ;
}
}
if ( outfile . write ( outBuffer + offset , outSizeProcessed ) ! = outSizeProcessed )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Can not write output file: %s " , name ) ;
2019-09-11 09:55:43 +02:00
res = SZ_ERROR_FAIL ;
break ;
}
outfile . close ( ) ;
2024-01-23 08:36:49 +01:00
# ifndef _WIN32
// Apply correct file permissions.
chmod ( name . c_str ( ) , permissions ) ;
# endif
2019-09-11 09:55:43 +02:00
}
error_free7z ( ) ;
if ( res )
return false ;
2020-07-01 20:48:19 +02:00
2024-09-20 14:14:06 +02:00
update_log . success ( " Update successful! " ) ;
2022-02-21 14:35:15 +01:00
# else
2020-07-01 20:48:19 +02:00
2024-01-20 09:04:25 +01:00
std : : string replace_path = fs : : get_executable_path ( ) ;
2022-02-12 02:19:46 +01:00
if ( replace_path . empty ( ) )
2020-07-01 20:48:19 +02:00
{
2022-02-12 02:19:46 +01:00
return false ;
2020-07-01 20:48:19 +02:00
}
// Move the appimage/exe and replace with new appimage
const std : : string move_dest = replace_path + " _old " ;
2022-02-22 06:32:38 +01:00
if ( ! fs : : rename ( replace_path , move_dest , true ) )
{
// Simply log error for now
update_log . error ( " Failed to move old AppImage file: %s (%s) " , replace_path , fs : : g_tls_error ) ;
}
2020-07-01 20:48:19 +02:00
fs : : file new_appimage ( replace_path , fs : : read + fs : : write + fs : : create + fs : : trunc ) ;
if ( ! new_appimage )
{
2022-02-22 06:32:38 +01:00
update_log . error ( " Failed to create new AppImage file: %s (%s) " , replace_path , fs : : g_tls_error ) ;
2020-07-01 20:48:19 +02:00
return false ;
}
2023-01-10 20:30:03 +01:00
if ( new_appimage . write ( data . data ( ) , data . size ( ) ) ! = data . size ( ) + 0ull )
2020-07-01 20:48:19 +02:00
{
update_log . error ( " Failed to write new AppImage file: %s " , replace_path ) ;
return false ;
}
if ( fchmod ( new_appimage . get_handle ( ) , S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH ) = = - 1 )
{
2022-02-22 06:32:38 +01:00
update_log . error ( " Failed to chmod rwxrxrx %s (%s) " , replace_path , strerror ( errno ) ) ;
2020-07-01 20:48:19 +02:00
return false ;
}
new_appimage . close ( ) ;
update_log . success ( " Successfully updated %s! " , replace_path ) ;
2019-09-11 09:55:43 +02:00
# endif
2020-07-01 20:48:19 +02:00
m_downloader - > close_progress_dialog ( ) ;
2021-10-05 22:52:31 +02:00
// Add new version to log file
if ( fs : : file update_file { fs : : get_config_dir ( ) + " update_history.log " , fs : : create + fs : : write + fs : : append } )
{
const std : : string update_time = QDateTime : : currentDateTime ( ) . toString ( " yyyy/MM/dd hh:mm:ss " ) . toStdString ( ) ;
const std : : string entry = fmt : : format ( " %s: Updated from \" %s \" to \" %s \" " , update_time , m_old_version , m_new_version ) ;
update_file . write ( fmt : : format ( " %s \n " , entry ) ) ;
update_log . notice ( " Added entry '%s' to update_history.log " , entry ) ;
}
else
{
update_log . error ( " Failed to append version to update_history.log " ) ;
}
2021-07-22 23:38:26 +02:00
if ( ! auto_accept )
{
2023-04-18 19:25:55 +02:00
m_gui_settings - > ShowInfoBox ( tr ( " Auto-updater " ) , tr ( " Update successful!<br>RPCS3 will now restart.<br> " ) , gui : : ib_restart_hint , m_parent ) ;
m_gui_settings - > sync ( ) ; // Make sure to sync before terminating RPCS3
2021-07-22 23:38:26 +02:00
}
2019-09-11 09:55:43 +02:00
2022-02-05 11:49:29 +01:00
Emu . GracefulShutdown ( false ) ;
2021-03-06 21:10:35 +01:00
Emu . CleanUp ( ) ;
2019-10-25 12:32:21 +02:00
# ifdef _WIN32
2024-09-20 14:14:06 +02:00
update_log . notice ( " Relaunching %s with _wexecl " , wchar_to_utf8 ( wchar_orig_path ) ) ;
2022-05-30 22:36:47 +02:00
const int ret = _wexecl ( wchar_orig_path . data ( ) , wchar_orig_path . data ( ) , L " --updating " , nullptr ) ;
2024-01-23 08:36:49 +01:00
# elif defined(__APPLE__)
// Execute helper script to replace the app and relaunch
const std : : string helper_script = fmt : : format ( " %s/Contents/Resources/update_helper.sh " , orig_path ) ;
const std : : string extracted_app = fmt : : format ( " %s/RPCS3.app " , tmp_folder ) ;
update_log . notice ( " Executing update helper script: '%s %s %s' " , helper_script , extracted_app , orig_path ) ;
const int ret = execl ( helper_script . c_str ( ) , helper_script . c_str ( ) , extracted_app . c_str ( ) , orig_path . c_str ( ) , nullptr ) ;
2019-10-25 12:32:21 +02:00
# else
2022-05-30 22:36:47 +02:00
// execv is used for compatibility with checkrt
2024-09-20 14:14:06 +02:00
update_log . notice ( " Relaunching %s with execv " , replace_path ) ;
2022-05-30 22:36:47 +02:00
const char * const params [ 3 ] = { replace_path . c_str ( ) , " --updating " , nullptr } ;
const int ret = execv ( replace_path . c_str ( ) , const_cast < char * const * > ( & params [ 0 ] ) ) ;
2019-10-25 12:32:21 +02:00
# endif
2019-09-11 09:55:43 +02:00
if ( ret = = - 1 )
{
2020-03-02 07:53:30 +01:00
update_log . error ( " Relaunching failed with result: %d(%s) " , ret , strerror ( errno ) ) ;
2019-09-11 09:55:43 +02:00
return false ;
}
return true ;
}