mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-16 11:48:36 +12:00
Auto-updater
This commit is contained in:
parent
56e6dd1c46
commit
9f9ac4b696
31 changed files with 2068 additions and 52 deletions
560
rpcs3/rpcs3qt/update_manager.cpp
Normal file
560
rpcs3/rpcs3qt/update_manager.cpp
Normal file
|
@ -0,0 +1,560 @@
|
|||
#include "stdafx.h"
|
||||
#include "update_manager.h"
|
||||
#include "rpcs3_version.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
#include "Crypto/sha256.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <CpuArch.h>
|
||||
#include <7z.h>
|
||||
#include <7zAlloc.h>
|
||||
#include <7zBuf.h>
|
||||
#include <7zCrc.h>
|
||||
#include <7zFile.h>
|
||||
#include <7zVersion.h>
|
||||
|
||||
#define PATH_MAX MAX_PATH
|
||||
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
update_manager::update_manager()
|
||||
{
|
||||
m_manager.setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
}
|
||||
|
||||
void update_manager::check_for_updates(bool automatic, QWidget* parent)
|
||||
{
|
||||
if (QSslSocket::supportsSsl() == false)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "Can't update! Please make sure your system supports SSL.");
|
||||
if (!automatic)
|
||||
QMessageBox::warning(parent, tr("Auto-updater"), tr("Can't update! Please make sure your system supports SSL."));
|
||||
return;
|
||||
}
|
||||
|
||||
m_parent = parent;
|
||||
|
||||
const std::string request_url = m_update_url + rpcs3::get_commit_and_hash().second;
|
||||
QNetworkReply* reply_json = m_manager.get(QNetworkRequest(QUrl(QString::fromStdString(request_url))));
|
||||
|
||||
m_progress_dialog = new progress_dialog(tr("Checking For Updates"), tr("Please wait..."), tr("Abort"), 0, 100, parent);
|
||||
m_progress_dialog->setAutoClose(false);
|
||||
m_progress_dialog->setAutoReset(false);
|
||||
m_progress_dialog->show();
|
||||
|
||||
connect(m_progress_dialog, &QProgressDialog::canceled, reply_json, &QNetworkReply::abort);
|
||||
connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater);
|
||||
|
||||
// clang-format off
|
||||
connect(reply_json, &QNetworkReply::downloadProgress, [&](qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
m_progress_dialog->setMaximum(bytesTotal);
|
||||
m_progress_dialog->setValue(bytesReceived);
|
||||
});
|
||||
|
||||
connect(reply_json, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &update_manager::handle_error);
|
||||
connect(reply_json, &QNetworkReply::finished, [=]()
|
||||
{
|
||||
handle_reply(reply_json, &update_manager::handle_json, automatic, "Retrieved Json Info");
|
||||
});
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
void update_manager::handle_error(QNetworkReply::NetworkError error)
|
||||
{
|
||||
if (error != QNetworkReply::NoError)
|
||||
{
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply *>(sender());
|
||||
if(!reply)
|
||||
return;
|
||||
|
||||
m_progress_dialog->close();
|
||||
|
||||
reply->deleteLater();
|
||||
if (error != QNetworkReply::OperationCanceledError)
|
||||
{
|
||||
QString error = reply->errorString();
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Network Error: %s", error.toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool update_manager::handle_reply(QNetworkReply* reply, std::function<bool(update_manager& man, const QByteArray&, bool)> func, bool automatic, const std::string& message)
|
||||
{
|
||||
if (auto error = reply->error(); error != QNetworkReply::NoError)
|
||||
return false;
|
||||
|
||||
// Read data from network reply
|
||||
const QByteArray data = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
LOG_NOTICE(GENERAL, "[Auto-updater] %s", message);
|
||||
if (!func(*this, data, automatic))
|
||||
{
|
||||
m_progress_dialog->close();
|
||||
|
||||
if (!automatic)
|
||||
QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occured during the auto-updating process.\nCheck the log for more information."));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update_manager::handle_json(const QByteArray& data, bool automatic)
|
||||
{
|
||||
const QJsonObject json_data = QJsonDocument::fromJson(data).object();
|
||||
int return_code = json_data["return_code"].toInt(-255);
|
||||
|
||||
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)
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] error: %s return code: %d", error_message, return_code);
|
||||
else
|
||||
LOG_WARNING(GENERAL, "[Auto-updater] error: %s return code: %d", error_message, return_code);
|
||||
|
||||
// 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)
|
||||
{
|
||||
if (QMessageBox::question(m_progress_dialog, tr("Auto-updater"), tr("You're currently using a custom or PR build.\n\nDo you want to update to the latest official rpcs3 version?"),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
|
||||
{
|
||||
m_progress_dialog->close();
|
||||
return true;
|
||||
}
|
||||
|
||||
hash_found = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const auto& latest = json_data["latest_build"];
|
||||
if (!latest.isObject())
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Json doesn't contain latest_build section");
|
||||
return false;
|
||||
}
|
||||
|
||||
QString os;
|
||||
|
||||
#ifdef _WIN32
|
||||
os = "windows";
|
||||
#elif defined(__linux__)
|
||||
os = "linux";
|
||||
#else
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Your os isn't currently supported by the auto-updater");
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// Check that every bit of info we need is there
|
||||
if (!latest[os].isObject() || !latest[os]["download"].isString() || !latest[os]["size"].isDouble() || !latest[os]["checksum"].isString() || !latest["version"].isString() ||
|
||||
!latest["datetime"].isString() ||
|
||||
(hash_found && (!json_data["current_build"].isObject() || !json_data["current_build"]["version"].isString() || !json_data["current_build"]["datetime"].isString())))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Some information seems unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hash_found && return_code == 0)
|
||||
{
|
||||
LOG_SUCCESS(GENERAL, "[Auto-updater] RPCS3 is up to date!");
|
||||
m_progress_dialog->close();
|
||||
|
||||
if (!automatic)
|
||||
QMessageBox::information(m_parent, tr("Auto-updater"), tr("Your version is already up to date!"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hash_found)
|
||||
{
|
||||
// Calculates how old the build is
|
||||
std::string cur_date = json_data["current_build"]["datetime"].toString().toStdString();
|
||||
std::string lts_date = latest["datetime"].toString().toStdString();
|
||||
|
||||
tm cur_tm, lts_tm;
|
||||
QString timediff = "";
|
||||
|
||||
auto time_from_str = [](const std::string& str, const std::string& format, tm* tm) -> bool
|
||||
{
|
||||
std::istringstream input(str);
|
||||
input.imbue(std::locale(setlocale(LC_ALL, nullptr)));
|
||||
input >> std::get_time(tm, format.c_str());
|
||||
if (input.fail())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (time_from_str(cur_date, "%Y-%m-%d %H:%M:%S", &cur_tm) && time_from_str(lts_date, "%Y-%m-%d %H:%M:%S", <s_tm))
|
||||
{
|
||||
time_t cur_time = mktime(&cur_tm);
|
||||
time_t lts_time = mktime(<s_tm);
|
||||
|
||||
s64 u_timediff = (s64)std::difftime(lts_time, cur_time);
|
||||
timediff = tr("Your version is %1 day(s), %2 hour(s) and %3 minute(s) old.").arg(u_timediff / (60 * 60 * 24)).arg((u_timediff / (60 * 60)) % 24).arg((u_timediff / 60) % 60);
|
||||
}
|
||||
|
||||
const QString to_show = tr("A new version of RPCS3 is available!\n\nCurrent version: %1\nLatest version: %2\n%3\n\nDo you want to update?")
|
||||
.arg(json_data["current_build"]["version"].toString())
|
||||
.arg(latest["version"].toString())
|
||||
.arg(timediff);
|
||||
if (QMessageBox::question(m_progress_dialog, tr("Update Available"), to_show, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
|
||||
{
|
||||
m_progress_dialog->close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
m_expected_hash = latest[os]["checksum"].toString().toStdString();
|
||||
m_expected_size = latest[os]["size"].toInt();
|
||||
|
||||
m_progress_dialog->setWindowTitle(tr("Downloading Update"));
|
||||
|
||||
// Download RPCS3
|
||||
m_progress_dialog->setValue(0);
|
||||
QNetworkReply* reply_rpcs3 = m_manager.get(QNetworkRequest(QUrl(latest[os]["download"].toString())));
|
||||
|
||||
connect(m_progress_dialog, &QProgressDialog::canceled, reply_rpcs3, &QNetworkReply::abort);
|
||||
|
||||
// clang-format off
|
||||
connect(reply_rpcs3, &QNetworkReply::downloadProgress, [&](qint64 bytesReceived, qint64 bytesTotal)
|
||||
{
|
||||
m_progress_dialog->setMaximum(bytesTotal);
|
||||
m_progress_dialog->setValue(bytesReceived);
|
||||
});
|
||||
|
||||
connect(reply_rpcs3, QOverload<QNetworkReply::NetworkError>::of(&QNetworkReply::error), this, &update_manager::handle_error);
|
||||
connect(reply_rpcs3, &QNetworkReply::finished, [=]()
|
||||
{
|
||||
handle_reply(reply_rpcs3, &update_manager::handle_rpcs3, automatic, "Retrieved RPCS3");
|
||||
});
|
||||
// clang-format on
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool update_manager::handle_rpcs3(const QByteArray& rpcs3_data, bool automatic)
|
||||
{
|
||||
if (m_expected_size != rpcs3_data.size())
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Download size mismatch: %d expected: %d", rpcs3_data.size(), m_expected_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 res_hash[32];
|
||||
mbedtls_sha256_context ctx;
|
||||
mbedtls_sha256_init(&ctx);
|
||||
mbedtls_sha256_starts_ret(&ctx, 0);
|
||||
mbedtls_sha256_update_ret(&ctx, (const unsigned char*)rpcs3_data.data(), rpcs3_data.size());
|
||||
mbedtls_sha256_finish_ret(&ctx, res_hash);
|
||||
|
||||
std::string res_hash_string("0000000000000000000000000000000000000000000000000000000000000000");
|
||||
for (size_t index = 0; index < 32; index++)
|
||||
{
|
||||
constexpr auto pal = "0123456789ABCDEF";
|
||||
res_hash_string[index * 2] = pal[res_hash[index] >> 4];
|
||||
res_hash_string[(index * 2) + 1] = pal[res_hash[index] & 15];
|
||||
}
|
||||
|
||||
if (m_expected_hash != res_hash_string)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Hash mismatch: %s expected: %s", res_hash_string, m_expected_hash);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string replace_path;
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
const char* appimage_path = ::getenv("APPIMAGE");
|
||||
if (appimage_path != nullptr)
|
||||
{
|
||||
replace_path = appimage_path;
|
||||
LOG_NOTICE(GENERAL, "[Auto-updater] Found appimage path: %s", appimage_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING(GENERAL, "[Auto-updater] Failed to find AppImage path");
|
||||
char exe_path[PATH_MAX];
|
||||
ssize_t len = ::readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
|
||||
if (len == -1)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to find executable path");
|
||||
return false;
|
||||
}
|
||||
|
||||
exe_path[len] = '\0';
|
||||
LOG_TRACE(GENERAL, "[Auto-updater] Found exec path: %s", exe_path);
|
||||
|
||||
replace_path = exe_path;
|
||||
}
|
||||
|
||||
m_progress_dialog->setWindowTitle(tr("Updating RPCS3"));
|
||||
|
||||
// Move the appimage/exe and replace with new appimage
|
||||
fs::rename(replace_path, "/tmp/rpcs3_old", true);
|
||||
fs::file new_appimage(replace_path, fs::read + fs::write + fs::create + fs::trunc);
|
||||
if (!new_appimage)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to create new AppImage file: %s", replace_path);
|
||||
return false;
|
||||
}
|
||||
if (new_appimage.write(rpcs3_data.data(), rpcs3_data.size()) != rpcs3_data.size())
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] 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)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to chmod rwxrxrx %s", replace_path);
|
||||
return false;
|
||||
}
|
||||
new_appimage.close();
|
||||
|
||||
LOG_SUCCESS(GENERAL, "[Auto-updater] Successfully updated %s!", replace_path);
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
char temp_path[PATH_MAX];
|
||||
|
||||
GetTempPathA(sizeof(temp_path) - 1, temp_path);
|
||||
temp_path[PATH_MAX - 1] = 0;
|
||||
|
||||
std::string tmpfile_path = temp_path;
|
||||
tmpfile_path += "\\rpcs3_update.7z";
|
||||
|
||||
fs::file tmpfile(tmpfile_path, fs::read + fs::write + fs::create + fs::trunc);
|
||||
if (!tmpfile)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to create temporary file: %s", tmpfile_path);
|
||||
return false;
|
||||
}
|
||||
if (tmpfile.write(rpcs3_data.data(), rpcs3_data.size()) != rpcs3_data.size())
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to write temporary file: %s", tmpfile_path);
|
||||
return false;
|
||||
}
|
||||
tmpfile.close();
|
||||
|
||||
m_progress_dialog->setWindowTitle(tr("Updating RPCS3"));
|
||||
|
||||
// 7z stuff (most of this stuff is from 7z Util sample and has been reworked to be more stl friendly)
|
||||
|
||||
ISzAlloc allocImp;
|
||||
ISzAlloc allocTempImp;
|
||||
|
||||
CFileInStream archiveStream;
|
||||
CLookToRead2 lookStream;
|
||||
CSzArEx db;
|
||||
SRes res;
|
||||
UInt16 temp_u16[PATH_MAX];
|
||||
u8 temp_u8[PATH_MAX];
|
||||
const size_t kInputBufSize = ((size_t)1 << 18);
|
||||
const ISzAlloc g_Alloc = {SzAlloc, SzFree};
|
||||
|
||||
allocImp = g_Alloc;
|
||||
allocTempImp = g_Alloc;
|
||||
|
||||
if (InFile_Open(&archiveStream.file, tmpfile_path.c_str()))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to open temporary storage file: %s", tmpfile_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
FileInStream_CreateVTable(&archiveStream);
|
||||
LookToRead2_CreateVTable(&lookStream, False);
|
||||
lookStream.buf = NULL;
|
||||
|
||||
res = SZ_OK;
|
||||
{
|
||||
lookStream.buf = (Byte*)ISzAlloc_Alloc(&allocImp, kInputBufSize);
|
||||
if (!lookStream.buf)
|
||||
res = SZ_ERROR_MEM;
|
||||
else
|
||||
{
|
||||
lookStream.bufSize = kInputBufSize;
|
||||
lookStream.realStream = &archiveStream.vt;
|
||||
LookToRead2_Init(&lookStream);
|
||||
}
|
||||
}
|
||||
|
||||
CrcGenerateTable();
|
||||
SzArEx_Init(&db);
|
||||
|
||||
auto error_free7z = [&]() {
|
||||
SzArEx_Free(&db, &allocImp);
|
||||
ISzAlloc_Free(&allocImp, lookStream.buf);
|
||||
|
||||
File_Close(&archiveStream.file);
|
||||
|
||||
switch (res)
|
||||
{
|
||||
case SZ_OK: break;
|
||||
case SZ_ERROR_UNSUPPORTED: LOG_ERROR(GENERAL, "[Auto-updater] 7z decoder doesn't support this archive"); break;
|
||||
case SZ_ERROR_MEM: LOG_ERROR(GENERAL, "[Auto-updater] 7z decoder failed to allocate memory"); break;
|
||||
case SZ_ERROR_CRC: LOG_ERROR(GENERAL, "[Auto-updater] 7z decoder CRC error"); break;
|
||||
default: LOG_ERROR(GENERAL, "[Auto-updater] 7z decoder error: %d", (u64)res); break;
|
||||
}
|
||||
};
|
||||
|
||||
if (res != SZ_OK)
|
||||
{
|
||||
error_free7z();
|
||||
return false;
|
||||
}
|
||||
|
||||
res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp);
|
||||
if (res != SZ_OK)
|
||||
{
|
||||
error_free7z();
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt32 blockIndex = 0xFFFFFFFF;
|
||||
Byte* outBuffer = 0;
|
||||
size_t outBufferSize = 0;
|
||||
|
||||
// Creates temp folder for moving active files
|
||||
std::string tmp_folder = Emulator::GetEmuDir() + m_tmp_folder;
|
||||
fs::create_dir(tmp_folder);
|
||||
|
||||
for (UInt32 i = 0; i < db.NumFiles; i++)
|
||||
{
|
||||
size_t offset = 0;
|
||||
size_t outSizeProcessed = 0;
|
||||
size_t len;
|
||||
unsigned isDir = SzArEx_IsDir(&db, i);
|
||||
len = SzArEx_GetFileNameUtf16(&db, i, NULL);
|
||||
|
||||
if (len >= PATH_MAX)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] 7z decoder error: filename longer or equal to PATH_MAX");
|
||||
error_free7z();
|
||||
return false;
|
||||
}
|
||||
|
||||
SzArEx_GetFileNameUtf16(&db, i, temp_u16);
|
||||
memset(temp_u8, 0, sizeof(temp_u8));
|
||||
// Simplistic conversion to UTF-8
|
||||
for (size_t index = 0; index < len; index++)
|
||||
{
|
||||
if (temp_u16[index] > 0xFF)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] 7z decoder error: Failed to convert utf-16 to utf-8");
|
||||
error_free7z();
|
||||
return false;
|
||||
}
|
||||
|
||||
temp_u8[index] = (u8)temp_u16[index];
|
||||
}
|
||||
temp_u8[len] = 0;
|
||||
std::string name((char*)temp_u8);
|
||||
|
||||
name = Emulator::GetEmuDir() + name;
|
||||
|
||||
if (!isDir)
|
||||
{
|
||||
res = SzArEx_Extract(&db, &lookStream.vt, i, &blockIndex, &outBuffer, &outBufferSize, &offset, &outSizeProcessed, &allocImp, &allocTempImp);
|
||||
if (res != SZ_OK)
|
||||
break;
|
||||
}
|
||||
|
||||
if (size_t pos = name.find_last_of('/'); pos != std::string::npos)
|
||||
{
|
||||
LOG_TRACE(GENERAL, "[Auto-updater] Creating path: %s", name.substr(0, pos));
|
||||
fs::create_path(name.substr(0, pos));
|
||||
}
|
||||
|
||||
if (isDir)
|
||||
{
|
||||
LOG_TRACE(GENERAL, "[Auto-updater] Creating dir: %s", name);
|
||||
fs::create_dir(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
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
|
||||
const auto pos = name.find_last_of('/');
|
||||
std::string filename;
|
||||
if (pos == std::string::npos)
|
||||
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;
|
||||
LOG_TRACE(GENERAL, "[Auto-updater] Renaming %s to %s", name, rename_target);
|
||||
if (!fs::rename(name, rename_target, true))
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Failed to rename %s to %s", name, rename_target);
|
||||
res = SZ_ERROR_FAIL;
|
||||
break;
|
||||
}
|
||||
|
||||
outfile.open(name, fs::read + fs::write + fs::create + fs::trunc);
|
||||
if (!outfile)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] can not open output file %s", name);
|
||||
res = SZ_ERROR_FAIL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (outfile.write(outBuffer + offset, outSizeProcessed) != outSizeProcessed)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] can not write output file: %s", name);
|
||||
res = SZ_ERROR_FAIL;
|
||||
break;
|
||||
}
|
||||
outfile.close();
|
||||
}
|
||||
|
||||
error_free7z();
|
||||
if (res)
|
||||
return false;
|
||||
|
||||
replace_path = Emulator::GetEmuDir() + "rpcs3.exe";
|
||||
|
||||
#endif
|
||||
|
||||
m_progress_dialog->close();
|
||||
|
||||
QMessageBox::information(m_parent, tr("Auto-updater"), tr("Update successful!"));
|
||||
int ret = execl(replace_path.c_str(), replace_path.c_str(), nullptr);
|
||||
|
||||
if (ret == -1)
|
||||
{
|
||||
LOG_ERROR(GENERAL, "[Auto-updater] Relaunching failed with result: %d(%s)", ret, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue