diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj
index 5ea2777541..8affbb182e 100644
--- a/rpcs3/rpcs3.vcxproj
+++ b/rpcs3/rpcs3.vcxproj
@@ -393,6 +393,11 @@
true
true
+
+ true
+ true
+ true
+
true
true
@@ -678,6 +683,11 @@
true
true
+
+ true
+ true
+ true
+
true
true
@@ -983,6 +993,11 @@
true
true
+
+ true
+ true
+ true
+
true
true
@@ -1268,6 +1283,11 @@
true
true
+
+ true
+ true
+ true
+
true
true
@@ -1507,6 +1527,7 @@
+
@@ -2269,6 +2290,24 @@
.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"
+
+ $(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing %(Identity)...
+ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"
+ $(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing %(Identity)...
+ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"
+ $(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing %(Identity)...
+ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"
+ $(QTDIR)\bin\moc.exe;%(FullPath)
+ Moc%27ing %(Identity)...
+ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp
+ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"
+
Moc%27ing %(Identity)...
diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters
index fc89752a84..594edda969 100644
--- a/rpcs3/rpcs3.vcxproj.filters
+++ b/rpcs3/rpcs3.vcxproj.filters
@@ -131,6 +131,9 @@
{e72a0cbe-fbcd-4a0b-8c17-a2a3b7a42258}
+
+ {bad5498c-a915-4a96-b0cc-f754c02d8e65}
+
@@ -901,9 +904,6 @@
Generated Files\Debug - LLVM
-
- rpcs3
-
Gui\screenshot manager
@@ -1057,6 +1057,24 @@
Generated Files\Debug - LLVM
+
+ Gui\network
+
+
+ Gui\network
+
+
+ Generated Files\Release - LLVM
+
+
+ Generated Files\Debug
+
+
+ Generated Files\Release
+
+
+ Generated Files\Debug - LLVM
+
@@ -1140,9 +1158,6 @@
Gui\game list
-
- rpcs3
-
Gui\settings
@@ -1158,6 +1173,9 @@
Generated Files
+
+ Gui\network
+
@@ -1376,6 +1394,9 @@
Form Files
+
+ Gui\network
+
diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt
index 4be54297cd..151f15d046 100644
--- a/rpcs3/rpcs3qt/CMakeLists.txt
+++ b/rpcs3/rpcs3qt/CMakeLists.txt
@@ -11,6 +11,7 @@
custom_dialog.cpp
debugger_frame.cpp
debugger_list.cpp
+ downloader.cpp
_discord_utils.cpp
emu_settings.cpp
fatal_error_dialog.cpp
diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp
new file mode 100644
index 0000000000..b21f2d4204
--- /dev/null
+++ b/rpcs3/rpcs3qt/downloader.cpp
@@ -0,0 +1,164 @@
+#include "stdafx.h"
+
+#include
+#include
+
+#include "downloader.h"
+#include "curl_handle.h"
+#include "progress_dialog.h"
+
+LOG_CHANNEL(network_log, "NETWORK");
+
+size_t curl_write_cb_compat(char* ptr, size_t /*size*/, size_t nmemb, void* userdata)
+{
+ downloader* download = reinterpret_cast(userdata);
+ return download->update_buffer(ptr, nmemb);
+}
+
+downloader::downloader(const std::string& thread_name, QWidget* parent)
+ : QObject(parent)
+ , m_parent(parent)
+ , m_thread_name(thread_name)
+{
+ m_curl = new curl_handle(this);
+}
+
+void downloader::start(const std::string& url, bool follow_location, bool show_progress_dialog, const QString& progress_dialog_title, bool keep_progress_dialog_open, int exptected_size)
+{
+ connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update);
+
+ m_keep_progress_dialog_open = keep_progress_dialog_open;
+ m_curl_buf.clear();
+ m_curl_abort = false;
+
+ curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, url.c_str());
+ curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb_compat);
+ curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this);
+ curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, follow_location ? 1 : 0);
+
+ const auto thread = QThread::create([this]
+ {
+ const auto result = curl_easy_perform(m_curl->get_curl());
+ m_curl_success = result == CURLE_OK;
+
+ if (!m_curl_success && !m_curl_abort)
+ {
+ const std::string error = "Curl error: " + std::string{ curl_easy_strerror(result) };
+ network_log.error("%s", error);
+ Q_EMIT signal_download_error(QString::fromStdString(error));
+ }
+ });
+
+ connect(thread, &QThread::finished, this, [this]()
+ {
+ if (m_curl_abort)
+ {
+ return;
+ }
+
+ if (m_progress_dialog && (!m_keep_progress_dialog_open || !m_curl_success))
+ {
+ m_progress_dialog->close();
+ m_progress_dialog = nullptr;
+ }
+
+ if (m_curl_success)
+ {
+ Q_EMIT signal_download_finished(m_curl_buf);
+ }
+ });
+
+ if (show_progress_dialog)
+ {
+ const int maximum = exptected_size > 0 ? exptected_size : 100;
+
+ if (m_progress_dialog)
+ {
+ m_progress_dialog->setWindowTitle(progress_dialog_title);
+ m_progress_dialog->setAutoClose(!m_keep_progress_dialog_open);
+ m_progress_dialog->setMaximum(maximum);
+ }
+ else
+ {
+ m_progress_dialog = new progress_dialog(progress_dialog_title, tr("Please wait..."), tr("Abort"), 0, maximum, true, m_parent);
+ m_progress_dialog->setAutoReset(false);
+ m_progress_dialog->setAutoClose(!m_keep_progress_dialog_open);
+ m_progress_dialog->show();
+
+ // Handle abort
+ connect(m_progress_dialog, &QProgressDialog::canceled, this, [this]()
+ {
+ m_curl_abort = true;
+ close_progress_dialog();
+ });
+ connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater);
+ }
+ }
+
+ thread->setObjectName("Compat Update");
+ thread->start();
+}
+
+void downloader::update_progress_dialog(const QString& title)
+{
+ if (m_progress_dialog)
+ {
+ m_progress_dialog->setWindowTitle(title);
+ }
+}
+
+void downloader::close_progress_dialog()
+{
+ if (m_progress_dialog)
+ {
+ m_progress_dialog->close();
+ m_progress_dialog = nullptr;
+ }
+}
+
+progress_dialog* downloader::get_progress_dialog() const
+{
+ return m_progress_dialog;
+}
+
+size_t downloader::update_buffer(char* data, size_t size)
+{
+ if (m_curl_abort)
+ {
+ return 0;
+ }
+
+ const auto old_size = m_curl_buf.size();
+ const auto new_size = old_size + size;
+ m_curl_buf.resize(static_cast(new_size));
+ memcpy(m_curl_buf.data() + old_size, data, size);
+
+ int max = 0;
+
+ if (m_actual_download_size < 0)
+ {
+ if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_download_size) == CURLE_OK && m_actual_download_size > 0)
+ {
+ max = static_cast(m_actual_download_size);
+ }
+ }
+
+ Q_EMIT signal_buffer_update(static_cast(new_size), max);
+
+ return size;
+}
+
+void downloader::handle_buffer_update(int size, int max)
+{
+ if (m_curl_abort)
+ {
+ return;
+ }
+
+ if (m_progress_dialog)
+ {
+ m_progress_dialog->setMaximum(max > 0 ? max : m_progress_dialog->maximum());
+ m_progress_dialog->setValue(size);
+ QApplication::processEvents();
+ }
+}
diff --git a/rpcs3/rpcs3qt/downloader.h b/rpcs3/rpcs3qt/downloader.h
new file mode 100644
index 0000000000..a0c9a80999
--- /dev/null
+++ b/rpcs3/rpcs3qt/downloader.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include
+
+#include
+
+class curl_handle;
+class progress_dialog;
+
+class downloader : public QObject
+{
+ Q_OBJECT
+
+public:
+ downloader(const std::string& thread_name, QWidget* parent = nullptr);
+
+ void start(const std::string& url, bool follow_location, bool show_progress_dialog, const QString& progress_dialog_title = "", bool keep_progress_dialog_open = false, int expected_size = -1);
+ size_t update_buffer(char* data, size_t size);
+
+ void update_progress_dialog(const QString& title);
+ void close_progress_dialog();
+
+ progress_dialog* get_progress_dialog() const;
+
+private Q_SLOTS:
+ void handle_buffer_update(int size, int max);
+
+Q_SIGNALS:
+ void signal_download_error(const QString& error);
+ void signal_download_finished(const QByteArray& data);
+ void signal_buffer_update(int size, int max);
+
+private:
+ QWidget* m_parent = nullptr;
+ std::string m_thread_name;
+
+ curl_handle* m_curl = nullptr;
+ QByteArray m_curl_buf;
+ std::atomic m_curl_abort = false;
+ std::atomic m_curl_success = false;
+ double m_actual_download_size = -1.0;
+
+ progress_dialog* m_progress_dialog = nullptr;
+ std::atomic m_keep_progress_dialog_open = false;
+ QString m_progress_dialog_title;
+};
diff --git a/rpcs3/rpcs3qt/game_compatibility.cpp b/rpcs3/rpcs3qt/game_compatibility.cpp
index 9f9072f83b..288e481d1c 100644
--- a/rpcs3/rpcs3qt/game_compatibility.cpp
+++ b/rpcs3/rpcs3qt/game_compatibility.cpp
@@ -1,77 +1,67 @@
#include "game_compatibility.h"
#include "gui_settings.h"
-#include "progress_dialog.h"
-#include "curl_handle.h"
+#include "downloader.h"
#include
#include
#include
-#include
LOG_CHANNEL(compat_log, "Compat");
constexpr auto qstr = QString::fromStdString;
inline std::string sstr(const QString& _in) { return _in.toStdString(); }
-size_t curl_write_cb_compat(char* ptr, size_t /*size*/, size_t nmemb, void* userdata)
+game_compatibility::game_compatibility(std::shared_ptr settings, QWidget* parent)
+ : QObject(parent)
+ , m_gui_settings(settings)
{
- game_compatibility* gm_cmp = reinterpret_cast(userdata);
- return gm_cmp->update_buffer(ptr, nmemb);
-}
-
-game_compatibility::game_compatibility(std::shared_ptr settings) : m_xgui_settings(settings)
-{
- m_filepath = m_xgui_settings->GetSettingsDir() + "/compat_database.dat";
- m_url = "https://rpcs3.net/compatibility?api=v1&export";
-
- m_curl = new curl_handle(this);
-
+ m_filepath = m_gui_settings->GetSettingsDir() + "/compat_database.dat";
+ m_downloader = new downloader("Compat Update", parent);
RequestCompatibility();
- // We need this signal in order to update the GUI from the main thread
- connect(this, &game_compatibility::signal_buffer_update, this, &game_compatibility::handle_buffer_update);
+ connect(m_downloader, &downloader::signal_download_error, this, &game_compatibility::handle_download_error);
+ connect(m_downloader, &downloader::signal_download_finished, this, &game_compatibility::handle_download_finished);
}
-void game_compatibility::handle_buffer_update(int size, int max)
+void game_compatibility::handle_download_error(const QString& error)
{
- if (m_progress_dialog)
- {
- m_progress_dialog->setMaximum(max);
- m_progress_dialog->setValue(size);
- QApplication::processEvents();
- }
+ Q_EMIT DownloadError(error);
}
-size_t game_compatibility::update_buffer(char* data, size_t size)
+void game_compatibility::handle_download_finished(const QByteArray& data)
{
- if (m_curl_abort)
+ compat_log.notice("Database download finished");
+
+ // Create new map from database and write database to file if database was valid
+ if (ReadJSON(QJsonDocument::fromJson(data).object(), true))
{
- return 0;
- }
+ // We have a new database in map, therefore refresh gamelist to new state
+ Q_EMIT DownloadFinished();
- const auto old_size = m_curl_buf.size();
- const auto new_size = old_size + size;
- m_curl_buf.resize(static_cast(new_size));
- memcpy(m_curl_buf.data() + old_size, data, size);
+ // Write database to file
+ QFile file(m_filepath);
- int max = m_progress_dialog ? m_progress_dialog->maximum() : 0;
-
- if (m_actual_dwnld_size < 0)
- {
- if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_dwnld_size) == CURLE_OK && m_actual_dwnld_size > 0)
+ if (file.exists())
{
- max = static_cast(m_actual_dwnld_size);
+ compat_log.notice("Database file found: %s", sstr(m_filepath));
}
+
+ if (!file.open(QIODevice::WriteOnly))
+ {
+ compat_log.error("Database Error - Could not write database to file: %s", sstr(m_filepath));
+ return;
+ }
+
+ file.write(data);
+ file.close();
+
+ compat_log.success("Wrote database to file: %s", sstr(m_filepath));
}
-
- Q_EMIT signal_buffer_update(static_cast(new_size), max);
-
- return size;
}
bool game_compatibility::ReadJSON(const QJsonObject& json_data, bool after_download)
{
- int return_code = json_data["return_code"].toInt();
+ const int return_code = json_data["return_code"].toInt();
if (return_code < 0)
{
@@ -167,77 +157,10 @@ void game_compatibility::RequestCompatibility(bool online)
return;
}
- compat_log.notice("Beginning compatibility database download from: %s", m_url);
+ const std::string url = "https://rpcs3.net/compatibility?api=v1&export";
+ compat_log.notice("Beginning compatibility database download from: %s", url);
- // Show Progress
- m_progress_dialog = new progress_dialog(tr("Downloading Database"), tr("Please wait..."), tr("Abort"), 0, 100, true);
- m_progress_dialog->show();
-
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, m_url.c_str());
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb_compat);
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this);
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, 1);
-
- m_curl_buf.clear();
- m_curl_abort = false;
-
- // Handle abort
- connect(m_progress_dialog, &QProgressDialog::canceled, [this] { m_curl_abort = true; });
- connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater);
-
- auto thread = QThread::create([&]
- {
- const auto result = curl_easy_perform(m_curl->get_curl());
- m_curl_result = result == CURLE_OK;
-
- if (!m_curl_result)
- {
- Q_EMIT DownloadError(qstr("Curl error: ") + qstr(curl_easy_strerror(result)));
- }
- });
- connect(thread, &QThread::finished, this, [this, online]()
- {
- if (m_progress_dialog)
- {
- m_progress_dialog->close();
- m_progress_dialog = nullptr;
- }
-
- if (!m_curl_result)
- {
- return;
- }
-
- compat_log.notice("Database download finished");
-
- // Create new map from database and write database to file if database was valid
- if (ReadJSON(QJsonDocument::fromJson(m_curl_buf).object(), online))
- {
- // We have a new database in map, therefore refresh gamelist to new state
- Q_EMIT DownloadFinished();
-
- // Write database to file
- QFile file(m_filepath);
-
- if (file.exists())
- {
- compat_log.notice("Database file found: %s", sstr(m_filepath));
- }
-
- if (!file.open(QIODevice::WriteOnly))
- {
- compat_log.error("Database Error - Could not write database to file: %s", sstr(m_filepath));
- return;
- }
-
- file.write(m_curl_buf);
- file.close();
-
- compat_log.success("Wrote database to file: %s", sstr(m_filepath));
- }
- });
- thread->setObjectName("Compat Update");
- thread->start();
+ m_downloader->start(url, true, true, tr("Downloading Database"));
// We want to retrieve a new database, therefore refresh gamelist and indicate that
Q_EMIT DownloadStarted();
diff --git a/rpcs3/rpcs3qt/game_compatibility.h b/rpcs3/rpcs3qt/game_compatibility.h
index 6496717861..ffc5f51040 100644
--- a/rpcs3/rpcs3qt/game_compatibility.h
+++ b/rpcs3/rpcs3qt/game_compatibility.h
@@ -5,9 +5,8 @@
#include
#include
-class curl_handle;
+class downloader;
class gui_settings;
-class progress_dialog;
struct compat_status
{
@@ -35,16 +34,9 @@ private:
{ "NoData", { 6, "", "", tr("Database missing"), tr("Right click here and download the current database.\nMake sure you are connected to the internet.") } },
{ "Download", { 7, "", "", tr("Retrieving..."), tr("Downloading the compatibility database. Please wait...") } }
};
- int m_timer_count = 0;
+ std::shared_ptr m_gui_settings;
QString m_filepath;
- std::string m_url;
- std::atomic m_curl_result = false;
- std::atomic m_curl_abort = false;
- double m_actual_dwnld_size = -1.0;
- curl_handle* m_curl = nullptr;
- QByteArray m_curl_buf;
- progress_dialog* m_progress_dialog = nullptr;
- std::shared_ptr m_xgui_settings;
+ downloader* m_downloader = nullptr;
std::map m_compat_database;
/** Creates new map from the database */
@@ -52,7 +44,7 @@ private:
public:
/** Handles reads, writes and downloads for the compatibility database */
- game_compatibility(std::shared_ptr settings);
+ game_compatibility(std::shared_ptr settings, QWidget* parent);
/** Reads database. If online set to true: Downloads and writes the database to file */
void RequestCompatibility(bool online = false);
@@ -63,16 +55,14 @@ public:
/** Returns the data for the requested status */
compat_status GetStatusData(const QString& status);
- size_t update_buffer(char* data, size_t size);
-
Q_SIGNALS:
void DownloadStarted();
void DownloadFinished();
void DownloadError(const QString& error);
- void signal_buffer_update(int size, int max);
private Q_SLOTS:
- void handle_buffer_update(int size, int max);
+ void handle_download_error(const QString& error);
+ void handle_download_finished(const QByteArray& data);
};
class compat_pixmap : public QPixmap
diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp
index fa7d065cf7..79b2e3b7e1 100644
--- a/rpcs3/rpcs3qt/game_list_frame.cpp
+++ b/rpcs3/rpcs3qt/game_list_frame.cpp
@@ -95,7 +95,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std
m_game_list->installEventFilter(this);
m_game_list->setColumnCount(gui::column_count);
- m_game_compat = std::make_unique(m_gui_settings);
+ m_game_compat = new game_compatibility(m_gui_settings, this);
m_central_widget = new QStackedWidget(this);
m_central_widget->addWidget(m_game_list);
@@ -143,7 +143,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std
connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::itemSelectionChangedSlot);
connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot);
- connect(m_game_compat.get(), &game_compatibility::DownloadStarted, [this]()
+ connect(m_game_compat, &game_compatibility::DownloadStarted, [this]()
{
for (const auto& game : m_game_data)
{
@@ -151,7 +151,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std
}
Refresh();
});
- connect(m_game_compat.get(), &game_compatibility::DownloadFinished, [this]()
+ connect(m_game_compat, &game_compatibility::DownloadFinished, [this]()
{
for (const auto& game : m_game_data)
{
@@ -159,14 +159,14 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std
}
Refresh();
});
- connect(m_game_compat.get(), &game_compatibility::DownloadError, [this](const QString& error)
+ connect(m_game_compat, &game_compatibility::DownloadError, [this](const QString& error)
{
for (const auto& game : m_game_data)
{
game->compat = m_game_compat->GetCompatibility(game->info.serial);
}
Refresh();
- QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n") + tr(qPrintable(error)));
+ QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n%0").arg(error));
});
for (int col = 0; col < m_columnActs.count(); ++col)
diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h
index 3f450d4ab6..c6f0b8e061 100644
--- a/rpcs3/rpcs3qt/game_list_frame.h
+++ b/rpcs3/rpcs3qt/game_list_frame.h
@@ -128,15 +128,15 @@ private:
game_info GetGameInfoFromItem(const QTableWidgetItem* item);
// Which widget we are displaying depends on if we are in grid or list mode.
- QMainWindow* m_game_dock;
- QStackedWidget* m_central_widget;
+ QMainWindow* m_game_dock = nullptr;
+ QStackedWidget* m_central_widget = nullptr;
// Game Grid
- game_list_grid* m_game_grid;
+ game_list_grid* m_game_grid = nullptr;
// Game List
- game_list* m_game_list;
- std::unique_ptr m_game_compat;
+ game_list* m_game_list = nullptr;
+ game_compatibility* m_game_compat = nullptr;
QList m_columnActs;
Qt::SortOrder m_col_sort_order;
int m_sort_column;
diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp
index c2f5b6c768..ea42fe2e73 100644
--- a/rpcs3/rpcs3qt/main_window.cpp
+++ b/rpcs3/rpcs3qt/main_window.cpp
@@ -176,10 +176,35 @@ void main_window::Init()
// Fix possible hidden game list columns. The game list has to be visible already. Use this after show()
m_game_list_frame->FixNarrowColumns();
-#if defined(_WIN32) || defined(__linux__)
- if (m_gui_settings->GetValue(gui::m_check_upd_start).toBool())
+ // RPCS3 Updater
+
+ QMenuBar *corner_bar = new QMenuBar(ui->menuBar);
+
+ QMenu *download_menu = new QMenu(tr("Update Available!"), corner_bar);
+ corner_bar->addMenu(download_menu);
+
+ QAction *download_action = new QAction(tr("Download Update"), download_menu);
+ connect(download_action, &QAction::triggered, this, [this]
{
- m_updater.check_for_updates(true, this);
+ m_updater.update(false);
+ });
+
+ download_menu->addAction(download_action);
+ ui->menuBar->setCornerWidget(corner_bar);
+ ui->menuBar->cornerWidget()->setVisible(false);
+
+ connect(&m_updater, &update_manager::signal_update_available, this, [this](bool update_available)
+ {
+ if (ui->menuBar->cornerWidget())
+ {
+ ui->menuBar->cornerWidget()->setVisible(update_available);
+ }
+ });
+
+#if defined(_WIN32) || defined(__linux__)
+ if (const auto update_value = m_gui_settings->GetValue(gui::m_check_upd_start).toString(); update_value != "false")
+ {
+ m_updater.check_for_updates(true, update_value != "true", this);
}
#endif
}
@@ -1538,7 +1563,7 @@ void main_window::CreateConnects()
std::unordered_map> games;
if (m_game_list_frame)
{
- for (const auto game : m_game_list_frame->GetGameInfo())
+ for (const auto& game : m_game_list_frame->GetGameInfo())
{
if (game)
{
@@ -1675,7 +1700,7 @@ void main_window::CreateConnects()
QMessageBox::warning(this, tr("Auto-updater"), tr("Please stop the emulation before trying to update."));
return;
}
- m_updater.check_for_updates(false, this);
+ m_updater.check_for_updates(false, false, this);
});
connect(ui->aboutAct, &QAction::triggered, [this]
diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp
index f7b2949a4d..7b44b90197 100644
--- a/rpcs3/rpcs3qt/qt_utils.cpp
+++ b/rpcs3/rpcs3qt/qt_utils.cpp
@@ -341,7 +341,7 @@ namespace gui
{
bool match = true;
- for (const auto [role, data] : criteria)
+ for (const auto& [role, data] : criteria)
{
if (item->data(0, role) != data)
{
diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp
index 885806fcd6..d1ff368004 100644
--- a/rpcs3/rpcs3qt/settings_dialog.cpp
+++ b/rpcs3/rpcs3qt/settings_dialog.cpp
@@ -1537,7 +1537,18 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std
ui->cb_show_pkg_install->setChecked(m_gui_settings->GetValue(gui::ib_pkg_success).toBool());
ui->cb_show_pup_install->setChecked(m_gui_settings->GetValue(gui::ib_pup_success).toBool());
- ui->cb_check_update_start->setChecked(m_gui_settings->GetValue(gui::m_check_upd_start).toBool());
+ const QString updates_yes = tr("Yes", "Updates");
+ const QString updates_background = tr("Background", "Updates");
+ const QString updates_no = tr("No", "Updates");
+
+ ui->combo_updates->addItem(updates_yes, "true");
+ ui->combo_updates->addItem(updates_background, "background");
+ ui->combo_updates->addItem(updates_no, "false");
+ ui->combo_updates->setCurrentIndex(ui->combo_updates->findData(m_gui_settings->GetValue(gui::m_check_upd_start).toString()));
+ connect(ui->combo_updates, static_cast(&QComboBox::currentIndexChanged), [this](int index)
+ {
+ m_gui_settings->SetValue(gui::m_check_upd_start, ui->combo_updates->itemData(index));
+ });
const bool enable_ui_colors = m_gui_settings->GetValue(gui::m_enableUIColors).toBool();
ui->cb_custom_colors->setChecked(enable_ui_colors);
@@ -1612,10 +1623,6 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std
{
m_gui_settings->SetValue(gui::ib_pup_success, val);
});
- connect(ui->cb_check_update_start, &QCheckBox::clicked, [this](bool val)
- {
- m_gui_settings->SetValue(gui::m_check_upd_start, val);
- });
connect(ui->cb_custom_colors, &QCheckBox::clicked, [this](bool val)
{
diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui
index a195cf244b..6958f17a3a 100644
--- a/rpcs3/rpcs3qt/settings_dialog.ui
+++ b/rpcs3/rpcs3qt/settings_dialog.ui
@@ -3142,13 +3142,6 @@
- -
-
-
- Check for updates on startup
-
-
-
-
@@ -3168,6 +3161,18 @@
+ -
+
+
+ Check for updates on startup
+
+
+
-
+
+
+
+
+
-
diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h
index 3e3cdf533c..194b77138c 100644
--- a/rpcs3/rpcs3qt/tooltips.h
+++ b/rpcs3/rpcs3qt/tooltips.h
@@ -163,7 +163,7 @@ public:
const QString show_boot_game = tr("Shows a confirmation dialog when a game was booted while another game is running.");
const QString show_pkg_install = tr("Shows a dialog when packages were installed successfully.");
const QString show_pup_install = tr("Shows a dialog when firmware was installed successfully.");
- const QString check_update_start = tr("Check if an update is available on startup.");
+ const QString check_update_start = tr("Checks if an update is available on startup and asks if you want to update.\nIf \"Background\" is selected, the check is done silently in the background and a new download option is shown in the top right corner of the menu if a new version was found.");
const QString use_rich_presence = tr("Enables use of Discord Rich Presence to show what game you are playing on Discord.\nRequires a restart of RPCS3 to completely close the connection.");
const QString discord_state = tr("Tell your friends what you are doing.");
const QString custom_colors = tr("Prioritize custom user interface colors over properties set in stylesheet.");
diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp
index 5c1e22c901..9e43368495 100644
--- a/rpcs3/rpcs3qt/update_manager.cpp
+++ b/rpcs3/rpcs3qt/update_manager.cpp
@@ -3,7 +3,7 @@
#include "progress_dialog.h"
#include "localized.h"
#include "rpcs3_version.h"
-#include "curl_handle.h"
+#include "downloader.h"
#include "Utilities/StrUtil.h"
#include "Crypto/sha256.h"
#include "Emu/System.h"
@@ -35,47 +35,11 @@
LOG_CHANNEL(update_log, "UPDATER");
-size_t curl_write_cb(char* ptr, size_t /*size*/, size_t nmemb, void* userdata)
-{
- update_manager* upd_mgr = reinterpret_cast(userdata);
- return upd_mgr->update_buffer(ptr, nmemb);
-}
-
update_manager::update_manager()
{
- m_curl = new curl_handle(this);
-
- // We need this signal in order to update the GUI from the main thread
- connect(this, &update_manager::signal_buffer_update, this, &update_manager::handle_buffer_update);
}
-void update_manager::handle_buffer_update(int size)
-{
- if (m_progress_dialog && m_update_dialog)
- {
- m_progress_dialog->setValue(size);
- QApplication::processEvents();
- }
-}
-
-size_t update_manager::update_buffer(char* data, size_t size)
-{
- if (m_curl_abort)
- {
- return 0;
- }
-
- const auto old_size = m_curl_buf.size();
- const auto new_size = old_size + size;
- m_curl_buf.resize(static_cast(new_size));
- memcpy(m_curl_buf.data() + old_size, data, size);
-
- Q_EMIT signal_buffer_update(static_cast(new_size));
-
- return size;
-}
-
-void update_manager::check_for_updates(bool automatic, QWidget* parent)
+void update_manager::check_for_updates(bool automatic, bool check_only, QWidget* parent)
{
#ifdef __linux__
if (automatic && !::getenv("APPIMAGE"))
@@ -85,59 +49,42 @@ void update_manager::check_for_updates(bool automatic, QWidget* parent)
}
#endif
- m_parent = parent;
- m_curl_abort = false;
- m_update_dialog = false;
- m_curl_buf.clear();
+ m_parent = parent;
+ m_downloader = new downloader("RPCS3 Updater", parent);
- m_progress_dialog = new progress_dialog(tr("Checking For Updates"), tr("Please wait..."), tr("Abort"), 0, 100, true, parent);
- m_progress_dialog->setAutoClose(false);
- m_progress_dialog->setAutoReset(false);
- m_progress_dialog->show();
-
- connect(m_progress_dialog, &QProgressDialog::canceled, [this]() { m_curl_abort = true; });
- connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater);
-
- const std::string request_url = "https://update.rpcs3.net/?api=v1&c=" + rpcs3::get_commit_and_hash().second;
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, request_url.c_str());
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb);
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this);
-
- auto thread = QThread::create([this]
+ connect(m_downloader, &downloader::signal_download_error, this, [this, automatic](const QString& /*error*/)
{
- const auto curl_result = curl_easy_perform(m_curl->get_curl());
- m_curl_result = curl_result == CURLE_OK;
-
- if (!m_curl_result)
+ if (!automatic)
{
- update_log.error("Curl error(query): %s", curl_easy_strerror(curl_result));
+ QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information."));
}
});
- connect(thread, &QThread::finished, this, [this, automatic]()
- {
- const bool result_json = m_curl_result && handle_json(automatic);
- if (!result_json && !m_curl_abort)
+ connect(m_downloader, &downloader::signal_download_finished, this, [this, automatic, check_only](const QByteArray& data)
+ {
+ const bool result_json = handle_json(automatic, check_only, data);
+
+ if (!result_json)
{
+ // The progress dialog is configured to stay open, so we need to close it manually if the download succeeds.
+ m_downloader->close_progress_dialog();
+
if (!automatic)
{
QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information."));
}
-
- if (m_progress_dialog)
- {
- m_progress_dialog->close();
- m_progress_dialog = nullptr;
- }
}
+
+ Q_EMIT signal_update_available(result_json);
});
- thread->setObjectName("RPCS3 Update Check");
- thread->start();
+
+ const std::string url = "https://update.rpcs3.net/?api=v1&c=" + rpcs3::get_commit_and_hash().second;
+ m_downloader->start(url, true, !automatic, tr("Checking For Updates"), true);
}
-bool update_manager::handle_json(bool automatic)
+bool update_manager::handle_json(bool automatic, bool check_only, const QByteArray& data)
{
- const QJsonObject json_data = QJsonDocument::fromJson(m_curl_buf).object();
+ const QJsonObject json_data = QJsonDocument::fromJson(data).object();
const int return_code = json_data["return_code"].toInt(-255);
bool hash_found = true;
@@ -202,7 +149,7 @@ bool update_manager::handle_json(bool automatic)
if (hash_found && return_code == 0)
{
update_log.success("RPCS3 is up to date!");
- m_progress_dialog->close();
+ m_downloader->close_progress_dialog();
if (!automatic)
QMessageBox::information(m_parent, tr("Auto-updater"), tr("Your version is already up to date!"));
@@ -225,11 +172,9 @@ bool update_manager::handle_json(bool automatic)
Localized localized;
- QString message;
-
if (hash_found)
{
- message = tr("A new version of RPCS3 is available!\n\nCurrent version: %0 (%1)\nLatest version: %2 (%3)\nYour version is %4 old.\n\nDo you want to update?")
+ m_update_message = tr("A new version of RPCS3 is available!\n\nCurrent version: %0 (%1)\nLatest version: %2 (%3)\nYour version is %4 old.\n\nDo you want to update?")
.arg(current["version"].toString())
.arg(cur_str)
.arg(latest["version"].toString())
@@ -238,73 +183,72 @@ bool update_manager::handle_json(bool automatic)
}
else
{
- message = tr("You're currently using a custom or PR build.\n\nLatest version: %0 (%1)\nThe latest version is %2 old.\n\nDo you want to update to the latest official RPCS3 version?")
+ m_update_message = tr("You're currently using a custom or PR build.\n\nLatest version: %0 (%1)\nThe latest version is %2 old.\n\nDo you want to update to the latest official RPCS3 version?")
.arg(latest["version"].toString())
.arg(lts_str)
.arg(localized.GetVerboseTimeByMs(std::abs(diff_msec), true));
}
- if (QMessageBox::question(m_progress_dialog, tr("Update Available"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
- {
- m_progress_dialog->close();
- return true;
- }
-
+ m_request_url = latest[os]["download"].toString().toStdString();
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->setMaximum(m_expected_size);
- m_progress_dialog->setValue(0);
- m_update_dialog = true;
-
- const std::string request_url = latest[os]["download"].toString().toStdString();
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, request_url.c_str());
- curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, 1);
-
- m_curl_buf.clear();
-
- auto thread = QThread::create([this]
+ if (check_only)
{
- const auto curl_result = curl_easy_perform(m_curl->get_curl());
- m_curl_result = curl_result == CURLE_OK;
+ m_downloader->close_progress_dialog();
+ return true;
+ }
- if (!m_curl_result)
+ update(automatic);
+ return true;
+}
+
+void update_manager::update(bool automatic)
+{
+ if (QMessageBox::question(m_downloader->get_progress_dialog(), tr("Update Available"), m_update_message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
+ {
+ m_downloader->close_progress_dialog();
+ return;
+ }
+
+ m_downloader->disconnect();
+
+ connect(m_downloader, &downloader::signal_download_error, this, [this, automatic](const QString& /*error*/)
+ {
+ if (!automatic)
{
- update_log.error("Curl error(download): %s", curl_easy_strerror(curl_result));
+ QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information."));
}
});
- connect(thread, &QThread::finished, this, [this, automatic]()
- {
- const bool result_rpcs3 = m_curl_result && handle_rpcs3();
- if (!result_rpcs3 && !m_curl_abort)
+ connect(m_downloader, &downloader::signal_download_finished, this, [this, automatic](const QByteArray& data)
+ {
+ const bool result_json = handle_rpcs3(data);
+
+ if (!result_json)
{
+ // The progress dialog is configured to stay open, so we need to close it manually if the download succeeds.
+ m_downloader->close_progress_dialog();
+
if (!automatic)
{
QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information."));
}
-
- if (m_progress_dialog)
- {
- m_progress_dialog->close();
- m_progress_dialog = nullptr;
- }
}
- });
- thread->setObjectName("RPCS3 Updater");
- thread->start();
- return true;
+ Q_EMIT signal_update_available(false);
+ });
+
+ m_downloader->start(m_request_url, true, true, tr("Downloading Update"), true, m_expected_size);
}
-bool update_manager::handle_rpcs3()
+bool update_manager::handle_rpcs3(const QByteArray& data)
{
- if (m_expected_size != m_curl_buf.size() + 0u)
+ m_downloader->update_progress_dialog(tr("Updating RPCS3"));
+
+ if (m_expected_size != data.size() + 0u)
{
- update_log.error("Download size mismatch: %d expected: %d", m_curl_buf.size(), m_expected_size);
+ update_log.error("Download size mismatch: %d expected: %d", data.size(), m_expected_size);
return false;
}
@@ -312,7 +256,7 @@ bool update_manager::handle_rpcs3()
mbedtls_sha256_context ctx;
mbedtls_sha256_init(&ctx);
mbedtls_sha256_starts_ret(&ctx, 0);
- mbedtls_sha256_update_ret(&ctx, reinterpret_cast(m_curl_buf.data()), m_curl_buf.size());
+ mbedtls_sha256_update_ret(&ctx, reinterpret_cast(data.data()), data.size());
mbedtls_sha256_finish_ret(&ctx, res_hash);
std::string res_hash_string("0000000000000000000000000000000000000000000000000000000000000000");
@@ -329,9 +273,8 @@ bool update_manager::handle_rpcs3()
return false;
}
- std::string replace_path;
-
#ifdef _WIN32
+
// Get executable path
const std::string orig_path = Emulator::GetExeDir() + "rpcs3.exe";
@@ -340,61 +283,6 @@ bool update_manager::handle_rpcs3()
wchar_orig_path.resize(tmp_size);
MultiByteToWideChar(CP_UTF8, 0, orig_path.c_str(), -1, wchar_orig_path.data(), tmp_size);
-#endif
-
-#ifdef __linux__
-
- const char* appimage_path = ::getenv("APPIMAGE");
- if (appimage_path != nullptr)
- {
- replace_path = appimage_path;
- update_log.notice("Found AppImage path: %s", appimage_path);
- }
- else
- {
- update_log.warning("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)
- {
- update_log.error("Failed to find executable path");
- return false;
- }
-
- exe_path[len] = '\0';
- update_log.trace("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
- const std::string move_dest = replace_path + "_old";
- fs::rename(replace_path, move_dest, true);
- fs::file new_appimage(replace_path, fs::read + fs::write + fs::create + fs::trunc);
- if (!new_appimage)
- {
- update_log.error("Failed to create new AppImage file: %s", replace_path);
- return false;
- }
- if (new_appimage.write(m_curl_buf.data(), m_curl_buf.size()) != m_curl_buf.size() + 0u)
- {
- 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)
- {
- update_log.error("Failed to chmod rwxrxrx %s", replace_path);
- return false;
- }
- new_appimage.close();
-
- update_log.success("Successfully updated %s!", replace_path);
-
-#elif defined(_WIN32)
-
char temp_path[PATH_MAX];
GetTempPathA(sizeof(temp_path) - 1, temp_path);
@@ -409,15 +297,13 @@ bool update_manager::handle_rpcs3()
update_log.error("Failed to create temporary file: %s", tmpfile_path);
return false;
}
- if (tmpfile.write(m_curl_buf.data(), m_curl_buf.size()) != m_curl_buf.size())
+ if (tmpfile.write(data.data(), data.size()) != data.size())
{
update_log.error("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;
@@ -596,8 +482,62 @@ bool update_manager::handle_rpcs3()
error_free7z();
if (res)
return false;
+
+#else
+
+ std::string replace_path;
+
+ const char* appimage_path = ::getenv("APPIMAGE");
+ if (appimage_path != nullptr)
+ {
+ replace_path = appimage_path;
+ update_log.notice("Found AppImage path: %s", appimage_path);
+ }
+ else
+ {
+ update_log.warning("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)
+ {
+ update_log.error("Failed to find executable path");
+ return false;
+ }
+
+ exe_path[len] = '\0';
+ update_log.trace("Found exec path: %s", exe_path);
+
+ replace_path = exe_path;
+ }
+
+ // Move the appimage/exe and replace with new appimage
+ const std::string move_dest = replace_path + "_old";
+ fs::rename(replace_path, move_dest, true);
+ fs::file new_appimage(replace_path, fs::read + fs::write + fs::create + fs::trunc);
+ if (!new_appimage)
+ {
+ update_log.error("Failed to create new AppImage file: %s", replace_path);
+ return false;
+ }
+ if (new_appimage.write(data.data(), data.size()) != data.size() + 0u)
+ {
+ 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)
+ {
+ update_log.error("Failed to chmod rwxrxrx %s", replace_path);
+ return false;
+ }
+ new_appimage.close();
+
+ update_log.success("Successfully updated %s!", replace_path);
+
#endif
+ m_downloader->close_progress_dialog();
+
QMessageBox::information(m_parent, tr("Auto-updater"), tr("Update successful!\nRPCS3 will now restart."));
#ifdef _WIN32
diff --git a/rpcs3/rpcs3qt/update_manager.h b/rpcs3/rpcs3qt/update_manager.h
index 5865c50e7d..7cef3799bd 100644
--- a/rpcs3/rpcs3qt/update_manager.h
+++ b/rpcs3/rpcs3qt/update_manager.h
@@ -4,37 +4,30 @@
#include
#include
-class curl_handle;
-class progress_dialog;
+class downloader;
class update_manager final : public QObject
{
Q_OBJECT
private:
- std::atomic m_update_dialog = false;
- progress_dialog* m_progress_dialog = nullptr;
- QWidget* m_parent = nullptr;
+ downloader* m_downloader = nullptr;
+ QWidget* m_parent = nullptr;
- curl_handle* m_curl = nullptr;
- QByteArray m_curl_buf;
- std::atomic m_curl_abort = false;
- std::atomic m_curl_result = false;
+ QString m_update_message;
+ std::string m_request_url;
std::string m_expected_hash;
u64 m_expected_size = 0;
- bool handle_json(bool automatic);
- bool handle_rpcs3();
+ bool handle_json(bool automatic, bool check_only, const QByteArray& data);
+ bool handle_rpcs3(const QByteArray& data);
public:
update_manager();
- void check_for_updates(bool automatic, QWidget* parent = nullptr);
- size_t update_buffer(char* data, size_t size);
+ void check_for_updates(bool automatic, bool check_only, QWidget* parent = nullptr);
+ void update(bool automatic);
Q_SIGNALS:
- void signal_buffer_update(int size);
-
-private Q_SLOTS:
- void handle_buffer_update(int size);
+ void signal_update_available(bool update_available);
};