From 6c9445ff28ca13466b3cc29887543e9a0f0364a3 Mon Sep 17 00:00:00 2001 From: oltolm Date: Fri, 10 Apr 2026 01:09:46 +0200 Subject: [PATCH 01/10] cmake: do not disable "WOLFSSL_HARDEN" and fix build with latest WolfSSL --- 3rdparty/curl/CMakeLists.txt | 30 +++++++++++++++--------------- 3rdparty/wolfssl/CMakeLists.txt | 31 +++++++++++++++---------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/3rdparty/curl/CMakeLists.txt b/3rdparty/curl/CMakeLists.txt index 2b725169f9..0079315267 100644 --- a/3rdparty/curl/CMakeLists.txt +++ b/3rdparty/curl/CMakeLists.txt @@ -7,24 +7,24 @@ if(USE_SYSTEM_CURL) target_link_libraries(3rdparty_libcurl INTERFACE CURL::libcurl) else() message(STATUS "RPCS3: building libcurl + wolfssl submodules") - set(BUILD_CURL_EXE OFF CACHE BOOL "Set to ON to build curl executable.") - set(BUILD_STATIC_CURL OFF CACHE BOOL "Set to ON to build curl executable with static libcurl.") - set(BUILD_STATIC_LIBS ON CACHE BOOL "Set to ON to build static libcurl.") - set(BUILD_SHARED_LIBS OFF CACHE BOOL "Set to ON to build shared libcurl.") + set(BUILD_CURL_EXE OFF CACHE INTERNAL "") + set(BUILD_STATIC_CURL OFF CACHE INTERNAL "") + set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") + set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") find_package(WolfSSL REQUIRED) - set(CURL_USE_WOLFSSL ON CACHE BOOL "enable wolfSSL for SSL/TLS") - set(CURL_USE_OPENSSL OFF CACHE BOOL "Use OpenSSL code. Experimental") - set(HTTP_ONLY ON CACHE BOOL "disables all protocols except HTTP (This overrides all CURL_DISABLE_* options)") - set(USE_LIBIDN2 OFF CACHE BOOL "Use libidn2 for IDN support") # Disabled because MacOS CI doesn't work otherwise - set(CURL_CA_PATH "none" CACHE STRING "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") - option(CURL_DISABLE_INSTALL "Disable installation targets" ON) + set(CURL_USE_WOLFSSL ON CACHE INTERNAL "") + set(CURL_USE_OPENSSL OFF CACHE INTERNAL "") + set(HTTP_ONLY ON CACHE INTERNAL "") + set(USE_LIBIDN2 OFF CACHE INTERNAL "") # Disabled because MacOS CI doesn't work otherwise + set(CURL_CA_PATH "none" CACHE INTERNAL "") + set(CURL_DISABLE_INSTALL ON CACHE INTERNAL "") if(WIN32) - set(ENABLE_UNICODE ON CACHE BOOL "enable Unicode") + set(ENABLE_UNICODE ON CACHE INTERNAL "") endif() - set(CURL_USE_LIBSSH2 OFF CACHE BOOL "Use libSSH2") - set(CURL_USE_LIBPSL OFF CACHE BOOL "Use libPSL") - option(BUILD_TESTING "Build tests" OFF) - option(BUILD_EXAMPLES "Build libcurl examples" OFF) + set(CURL_USE_LIBSSH2 OFF CACHE INTERNAL "") + set(CURL_USE_LIBPSL OFF CACHE INTERNAL "") + set(BUILD_TESTING OFF CACHE INTERNAL "") + set(BUILD_EXAMPLES OFF CACHE INTERNAL "") add_subdirectory(curl EXCLUDE_FROM_ALL) diff --git a/3rdparty/wolfssl/CMakeLists.txt b/3rdparty/wolfssl/CMakeLists.txt index cf1a66a1f5..2c19c37d2f 100644 --- a/3rdparty/wolfssl/CMakeLists.txt +++ b/3rdparty/wolfssl/CMakeLists.txt @@ -4,23 +4,22 @@ if(USE_SYSTEM_WOLFSSL) add_library(wolfssl INTERFACE) target_link_libraries(wolfssl INTERFACE PkgConfig::WolfSSL) else() - option(WOLFSSL_TLS13 "Enable wolfSSL TLS v1.3 (default: enabled)" OFF) - set(WOLFSSL_SHA3 ON CACHE STRING "Enable wolfSSL SHA-3 support (default: enabled on x86_64/aarch64)") - set(WOLFSSL_SHAKE256 ON CACHE STRING "Enable wolfSSL SHAKE256 support (default: enabled on x86_64/aarch64)") - option(WOLFSSL_BASE64_ENCODE "Enable Base64 encoding (default: enabled on x86_64)" OFF) - option(WOLFSSL_DES3 "Enable DES3 (default: disabled)" ON) - option(WOLFSSL_PWDBASED "Enable PWDBASED (default: disabled)" ON) - option(WOLFSSL_FAST_MATH "Enable fast math ops (default: disabled)" ON) - option(WOLFSSL_EXAMPLES "Enable examples (default: enabled)" OFF) - option(WOLFSSL_CRYPT_TESTS "Enable Crypt Bench/Test (default: enabled)" OFF) - option(WOLFSSL_ASYNC_THREADS "Enable Asynchronous Threading (default: enabled)" OFF) - option(WOLFSSL_BUILD_OUT_OF_TREE "Don't generate files in the source tree (default: yes)" ON) - option(WOLFSSL_SNI "Enable SNI (default: disabled)" ON) - option(WOLFSSL_OPENSSLEXTRA "Enable extra OpenSSL API, size+ (default: disabled)" ON) - option(WOLFSSL_HARDEN "Enable Hardened build, Enables Timing Resistance and Blinding (default: enabled)" OFF) - option(WOLFSSL_ALT_CERT_CHAINS "Enable support for Alternate certification chains (default: disabled)" ON) + set(WOLFSSL_TLS13 OFF CACHE INTERNAL "") + set(WOLFSSL_SHA3 ON CACHE INTERNAL "") + set(WOLFSSL_SHAKE256 ON CACHE INTERNAL "") + set(WOLFSSL_BASE64_ENCODE OFF CACHE INTERNAL "") + set(WOLFSSL_DES3 ON CACHE INTERNAL "") + set(WOLFSSL_PWDBASED ON CACHE INTERNAL "") + set(WOLFSSL_FAST_MATH ON CACHE INTERNAL "") + set(WOLFSSL_EXAMPLES OFF CACHE INTERNAL "") + set(WOLFSSL_CRYPT_TESTS OFF CACHE INTERNAL "") + set(WOLFSSL_ASYNC_THREADS OFF CACHE INTERNAL "") + set(WOLFSSL_BUILD_OUT_OF_TREE ON CACHE INTERNAL "") + set(WOLFSSL_SNI ON CACHE INTERNAL "") + set(WOLFSSL_OPENSSLEXTRA ON CACHE INTERNAL "") + set(WOLFSSL_ALT_CERT_CHAINS ON CACHE INTERNAL "") add_subdirectory(wolfssl EXCLUDE_FROM_ALL) - target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP FP_MAX_BITS=8192 WOLFSSL_NO_OPTIONS_H) + target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP FP_MAX_BITS=8192 WOLFSSL_USE_OPTIONS_H) endif() From d4b7c31ec8ee6f564f230c5ac01cc2c56b8b7b3c Mon Sep 17 00:00:00 2001 From: digant73 Date: Wed, 15 Apr 2026 23:48:24 +0200 Subject: [PATCH 02/10] Allow to skip optional packages installation minor cleanup minor cleanup --- rpcs3/rpcs3qt/main_window.cpp | 5 +++-- rpcs3/rpcs3qt/pkg_install_dialog.cpp | 7 ++++++- rpcs3/rpcs3qt/pkg_install_dialog.h | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index da66bd4f38..c5e7eb09b4 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -984,7 +984,7 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo const game_compatibility* compat = m_game_list_frame ? m_game_list_frame->GetGameCompatibility() : nullptr; // Let the user choose the packages to install and select the order in which they shall be installed. - pkg_install_dialog dlg(file_paths, compat, this); + pkg_install_dialog dlg(file_paths, from_boot, compat, this); connect(&dlg, &QDialog::finished, this, [&](int result) { if (result != QDialog::Accepted) @@ -1009,7 +1009,8 @@ bool main_window::HandlePackageInstallation(QStringList file_paths, bool from_bo if (canceled) { - return false; + // return "true" if installation of optional packages (requested by some games at first boot) is skipped + return from_boot; } if (!from_boot) diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp index a97f656153..a1ebb9a8d1 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.cpp +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -24,7 +24,7 @@ enum Roles DataSizeRole = Qt::UserRole + 5, }; -pkg_install_dialog::pkg_install_dialog(const QStringList& paths, const game_compatibility* compat, QWidget* parent) +pkg_install_dialog::pkg_install_dialog(const QStringList& paths, bool from_boot, const game_compatibility* compat, QWidget* parent) : QDialog(parent) { ensure(!paths.empty()); @@ -148,6 +148,11 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, const game_comp buttons->button(QDialogButtonBox::Ok)->setText(tr("Install")); buttons->button(QDialogButtonBox::Ok)->setDefault(true); + if (from_boot) + { + buttons->button(QDialogButtonBox::Cancel)->setText(tr("Skip")); + } + m_dir_list->sortItems(); m_dir_list->setCurrentRow(0); m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100); diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.h b/rpcs3/rpcs3qt/pkg_install_dialog.h index a6174276c3..979d7f5682 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.h +++ b/rpcs3/rpcs3qt/pkg_install_dialog.h @@ -17,7 +17,7 @@ class pkg_install_dialog : public QDialog Q_OBJECT public: - explicit pkg_install_dialog(const QStringList& paths, const game_compatibility* compat, QWidget* parent = nullptr); + explicit pkg_install_dialog(const QStringList& paths, bool from_boot, const game_compatibility* compat, QWidget* parent = nullptr); std::vector get_paths_to_install() const; bool precompile_caches() const { return m_precompile_caches; } bool create_desktop_shortcuts() const { return m_create_desktop_shortcuts; } From 2b144101ab6acaa6f62b4dd92c66d215aec5e19f Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 18 Apr 2026 16:54:13 +0200 Subject: [PATCH 03/10] Update libpng to 1.6.58 --- 3rdparty/libpng/libpng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index 95ab3fdca8..3061454d98 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit 95ab3fdca83ea294efd3b092e9a53c5a39886444 +Subproject commit 3061454d980de7d53608f594194cfac722721d2a From aff082e0c178cd6cbe600a04b2b802eb422f7741 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 18 Apr 2026 17:14:18 +0200 Subject: [PATCH 04/10] version_check: fix formating of result output --- 3rdparty/version_check.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/3rdparty/version_check.sh b/3rdparty/version_check.sh index e0632da3e7..7947a006c1 100644 --- a/3rdparty/version_check.sh +++ b/3rdparty/version_check.sh @@ -119,7 +119,8 @@ echo -e "\n\nResult:\n" # Find the max length of the paths (before '->') max_len=0 -while IFS='->' read -r left _; do +while read -r line; do + left="${line%%->*}" len=$(echo -n "$left" | wc -c) if (( len > max_len )); then max_len=$len @@ -127,8 +128,10 @@ while IFS='->' read -r left _; do done < "$resultfile" # Print with padding so '->' lines up -while IFS='->' read -r left right; do - right=$(echo "$right" | sed 's/^[[:space:]]*>*[[:space:]]*//') +while read -r line; do + left="${line%%->*}" + right="${line#*->}" + right=$(echo "$right" | sed 's/^[[:space:]]*//') printf "%-${max_len}s -> %s\n" "$left" "$right" done < "$resultfile" From 7a4d7aa936058b9734309f7901d18e1324a0273d Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 18 Apr 2026 17:37:13 +0200 Subject: [PATCH 05/10] Improve quit logging --- rpcs3/Emu/System.cpp | 4 +++- rpcs3/headless_application.cpp | 2 +- rpcs3/rpcs3qt/gui_application.cpp | 4 +++- rpcs3/rpcs3qt/main_window.cpp | 1 + 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 40a87fcab8..e92dc9f8a0 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -4018,7 +4018,9 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s if (allow_autoexit) { - Quit(g_cfg.misc.autoexit.get()); + const bool autoexit = g_cfg.misc.autoexit.get(); + sys_log.notice("Quit with main_window::closeEvent. (autoexit=%d)", autoexit); + Quit(autoexit); } if (after_kill_callback) diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index 5d9240254d..cd8e027f57 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -58,7 +58,7 @@ void headless_application::InitializeCallbacks() on_exit(); } - sys_log.notice("Quitting headless application"); + sys_log.notice("Quitting headless application (force_quit=%d)", force_quit); quit(); return true; } diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 8379e2f5e0..756dbe18ed 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -640,13 +640,15 @@ void gui_application::InitializeCallbacks() on_exit(); } + const bool no_gui = !m_main_window; + if (m_main_window) { // Close main window in order to save its window state m_main_window->close(); } - gui_log.notice("Quitting gui application"); + gui_log.notice("Quitting gui application (force_quit=%d, no-gui=%d)", force_quit, no_gui); quit(); return true; } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c5e7eb09b4..cfd2394d7c 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3944,6 +3944,7 @@ void main_window::closeEvent(QCloseEvent* closeEvent) Q_EMIT NotifyWindowCloseEvent(true); + gui_log.notice("Quit with main_window::closeEvent"); Emu.Quit(true); } From 36cd81fb5faba23619be66c6ac2cc4f1b037075c Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 18 Apr 2026 17:40:07 +0200 Subject: [PATCH 06/10] Log url of downloader --- rpcs3/rpcs3qt/downloader.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp index 9a5038fcc1..9bcdbf495a 100644 --- a/rpcs3/rpcs3qt/downloader.cpp +++ b/rpcs3/rpcs3qt/downloader.cpp @@ -103,7 +103,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p { if (m_curl_abort) { - network_log.notice("Download aborted"); + network_log.notice("Download aborted (url='%s')", url); return; } @@ -114,7 +114,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p if (m_curl_success) { - network_log.notice("Download finished"); + network_log.notice("Download finished (url='%s')", url); if (check_return_code && m_download_attempts < 3) { From d3804de24300e1ded41a65ce7d7fe15aa14fcabb Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 18 Apr 2026 16:09:01 +0200 Subject: [PATCH 07/10] Loader: try to retrieve database config if not booted through the UI --- rpcs3/Emu/System.cpp | 17 ++++++++++++----- rpcs3/Emu/System.h | 8 +++++--- rpcs3/main_application.cpp | 25 +++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index e92dc9f8a0..aa1180131a 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -933,7 +933,7 @@ game_boot_result Emulator::GetElfPathFromDir(std::string& elf_path, const std::s return game_boot_result::invalid_file_or_folder; } -game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, cfg_mode config_mode, const std::string& config_path, const std::string& db_config) +game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, cfg_mode config_mode, const std::string& config_path, const std::optional& db_config) { if (m_restrict_emu_state_change) { @@ -1565,8 +1565,15 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, sys_log.notice("Version: APP_VER=%s VERSION=%s", version_app, version_disc); { + if (!m_db_config && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom)) + { + // Get database config if possible. This only happens if the database config hasn't been set by the UI (e.g. if booted with no-gui). + // We only know the title_id for sure at this point, so it doesn't make sense to retrieve it earlier. + m_db_config = Emu.GetCallbacks().get_database_config(m_title_id); + } + // We add the database configuration if it is set, unless we are using a mode that specifically selects a different configuration. - bool add_database_config = !m_db_config.empty() && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom || m_config_mode == cfg_mode::continuous); + bool add_database_config = m_db_config && !m_db_config->empty() && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom || m_config_mode == cfg_mode::continuous); if (m_config_mode == cfg_mode::custom_selection || (m_config_mode == cfg_mode::continuous && !m_config_path.empty())) { @@ -1619,12 +1626,12 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } } - if (add_database_config) + if (add_database_config && m_db_config && !m_db_config->empty()) { // Add database config sys_log.notice("Applying database config"); - if (g_cfg.from_string(m_db_config)) + if (g_cfg.from_string(*m_db_config)) { g_cfg.name = "database_config"; } @@ -3373,7 +3380,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s klic.clear(); hdd1.clear(); init_mem_containers = nullptr; - m_db_config.clear(); + m_db_config = std::nullopt; m_config_path.clear(); m_config_mode = cfg_mode::custom; read_used_savestate_versions(); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 08db927452..f613bb94d6 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -115,6 +115,7 @@ struct EmuCallbacks std::function check_microphone_permissions; std::function()> make_video_source; std::function enable_gamemode; + std::function get_database_config; }; namespace utils @@ -146,7 +147,7 @@ class Emulator final cfg_mode m_config_mode = cfg_mode::custom; std::string m_config_path; - std::string m_db_config; + std::optional m_db_config; // std::nullopt means it has not been retrieved yet std::string m_path; std::string m_path_old; std::string m_path_original; @@ -370,7 +371,8 @@ public: const std::string& GetUsedDatabaseConfig() const { - return m_db_config; + static std::string empty_db_config; + return m_db_config ? *m_db_config : empty_db_config; } bool IsChildProcess() const @@ -422,7 +424,7 @@ public: return emulation_state_guard_t{this}; } - game_boot_result BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::string& db_config = ""); + game_boot_result BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::optional& db_config = std::nullopt); bool BootRsxCapture(const std::string& path); void SetForceBoot(bool force_boot); diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index b5d4401b7e..8c067f3975 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -2,6 +2,8 @@ #include "main_application.h" #include "display_sleep_control.h" #include "gamemode_control.h" +#include "rpcs3qt/gui_settings.h" +#include "rpcs3qt/config_database.h" #include "util/types.hpp" #include "util/logs.hpp" @@ -409,5 +411,28 @@ EmuCallbacks main_application::CreateCallbacks() return path + suffix; }; + callbacks.get_database_config = [](const std::string& title_id) + { + if (title_id.empty()) + return std::string(); + + sys_log.notice("Trying to retrieve database config for: '%s'", title_id); + + const auto settings = std::make_shared(); + config_database config_db(settings, nullptr); + config_db.request_config_database(false); + + if (!config_db.has_config(title_id)) + return std::string(); + + if (const auto config = config_db.get_config(title_id)) + { + sys_log.notice("Found database config for: '%s'", title_id); + return config.value(); + } + + return std::string(); + }; + return callbacks; } From bd5c10fd4835b9dc13db56c3a6da80530c1feb13 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 19 Apr 2026 08:02:26 +0200 Subject: [PATCH 08/10] Fix database config application in continuous mode --- rpcs3/Emu/System.cpp | 26 +++++++++++++++++--------- rpcs3/Emu/System.h | 1 + 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index aa1180131a..5a1c41072b 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1565,15 +1565,23 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, sys_log.notice("Version: APP_VER=%s VERSION=%s", version_app, version_disc); { - if (!m_db_config && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom)) + if (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom) { - // Get database config if possible. This only happens if the database config hasn't been set by the UI (e.g. if booted with no-gui). - // We only know the title_id for sure at this point, so it doesn't make sense to retrieve it earlier. - m_db_config = Emu.GetCallbacks().get_database_config(m_title_id); - } + if (!m_db_config) + { + // Get database config if possible. This only happens if the database config hasn't been set by the UI (e.g. if booted with no-gui). + // We only know the title_id for sure at this point, so it doesn't make sense to retrieve it earlier. + m_db_config = Emu.GetCallbacks().get_database_config(m_title_id); + } - // We add the database configuration if it is set, unless we are using a mode that specifically selects a different configuration. - bool add_database_config = m_db_config && !m_db_config->empty() && (m_config_mode == cfg_mode::database_config || m_config_mode == cfg_mode::custom || m_config_mode == cfg_mode::continuous); + // We add the database configuration if it is set, unless we are using a mode that specifically selects a different configuration. + m_add_database_config = m_db_config && !m_db_config->empty(); + } + else if (m_config_mode != cfg_mode::continuous) + { + // Reset flag unless in continuous mode + m_add_database_config = false; + } if (m_config_mode == cfg_mode::custom_selection || (m_config_mode == cfg_mode::continuous && !m_config_path.empty())) { @@ -1617,7 +1625,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, { g_cfg.name = config_path; m_config_path = config_path; - add_database_config = false; // A custom config exists. Do not add the database config. + m_add_database_config = false; // A custom config exists. Do not add the database config. break; } @@ -1626,7 +1634,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } } - if (add_database_config && m_db_config && !m_db_config->empty()) + if (m_add_database_config && m_db_config && !m_db_config->empty()) { // Add database config sys_log.notice("Applying database config"); diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index f613bb94d6..8bee2dc7a5 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -172,6 +172,7 @@ class Emulator final bool m_continuous_mode = false; bool m_has_gui = true; + bool m_add_database_config = false; bool m_state_inspection_savestate = false; From 06a6880c6c21f28ed7eddf03883a74d3ff993399 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 19 Apr 2026 11:21:46 +0200 Subject: [PATCH 09/10] Qt: Allow to compare configurations in gamelist context menu --- rpcs3/Emu/system_config.h | 2 +- rpcs3/rpcs3qt/config_checker.cpp | 287 +++++++++++++++++++---- rpcs3/rpcs3qt/config_checker.h | 25 +- rpcs3/rpcs3qt/game_list_context_menu.cpp | 21 ++ rpcs3/rpcs3qt/log_viewer.cpp | 2 +- rpcs3/rpcs3qt/main_window.cpp | 2 +- 6 files changed, 290 insertions(+), 49 deletions(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index befb64f282..ba52f14d5d 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -282,7 +282,7 @@ struct cfg_root : cfg::node cfg::_bool paint_move_spheres{this, "Paint move spheres", false, true}; cfg::_bool allow_move_hue_set_by_game{this, "Allow move hue set by game", false, true}; cfg::_bool lock_overlay_input_to_player_one{this, "Lock overlay input to player one", false, true}; - cfg::string midi_devices{this, "Emulated Midi devices", "ßßß@@@ßßß@@@ßßß@@@"}; + cfg::string midi_devices{this, "Emulated Midi devices", "Keyboardßßß@@@Keyboardßßß@@@Keyboardßßß@@@"}; cfg::_bool load_sdl_mappings{ this, "Load SDL GameController Mappings", true }; cfg::_bool pad_debug_overlay{ this, "IO Debug overlay", false, true }; cfg::_bool mouse_debug_overlay{ this, "Mouse Debug overlay", false, true }; diff --git a/rpcs3/rpcs3qt/config_checker.cpp b/rpcs3/rpcs3qt/config_checker.cpp index 6a0851362a..f8ff600205 100644 --- a/rpcs3/rpcs3qt/config_checker.cpp +++ b/rpcs3/rpcs3qt/config_checker.cpp @@ -1,76 +1,127 @@ #include "stdafx.h" #include "config_checker.h" +#include "midi_creator.h" +#include "microphone_creator.h" #include "Emu/system_config.h" +#include "Emu/system_utils.hpp" +#include #include #include #include -#include #include -#include LOG_CHANNEL(gui_log, "GUI"); -config_checker::config_checker(QWidget* parent, const QString& content, bool is_log) : QDialog(parent) +config_checker::config_checker(QWidget* parent, const QString& content_or_serial, checker_mode mode, const std::string& db_config) + : QDialog(parent) + , m_checker_mode(mode) + , m_content_or_serial(content_or_serial) + , m_db_config(db_config) { setObjectName("config_checker"); + setWindowTitle(tr("Config Checker")); setAttribute(Qt::WA_DeleteOnClose); QVBoxLayout* layout = new QVBoxLayout(); - QLabel* label = new QLabel(this); - layout->addWidget(label); + QComboBox* combo = nullptr; - QString result; - - if (check_config(content, result, is_log)) + if (mode == checker_mode::gamelist) { - setWindowTitle(tr("Interesting!")); + m_serial = content_or_serial.toStdString(); - if (result.isEmpty()) + combo = new QComboBox(this); + + std::string custom_config_path; + if (std::string config_path = rpcs3::utils::get_custom_config_path(m_serial); fs::is_file(config_path)) { - label->setText(tr("Found config.\nIt seems to match the default config.")); + custom_config_path = std::move(config_path); + combo->addItem(tr("Custom Configuration"), static_cast(cfg_mode::custom)); } - else + + combo->addItem(tr("Database + Global Configuration"), static_cast(cfg_mode::database_config)); + combo->setCurrentIndex(combo->findData(static_cast(custom_config_path.empty() ? cfg_mode::database_config : cfg_mode::custom))); + + connect(combo, &QComboBox::currentIndexChanged, this, [this, combo]() { - label->setText(tr("Found config.\nSome settings seem to deviate from the default config:")); + check_config(static_cast(combo->currentData().toInt())); + }); - QTextEdit* text_box = new QTextEdit(); - text_box->setReadOnly(true); - text_box->setHtml(result); - layout->addWidget(text_box); + layout->addWidget(combo); + } - resize(400, 600); - } - } - else - { - setWindowTitle(tr("Ooops!")); - label->setText(result); - } + m_label = new QLabel(this); + layout->addWidget(m_label); + + m_text_box = new QTextEdit(); + m_text_box->setReadOnly(true); + layout->addWidget(m_text_box); QDialogButtonBox* box = new QDialogButtonBox(QDialogButtonBox::Close); connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(box); setLayout(layout); + resize(400, 600); + + check_config(combo ? static_cast(combo->currentData().toInt()) : cfg_mode::database_config); } -bool config_checker::check_config(QString content, QString& result, bool is_log) +void config_checker::check_config(cfg_mode mode) { - cfg_root config{}; + QString result; - if (is_log) + if (check_config(mode, m_content_or_serial, result)) + { + if (m_checker_mode == checker_mode::gamelist) + { + if (result.isEmpty()) + { + m_label->setText(tr("The configuration seems to match the default config.")); + } + else + { + m_label->setText(tr("Config database settings are marked with an * in front of the name.\nSome settings seem to deviate from the default config:")); + } + } + else + { + if (result.isEmpty()) + { + m_label->setText(tr("Found config.\nIt seems to match the default config.")); + } + else + { + m_label->setText(tr("Found config.\nSome settings seem to deviate from the default config:")); + } + } + + m_text_box->setVisible(!result.isEmpty()); + m_text_box->setHtml(result); + } + else + { + m_label->setText(result); + } +} + +bool config_checker::check_config(cfg_mode mode, QString content_or_serial, QString& result) +{ + std::unique_ptr config = std::make_unique(); + std::unique_ptr config_db_only; + + if (m_checker_mode == checker_mode::log) { const QString start_token = "SYS: Used configuration:\n"; const QString end_token = "\n·"; - qsizetype start = content.indexOf(start_token); + qsizetype start = content_or_serial.indexOf(start_token); qsizetype end = -1; if (start >= 0) { start += start_token.size(); - end = content.indexOf(end_token, start); + end = content_or_serial.indexOf(end_token, start); } if (end < 0) @@ -79,24 +130,93 @@ bool config_checker::check_config(QString content, QString& result, bool is_log) return false; } - content = content.mid(start, end - start); + content_or_serial = content_or_serial.mid(start, end - start); } - if (!config.from_string(content.toStdString())) + if (m_checker_mode == checker_mode::gamelist) { - gui_log.error("log_viewer: Failed to parse config:\n%s", content); + config->from_default(); + + // Load global config + const std::string cfg_path = fs::get_config_dir(true) + "config.yml"; + if (const fs::file cfg_file{cfg_path}) + { + gui_log.notice("config_checker: Applying global config: %s", cfg_path); + + if (!config->from_string(cfg_file.to_string())) + { + gui_log.error("config_checker: Failed to apply global config: %s", cfg_path); + result = tr("Failed to apply global config!"); + return false; + } + } + + // Load custom config + const std::string custom_config_path = rpcs3::utils::get_custom_config_path(m_serial); + if (mode == cfg_mode::custom && !custom_config_path.empty()) + { + if (const fs::file cfg_file{custom_config_path}) + { + gui_log.notice("config_checker: Applying custom config: %s", custom_config_path); + + if (!config->from_string(cfg_file.to_string())) + { + gui_log.error("config_checker: Failed to apply custom config: %s", custom_config_path); + result = tr("Failed to apply custom config!"); + return false; + } + } + } + + if (mode == cfg_mode::database_config && !m_db_config.empty()) + { + gui_log.notice("config_checker: Applying database config: %s", custom_config_path); + + if (!config->from_string(m_db_config)) + { + gui_log.error("config_checker: Failed to apply database config:\n%s", m_db_config); + result = tr("Failed to apply database config!"); + return false; + } + + config_db_only = std::make_unique(); + config_db_only->from_default(); + if (!config_db_only->from_string(m_db_config)) + { + gui_log.error("config_checker: Failed to apply database config:\n%s", m_db_config); + result = tr("Failed to apply database config!"); + return false; + } + } + } + else if (!config->from_string(content_or_serial.toStdString())) + { + gui_log.error("config_checker: Failed to parse config:\n%s", content_or_serial); result = tr("Cannot find any config!"); return false; } - std::function print_diff_recursive; - print_diff_recursive = [&print_diff_recursive](const cfg::_base* base, std::string& diff, int indentation) -> void + std::function print_diff_recursive; + print_diff_recursive = [this, &print_diff_recursive, &config](const cfg::_base* base, const cfg::_base* base_db_only, std::string& diff, int indentation) -> void { if (!base) { return; } + // Ignore some irrelevant settings in gamelist mode + if (m_checker_mode == checker_mode::gamelist && base->get_type() != cfg::type::node) + { + const std::string key = base->get_name(); + + if (key == config->sys.console_psid.get_name() || + key == config->sys.system_name.get_name() || + key == config->video.vk.adapter.get_name()) + { + return; + } + } + const auto indent = [](std::string& str, int indentation) { for (int i = 0; i < indentation * 2; i++) @@ -105,6 +225,16 @@ bool config_checker::check_config(QString content, QString& result, bool is_log) } }; + const auto base_name_db = [base](bool is_db_config) + { + if (is_db_config) + { + return "*" + base->get_name(); + } + + return base->get_name(); + }; + switch (base->get_type()) { case cfg::type::node: @@ -115,7 +245,20 @@ bool config_checker::check_config(QString content, QString& result, bool is_log) for (const auto& n : node->get_nodes()) { - print_diff_recursive(n, diff_tmp, indentation + 1); + const cfg::_base* n_db_only = nullptr; + if (const auto& node_db_only = static_cast(base_db_only)) + { + for (const auto& n_db : node_db_only->get_nodes()) + { + if (n_db->get_name() == n->get_name()) + { + n_db_only = n_db; + break; + } + } + } + + print_diff_recursive(n, n_db_only, diff_tmp, indentation + 1); } if (!diff_tmp.empty()) @@ -142,19 +285,75 @@ bool config_checker::check_config(QString content, QString& result, bool is_log) const std::string val = base->to_string(); const std::string def = base->def_to_string(); - if (val != def) - { - indent(diff, indentation); + if (val == def) + break; - if (def.empty()) + indent(diff, indentation); + + if (m_checker_mode == checker_mode::gamelist) + { + if (base->get_name() == config->io.midi_devices.get_name()) { - fmt::append(diff, "%s: %s
", base->get_name(), val); + fmt::append(diff, "%s:
", base->get_name()); + + midi_creator mc {}; + + mc.parse_devices(def); + const std::array def_devices = mc.get_selection_list(); + + mc.parse_devices(val); + const std::array devices = mc.get_selection_list(); + + for (usz i = 0; i < devices.size(); i++) + { + const midi_device& def_device = def_devices[i]; + const midi_device& device = devices[i]; + + if (device.name == def_device.name) + continue; + + indent(diff, indentation + 1); + fmt::append(diff, "Device %d: %s: %s
", i + 1, device.type, device.name); + } + break; } - else + else if (base->get_name() == config->audio.microphone_devices.get_name()) { - fmt::append(diff, "%s: %s default: %s
", base->get_name(), val, def); + fmt::append(diff, "%s:
", base->get_name()); + + microphone_creator mc {}; + + mc.parse_devices(def); + const std::array def_devices = mc.get_selection_list(); + + mc.parse_devices(val); + const std::array devices = mc.get_selection_list(); + + for (usz i = 0; i < devices.size(); i++) + { + const std::string& def_device = def_devices[i]; + const std::string& device = devices[i]; + + if (device == def_device) + continue; + + indent(diff, indentation + 1); + fmt::append(diff, "Device %d: %s
", i + 1, device); + } + break; } } + + const bool is_db_config = base_db_only && base_db_only->to_string() != def; + + if (def.empty()) + { + fmt::append(diff, "%s: %s
", base_name_db(is_db_config), val); + } + else + { + fmt::append(diff, "%s: %s default: %s
", base_name_db(is_db_config), val, def); + } break; } case cfg::type::set: @@ -208,7 +407,7 @@ bool config_checker::check_config(QString content, QString& result, bool is_log) }; std::string diff; - print_diff_recursive(&config, diff, 0); + print_diff_recursive(config.get(), config_db_only.get(), diff, 0); result = QString::fromStdString(diff); return true; diff --git a/rpcs3/rpcs3qt/config_checker.h b/rpcs3/rpcs3qt/config_checker.h index 900fb30351..71b4a27de3 100644 --- a/rpcs3/rpcs3qt/config_checker.h +++ b/rpcs3/rpcs3qt/config_checker.h @@ -1,13 +1,34 @@ #pragma once +#include "Emu/config_mode.h" + #include +#include +#include class config_checker : public QDialog { Q_OBJECT public: - config_checker(QWidget* parent, const QString& path, bool is_log); + enum class checker_mode + { + config, + log, + gamelist + }; - bool check_config(QString content, QString& result, bool is_log); + config_checker(QWidget* parent, const QString& content_or_serial, checker_mode mode, const std::string& db_config = {}); + +private: + void check_config(cfg_mode mode); + bool check_config(cfg_mode mode, QString content_or_serial, QString& result); + + QLabel* m_label = nullptr; + QTextEdit* m_text_box = nullptr; + + checker_mode m_checker_mode = checker_mode::config; + QString m_content_or_serial; + std::string m_db_config; + std::string m_serial; }; diff --git a/rpcs3/rpcs3qt/game_list_context_menu.cpp b/rpcs3/rpcs3qt/game_list_context_menu.cpp index 05fc9fdb8d..f8a26d3f80 100644 --- a/rpcs3/rpcs3qt/game_list_context_menu.cpp +++ b/rpcs3/rpcs3qt/game_list_context_menu.cpp @@ -12,6 +12,7 @@ #include "patch_manager_dialog.h" #include "persistent_settings.h" #include "config_database.h" +#include "config_checker.h" #include "Utilities/File.h" #include "Emu/system_utils.hpp" @@ -167,6 +168,26 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info& QAction* pad_configure = addAction(gameinfo->has_custom_pad_config ? tr("&Change Custom Gamepad Configuration") : tr("&Create Custom Gamepad Configuration")); + + QAction* compare_config = addAction(tr("&Compare Configurations")); + connect(compare_config, &QAction::triggered, this, [this, serial]() + { + std::string db_config; + if (config_database* db = m_game_list_frame->GetConfigDatabase(); db->has_config(serial)) + { + if (const std::optional config = db->get_config(serial)) + { + db_config = *config; + } + else + { + game_list_log.error("No database config found for '%s'", serial); + } + } + config_checker* dlg = new config_checker(m_game_list_frame, QString::fromStdString(serial), config_checker::checker_mode::gamelist, db_config); + dlg->open(); + }); + QAction* configure_patches = addAction(tr("&Manage Game Patches")); addSeparator(); diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index b69c07901e..623a74b48a 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -201,7 +201,7 @@ void log_viewer::show_context_menu(const QPoint& pos) connect(config, &QAction::triggered, this, [this]() { - config_checker* dlg = new config_checker(this, m_full_log, true); + config_checker* dlg = new config_checker(this, m_full_log, config_checker::checker_mode::log); dlg->open(); }); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index cfd2394d7c..9dd437b535 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3250,7 +3250,7 @@ void main_window::CreateConnects() m_gui_settings->SetValue(gui::fd_cfg_check, file_info.path()); - config_checker* dlg = new config_checker(this, content, file_path.endsWith(".log") || file_path.endsWith(".log.gz")); + config_checker* dlg = new config_checker(this, content, (file_path.endsWith(".log") || file_path.endsWith(".log.gz")) ? config_checker::checker_mode::log : config_checker::checker_mode::config); dlg->open(); }); From b6c8374aa5370a386d069b8f20a85b2323a18e9e Mon Sep 17 00:00:00 2001 From: digant73 Date: Sun, 19 Apr 2026 16:19:51 +0200 Subject: [PATCH 10/10] Fix region range check on ISO file --- rpcs3/Loader/ISO.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 8ec93ec8c7..0dd50501b6 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -199,7 +199,7 @@ bool iso_file_decryption::init(const std::string& path) const u32 region_count = char_arr_BE_to_uint(sec0_sec1.data()); // Ensure the region count is a proper value - if (region_count < 1 || region_count > 31) // It's non-PS3ISO + if (region_count < 1 || region_count > 127) // It's non-PS3ISO { iso_log.error("init: Failed to read region information: '%s' (region_count=%d)", path, region_count); return false;