From c48ae344a8a2b5266eb2a733a53ccd2af4b8f28b Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 29 Nov 2025 07:43:50 +0200 Subject: [PATCH 01/35] SPU LLVM (Mega): Fix quarter of missing jumptable discoveries --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index e3455fcb2d..2a792f677e 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -3165,6 +3165,15 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s break; } + if (target >= SPU_LS_SIZE && target <= 0u - SPU_LS_SIZE) + { + if (g_spu_itype.decode(target) != spu_itype::UNK) + { + // End of jumptable: valid instruction + break; + } + } + if (target >= lsa && target < SPU_LS_SIZE) { // Possible jump table entry (absolute) From 4a042ae84ff43d35f631b62c20ab69a7934e862c Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 29 Nov 2025 16:38:00 +0200 Subject: [PATCH 02/35] SPU/config: Make SPU LLVM Mega block size default --- rpcs3/Emu/system_config.h | 2 +- rpcs3/rpcs3qt/emu_settings.cpp | 6 +++--- rpcs3/rpcs3qt/emu_settings_type.h | 2 +- rpcs3/rpcs3qt/tooltips.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index c7b5e8e0fb..18f11da896 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -36,7 +36,7 @@ struct cfg_root : cfg::node cfg::_int<0, 16> spu_delay_penalty{ this, "SPU delay penalty", 3 }; // Number of milliseconds to block a thread if a virtual 'core' isn't free cfg::_bool spu_loop_detection{ this, "SPU loop detection", false }; // Try to detect wait loops and trigger thread yield cfg::_int<1, 6> max_spurs_threads{ this, "Max SPURS Threads", 6, true }; // HACK. If less then 6, max number of running SPURS threads in each thread group. - cfg::_enum spu_block_size{ this, "SPU Block Size", spu_block_size_type::safe }; + cfg::_enum spu_block_size{ this, "SPU Analyzer Block Size", spu_block_size_type::mega }; cfg::_bool spu_accurate_dma{ this, "Accurate SPU DMA", false }; cfg::_bool spu_accurate_reservations{ this, "Accurate SPU Reservations", true }; cfg::_bool accurate_cache_line_stores{ this, "Accurate Cache Line Stores", false }; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 0c99cfc119..bba3b0b235 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -975,9 +975,9 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case emu_settings_type::SPUBlockSize: switch (static_cast(index)) { - case spu_block_size_type::safe: return tr("Safe", "SPU block size"); - case spu_block_size_type::mega: return tr("Mega", "SPU block size"); - case spu_block_size_type::giga: return tr("Giga", "SPU block size"); + case spu_block_size_type::safe: return tr("Safe", "SPU Analyzer Block Size"); + case spu_block_size_type::mega: return tr("Mega", "SPU Analyzer Block Size"); + case spu_block_size_type::giga: return tr("Giga", "SPU Analyzer Block Size"); } break; case emu_settings_type::ThreadSchedulerMode: diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index c398b07a28..d90aa7c862 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -239,7 +239,7 @@ inline static const std::map settings_location { emu_settings_type::XFloatAccuracy, { "Core", "XFloat Accuracy"}}, { emu_settings_type::MFCCommandsShuffling, { "Core", "MFC Commands Shuffling Limit"}}, { emu_settings_type::SetDAZandFTZ, { "Core", "Set DAZ and FTZ"}}, - { emu_settings_type::SPUBlockSize, { "Core", "SPU Block Size"}}, + { emu_settings_type::SPUBlockSize, { "Core", "SPU Analyzer Block Size"}}, { emu_settings_type::SPUCache, { "Core", "SPU Cache"}}, { emu_settings_type::DebugConsoleMode, { "Core", "Debug Console Mode"}}, { emu_settings_type::MaxSPURSThreads, { "Core", "Max SPURS Threads"}}, diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index f7ec927332..3b84a66270 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -91,7 +91,7 @@ public: const QString xfloat = tr("Control accuracy to SPU float vectors processing.\nFixes bugs in various games at the cost of performance.\nThis setting is only applied when SPU Decoder is set to Dynamic or LLVM."); const QString enable_thread_scheduler = tr("Control how RPCS3 utilizes the threads of your system.\nEach option heavily depends on the game and on your CPU. It's recommended to try each option to find out which performs the best.\nChanging the thread scheduler is not supported on CPUs with less than 12 threads."); const QString spu_loop_detection = tr("Try to detect loop conditions in SPU kernels and use them as scheduling hints.\nImproves performance and reduces CPU usage.\nMay cause severe audio stuttering in rare cases."); - const QString spu_block_size = tr("This option controls the SPU analyser, particularly the size of compiled units. The Mega and Giga modes may improve performance by tying smaller units together, decreasing the number of compiled units but increasing their size.\nUse the Safe mode for maximum compatibility."); + const QString spu_block_size = tr("This option controls the SPU analyser, particularly the size of compiled units. The Mega and Giga modes may improve performance by tying smaller units together, decreasing the number of compiled units but increasing their size.\nUse the Safe mode for maximum compatibility at the cost of lower performance."); const QString preferred_spu_threads = tr("Some SPU stages are sensitive to race conditions and allowing a limited number at a time helps alleviate performance stalls.\nSetting this to a smaller value might improve performance and reduce stuttering in some games.\nLeave this on auto if performance is negatively affected when setting a small value."); const QString max_cpu_preempt = tr("Reduces CPU usage and power consumption, improving battery life on mobile devices. (0 means disabled)\nHigher values cause a more pronounced effect, but may cause audio or performance issues. A value of 50 or less is recommended.\nThis option forces an FPS limit because it's active when framerate is stable.\nThe lighter the game is on the hardware, the more power is saved by it. (until the preemption count barrier is reached)"); From e938b93f487532200d68d75d2bf15af6730e1a08 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 29 Nov 2025 18:47:45 +0200 Subject: [PATCH 03/35] SPU Analyzer: Ignore large switch state --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 2a792f677e..c9d784d3ac 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -8435,8 +8435,9 @@ std::array& block_reg_info::evaluate_start_state(const s { // TODO: The true maximum occurence count need to depend on the amount of branching-outs passed through // Currently allow 2 for short-term code and 1 for long-term code + // Ignore large jumptables as well const bool loop_terminator_detected = std::count(been_there.begin(), been_there.end(), prev_pc) >= (qi < 20 ? 2u : 1u); - const bool avoid_extensive_analysis = qi >= (extensive_evaluation ? 22 : 16); + const bool avoid_extensive_analysis = qi >= (extensive_evaluation ? 22 : 16) || it->state_prev.size() >= 8; if (!loop_terminator_detected && !avoid_extensive_analysis) { From e2cbbcf646060faab1c368802296d2c13c3b94b8 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 18 Nov 2025 18:28:03 +0100 Subject: [PATCH 04/35] overlays: use enum for sound effects --- .../overlay_friends_list_dialog.cpp | 7 +++--- .../overlay_home_menu_message_box.cpp | 5 ++--- .../HomeMenu/overlay_home_menu_page.cpp | 6 ++--- .../Network/overlay_recvmessage_dialog.cpp | 6 ++--- .../Network/overlay_sendmessage_dialog.cpp | 6 ++--- .../Trophies/overlay_trophy_list_dialog.cpp | 4 ++-- .../Overlays/overlay_media_list_dialog.cpp | 7 +++--- .../RSX/Overlays/overlay_message_dialog.cpp | 9 +++----- rpcs3/Emu/RSX/Overlays/overlay_osk.cpp | 8 +++---- .../Emu/RSX/Overlays/overlay_save_dialog.cpp | 7 +++--- .../Overlays/overlay_trophy_notification.cpp | 3 +-- .../RSX/Overlays/overlay_user_list_dialog.cpp | 6 ++--- rpcs3/Emu/RSX/Overlays/overlays.cpp | 22 +++++++++++++++++++ rpcs3/Emu/RSX/Overlays/overlays.h | 14 ++++++++++++ 14 files changed, 69 insertions(+), 41 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/FriendsList/overlay_friends_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/FriendsList/overlay_friends_list_dialog.cpp index 2df72329bf..f96505ea3c 100644 --- a/rpcs3/Emu/RSX/Overlays/FriendsList/overlay_friends_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/FriendsList/overlay_friends_list_dialog.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "../overlay_manager.h" #include "overlay_friends_list_dialog.h" -#include "Emu/System.h" #include "Emu/NP/rpcn_config.h" #include "Emu/vfs_config.h" @@ -306,11 +305,11 @@ namespace rsx } } - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); return; } case pad_button::circle: - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); close_dialog = true; break; case pad_button::square: @@ -359,7 +358,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_message_box.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_message_box.cpp index 4d9196ad07..ed42f4f815 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_message_box.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_message_box.cpp @@ -1,6 +1,5 @@ #include "stdafx.h" #include "overlay_home_menu_message_box.h" -#include "Emu/System.h" #include "Emu/system_config.h" namespace rsx @@ -77,7 +76,7 @@ namespace rsx { case pad_button::cross: { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); if (m_on_accept) { m_on_accept(); @@ -86,7 +85,7 @@ namespace rsx } case pad_button::circle: { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); if (m_on_cancel) { m_on_cancel(); diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp index f298de0a81..81c1ebecba 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp @@ -160,7 +160,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise if (!is_auto_repeat || auto_repeat_interval_ms >= user_interface::m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); } return func(button_press); } @@ -169,7 +169,7 @@ namespace rsx } case pad_button::circle: { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); if (parent) { set_current_page(parent); @@ -244,7 +244,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise if (!is_auto_repeat || auto_repeat_interval_ms >= user_interface::m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } return page_navigation::stay; } diff --git a/rpcs3/Emu/RSX/Overlays/Network/overlay_recvmessage_dialog.cpp b/rpcs3/Emu/RSX/Overlays/Network/overlay_recvmessage_dialog.cpp index cb1837b78d..278510e8a9 100644 --- a/rpcs3/Emu/RSX/Overlays/Network/overlay_recvmessage_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/Network/overlay_recvmessage_dialog.cpp @@ -127,11 +127,11 @@ namespace rsx { return_code = selection_code::error; } - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); close_dialog = true; break; case pad_button::circle: - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); close_dialog = true; break; case pad_button::dpad_up: @@ -167,7 +167,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/Network/overlay_sendmessage_dialog.cpp b/rpcs3/Emu/RSX/Overlays/Network/overlay_sendmessage_dialog.cpp index 5369afb097..5d2d5c1bef 100644 --- a/rpcs3/Emu/RSX/Overlays/Network/overlay_sendmessage_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/Network/overlay_sendmessage_dialog.cpp @@ -82,7 +82,7 @@ namespace rsx if (m_list->m_items.empty() || is_auto_repeat) break; - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); if (!get_current_selection().empty()) { @@ -95,7 +95,7 @@ namespace rsx close_dialog = true; break; case pad_button::circle: - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); close_dialog = true; break; case pad_button::dpad_up: @@ -131,7 +131,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp index 7867716355..ea47ad80b6 100644 --- a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp @@ -132,7 +132,7 @@ namespace rsx switch (button_press) { case pad_button::circle: - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); close_dialog = true; break; case pad_button::square: @@ -172,7 +172,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp index 2cf113243a..cc7affc983 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_media_list_dialog.cpp @@ -4,7 +4,6 @@ #include "overlay_media_list_dialog.h" #include "Emu/Cell/Modules/cellMusic.h" -#include "Emu/System.h" #include "Emu/VFS.h" #include "Utilities/StrUtil.h" #include "Utilities/Thread.h" @@ -154,13 +153,13 @@ namespace rsx return_code = m_list->get_selected_index(); m_stop_input_loop = true; play_cursor_sound = false; - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); break; case pad_button::circle: return_code = selection_code::canceled; m_stop_input_loop = true; play_cursor_sound = false; - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); break; case pad_button::dpad_up: m_list->select_previous(); @@ -182,7 +181,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise if (play_cursor_sound && (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default)) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp index 578967de1a..a0be8ce5cf 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_message_dialog.cpp @@ -155,7 +155,7 @@ namespace rsx return_code = CELL_MSGDIALOG_BUTTON_YES; } - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); break; } case pad_button::circle: @@ -175,7 +175,7 @@ namespace rsx return_code = CELL_MSGDIALOG_BUTTON_NO; } - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); break; } default: return; @@ -238,10 +238,7 @@ namespace rsx if (!type.se_mute_on) { - if (type.se_normal) - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_system_ok.wav"); - else - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_system_ng.wav"); + play_sound(type.se_normal ? sound_effect::dialog_ok : sound_effect::dialog_error); } set_text(text); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp b/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp index 82047a72bd..428c68a541 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_osk.cpp @@ -823,7 +823,7 @@ namespace rsx } case pad_button::start: { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_oskenter.wav"); + play_sound(sound_effect::osk_accept); Close(CELL_OSKDIALOG_CLOSE_CONFIRM); play_cursor_sound = false; break; @@ -840,7 +840,7 @@ namespace rsx } case pad_button::cross: { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_oskenter.wav"); + play_sound(sound_effect::osk_accept); on_accept(); m_reset_pulse = true; play_cursor_sound = false; @@ -848,7 +848,7 @@ namespace rsx } case pad_button::circle: { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_oskcancel.wav"); + play_sound(sound_effect::osk_cancel); Close(CELL_OSKDIALOG_CLOSE_CANCEL); play_cursor_sound = false; break; @@ -890,7 +890,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise if (play_cursor_sound && (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default)) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } if (m_reset_pulse) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp index 86e9905f2f..604dee7fc4 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_save_dialog.cpp @@ -2,7 +2,6 @@ #include "overlay_save_dialog.h" #include "overlay_video.h" #include "Utilities/date_time.h" -#include "Emu/System.h" namespace rsx { @@ -133,11 +132,11 @@ namespace rsx if (m_no_saves) break; return_code = m_list->get_selected_index(); - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); close_dialog = true; break; case pad_button::circle: - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); close_dialog = true; break; case pad_button::dpad_up: @@ -173,7 +172,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_trophy_notification.cpp b/rpcs3/Emu/RSX/Overlays/overlay_trophy_notification.cpp index 595828afd9..ba927095c8 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_trophy_notification.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_trophy_notification.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "overlay_trophy_notification.h" #include "Emu/Cell/ErrorCodes.h" -#include "Emu/System.h" namespace rsx { @@ -70,7 +69,7 @@ namespace rsx { // First tick creation_time_us = timestamp_us; - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_trophy.wav"); + play_sound(sound_effect::trophy); return; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp index 186ac0ee83..f534ce5587 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_user_list_dialog.cpp @@ -113,11 +113,11 @@ namespace rsx { return_code = selection_code::error; } - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav"); + play_sound(sound_effect::accept); close_dialog = true; break; case pad_button::circle: - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav"); + play_sound(sound_effect::cancel); close_dialog = true; break; case pad_button::dpad_up: @@ -153,7 +153,7 @@ namespace rsx // Play a sound unless this is a fast auto repeat which would induce a nasty noise else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default) { - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav"); + play_sound(sound_effect::cursor); } } diff --git a/rpcs3/Emu/RSX/Overlays/overlays.cpp b/rpcs3/Emu/RSX/Overlays/overlays.cpp index d24c8550f3..97447802b9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlays.cpp @@ -15,6 +15,28 @@ namespace rsx { namespace overlays { + void play_sound(sound_effect sound) + { + const auto get_sound_filename = [sound]() + { + switch (sound) + { + case sound_effect::cursor: return "snd_cursor"sv; + case sound_effect::accept: return "snd_decide"sv; + case sound_effect::cancel: return "snd_cancel"sv; + case sound_effect::osk_accept: return "snd_oskenter"sv; + case sound_effect::osk_cancel: return "snd_oskcancel"sv; + case sound_effect::dialog_ok: return "snd_system_ok"sv; + case sound_effect::dialog_error: return "snd_system_ng"sv; + case sound_effect::trophy: return "snd_trophy"sv; + } + + fmt::throw_exception("Unreachable (sound=%d)", static_cast(sound)); + }; + + Emu.GetCallbacks().play_sound(fmt::format("%ssounds/%s.wav", fs::get_config_dir(), get_sound_filename())); + } + thread_local DECLARE(user_interface::g_thread_bit) = 0; u32 user_interface::alloc_thread_bit() diff --git a/rpcs3/Emu/RSX/Overlays/overlays.h b/rpcs3/Emu/RSX/Overlays/overlays.h index 9bcef52482..b3d50d3fea 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.h +++ b/rpcs3/Emu/RSX/Overlays/overlays.h @@ -17,6 +17,20 @@ namespace rsx { namespace overlays { + enum class sound_effect + { + cursor, + accept, + cancel, + osk_accept, + osk_cancel, + dialog_ok, + dialog_error, + trophy, + }; + + void play_sound(sound_effect sound); + // Bitfield of UI signals to overlay manager enum status_bits : u32 { From 7f9cc357e85762c2045e59a0f3e92d272481c816 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 18 Nov 2025 19:52:58 +0100 Subject: [PATCH 05/35] Qt: Add sound effect manager --- rpcs3/Emu/RSX/Overlays/overlays.cpp | 9 +- rpcs3/Emu/RSX/Overlays/overlays.h | 3 +- rpcs3/Emu/System.h | 2 +- rpcs3/headless_application.cpp | 2 +- rpcs3/rpcs3.vcxproj | 17 +++ rpcs3/rpcs3.vcxproj.filters | 15 ++ rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/gs_frame.cpp | 4 +- rpcs3/rpcs3qt/gui_application.cpp | 8 +- rpcs3/rpcs3qt/main_window.cpp | 7 + rpcs3/rpcs3qt/main_window.ui | 6 + rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp | 137 ++++++++++++++++++ rpcs3/rpcs3qt/sound_effect_manager_dialog.h | 26 ++++ rpcs3/rpcs3qt/trophy_notification_helper.cpp | 2 +- 14 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp create mode 100644 rpcs3/rpcs3qt/sound_effect_manager_dialog.h diff --git a/rpcs3/Emu/RSX/Overlays/overlays.cpp b/rpcs3/Emu/RSX/Overlays/overlays.cpp index 97447802b9..f44647daa0 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlays.cpp @@ -15,7 +15,7 @@ namespace rsx { namespace overlays { - void play_sound(sound_effect sound) + std::string get_sound_filepath(sound_effect sound) { const auto get_sound_filename = [sound]() { @@ -34,7 +34,12 @@ namespace rsx fmt::throw_exception("Unreachable (sound=%d)", static_cast(sound)); }; - Emu.GetCallbacks().play_sound(fmt::format("%ssounds/%s.wav", fs::get_config_dir(), get_sound_filename())); + return fmt::format("%ssounds/%s.wav", fs::get_config_dir(), get_sound_filename()); + } + + void play_sound(sound_effect sound, std::optional volume) + { + Emu.GetCallbacks().play_sound(get_sound_filepath(sound), volume); } thread_local DECLARE(user_interface::g_thread_bit) = 0; diff --git a/rpcs3/Emu/RSX/Overlays/overlays.h b/rpcs3/Emu/RSX/Overlays/overlays.h index b3d50d3fea..2aac6798d2 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.h +++ b/rpcs3/Emu/RSX/Overlays/overlays.h @@ -29,7 +29,8 @@ namespace rsx trophy, }; - void play_sound(sound_effect sound); + std::string get_sound_filepath(sound_effect sound); + void play_sound(sound_effect sound, std::optional volume = std::nullopt); // Bitfield of UI signals to overlay manager enum status_bits : u32 diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 954a041e9e..088fc70ef5 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -101,7 +101,7 @@ struct EmuCallbacks std::function get_localized_string; std::function get_localized_u32string; std::function get_localized_setting; - std::function play_sound; + std::function)> play_sound; std::function get_image_info; // (filename, sub_type, width, height, CellSearchOrientation) std::function get_scaled_image; // (filename, target_width, target_height, width, height, dst, force_fit) std::string(*resolve_path)(std::string_view) = [](std::string_view arg){ return std::string{arg}; }; // Resolve path using Qt diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index 43de0e108f..58047671a6 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -169,7 +169,7 @@ void headless_application::InitializeCallbacks() callbacks.get_localized_u32string = [](localized_string_id, const char*) -> std::u32string { return {}; }; callbacks.get_localized_setting = [](const cfg::_base*, u32) -> std::string { return {}; }; - callbacks.play_sound = [](const std::string&){}; + callbacks.play_sound = [](const std::string&, std::optional){}; callbacks.add_breakpoint = [](u32 /*addr*/){}; callbacks.display_sleep_control_supported = [](){ return false; }; diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index fe26cc1968..b5ffb8ebd8 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -430,6 +430,9 @@ true + + true + true @@ -718,6 +721,9 @@ true + + true + true @@ -851,6 +857,7 @@ + @@ -1311,6 +1318,16 @@ .\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_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + + 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_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + 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_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 7c98b7c735..aa90e50cd0 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -205,6 +205,9 @@ {149c596b-83e7-43f8-b5db-6108694434ef} + + {640b7d83-1522-4384-8bf7-a4fbd5873ae7} + @@ -738,6 +741,15 @@ Generated Files\Release + + Gui\sound effect manager + + + Generated Files\Debug + + + Generated Files\Release + Gui\screenshot manager @@ -1627,6 +1639,9 @@ Gui\screenshot manager + + Gui\sound effect manager + Gui\dimensions diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index f194b7550b..43c0024905 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -99,6 +99,7 @@ add_library(rpcs3_ui STATIC shortcut_handler.cpp shortcut_settings.cpp skylander_dialog.cpp + sound_effect_manager_dialog.cpp syntax_highlighter.cpp system_cmd_dialog.cpp table_item_delegate.cpp diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 5298ac2b98..df6aac317a 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -447,7 +447,7 @@ void gs_frame::toggle_recording() // Play a sound if (const std::string sound_path = fs::get_config_dir() + "sounds/snd_recording.wav"; fs::is_file(sound_path)) { - Emu.GetCallbacks().play_sound(sound_path); + Emu.GetCallbacks().play_sound(sound_path, std::nullopt); } else { @@ -1070,7 +1070,7 @@ void gs_frame::take_screenshot(std::vector&& data, u32 sshot_width, u32 ssho { if (const std::string sound_path = fs::get_config_dir() + "sounds/snd_screenshot.wav"; fs::is_file(sound_path)) { - Emu.GetCallbacks().play_sound(sound_path); + Emu.GetCallbacks().play_sound(sound_path, std::nullopt); } else { diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 82f29fcdbd..e028aaefa8 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -743,9 +743,9 @@ void gui_application::InitializeCallbacks() return m_emu_settings->GetLocalizedSetting(node, enum_index); }; - callbacks.play_sound = [this](const std::string& path) + callbacks.play_sound = [this](const std::string& path, std::optional volume) { - Emu.CallFromMainThread([this, path]() + Emu.CallFromMainThread([this, path, volume]() { if (fs::is_file(path)) { @@ -758,12 +758,12 @@ void gui_application::InitializeCallbacks() // Create a new sound effect. Re-using the same object seems to be broken for some users starting with Qt 6.6.3. std::unique_ptr sound_effect = std::make_unique(); sound_effect->setSource(QUrl::fromLocalFile(QString::fromStdString(path))); - sound_effect->setVolume(audio::get_volume()); + sound_effect->setVolume(volume ? *volume : audio::get_volume()); sound_effect->play(); m_sound_effects.push_back(std::move(sound_effect)); } - }); + }, nullptr, false); }; if (m_show_gui) // If this is false, we already have a fallback in the main_application. diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 34154d846d..3efbae75d2 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -44,6 +44,7 @@ #include "vfs_tool_dialog.h" #include "welcome_dialog.h" #include "music_player_dialog.h" +#include "sound_effect_manager_dialog.h" #include #include @@ -3053,6 +3054,12 @@ void main_window::CreateConnects() screenshot_manager->show(); }); + connect(ui->actionManage_SoundEffects, &QAction::triggered, this, [this] + { + sound_effect_manager_dialog* dlg = new sound_effect_manager_dialog(); + dlg->show(); + }); + connect(ui->toolsCgDisasmAct, &QAction::triggered, this, [this] { cg_disasm_window* cgdw = new cg_disasm_window(m_gui_settings); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index e10abf4668..5ef0b98f25 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -306,6 +306,7 @@ + @@ -1442,6 +1443,11 @@ Music Player + + + Sound Effects + + diff --git a/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp b/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp new file mode 100644 index 0000000000..cfd93246eb --- /dev/null +++ b/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp @@ -0,0 +1,137 @@ +#include "stdafx.h" +#include "sound_effect_manager_dialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_CHANNEL(gui_log, "GUI"); + +sound_effect_manager_dialog::sound_effect_manager_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Sound Effects")); + setAttribute(Qt::WA_DeleteOnClose); + + QLabel* description = new QLabel(tr("You can import sound effects for the RPCS3 overlays here.\nThe file format is .wav and you should try to make the sounds as short as possible."), this); + + QVBoxLayout* main_layout = new QVBoxLayout(this); + main_layout->addWidget(description); + + const auto add_sound_widget = [this, main_layout](rsx::overlays::sound_effect sound) + { + ensure(!m_widgets.contains(sound)); + + QString name; + switch (sound) + { + case rsx::overlays::sound_effect::cursor: name = tr("Cursor"); break; + case rsx::overlays::sound_effect::accept: name = tr("Accept"); break; + case rsx::overlays::sound_effect::cancel: name = tr("Cancel"); break; + case rsx::overlays::sound_effect::osk_accept: name = tr("Onscreen keyboard accept"); break; + case rsx::overlays::sound_effect::osk_cancel: name = tr("Onscreen keyboard cancel"); break; + case rsx::overlays::sound_effect::dialog_ok: name = tr("Dialog popup"); break; + case rsx::overlays::sound_effect::dialog_error: name = tr("Error dialog popup"); break; + case rsx::overlays::sound_effect::trophy: name = tr("Trophy popup"); break; + } + + QPushButton* button = new QPushButton("", this); + connect(button, &QAbstractButton::clicked, this, [this, button, sound, name]() + { + const std::string path = rsx::overlays::get_sound_filepath(sound); + if (fs::is_file(path)) + { + if (QMessageBox::question(this, tr("Remove sound effect?"), tr("Do you really want to remove the '%0' sound effect.").arg(name)) == QMessageBox::Yes) + { + if (!fs::remove_file(path)) + { + gui_log.error("Failed to remove sound effect file '%s': %s", path, fs::g_tls_error); + } + + update_widgets(); + } + } + else + { + const QString src_path = QFileDialog::getOpenFileName(this, tr("Select Audio File to Import"), "", tr("WAV (*.wav);;")); + if (!src_path.isEmpty()) + { + if (!fs::copy_file(src_path.toStdString(), path, true)) + { + gui_log.error("Failed to import sound effect file '%s' to '%s': %s", src_path, path, fs::g_tls_error); + } + update_widgets(); + } + } + }); + + QPushButton* play_button = new QPushButton(this); + play_button->setIcon(QApplication::style()->standardIcon(QStyle::SP_MediaPlay)); + play_button->setIconSize(QSize(16, 16)); + play_button->setFixedSize(24, 24); + connect(play_button, &QAbstractButton::clicked, this, [sound]() + { + rsx::overlays::play_sound(sound, 1.0f); + }); + + QHBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(button); + layout->addWidget(play_button); + layout->addStretch(1); + + QGroupBox* gb = new QGroupBox(name, this); + gb->setLayout(layout); + + main_layout->addWidget(gb); + + m_widgets[sound] = { + .button = button, + .play_button = play_button + }; + }; + + add_sound_widget(rsx::overlays::sound_effect::cursor); + add_sound_widget(rsx::overlays::sound_effect::accept); + add_sound_widget(rsx::overlays::sound_effect::cancel); + add_sound_widget(rsx::overlays::sound_effect::osk_accept); + add_sound_widget(rsx::overlays::sound_effect::osk_cancel); + add_sound_widget(rsx::overlays::sound_effect::dialog_ok); + add_sound_widget(rsx::overlays::sound_effect::dialog_error); + add_sound_widget(rsx::overlays::sound_effect::trophy); + + setLayout(main_layout); + update_widgets(); + resize(sizeHint()); +} + +sound_effect_manager_dialog::~sound_effect_manager_dialog() +{ +} + +void sound_effect_manager_dialog::update_widgets() +{ + for (auto& [sound, widget] : m_widgets) + { + const bool file_exists = fs::is_file(rsx::overlays::get_sound_filepath(sound)); + + widget.play_button->setEnabled(file_exists); + + if (file_exists) + { + widget.button->setText(tr("Remove")); + widget.button->setIcon(QApplication::style()->standardIcon(QStyle::SP_TrashIcon)); + widget.button->setIconSize(QSize(16, 16)); + } + else + { + widget.button->setText(tr("Import")); + widget.button->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton)); + widget.button->setIconSize(QSize(16, 16)); + } + } +} diff --git a/rpcs3/rpcs3qt/sound_effect_manager_dialog.h b/rpcs3/rpcs3qt/sound_effect_manager_dialog.h new file mode 100644 index 0000000000..e8e52440f1 --- /dev/null +++ b/rpcs3/rpcs3qt/sound_effect_manager_dialog.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Emu/RSX/Overlays/overlays.h" + +#include +#include + +class sound_effect_manager_dialog : public QDialog +{ + Q_OBJECT + +public: + explicit sound_effect_manager_dialog(QWidget* parent = nullptr); + ~sound_effect_manager_dialog(); + +private: + void update_widgets(); + + struct widget + { + QPushButton* button = nullptr; + QPushButton* play_button = nullptr; + }; + + std::map m_widgets; +}; diff --git a/rpcs3/rpcs3qt/trophy_notification_helper.cpp b/rpcs3/rpcs3qt/trophy_notification_helper.cpp index 910b16ffcc..87b69c42b2 100644 --- a/rpcs3/rpcs3qt/trophy_notification_helper.cpp +++ b/rpcs3/rpcs3qt/trophy_notification_helper.cpp @@ -31,7 +31,7 @@ s32 trophy_notification_helper::ShowTrophyNotification(const SceNpTrophyDetails& trophy_notification->move(m_game_window->mapToGlobal(QPoint(0, 0))); trophy_notification->show(); - Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_trophy.wav"); + Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_trophy.wav", std::nullopt); }); return 0; From 53edd410c74449f049b97fcd8a069f4a505c611a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 21 Nov 2025 19:05:53 +0100 Subject: [PATCH 06/35] ppu: move alignment below llvm check --- rpcs3/Emu/Cell/PPUThread.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 0982ed79e4..a9bef5e640 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -827,14 +827,14 @@ extern void ppu_register_function_at(u32 addr, u32 size, ppu_intrp_func_t ptr = return; } - size = utils::align(size + addr % 4, 4); - addr &= -4; - if (g_cfg.core.ppu_decoder == ppu_decoder_type::llvm) { return; } + size = utils::align(size + addr % 4, 4); + addr &= -4; + // Initialize interpreter cache while (size) { From 84d5e428960512e1ec160be7d313df0d040ab8f6 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 09:59:19 +0100 Subject: [PATCH 07/35] Update Qt to 6.10.1 --- .ci/build-mac-arm64.sh | 4 ++-- .ci/build-mac.sh | 4 ++-- .github/workflows/rpcs3.yml | 4 ++-- BUILDING.md | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index b69c634f48..0efe2fda87 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -38,7 +38,7 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then git clone https://github.com/engnr/qt-downloader.git cd qt-downloader git checkout f52efee0f18668c6d6de2dec0234b8c4bc54c597 - # nested Qt 6.10.0 URL workaround + # nested Qt 6.10.1 URL workaround # sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader # sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader # archived Qt 6.7.3 URL workaround @@ -47,7 +47,7 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then "$BREW_PATH/bin/pipenv" run pip3 uninstall py7zr requests semantic_version lxml "$BREW_PATH/bin/pipenv" run pip3 install py7zr requests semantic_version lxml --no-cache mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" - # sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" # Qt 6.10.0 workaround + # sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" # Qt 6.10.1 workaround "$BREW_PATH/bin/pipenv" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats # -o "$QT_VER/clang_64" fi diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 202e2f3a86..6993c7ec9c 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -38,7 +38,7 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then git clone https://github.com/engnr/qt-downloader.git cd qt-downloader git checkout f52efee0f18668c6d6de2dec0234b8c4bc54c597 - # nested Qt 6.10.0 URL workaround + # nested Qt 6.10.1 URL workaround # sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader # sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader # archived Qt 6.7.3 URL workaround @@ -46,7 +46,7 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then cd "/tmp/Qt" "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run pip3 install py7zr requests semantic_version lxml mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" - # sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" # Qt 6.10.0 workaround + # sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" # Qt 6.10.1 workaround "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats # -o "$QT_VER/clang_64" fi diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 9964c045f5..4a906c024d 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -212,9 +212,9 @@ jobs: env: COMPILER: msvc QT_VER_MAIN: '6' - QT_VER: '6.10.0' + QT_VER: '6.10.1' QT_VER_MSVC: 'msvc2022' - QT_DATE: '202510021201' + QT_DATE: '202511161843' LLVM_VER: '19.1.7' VULKAN_VER: '1.3.268.0' VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5' diff --git a/BUILDING.md b/BUILDING.md index 2de1dc1c50..04a01364d7 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -19,26 +19,26 @@ The following tools are required to build RPCS3 on Windows 10 or later: with standalone **CMake** tool. - [Python 3.6+](https://www.python.org/downloads/) (add to PATH) -- [Qt 6.10.0](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) +- [Qt 6.10.1](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) - [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode. In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs: -- add and set the `QTDIR` environment variable, e.g. `\6.10.0\msvc2022_64\` +- add and set the `QTDIR` environment variable, e.g. `\6.10.1\msvc2022_64\` - or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022) **NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead. In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool): -- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.10.0\msvc2022_64\` +- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.10.1\msvc2022_64\` ### Linux These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager: - Clang 17+ or GCC 13+ - [CMake 3.28.0+](https://www.cmake.org/download/) -- [Qt 6.10.0](https://www.qt.io/download-qt-installer) +- [Qt 6.10.1](https://www.qt.io/download-qt-installer) - [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. - [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend) @@ -121,7 +121,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r ##### Configuring the Qt Plugin (if used) 1) go to `Extensions->Qt VS Tools->Qt Versions` -2) add the path to your Qt installation with compiler e.g. `\6.10.0\msvc2022_64`, version will fill in automatically +2) add the path to your Qt installation with compiler e.g. `\6.10.1\msvc2022_64`, version will fill in automatically 3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**) 4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**) From c80aba2342a95a84d44cc5559276bf4609924c72 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 09:59:34 +0100 Subject: [PATCH 08/35] Update libpng to 1.6.51 --- 3rdparty/libpng/libpng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index 2b978915d8..49363adcfa 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit 2b978915d82377df13fcbb1fb56660195ded868a +Subproject commit 49363adcfaf098748d7a4c8c624ad8c45a8c3a86 From 3c5c74c4965f6865b327f9b1e51ab2f6b2deed4c Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:26:30 +0200 Subject: [PATCH 09/35] SPU Cache debug --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index c9d784d3ac..d140b4a149 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -720,9 +720,19 @@ void spu_cache::initialize(bool build_existing_cache) } // SPU cache file (version + block size type) - const std::string loc = ppu_cache + "spu-" + fmt::to_lower(g_cfg.core.spu_block_size.to_string()) + "-v1-tane.dat"; + const std::string filename = "spu-" + fmt::to_lower(g_cfg.core.spu_block_size.to_string()) + "-v1-tane.dat"; + const std::string loc = ppu_cache + filename; + const std::string loc_debug = fs::get_cache_dir() + "DEBUG/" + filename; - spu_cache cache(loc); + bool is_debug = false; + + if (fs::is_file(loc_debug)) + { + spu_log.success("SPU Cache override applied!"); + is_debug = true; + } + + spu_cache cache(is_debug ? loc_debug : loc); if (!cache) { From 70019404837c05b9a8f995814d2b5b79c7c92578 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 30 Nov 2025 08:29:56 +0200 Subject: [PATCH 10/35] SPU Analyzer: Be more strict with loads --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index d140b4a149..1a9b30ed9c 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -5018,7 +5018,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s ls_invalid = true; ls_write |= write; - if (write) + if (ls_write) { return discard(); } From d822d85ea11081c1e9473dd094344ae25af8a702 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 30 Nov 2025 15:11:16 +0200 Subject: [PATCH 11/35] SPU: Tame PUTLLC16 --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 45 ++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 1a9b30ed9c..4186c613af 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -4973,6 +4973,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 lsa_last_pc = SPU_LS_SIZE; // PC of first LSA write u32 get_pc = SPU_LS_SIZE; // PC of GETLLAR u32 put_pc = SPU_LS_SIZE; // PC of PUTLLC + u32 rdatomic_pc = SPU_LS_SIZE; // PC of last RdAtomcStat read reg_state_t ls{}; // state of LS load/store address register reg_state_t ls_offs = reg_state_t::from_value(0); // Added value to ls reg_state_t lsa{}; // state of LSA register on GETLLAR @@ -6333,6 +6334,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s break; } + atomic16->rdatomic_pc = pos; + const auto it = atomic16_all.find(pos); if (it == atomic16_all.end()) @@ -7273,7 +7276,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (const auto& [pc_commited, pattern] : atomic16_all) { - if (!pattern.active) + if (!pattern.active || pattern.lsa_pc >= pattern.rdatomic_pc) { continue; } @@ -7283,6 +7286,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s continue; } + std::string pattern_hash; + { + sha1_context ctx; + u8 output[20]{}; + + sha1_starts(&ctx); + sha1_update(&ctx, reinterpret_cast(result.data.data()) + (pattern.lsa_pc - result.lower_bound), pattern.rdatomic_pc - pattern.lsa_pc); + sha1_finish(&ctx, output); + fmt::append(pattern_hash, "%s", fmt::base57(output)); + } + union putllc16_or_0_info { u64 data; @@ -7373,16 +7387,35 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s value.reg2 = pattern.reg2; } + bool allow_pattern = true; + if (g_cfg.core.spu_accurate_reservations) { - // Because enabling it is a hack, as it turns out - // continue; + // The problem with PUTLLC16 optimization, that it is in theory correct at the bounds of the spu function. + // But if the SPU code reuses the cache line data observed, it is not truly atomic. + // So we may enable it only for known cases where SPU atomic data is not used after the function leaves. + + // So the two options are: + + // 1. Atomic compare exchange 16 bytes operation. (rest of data is not read) -> good for RPCS3 to optimize. + // 2. Fetch 128 bytes (read them later), modify only 16 bytes. -> Bad for RPCS3 to optimize. + + // This difference cannot be known at analyzer time but from observing callers. + static constexpr std::initializer_list allowed_patterns = + { + "620oYSe8uQqq9eTkhWfMqoEXX0us"sv, // CellSpurs JobChain acquire pattern + }; + + allow_pattern = std::any_of(allowed_patterns.begin(), allowed_patterns.end(), FN(pattern_hash == x)); } - add_pattern(false, inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); + if (allow_pattern) + { + add_pattern(false, inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); + } - spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s) (putllc0=%d, putllc16+0=%d, all=%d)" - , pattern.mem_count, pattern.put_pc, value.type == v_relative, value.off18, value.type == v_const, value.type == v_reg2, value.reg, value.runtime16_select, entry_point, func_hash, +stats.nowrite, ++stats.single, +stats.all); + spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)" + , pattern.mem_count, pattern.put_pc, value.type == v_relative, value.off18, value.type == v_const, value.type == v_reg2, value.reg, value.runtime16_select, entry_point, func_hash, pattern_hash, +stats.nowrite, ++stats.single, +stats.all); } for (const auto& [read_pc, pattern] : rchcnt_loop_all) From 4bda2f9b0f81557386ff935c944a596e3c5c1ae1 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:43:43 +0200 Subject: [PATCH 12/35] Test: Disable PUTLLC0 --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 4186c613af..d23903997f 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -7319,8 +7319,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s value.required_pc = pattern.required_pc; } - spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all); - add_pattern(false, inst_attr::putllc0, pattern.put_pc - lsa, value.data); + // spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all); + // add_pattern(false, inst_attr::putllc0, pattern.put_pc - lsa, value.data); continue; } From bc55f7787e907b6e2f5da7a44b56796678d184e4 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 11:04:29 +0100 Subject: [PATCH 13/35] Add notes for VS 2026 to BUILDING.md --- BUILDING.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 04a01364d7..60b7046cb2 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -8,11 +8,12 @@ Other instructions may be found [here](https://wiki.rpcs3.net/index.php?title=Bu ### Windows 10 or later The following tools are required to build RPCS3 on Windows 10 or later: -- [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) +- [Visual Studio 2022/2026](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) - **Optional** - [CMake 3.28.0+](https://www.cmake.org/download/) (add to PATH) **NOTES:** - - **Visual Studio 2022** integrates **CMake 3.29+** and it also supports both the `sln` solution (`.sln`, `.vcxproj`) and `CMake` solution (`CMakeLists.txt`, `CMakePresets.json`). + - **Visual Studio 2026** needs at least **CMake 4.2.0+**. + - **Visual Studio 2022/2026** integrates **CMake 3.29+** and it also supports both the `sln` solution (`.sln`, `.vcxproj`) and `CMake` solution (`CMakeLists.txt`, `CMakePresets.json`). See sections [Building with Visual Studio sln solution](#building-with-visual-studio-sln-solution) and [Building with Visual Studio CMake solution](#building-with-visual-studio-cmake-solution) on how to build the project with **Visual Studio**. - Install and use this standalone **CMake** tool just in case of your preference. See section [Building with standalone CMake tool](#building-with-standalone-cmake-tool) on how to build the project From 314b69c7d80ac43eb372f9732deb3c701c4f0bed Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 1 Dec 2025 00:48:30 +0100 Subject: [PATCH 14/35] Qt: save spinbox values with fmt::format This prevents saving localized strings with different decimal characters --- rpcs3/rpcs3qt/emu_settings.cpp | 10 ++++------ rpcs3/rpcs3qt/memory_viewer_panel.cpp | 4 ++-- rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp | 2 +- rpcs3/rpcs3qt/patch_manager_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index bba3b0b235..e9aca513a7 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -668,10 +668,9 @@ void emu_settings::EnhanceSpinBox(QSpinBox* spinbox, emu_settings_type type, con spinbox->setRange(min, max); spinbox->setValue(val); - connect(spinbox, &QSpinBox::textChanged, this, [type, spinbox, this](const QString& /* text*/) + connect(spinbox, &QSpinBox::valueChanged, this, [type, this](int value) { - if (!spinbox) return; - SetSetting(type, spinbox->cleanText().toStdString()); + SetSetting(type, fmt::format("%d", value)); }); connect(this, &emu_settings::RestoreDefaultsSignal, spinbox, [def, spinbox]() @@ -724,10 +723,9 @@ void emu_settings::EnhanceDoubleSpinBox(QDoubleSpinBox* spinbox, emu_settings_ty spinbox->setRange(min, max); spinbox->setValue(val); - connect(spinbox, &QDoubleSpinBox::textChanged, this, [type, spinbox, this](const QString& /* text*/) + connect(spinbox, &QDoubleSpinBox::valueChanged, this, [type, this](double value) { - if (!spinbox) return; - SetSetting(type, spinbox->cleanText().toStdString()); + SetSetting(type, fmt::format("%f", value)); }); connect(this, &emu_settings::RestoreDefaultsSignal, spinbox, [def, spinbox]() diff --git a/rpcs3/rpcs3qt/memory_viewer_panel.cpp b/rpcs3/rpcs3qt/memory_viewer_panel.cpp index 951ff41087..2a8b6c29b1 100644 --- a/rpcs3/rpcs3qt/memory_viewer_panel.cpp +++ b/rpcs3/rpcs3qt/memory_viewer_panel.cpp @@ -432,9 +432,9 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr(&QSpinBox::valueChanged), this, [=, this]() + connect(sb_words, &QSpinBox::valueChanged, this, [this](int value) { - m_colcount = 1 << sb_words->value(); + m_colcount = 1 << value; ShowMemory(); }); diff --git a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp index 433f9adb5a..0899096c95 100644 --- a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp @@ -105,7 +105,7 @@ pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::sha m_config_entries[i]->mirrored.set(state != Qt::Unchecked); }); - connect(m_shifts[i], QOverload::of(&QSpinBox::valueChanged), this, [this, i](int value) + connect(m_shifts[i], &QSpinBox::valueChanged, this, [this, i](int value) { std::lock_guard lock(m_config_mutex); m_config_entries[i]->shift.set(value); diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.cpp b/rpcs3/rpcs3qt/patch_manager_dialog.cpp index a58c4b8662..0df58fb35e 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_manager_dialog.cpp @@ -123,8 +123,8 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set handle_config_value_changed(ui->configurable_combo_box->itemData(index).toDouble()); } }); - connect(ui->configurable_spin_box, QOverload::of(&QSpinBox::valueChanged), this, &patch_manager_dialog::handle_config_value_changed); - connect(ui->configurable_double_spin_box, QOverload::of(&QDoubleSpinBox::valueChanged), this, &patch_manager_dialog::handle_config_value_changed); + connect(ui->configurable_spin_box, &QSpinBox::valueChanged, this, &patch_manager_dialog::handle_config_value_changed); + connect(ui->configurable_double_spin_box, &QDoubleSpinBox::valueChanged, this, &patch_manager_dialog::handle_config_value_changed); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { diff --git a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp index eaacc9c524..57b4f70d02 100644 --- a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp @@ -281,7 +281,7 @@ void raw_mouse_settings_dialog::add_tabs(QTabWidget* tabs) m_accel_spin_boxes.push_back(mouse_acceleration_spin_box); mouse_acceleration_spin_box->setRange(0.1, 10.0); mouse_acceleration_spin_box->setValue(config->mouse_acceleration.get() / 100.0); - connect(mouse_acceleration_spin_box, QOverload::of(&QDoubleSpinBox::valueChanged), this, [player](double value) + connect(mouse_acceleration_spin_box, &QDoubleSpinBox::valueChanged, this, [player](double value) { auto& config = ::at32(g_cfg_raw_mouse.players, player)->mouse_acceleration; config.set(std::clamp(value * 100.0, config.min, config.max)); From 25badf9534440e7a723aa14a0f16ddd5aa24b793 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 12:47:01 +0100 Subject: [PATCH 15/35] Qt: Update header texts and actions when language changes --- rpcs3/rpcs3qt/game_list.cpp | 25 ++-- rpcs3/rpcs3qt/game_list.h | 4 +- rpcs3/rpcs3qt/game_list_frame.cpp | 118 ++++++++++++++----- rpcs3/rpcs3qt/game_list_frame.h | 15 ++- rpcs3/rpcs3qt/main_window.cpp | 4 +- rpcs3/rpcs3qt/save_manager_dialog.cpp | 83 +++++++------ rpcs3/rpcs3qt/save_manager_dialog.h | 3 +- rpcs3/rpcs3qt/savestate_manager_dialog.cpp | 108 ++++++++++++++--- rpcs3/rpcs3qt/savestate_manager_dialog.h | 10 +- rpcs3/rpcs3qt/trophy_manager_dialog.cpp | 128 ++++++++++++++++++--- rpcs3/rpcs3qt/trophy_manager_dialog.h | 10 +- 11 files changed, 393 insertions(+), 115 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list.cpp b/rpcs3/rpcs3qt/game_list.cpp index 62ea8f73ba..c5eba4efd3 100644 --- a/rpcs3/rpcs3qt/game_list.cpp +++ b/rpcs3/rpcs3qt/game_list.cpp @@ -14,16 +14,16 @@ game_list::game_list() : QTableWidget(), game_list_base() }; } -void game_list::sync_header_actions(QList& actions, std::function get_visibility) +void game_list::sync_header_actions(std::map& actions, std::function get_visibility) { ensure(get_visibility); bool is_dirty = false; - for (int col = 0; col < actions.count(); ++col) + for (auto& [col, action] : actions) { const bool is_hidden = !get_visibility(col); - actions[col]->setChecked(!is_hidden); + action->setChecked(!is_hidden); if (isColumnHidden(col) != is_hidden) { @@ -38,7 +38,7 @@ void game_list::sync_header_actions(QList& actions, std::function& actions, std::function get_visibility, std::function set_visibility) +void game_list::create_header_actions(std::map& actions, std::function get_visibility, std::function set_visibility) { ensure(get_visibility); ensure(set_visibility); @@ -48,27 +48,30 @@ void game_list::create_header_actions(QList& actions, std::functionaddActions(actions); + for (auto& [col, action] : actions) + { + configure->addAction(action); + } configure->exec(horizontalHeader()->viewport()->mapToGlobal(pos)); }); - for (int col = 0; col < actions.count(); ++col) + for (auto& [col, action] : actions) { - actions[col]->setCheckable(true); + action->setCheckable(true); - connect(actions[col], &QAction::triggered, this, [this, &actions, get_visibility, set_visibility, col](bool checked) + connect(action, &QAction::triggered, this, [this, &actions, get_visibility, set_visibility, col](bool checked) { if (!checked) // be sure to have at least one column left so you can call the context menu at all time { int c = 0; - for (int i = 0; i < actions.count(); ++i) + for (auto& [col, action] : actions) { - if (get_visibility(i) && ++c > 1) + if (get_visibility(col) && ++c > 1) break; } if (c < 2) { - actions[col]->setChecked(true); // re-enable the checkbox if we don't change the actual state + ::at32(actions, col)->setChecked(true); // re-enable the checkbox if we don't change the actual state return; } } diff --git a/rpcs3/rpcs3qt/game_list.h b/rpcs3/rpcs3qt/game_list.h index db59b07913..6d5a3f5a3e 100644 --- a/rpcs3/rpcs3qt/game_list.h +++ b/rpcs3/rpcs3qt/game_list.h @@ -23,8 +23,8 @@ class game_list : public QTableWidget, public game_list_base public: game_list(); - void sync_header_actions(QList& actions, std::function get_visibility); - void create_header_actions(QList& actions, std::function get_visibility, std::function set_visibility); + void sync_header_actions(std::map& actions, std::function get_visibility); + void create_header_actions(std::map& actions, std::function get_visibility, std::function set_visibility); void clear_list() override; // Use this instead of clearContents diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 162b8cb0f6..7bf27c4695 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -100,27 +100,28 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std m_game_dock->setCentralWidget(m_central_widget); // Actions regarding showing/hiding columns - auto add_column = [this](gui::game_list_columns col, const QString& header_text, const QString& action_text) + const auto add_column = [this](gui::game_list_columns col) { - m_game_list->setHorizontalHeaderItem(static_cast(col), new QTableWidgetItem(header_text)); - m_columnActs.append(new QAction(action_text, this)); + const int column = static_cast(col); + m_game_list->setHorizontalHeaderItem(column, new QTableWidgetItem(get_header_text(column))); + m_column_acts[column] = new QAction(get_action_text(column), this); }; - add_column(gui::game_list_columns::icon, tr("Icon"), tr("Show Icons")); - add_column(gui::game_list_columns::name, tr("Name"), tr("Show Names")); - add_column(gui::game_list_columns::serial, tr("Serial"), tr("Show Serials")); - add_column(gui::game_list_columns::firmware, tr("Firmware"), tr("Show Firmwares")); - add_column(gui::game_list_columns::version, tr("Version"), tr("Show Versions")); - add_column(gui::game_list_columns::category, tr("Category"), tr("Show Categories")); - add_column(gui::game_list_columns::path, tr("Path"), tr("Show Paths")); - add_column(gui::game_list_columns::move, tr("PlayStation Move"), tr("Show PlayStation Move")); - add_column(gui::game_list_columns::resolution, tr("Supported Resolutions"), tr("Show Supported Resolutions")); - add_column(gui::game_list_columns::sound, tr("Sound Formats"), tr("Show Sound Formats")); - add_column(gui::game_list_columns::parental, tr("Parental Level"), tr("Show Parental Levels")); - add_column(gui::game_list_columns::last_play, tr("Last Played"), tr("Show Last Played")); - add_column(gui::game_list_columns::playtime, tr("Time Played"), tr("Show Time Played")); - add_column(gui::game_list_columns::compat, tr("Compatibility"), tr("Show Compatibility")); - add_column(gui::game_list_columns::dir_size, tr("Space On Disk"), tr("Show Space On Disk")); + add_column(gui::game_list_columns::icon); + add_column(gui::game_list_columns::name); + add_column(gui::game_list_columns::serial); + add_column(gui::game_list_columns::firmware); + add_column(gui::game_list_columns::version); + add_column(gui::game_list_columns::category); + add_column(gui::game_list_columns::path); + add_column(gui::game_list_columns::move); + add_column(gui::game_list_columns::resolution); + add_column(gui::game_list_columns::sound); + add_column(gui::game_list_columns::parental); + add_column(gui::game_list_columns::last_play); + add_column(gui::game_list_columns::playtime); + add_column(gui::game_list_columns::compat); + add_column(gui::game_list_columns::dir_size); m_progress_dialog = new progress_dialog(tr("Loading games"), tr("Loading games, please wait..."), tr("Cancel"), 0, 0, false, this, Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint); m_progress_dialog->setMinimumDuration(200); // Only show the progress dialog after some time has passed @@ -211,7 +212,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std connect(m_game_list, &game_list::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); connect(m_game_grid, &game_list_grid::FocusToSearchBar, this, &game_list_frame::FocusToSearchBar); - m_game_list->create_header_actions(m_columnActs, + m_game_list->create_header_actions(m_column_acts, [this](int col) { return m_gui_settings->GetGamelistColVisibility(static_cast(col)); }, [this](int col, bool visible) { m_gui_settings->SetGamelistColVisibility(static_cast(col), visible); }); } @@ -227,7 +228,7 @@ void game_list_frame::LoadSettings() m_show_custom_icons = m_gui_settings->GetValue(gui::gl_custom_icon).toBool(); m_play_hover_movies = m_gui_settings->GetValue(gui::gl_hover_gifs).toBool(); - m_game_list->sync_header_actions(m_columnActs, [this](int col) { return m_gui_settings->GetGamelistColVisibility(static_cast(col)); }); + m_game_list->sync_header_actions(m_column_acts, [this](int col) { return m_gui_settings->GetGamelistColVisibility(static_cast(col)); }); } game_list_frame::~game_list_frame() @@ -238,6 +239,54 @@ game_list_frame::~game_list_frame() gui::utils::stop_future_watcher(m_refresh_watcher, true); } +QString game_list_frame::get_header_text(int col) const +{ + switch (static_cast(col)) + { + case gui::game_list_columns::icon: return tr("Icon"); + case gui::game_list_columns::name: return tr("Name"); + case gui::game_list_columns::serial: return tr("Serial"); + case gui::game_list_columns::firmware: return tr("Firmware"); + case gui::game_list_columns::version: return tr("Version"); + case gui::game_list_columns::category: return tr("Category"); + case gui::game_list_columns::path: return tr("Path"); + case gui::game_list_columns::move: return tr("PlayStation Move"); + case gui::game_list_columns::resolution: return tr("Supported Resolutions"); + case gui::game_list_columns::sound: return tr("Sound Formats"); + case gui::game_list_columns::parental: return tr("Parental Level"); + case gui::game_list_columns::last_play: return tr("Last Played"); + case gui::game_list_columns::playtime: return tr("Time Played"); + case gui::game_list_columns::compat: return tr("Compatibility"); + case gui::game_list_columns::dir_size: return tr("Space On Disk"); + case gui::game_list_columns::count: break; + } + return {}; +} + +QString game_list_frame::get_action_text(int col) const +{ + switch (static_cast(col)) + { + case gui::game_list_columns::icon: return tr("Show Icons"); + case gui::game_list_columns::name: return tr("Show Names"); + case gui::game_list_columns::serial: return tr("Show Serials"); + case gui::game_list_columns::firmware: return tr("Show Firmwares"); + case gui::game_list_columns::version: return tr("Show Versions"); + case gui::game_list_columns::category: return tr("Show Categories"); + case gui::game_list_columns::path: return tr("Show Paths"); + case gui::game_list_columns::move: return tr("Show PlayStation Move"); + case gui::game_list_columns::resolution: return tr("Show Supported Resolutions"); + case gui::game_list_columns::sound: return tr("Show Sound Formats"); + case gui::game_list_columns::parental: return tr("Show Parental Levels"); + case gui::game_list_columns::last_play: return tr("Show Last Played"); + case gui::game_list_columns::playtime: return tr("Show Time Played"); + case gui::game_list_columns::compat: return tr("Show Compatibility"); + case gui::game_list_columns::dir_size: return tr("Show Space On Disk"); + case gui::game_list_columns::count: break; + } + return {}; +} + void game_list_frame::OnColClicked(int col) { if (col == static_cast(gui::game_list_columns::icon)) return; // Don't "sort" icons. @@ -417,6 +466,21 @@ void game_list_frame::Refresh(const bool from_drive, const std::vectorSetValue(0); } + // Update headers + for (int col = 0; col < m_game_list->horizontalHeader()->count(); col++) + { + if (auto item = m_game_list->horizontalHeaderItem(col)) + { + item->setText(get_header_text(col)); + } + } + + // Update actions + for (auto& [col, action] : m_column_acts) + { + action->setText(get_action_text(col)); + } + const std::string games_dir = rpcs3::utils::get_games_dir(); // Remove the specified and detected serials (title id) belonging to "games_dir" folder only from the game list in memory @@ -920,7 +984,7 @@ void game_list_frame::OnRefreshFinished() if (!std::exchange(m_initial_refresh_done, true)) { m_game_list->restore_layout(m_gui_settings->GetValue(gui::gl_state).toByteArray()); - m_game_list->sync_header_actions(m_columnActs, [this](int col) { return m_gui_settings->GetGamelistColVisibility(static_cast(col)); }); + m_game_list->sync_header_actions(m_column_acts, [this](int col) { return m_gui_settings->GetGamelistColVisibility(static_cast(col)); }); } // Emit signal and remove slots @@ -959,9 +1023,9 @@ void game_list_frame::ToggleCategoryFilter(const QStringList& categories, bool s void game_list_frame::SaveSettings() { - for (int col = 0; col < m_columnActs.count(); ++col) + for (const auto& [col, action] : m_column_acts) { - m_gui_settings->SetGamelistColVisibility(static_cast(col), m_columnActs[col]->isChecked()); + m_gui_settings->SetGamelistColVisibility(static_cast(col), action->isChecked()); } m_gui_settings->SetValue(gui::gl_sortCol, m_sort_column, false); m_gui_settings->SetValue(gui::gl_sortAsc, m_col_sort_order == Qt::AscendingOrder, false); @@ -1121,7 +1185,7 @@ void game_list_frame::CreateShortcuts(const std::vector& games, const } } -void game_list_frame::ShowContextMenu(const QPoint &pos) +void game_list_frame::ShowContextMenu(const QPoint& pos) { QPoint global_pos; game_info gameinfo; @@ -2704,7 +2768,7 @@ void game_list_frame::ShowCustomConfigIcon(const game_info& game) RepaintIcons(); } -void game_list_frame::ResizeIcons(const int& slider_pos) +void game_list_frame::ResizeIcons(int slider_pos) { m_icon_size_index = slider_pos; m_icon_size = gui_settings::SizeFromSlider(slider_pos); @@ -2712,7 +2776,7 @@ void game_list_frame::ResizeIcons(const int& slider_pos) RepaintIcons(); } -void game_list_frame::RepaintIcons(const bool& from_settings) +void game_list_frame::RepaintIcons(bool from_settings) { gui::utils::stop_future_watcher(m_parsing_watcher, false); gui::utils::stop_future_watcher(m_refresh_watcher, false); @@ -2746,7 +2810,7 @@ void game_list_frame::SetShowHidden(bool show) m_show_hidden = show; } -void game_list_frame::SetListMode(const bool& is_list) +void game_list_frame::SetListMode(bool is_list) { m_old_layout_is_list = m_is_list_layout; m_is_list_layout = is_list; diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index fb366c933c..c31e6aa66a 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -48,10 +48,10 @@ public: void SaveSettings(); /** Resize Gamelist Icons to size given by slider position */ - void ResizeIcons(const int& slider_pos); + void ResizeIcons(int slider_pos); /** Repaint Gamelist Icons with new background color */ - void RepaintIcons(const bool& from_settings = false); + void RepaintIcons(bool from_settings = false); void SetShowHidden(bool show); @@ -70,7 +70,7 @@ public Q_SLOTS: void BatchRemoveCustomConfigurations(); void BatchRemoveCustomPadConfigurations(); void BatchRemoveShaderCaches(); - void SetListMode(const bool& is_list); + void SetListMode(bool is_list); void SetSearchText(const QString& text); void SetShowCompatibilityInGrid(bool show); void SetPreferGameDataIcons(bool enabled); @@ -83,7 +83,7 @@ private Q_SLOTS: void OnRefreshFinished(); void OnCompatFinished(); void OnColClicked(int col); - void ShowContextMenu(const QPoint &pos); + void ShowContextMenu(const QPoint& pos); void doubleClickedSlot(QTableWidgetItem* item); void doubleClickedSlot(const game_info& game); void ItemSelectionChangedSlot(); @@ -91,7 +91,7 @@ Q_SIGNALS: void GameListFrameClosed(); void NotifyGameSelection(const game_info& game); void RequestBoot(const game_info& game, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::string& savestate = ""); - void RequestIconSizeChange(const int& val); + void RequestIconSizeChange(int val); void NotifyEmuSettingsChange(); void FocusToSearchBar(); void Refreshed(); @@ -127,6 +127,9 @@ protected: private: void push_path(const std::string& path, std::vector& legit_paths); + QString get_header_text(int col) const; + QString get_action_text(int col) const; + void ShowCustomConfigIcon(const game_info& game); bool SearchMatchesApp(const QString& name, const QString& serial, bool fallback = false) const; @@ -165,7 +168,7 @@ private: game_list_table* m_game_list = nullptr; game_compatibility* m_game_compat = nullptr; progress_dialog* m_progress_dialog = nullptr; - QList m_columnActs; + std::map m_column_acts; Qt::SortOrder m_col_sort_order{}; int m_sort_column{}; bool m_initial_refresh_done = false; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 3efbae75d2..c0dcb9bfcd 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2344,6 +2344,8 @@ void main_window::RetranslateUI(const QStringList& language_codes, const QString { m_game_list_frame->Refresh(true); } + + Q_EMIT RequestDialogRepaint(); } void main_window::ShowTitleBars(bool show) const @@ -3343,7 +3345,7 @@ void main_window::CreateConnects() connect(ui->showCustomIconsAct, &QAction::triggered, m_game_list_frame, &game_list_frame::SetShowCustomIcons); connect(ui->playHoverGifsAct, &QAction::triggered, m_game_list_frame, &game_list_frame::SetPlayHoverGifs); - connect(m_game_list_frame, &game_list_frame::RequestIconSizeChange, this, [this](const int& val) + connect(m_game_list_frame, &game_list_frame::RequestIconSizeChange, this, [this](int val) { const int idx = ui->sizeSlider->value() + val; m_save_slider_pos = true; diff --git a/rpcs3/rpcs3qt/save_manager_dialog.cpp b/rpcs3/rpcs3qt/save_manager_dialog.cpp index 006e0d9043..2dd2a14e86 100644 --- a/rpcs3/rpcs3qt/save_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/save_manager_dialog.cpp @@ -32,7 +32,7 @@ LOG_CHANNEL(gui_log, "GUI"); -enum SaveColumns +enum class SaveColumns { Icon = 0, Name = 1, @@ -60,30 +60,26 @@ save_manager_dialog::save_manager_dialog(std::shared_ptr gui_setti setMinimumSize(QSize(400, 400)); setAttribute(Qt::WA_DeleteOnClose); - Init(); -} - -/* - * Future proofing. Makes it easier in future if I add ability to change directories - */ -void save_manager_dialog::Init() -{ // Table m_list = new game_list(); m_list->setItemDelegate(new game_list_delegate(m_list)); m_list->setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection); m_list->setSelectionBehavior(QAbstractItemView::SelectRows); m_list->setContextMenuPolicy(Qt::CustomContextMenu); - m_list->setColumnCount(SaveColumns::Count); + m_list->setColumnCount(static_cast(SaveColumns::Count)); m_list->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); m_list->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); m_list->verticalScrollBar()->setSingleStep(20); m_list->horizontalScrollBar()->setSingleStep(10); - m_list->setHorizontalHeaderLabels(QStringList() << tr("Icon") << tr("Title & Subtitle") << tr("Last Modified") << tr("Save ID") << tr("Notes")); m_list->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); m_list->horizontalHeader()->setStretchLastSection(true); m_list->setMouseTracking(true); + for (int column = 0; column < static_cast(SaveColumns::Count); column++) + { + m_list->setHorizontalHeaderItem(column, new QTableWidgetItem(get_header_text(column))); + } + // Bottom bar const int icon_size = m_gui_settings->GetValue(gui::sd_icon_size).toInt(); m_icon_size = QSize(icon_size, icon_size * 176 / 320); @@ -162,7 +158,7 @@ void save_manager_dialog::Init() connect(m_button_folder, &QAbstractButton::clicked, [this]() { const int idx = m_list->currentRow(); - QTableWidgetItem* item = m_list->item(idx, SaveColumns::Name); + QTableWidgetItem* item = m_list->item(idx, static_cast(SaveColumns::Name)); if (!item) { return; @@ -176,12 +172,12 @@ void save_manager_dialog::Init() connect(m_list, &QTableWidget::customContextMenuRequested, this, &save_manager_dialog::ShowContextMenu); connect(m_list, &QTableWidget::cellChanged, [&](int row, int col) { - if (col != SaveColumns::Note) + if (col != static_cast(SaveColumns::Note)) { return; } - QTableWidgetItem* user_item = m_list->item(row, SaveColumns::Name); - QTableWidgetItem* text_item = m_list->item(row, SaveColumns::Note); + QTableWidgetItem* user_item = m_list->item(row, static_cast(SaveColumns::Name)); + QTableWidgetItem* text_item = m_list->item(row, static_cast(SaveColumns::Note)); if (!user_item || !text_item) { return; @@ -196,7 +192,7 @@ void save_manager_dialog::Init() connect(m_list, &QTableWidget::itemSelectionChanged, this, &save_manager_dialog::UpdateDetails); connect(this, &save_manager_dialog::IconReady, this, [this](int index, const QPixmap& pixmap) { - if (movie_item* item = static_cast(m_list->item(index, SaveColumns::Icon))) + if (movie_item* item = static_cast(m_list->item(index, static_cast(SaveColumns::Icon)))) { item->setData(SaveUserRole::PixmapScaled, pixmap); item->image_change_callback(); @@ -205,6 +201,20 @@ void save_manager_dialog::Init() connect(search_bar, &QLineEdit::textChanged, this, &save_manager_dialog::text_changed); } +QString save_manager_dialog::get_header_text(int col) const +{ + switch (static_cast(col)) + { + case SaveColumns::Icon: return tr("Icon"); + case SaveColumns::Name: return tr("Title & Subtitle"); + case SaveColumns::Time: return tr("Last Modified"); + case SaveColumns::Dir: return tr("Save ID"); + case SaveColumns::Note: return tr("Notes"); + case SaveColumns::Count: break; + } + return {}; +} + /** * This certainly isn't ideal for this code, as it essentially copies cellSaveData. But, I have no other choice without adding public methods to cellSaveData. */ @@ -300,6 +310,15 @@ void save_manager_dialog::UpdateList() m_list->clearContents(); m_list->setRowCount(static_cast(m_save_entries.size())); + // Update headers + for (int col = 0; col < m_list->horizontalHeader()->count(); col++) + { + if (auto item = m_list->horizontalHeaderItem(col)) + { + item->setText(get_header_text(col)); + } + } + const QVariantMap notes = m_persistent_settings->GetValue(gui::persistent::save_notes).toMap(); if (m_gui_settings->GetValue(gui::m_enableUIColors).toBool()) @@ -358,20 +377,20 @@ void save_manager_dialog::UpdateList() icon_item->stop_movie(); } }); - m_list->setItem(i, SaveColumns::Icon, icon_item); + m_list->setItem(i, static_cast(SaveColumns::Icon), icon_item); custom_table_widget_item* titleItem = new custom_table_widget_item(title); titleItem->setData(Qt::UserRole, i); // For sorting to work properly titleItem->setFlags(titleItem->flags() & ~Qt::ItemIsEditable); - m_list->setItem(i, SaveColumns::Name, titleItem); + m_list->setItem(i, static_cast(SaveColumns::Name), titleItem); custom_table_widget_item* timeItem = new custom_table_widget_item(gui::utils::format_timestamp(entry.mtime)); timeItem->setFlags(timeItem->flags() & ~Qt::ItemIsEditable); - m_list->setItem(i, SaveColumns::Time, timeItem); + m_list->setItem(i, static_cast(SaveColumns::Time), timeItem); custom_table_widget_item* dirNameItem = new custom_table_widget_item(dir_name); dirNameItem->setFlags(dirNameItem->flags() & ~Qt::ItemIsEditable); - m_list->setItem(i, SaveColumns::Dir, dirNameItem); + m_list->setItem(i, static_cast(SaveColumns::Dir), dirNameItem); custom_table_widget_item* noteItem = new custom_table_widget_item(); noteItem->setFlags(noteItem->flags() | Qt::ItemIsEditable); @@ -379,7 +398,7 @@ void save_manager_dialog::UpdateList() { noteItem->setText(notes[dir_name].toString()); } - m_list->setItem(i, SaveColumns::Note, noteItem); + m_list->setItem(i, static_cast(SaveColumns::Note), noteItem); } m_list->setSortingEnabled(true); // Enable sorting only after using setItem calls @@ -422,7 +441,7 @@ void save_manager_dialog::UpdateIcons() for (int i = 0; i < m_list->rowCount(); ++i) { - if (movie_item* icon_item = static_cast(m_list->item(i, SaveColumns::Icon))) + if (movie_item* icon_item = static_cast(m_list->item(i, static_cast(SaveColumns::Icon)))) { icon_item->setData(SaveUserRole::PixmapScaled, placeholder); icon_item->setData(Qt::DecorationRole, placeholder); @@ -430,14 +449,14 @@ void save_manager_dialog::UpdateIcons() } m_list->resizeRowsToContents(); - m_list->resizeColumnToContents(SaveColumns::Icon); + m_list->resizeColumnToContents(static_cast(SaveColumns::Icon)); const s32 language_index = gui_application::get_language_id(); const std::string localized_icon = fmt::format("ICON0_%02d.PNG", language_index); for (int i = 0; i < m_list->rowCount(); ++i) { - if (movie_item* icon_item = static_cast(m_list->item(i, SaveColumns::Icon))) + if (movie_item* icon_item = static_cast(m_list->item(i, static_cast(SaveColumns::Icon)))) { icon_item->set_icon_load_func([this, cancel = icon_item->icon_loading_aborted(), dpr, localized_icon](int index) { @@ -448,12 +467,12 @@ void save_manager_dialog::UpdateIcons() QPixmap icon; - if (movie_item* item = static_cast(m_list->item(index, SaveColumns::Icon))) + if (movie_item* item = static_cast(m_list->item(index, static_cast(SaveColumns::Icon)))) { if (!item->data(SaveUserRole::PixmapLoaded).toBool()) { // Load game icon - if (QTableWidgetItem* user_item = m_list->item(index, SaveColumns::Name)) + if (QTableWidgetItem* user_item = m_list->item(index, static_cast(SaveColumns::Name))) { const int idx_real = user_item->data(Qt::UserRole).toInt(); const SaveDataEntry& entry = ::at32(m_save_entries, idx_real); @@ -534,7 +553,7 @@ void save_manager_dialog::OnSort(int logicalIndex) // Remove a save file, need to be confirmed. void save_manager_dialog::OnEntryRemove(int row, bool user_interaction) { - if (QTableWidgetItem* item = m_list->item(row, SaveColumns::Name)) + if (QTableWidgetItem* item = m_list->item(row, static_cast(SaveColumns::Name))) { const int idx_real = item->data(Qt::UserRole).toInt(); const SaveDataEntry& entry = ::at32(m_save_entries, idx_real); @@ -599,7 +618,7 @@ void save_manager_dialog::ShowContextMenu(const QPoint& pos) connect(removeAct, &QAction::triggered, this, &save_manager_dialog::OnEntriesRemove); // entriesremove handles case of one as well connect(showDirAct, &QAction::triggered, [this, idx]() { - QTableWidgetItem* item = m_list->item(idx, SaveColumns::Name); + QTableWidgetItem* item = m_list->item(idx, static_cast(SaveColumns::Name)); if (!item) { return; @@ -655,8 +674,8 @@ void save_manager_dialog::UpdateDetails() WaitForRepaintThreads(false); const int row = m_list->currentRow(); - QTableWidgetItem* item = m_list->item(row, SaveColumns::Name); - movie_item* icon_item = static_cast(m_list->item(row, SaveColumns::Icon)); + QTableWidgetItem* item = m_list->item(row, static_cast(SaveColumns::Name)); + movie_item* icon_item = static_cast(m_list->item(row, static_cast(SaveColumns::Icon))); if (!item || !icon_item) { @@ -692,7 +711,7 @@ void save_manager_dialog::WaitForRepaintThreads(bool abort) { for (int i = 0; i < m_list->rowCount(); i++) { - if (movie_item* item = static_cast(m_list->item(i, SaveColumns::Icon))) + if (movie_item* item = static_cast(m_list->item(i, static_cast(SaveColumns::Icon)))) { item->wait_for_icon_loading(abort); } @@ -706,7 +725,7 @@ void save_manager_dialog::text_changed(const QString& text) if (text.isEmpty()) return true; - for (int col = SaveColumns::Name; col < SaveColumns::Count; col++) + for (int col = static_cast(SaveColumns::Name); col < static_cast(SaveColumns::Count); col++) { const QTableWidgetItem* item = m_list->item(row, col); diff --git a/rpcs3/rpcs3qt/save_manager_dialog.h b/rpcs3/rpcs3qt/save_manager_dialog.h index 791ee9a814..819f9cf43d 100644 --- a/rpcs3/rpcs3qt/save_manager_dialog.h +++ b/rpcs3/rpcs3qt/save_manager_dialog.h @@ -39,7 +39,6 @@ Q_SIGNALS: void IconReady(int index, const QPixmap& new_icon); private: - void Init(); void UpdateList(); void UpdateIcons(); void ShowContextMenu(const QPoint& pos); @@ -49,6 +48,8 @@ private: std::vector GetSaveEntries(const std::string& base_dir); + QString get_header_text(int col) const; + game_list* m_list = nullptr; std::string m_dir; std::vector m_save_entries; diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp index 93c87c01d8..44620bd24c 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp @@ -63,15 +63,16 @@ savestate_manager_dialog::savestate_manager_dialog(std::shared_ptr m_game_table->setAlternatingRowColors(true); m_game_table->installEventFilter(this); - auto add_game_column = [this](gui::savestate_game_list_columns col, const QString& header_text, const QString& action_text) + const auto add_game_column = [this](gui::savestate_game_list_columns col) { - m_game_table->setHorizontalHeaderItem(static_cast(col), new QTableWidgetItem(header_text)); - m_game_column_acts.append(new QAction(action_text, this)); + const int column = static_cast(col); + m_game_table->setHorizontalHeaderItem(column, new QTableWidgetItem(get_gamelist_header_text(column))); + m_game_column_acts[column] = new QAction(get_gamelist_action_text(column), this); }; - add_game_column(gui::savestate_game_list_columns::icon, tr("Icon"), tr("Show Icons")); - add_game_column(gui::savestate_game_list_columns::name, tr("Game"), tr("Show Games")); - add_game_column(gui::savestate_game_list_columns::savestates, tr("Savestates"), tr("Show Savestates")); + add_game_column(gui::savestate_game_list_columns::icon); + add_game_column(gui::savestate_game_list_columns::name); + add_game_column(gui::savestate_game_list_columns::savestates); // Savestate Table m_savestate_table = new game_list(); @@ -94,16 +95,17 @@ savestate_manager_dialog::savestate_manager_dialog(std::shared_ptr m_savestate_table->setAlternatingRowColors(true); m_savestate_table->installEventFilter(this); - auto add_savestate_column = [this](gui::savestate_list_columns col, const QString& header_text, const QString& action_text) + const auto add_savestate_column = [this](gui::savestate_list_columns col) { - m_savestate_table->setHorizontalHeaderItem(static_cast(col), new QTableWidgetItem(header_text)); - m_savestate_column_acts.append(new QAction(action_text, this)); + const int column = static_cast(col); + m_savestate_table->setHorizontalHeaderItem(column, new QTableWidgetItem(get_savestate_header_text(column))); + m_savestate_column_acts[column] = new QAction(get_savestate_action_text(column), this); }; - add_savestate_column(gui::savestate_list_columns::name, tr("Name"), tr("Show Names")); - add_savestate_column(gui::savestate_list_columns::compatible, tr("Compatible"), tr("Show Compatible")); - add_savestate_column(gui::savestate_list_columns::date, tr("Created"), tr("Show Created")); - add_savestate_column(gui::savestate_list_columns::path, tr("Path"), tr("Show Paths")); + add_savestate_column(gui::savestate_list_columns::name); + add_savestate_column(gui::savestate_list_columns::compatible); + add_savestate_column(gui::savestate_list_columns::date); + add_savestate_column(gui::savestate_list_columns::path); m_splitter = new QSplitter(); m_splitter->addWidget(m_game_table); @@ -220,6 +222,56 @@ savestate_manager_dialog::~savestate_manager_dialog() WaitAndAbortGameRepaintThreads(); } +QString savestate_manager_dialog::get_savestate_header_text(int col) const +{ + switch (static_cast(col)) + { + case gui::savestate_list_columns::name: return tr("Name"); + case gui::savestate_list_columns::compatible: return tr("Compatible"); + case gui::savestate_list_columns::date: return tr("Created"); + case gui::savestate_list_columns::path: return tr("Path"); + case gui::savestate_list_columns::count: break; + } + return {}; +} + +QString savestate_manager_dialog::get_savestate_action_text(int col) const +{ + switch (static_cast(col)) + { + case gui::savestate_list_columns::name: return tr("Show Names"); + case gui::savestate_list_columns::compatible: return tr("Show Compatible"); + case gui::savestate_list_columns::date: return tr("Show Created"); + case gui::savestate_list_columns::path: return tr("Show Paths"); + case gui::savestate_list_columns::count: break; + } + return {}; +} + +QString savestate_manager_dialog::get_gamelist_header_text(int col) const +{ + switch (static_cast(col)) + { + case gui::savestate_game_list_columns::icon: return tr("Icon"); + case gui::savestate_game_list_columns::name: return tr("Game"); + case gui::savestate_game_list_columns::savestates: return tr("Savestates"); + case gui::savestate_game_list_columns::count: break; + } + return {}; +} + +QString savestate_manager_dialog::get_gamelist_action_text(int col) const +{ + switch (static_cast(col)) + { + case gui::savestate_game_list_columns::icon: return tr("Show Icons"); + case gui::savestate_game_list_columns::name: return tr("Show Games"); + case gui::savestate_game_list_columns::savestates: return tr("Show Savestates"); + case gui::savestate_game_list_columns::count: break; + } + return {}; +} + bool savestate_manager_dialog::LoadSavestateFolderToDB(std::unique_ptr&& game_savestates) { ensure(!!game_savestates); @@ -625,6 +677,21 @@ void savestate_manager_dialog::PopulateGameTable() m_game_table->clearContents(); m_game_table->setRowCount(static_cast(m_savestate_db.size())); + // Update headers + for (int col = 0; col < m_game_table->horizontalHeader()->count(); col++) + { + if (auto item = m_game_table->horizontalHeaderItem(col)) + { + item->setText(get_gamelist_header_text(col)); + } + } + + // Update actions + for (auto& [col, action] : m_game_column_acts) + { + action->setText(get_gamelist_action_text(col)); + } + m_game_combo->clear(); m_game_combo->blockSignals(true); @@ -681,6 +748,21 @@ void savestate_manager_dialog::PopulateSavestateTable() m_savestate_table->setRowCount(static_cast(savestates.size())); m_savestate_table->setSortingEnabled(false); // Disable sorting before using setItem calls + // Update headers + for (int col = 0; col < m_savestate_table->horizontalHeader()->count(); col++) + { + if (auto item = m_savestate_table->horizontalHeaderItem(col)) + { + item->setText(get_savestate_header_text(col)); + } + } + + // Update actions + for (auto& [col, action] : m_savestate_column_acts) + { + action->setText(get_savestate_action_text(col)); + } + for (int i = 0; i < static_cast(savestates.size()); i++) { const savestate_data& savestate = savestates[i]; diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.h b/rpcs3/rpcs3qt/savestate_manager_dialog.h index 950fede559..28c3eddc8c 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.h +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.h @@ -64,6 +64,12 @@ private: void closeEvent(QCloseEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; + QString get_savestate_header_text(int col) const; + QString get_savestate_action_text(int col) const; + + QString get_gamelist_header_text(int col) const; + QString get_gamelist_action_text(int col) const; + std::shared_ptr m_gui_settings; std::vector m_game_info; @@ -74,8 +80,8 @@ private: game_list* m_savestate_table; //! UI element to display savestate stuff. game_list* m_game_table; //! UI element to display games. - QList m_savestate_column_acts; - QList m_game_column_acts; + std::map m_savestate_column_acts; + std::map m_game_column_acts; int m_game_icon_size_index = 25; QSize m_game_icon_size = QSize(m_game_icon_size_index, m_game_icon_size_index); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp index bae7aae25d..d784f16f6e 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp @@ -100,16 +100,17 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s m_game_table->setAlternatingRowColors(true); m_game_table->installEventFilter(this); - auto add_game_column = [this](gui::trophy_game_list_columns col, const QString& header_text, const QString& action_text) + const auto add_game_column = [this](gui::trophy_game_list_columns col) { - m_game_table->setHorizontalHeaderItem(static_cast(col), new QTableWidgetItem(header_text)); - m_game_column_acts.append(new QAction(action_text, this)); + const int column = static_cast(col); + m_game_table->setHorizontalHeaderItem(column, new QTableWidgetItem(get_gamelist_header_text(column))); + m_game_column_acts[column] = new QAction(get_gamelist_action_text(column), this); }; - add_game_column(gui::trophy_game_list_columns::icon, tr("Icon"), tr("Show Icons")); - add_game_column(gui::trophy_game_list_columns::name, tr("Game"), tr("Show Games")); - add_game_column(gui::trophy_game_list_columns::progress, tr("Progress"), tr("Show Progress")); - add_game_column(gui::trophy_game_list_columns::trophies, tr("Trophies"), tr("Show Trophies")); + add_game_column(gui::trophy_game_list_columns::icon); + add_game_column(gui::trophy_game_list_columns::name); + add_game_column(gui::trophy_game_list_columns::progress); + add_game_column(gui::trophy_game_list_columns::trophies); // Trophy Table m_trophy_table = new game_list(); @@ -133,20 +134,21 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s m_trophy_table->setAlternatingRowColors(true); m_trophy_table->installEventFilter(this); - auto add_trophy_column = [this](gui::trophy_list_columns col, const QString& header_text, const QString& action_text) + const auto add_trophy_column = [this](gui::trophy_list_columns col) { - m_trophy_table->setHorizontalHeaderItem(static_cast(col), new QTableWidgetItem(header_text)); - m_trophy_column_acts.append(new QAction(action_text, this)); + const int column = static_cast(col); + m_trophy_table->setHorizontalHeaderItem(column, new QTableWidgetItem(get_trophy_header_text(column))); + m_trophy_column_acts[column] = new QAction(get_trophy_action_text(column), this); }; - add_trophy_column(gui::trophy_list_columns::icon, tr("Icon"), tr("Show Icons")); - add_trophy_column(gui::trophy_list_columns::name, tr("Name"), tr("Show Names")); - add_trophy_column(gui::trophy_list_columns::description, tr("Description"), tr("Show Descriptions")); - add_trophy_column(gui::trophy_list_columns::type, tr("Type"), tr("Show Types")); - add_trophy_column(gui::trophy_list_columns::is_unlocked, tr("Status"), tr("Show Status")); - add_trophy_column(gui::trophy_list_columns::id, tr("ID"), tr("Show IDs")); - add_trophy_column(gui::trophy_list_columns::platinum_link, tr("Platinum Relevant"), tr("Show Platinum Relevant")); - add_trophy_column(gui::trophy_list_columns::time_unlocked, tr("Time Unlocked"), tr("Show Time Unlocked")); + add_trophy_column(gui::trophy_list_columns::icon); + add_trophy_column(gui::trophy_list_columns::name); + add_trophy_column(gui::trophy_list_columns::description); + add_trophy_column(gui::trophy_list_columns::type); + add_trophy_column(gui::trophy_list_columns::is_unlocked); + add_trophy_column(gui::trophy_list_columns::id); + add_trophy_column(gui::trophy_list_columns::platinum_link); + add_trophy_column(gui::trophy_list_columns::time_unlocked); m_splitter = new QSplitter(); m_splitter->addWidget(m_game_table); @@ -406,6 +408,66 @@ trophy_manager_dialog::~trophy_manager_dialog() WaitAndAbortTrophyRepaintThreads(); } +QString trophy_manager_dialog::get_trophy_header_text(int col) const +{ + switch (static_cast(col)) + { + case gui::trophy_list_columns::icon: return tr("Icon"); + case gui::trophy_list_columns::name: return tr("Name"); + case gui::trophy_list_columns::description: return tr("Description"); + case gui::trophy_list_columns::type: return tr("Type"); + case gui::trophy_list_columns::is_unlocked: return tr("Status"); + case gui::trophy_list_columns::id: return tr("ID"); + case gui::trophy_list_columns::platinum_link: return tr("Platinum Relevant"); + case gui::trophy_list_columns::time_unlocked: return tr("Time Unlocked"); + case gui::trophy_list_columns::count: break; + } + return {}; +} + +QString trophy_manager_dialog::get_trophy_action_text(int col) const +{ + switch (static_cast(col)) + { + case gui::trophy_list_columns::icon: return tr("Show Icons"); + case gui::trophy_list_columns::name: return tr("Show Names"); + case gui::trophy_list_columns::description: return tr("Show Descriptions"); + case gui::trophy_list_columns::type: return tr("Show Types"); + case gui::trophy_list_columns::is_unlocked: return tr("Show Status"); + case gui::trophy_list_columns::id: return tr("Show IDs"); + case gui::trophy_list_columns::platinum_link: return tr("Show Platinum Relevant"); + case gui::trophy_list_columns::time_unlocked: return tr("Show Time Unlocked"); + case gui::trophy_list_columns::count: break; + } + return {}; +} + +QString trophy_manager_dialog::get_gamelist_header_text(int col) const +{ + switch (static_cast(col)) + { + case gui::trophy_game_list_columns::icon: return tr("Icon"); + case gui::trophy_game_list_columns::name: return tr("Game"); + case gui::trophy_game_list_columns::progress: return tr("Progress"); + case gui::trophy_game_list_columns::trophies: return tr("Trophies"); + case gui::trophy_game_list_columns::count: break; + } + return {}; +} + +QString trophy_manager_dialog::get_gamelist_action_text(int col) const +{ + switch (static_cast(col)) + { + case gui::trophy_game_list_columns::icon: return tr("Show Icons"); + case gui::trophy_game_list_columns::name: return tr("Show Games"); + case gui::trophy_game_list_columns::progress: return tr("Show Progress"); + case gui::trophy_game_list_columns::trophies: return tr("Show Trophies"); + case gui::trophy_game_list_columns::count: break; + } + return {}; +} + bool trophy_manager_dialog::LoadTrophyFolderToDB(const std::string& trop_name) { const std::string trophy_path = m_trophy_dir + trop_name; @@ -1062,6 +1124,21 @@ void trophy_manager_dialog::PopulateGameTable() m_game_table->clearContents(); m_game_table->setRowCount(static_cast(m_trophies_db.size())); + // Update headers + for (int col = 0; col < m_game_table->horizontalHeader()->count(); col++) + { + if (auto item = m_game_table->horizontalHeaderItem(col)) + { + item->setText(get_gamelist_header_text(col)); + } + } + + // Update actions + for (auto& [col, action] : m_game_column_acts) + { + action->setText(get_gamelist_action_text(col)); + } + m_game_combo->clear(); m_game_combo->blockSignals(true); @@ -1126,6 +1203,21 @@ void trophy_manager_dialog::PopulateTrophyTable() m_trophy_table->setRowCount(all_trophies); m_trophy_table->setSortingEnabled(false); // Disable sorting before using setItem calls + // Update headers + for (int col = 0; col < m_trophy_table->horizontalHeader()->count(); col++) + { + if (auto item = m_trophy_table->horizontalHeaderItem(col)) + { + item->setText(get_trophy_header_text(col)); + } + } + + // Update actions + for (auto& [col, action] : m_trophy_column_acts) + { + action->setText(get_trophy_action_text(col)); + } + QPixmap placeholder(m_icon_height, m_icon_height); placeholder.fill(Qt::transparent); diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.h b/rpcs3/rpcs3qt/trophy_manager_dialog.h index ead945114b..736f099b0e 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.h +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.h @@ -83,6 +83,12 @@ private: static QDateTime TickToDateTime(u64 tick); static u64 DateTimeToTick(QDateTime date_time); + QString get_trophy_header_text(int col) const; + QString get_trophy_action_text(int col) const; + + QString get_gamelist_header_text(int col) const; + QString get_gamelist_action_text(int col) const; + std::shared_ptr m_gui_settings; std::vector> m_trophies_db; //! Holds all the trophy information. @@ -93,8 +99,8 @@ private: game_list* m_trophy_table; //! UI element to display trophy stuff. game_list* m_game_table; //! UI element to display games. - QList m_trophy_column_acts; - QList m_game_column_acts; + std::map m_trophy_column_acts; + std::map m_game_column_acts; bool m_show_hidden_trophies = false; bool m_show_unlocked_trophies = true; From d396a778b7224788e8b683d8276dc74c1ddfbca3 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 15:31:07 +0100 Subject: [PATCH 16/35] rsx: fix capture path if title id is empty --- rpcs3/Emu/RSX/RSXThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 403a9165a3..73c86907f1 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -3073,7 +3073,7 @@ namespace rsx { capture_current_frame = false; - std::string file_path = fs::get_config_dir() + "captures/" + Emu.GetTitleID() + "_" + date_time::current_time_narrow() + "_capture.rrc.gz"; + const std::string file_path = fs::get_config_dir() + "captures/" + (Emu.GetTitleID().empty() ? Emu.GetTitle() : Emu.GetTitleID()) + "_" + date_time::current_time_narrow() + "_capture.rrc.gz"; fs::pending_file temp(file_path); From 7e8ed5ecc102609fa0fc80d6a5ffe78d5c21e61e Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 15:47:21 +0100 Subject: [PATCH 17/35] rsx: clear unused parameters on capture boot --- rpcs3/Emu/System.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index c738474499..dd6c3506b2 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -840,6 +840,19 @@ bool Emulator::BootRsxCapture(const std::string& path) return false; } + m_path.clear(); + m_path_old.clear(); + m_path_original.clear(); + m_title_id.clear(); + m_title.clear(); + m_localized_title.clear(); + m_app_version.clear(); + m_hash.clear(); + m_cat.clear(); + m_dir.clear(); + m_sfo_dir.clear(); + m_ar.reset(); + Init(); g_cfg.video.disable_on_disk_shader_cache.set(true); From 6dd37cb2d5db99c7188246df05678b532fff041a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 1 Dec 2025 20:30:58 +0100 Subject: [PATCH 18/35] Qt: fix game list table multiselection Apparently table items need one common role set for internal indexing. --- rpcs3/rpcs3qt/movie_item.cpp | 3 +++ rpcs3/rpcs3qt/savestate_manager_dialog.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/rpcs3/rpcs3qt/movie_item.cpp b/rpcs3/rpcs3qt/movie_item.cpp index 3cfb269709..eee10cc3ba 100644 --- a/rpcs3/rpcs3qt/movie_item.cpp +++ b/rpcs3/rpcs3qt/movie_item.cpp @@ -3,12 +3,15 @@ movie_item::movie_item() : QTableWidgetItem(), movie_item_base() { + setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } movie_item::movie_item(const QString& text, int type) : QTableWidgetItem(text, type), movie_item_base() { + setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } movie_item::movie_item(const QIcon& icon, const QString& text, int type) : QTableWidgetItem(icon, text, type), movie_item_base() { + setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp index 44620bd24c..ed664e1b65 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp @@ -85,6 +85,7 @@ savestate_manager_dialog::savestate_manager_dialog(std::shared_ptr m_savestate_table->horizontalScrollBar()->setSingleStep(20); m_savestate_table->setItemDelegate(new table_item_delegate(m_savestate_table, false)); m_savestate_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_savestate_table->setSelectionMode(QAbstractItemView::SingleSelection); m_savestate_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_savestate_table->setColumnCount(static_cast(gui::savestate_list_columns::count)); m_savestate_table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); From a859e14e1940a2005bbc39611dd181229e66f6e9 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 2 Dec 2025 03:09:27 +0100 Subject: [PATCH 19/35] Qt: update main window elements on language change --- rpcs3/rpcs3qt/main_window.cpp | 54 +++++++++++++++++++++++------------ rpcs3/rpcs3qt/main_window.h | 10 ++++--- rpcs3/rpcs3qt/main_window.ui | 12 ++++++++ 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c0dcb9bfcd..fac0ea5b92 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -230,36 +230,28 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) // RPCS3 Updater - QMenu* download_menu = new QMenu(tr("Update Available!")); - - QAction* download_action = new QAction(tr("Download Update"), download_menu); - connect(download_action, &QAction::triggered, this, [this] + connect(ui->actionDownload_Update, &QAction::triggered, this, [this] { m_updater.update(false); }); - download_menu->addAction(download_action); - #ifdef _WIN32 // Use a menu at the top right corner to indicate the new version. - QMenuBar *corner_bar = new QMenuBar(ui->menuBar); - m_download_menu_action = corner_bar->addMenu(download_menu); + // Some distros just can't handle corner widgets at the moment. + QMenuBar* corner_bar = new QMenuBar(ui->menuBar); + corner_bar->addMenu(ui->menuUpdate_Available); ui->menuBar->setCornerWidget(corner_bar); ui->menuBar->cornerWidget()->setVisible(false); -#else - // Append a menu to the right of the regular menus to indicate the new version. - // Some distros just can't handle corner widgets at the moment. - m_download_menu_action = ui->menuBar->addMenu(download_menu); + ui->menuBar->removeAction(ui->menuUpdate_Available->menuAction()); #endif - ensure(m_download_menu_action); - m_download_menu_action->setVisible(false); + ui->menuUpdate_Available->setVisible(false); connect(&m_updater, &update_manager::signal_update_available, this, [this](bool update_available) { - if (m_download_menu_action) + if (ui->menuUpdate_Available) { - m_download_menu_action->setVisible(update_available); + ui->menuUpdate_Available->setVisible(update_available); } if (ui->menuBar && ui->menuBar->cornerWidget()) { @@ -1933,9 +1925,11 @@ void main_window::OnEmuRun(bool /*start_playtime*/) EnableMenus(true); update_gui_pad_thread(); + + m_system_state = system_state::running; } -void main_window::OnEmuResume() const +void main_window::OnEmuResume() { const QString title = GetCurrentTitle(); const QString restart_tooltip = tr("Restart %0").arg(title); @@ -1948,9 +1942,11 @@ void main_window::OnEmuResume() const ui->toolbar_start->setText(tr("Pause")); ui->toolbar_start->setToolTip(pause_tooltip); ui->toolbar_stop->setToolTip(stop_tooltip); + + m_system_state = system_state::starting; // Let's just use this state to distinguish between resumed and running } -void main_window::OnEmuPause() const +void main_window::OnEmuPause() { const QString title = GetCurrentTitle(); const QString resume_tooltip = tr("Resume %0").arg(title); @@ -1966,6 +1962,8 @@ void main_window::OnEmuPause() const { m_game_list_frame->Refresh(); } + + m_system_state = system_state::paused; } void main_window::OnEmuStop() @@ -2026,9 +2024,11 @@ void main_window::OnEmuStop() } update_gui_pad_thread(); + + m_system_state = system_state::stopped; } -void main_window::OnEmuReady() const +void main_window::OnEmuReady() { const QString title = GetCurrentTitle(); const QString play_tooltip = tr("Play %0").arg(title); @@ -2054,6 +2054,8 @@ void main_window::OnEmuReady() const ui->removeAllCachesAct->setEnabled(false); ui->removeSavestatesAct->setEnabled(false); ui->cleanUpGameListAct->setEnabled(false); + + m_system_state = system_state::ready; } void main_window::EnableMenus(bool enabled) const @@ -2340,6 +2342,20 @@ void main_window::RetranslateUI(const QStringList& language_codes, const QString ui->retranslateUi(this); + // Update menu bar size (needed if the corner widget changes its size) + ui->menuBar->adjustSize(); + + // Update toolbar elements + switch (m_system_state) + { + case system_state::running: OnEmuRun(false); break; + case system_state::stopped: OnEmuStop(); break; + case system_state::paused: OnEmuPause(); break; + case system_state::starting: OnEmuResume(); break; + case system_state::ready: OnEmuReady(); break; + default: break; + } + if (m_game_list_frame) { m_game_list_frame->Refresh(true); diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 4e5b498587..960a70c722 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -11,6 +11,7 @@ #include "settings.h" #include "shortcut_handler.h" #include "Emu/config_mode.h" +#include "Emu/System.h" #include @@ -88,9 +89,9 @@ Q_SIGNALS: public Q_SLOTS: void OnEmuStop(); void OnEmuRun(bool start_playtime); - void OnEmuResume() const; - void OnEmuPause() const; - void OnEmuReady() const; + void OnEmuResume(); + void OnEmuPause(); + void OnEmuReady(); void OnEnableDiscEject(bool enabled) const; void OnEnableDiscInsert(bool enabled) const; void OnAddBreakpoint(u32 addr) const; @@ -196,9 +197,10 @@ private: std::shared_ptr m_persistent_settings; update_manager m_updater; - QAction* m_download_menu_action = nullptr; shortcut_handler* m_shortcut_handler = nullptr; std::unique_ptr m_gui_pad_thread; + + system_state m_system_state = system_state::stopped; }; diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 5ef0b98f25..72861a5d72 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -412,6 +412,12 @@ + + + Update Available! + + + @@ -419,6 +425,7 @@ + @@ -1448,6 +1455,11 @@ Sound Effects + + + Download Update + + From 3c747b377f8e7112cfe85ed3b2a8147a9946ca39 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 2 Dec 2025 04:23:20 +0100 Subject: [PATCH 20/35] Qt: fix translation of update dialog if the language was changed between downloading the json and clicking on update --- rpcs3/rpcs3qt/update_manager.cpp | 138 +++++++++++++++---------------- rpcs3/rpcs3qt/update_manager.h | 20 +++-- 2 files changed, 83 insertions(+), 75 deletions(-) diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp index 8b07693e34..6929e08318 100644 --- a/rpcs3/rpcs3qt/update_manager.cpp +++ b/rpcs3/rpcs3qt/update_manager.cpp @@ -56,8 +56,7 @@ void update_manager::check_for_updates(bool automatic, bool check_only, bool aut { update_log.notice("Checking for updates: automatic=%d, check_only=%d, auto_accept=%d", automatic, check_only, auto_accept); - m_update_message.clear(); - m_changelog.clear(); + m_update_info = {}; if (automatic) { @@ -103,7 +102,7 @@ void update_manager::check_for_updates(bool automatic, bool check_only, bool aut } } - Q_EMIT signal_update_available(result_json && !m_update_message.isEmpty()); + Q_EMIT signal_update_available(result_json && m_update_info.update_found); }); const utils::OS_version os = utils::get_OS_version(); @@ -121,7 +120,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce const QJsonObject json_data = QJsonDocument::fromJson(data).object(); const int return_code = json_data["return_code"].toInt(-255); - bool hash_found = true; + m_update_info.hash_found = true; if (return_code < 0) { @@ -143,7 +142,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce // If a user clicks "Check for Updates" with a custom build ask him if he's sure he wants to update to latest version if (!automatic && return_code == -1) { - hash_found = false; + m_update_info.hash_found = false; } else { @@ -187,7 +186,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce check_json(latest["version"].isString(), "Node 'latest_build: version' not found or not a string") && check_json(latest["datetime"].isString(), "Node 'latest_build: datetime' not found or not a string") ) || - (hash_found && !( + (m_update_info.hash_found && !( check_json(current.isObject(), "JSON doesn't contain current_build section") && check_json(current["version"].isString(), "Node 'current_build: datetime' not found or not a string") && check_json(current["datetime"].isString(), "Node 'current_build: version' not found or not a string") @@ -196,7 +195,7 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce return false; } - if (hash_found && return_code == 0) + if (m_update_info.hash_found && return_code == 0) { update_log.success("RPCS3 is up to date!"); m_downloader->close_progress_dialog(); @@ -210,59 +209,26 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce // Calculate how old the build is const QString date_fmt = QStringLiteral("yyyy-MM-dd hh:mm:ss"); - const QDateTime cur_date = hash_found ? QDateTime::fromString(current["datetime"].toString(), date_fmt) : QDateTime::currentDateTimeUtc(); + const QDateTime cur_date = m_update_info.hash_found ? QDateTime::fromString(current["datetime"].toString(), date_fmt) : QDateTime::currentDateTimeUtc(); const QDateTime lts_date = QDateTime::fromString(latest["datetime"].toString(), date_fmt); - const QString cur_str = cur_date.toString(date_fmt); - const QString lts_str = lts_date.toString(date_fmt); + m_update_info.update_found = true; + m_update_info.cur_date = cur_date.toString(date_fmt); + m_update_info.lts_date = lts_date.toString(date_fmt); + m_update_info.diff_msec = cur_date.msecsTo(lts_date); + m_update_info.new_version = latest["version"].toString(); - const qint64 diff_msec = cur_date.msecsTo(lts_date); - - update_log.notice("Current: %s, latest: %s, difference: %lld ms", cur_str, lts_str, diff_msec); - - const Localized localized; - - const QString new_version = latest["version"].toString(); - m_new_version = new_version.toStdString(); - const QString support_message = tr("
You can empower our project at RPCS3 Patreon.
"); - - if (hash_found) + if (m_update_info.hash_found) { - const QString old_version = current["version"].toString(); - m_old_version = old_version.toStdString(); - - if (diff_msec < 0) - { - // This usually means that the current version was marked as broken and won't be shipped anymore, so we need to downgrade to avoid certain bugs. - m_update_message = tr("A better version of RPCS3 is available!

Current version: %0 (%1)
Better version: %2 (%3)
%4
Do you want to update?") - .arg(old_version) - .arg(cur_str) - .arg(new_version) - .arg(lts_str) - .arg(support_message); - } - else - { - m_update_message = tr("A new version of RPCS3 is available!

Current version: %0 (%1)
Latest version: %2 (%3)
Your version is %4 behind.
%5
Do you want to update?") - .arg(old_version) - .arg(cur_str) - .arg(new_version) - .arg(lts_str) - .arg(localized.GetVerboseTimeByMs(diff_msec, true)) - .arg(support_message); - } + m_update_info.old_version = current["version"].toString(); } else { - m_old_version = fmt::format("%s-%s-%s", rpcs3::get_full_branch(), rpcs3::get_branch(), rpcs3::get_version().to_string()); - - m_update_message = tr("You're currently using a custom or PR build.

Latest version: %0 (%1)
The latest version is %2 old.
%3
Do you want to update to the latest official RPCS3 version?") - .arg(new_version) - .arg(lts_str) - .arg(localized.GetVerboseTimeByMs(std::abs(diff_msec), true)) - .arg(support_message); + m_update_info.old_version = QString::fromStdString(fmt::format("%s-%s-%s", rpcs3::get_full_branch(), rpcs3::get_branch(), rpcs3::get_version().to_string())); } + update_log.notice("Current: %s, latest: %s, difference: %lld ms", m_update_info.cur_date, m_update_info.lts_date, m_update_info.diff_msec); + m_request_url = latest[os]["download"].toString().toStdString(); m_expected_hash = latest[os]["checksum"].toString().toStdString(); m_expected_size = latest[os]["size"].toInt(); @@ -277,9 +243,9 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce if (!auto_accept) { - if (automatic && m_gui_settings->GetValue(gui::ib_skip_version).toString() == new_version) + if (automatic && m_gui_settings->GetValue(gui::ib_skip_version).toString() == m_update_info.new_version) { - update_log.notice("Skipping automatic update notification for version '%s' due to user preference", new_version); + update_log.notice("Skipping automatic update notification for version '%s' due to user preference", m_update_info.new_version); m_downloader->close_progress_dialog(); return true; } @@ -300,7 +266,6 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce } else { - entry.version = tr("N/A"); update_log.notice("JSON changelog entry does not contain a version string."); } @@ -310,11 +275,10 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce } else { - entry.title = tr("N/A"); update_log.notice("JSON changelog entry does not contain a title string."); } - m_changelog.push_back(entry); + m_update_info.changelog.push_back(std::move(entry)); } else { @@ -351,25 +315,61 @@ void update_manager::update(bool auto_accept) if (!auto_accept) { - if (m_update_message.isEmpty()) + if (!m_update_info.update_found) { // This can happen if we abort the check_for_updates download. Just check again in this case. - update_log.notice("Aborting update: Update message is empty. Trying again..."); + update_log.notice("Aborting update: Update not found. Trying again..."); m_downloader->close_progress_dialog(); check_for_updates(false, false, false, m_parent); return; } + const Localized localized; + const QString support_message = tr("
You can empower our project at RPCS3 Patreon.
"); + QString update_message; + + if (m_update_info.hash_found) + { + if (m_update_info.diff_msec < 0) + { + // This usually means that the current version was marked as broken and won't be shipped anymore, so we need to downgrade to avoid certain bugs. + update_message = tr("A better version of RPCS3 is available!

Current version: %0 (%1)
Better version: %2 (%3)
%4
Do you want to update?") + .arg(m_update_info.old_version) + .arg(m_update_info.cur_date) + .arg(m_update_info.new_version) + .arg(m_update_info.lts_date) + .arg(support_message); + } + else + { + update_message = tr("A new version of RPCS3 is available!

Current version: %0 (%1)
Latest version: %2 (%3)
Your version is %4 behind.
%5
Do you want to update?") + .arg(m_update_info.old_version) + .arg(m_update_info.cur_date) + .arg(m_update_info.new_version) + .arg(m_update_info.lts_date) + .arg(localized.GetVerboseTimeByMs(m_update_info.diff_msec, true)) + .arg(support_message); + } + } + else + { + update_message = tr("You're currently using a custom or PR build.

Latest version: %0 (%1)
The latest version is %2 old.
%3
Do you want to update to the latest official RPCS3 version?") + .arg(m_update_info.new_version) + .arg(m_update_info.lts_date) + .arg(localized.GetVerboseTimeByMs(std::abs(m_update_info.diff_msec), true)) + .arg(support_message); + } + QString changelog_content; - for (const changelog_data& entry : m_changelog) + for (const changelog_data& entry : m_update_info.changelog) { if (!changelog_content.isEmpty()) changelog_content.append('\n'); - changelog_content.append(tr("• %0: %1").arg(entry.version, entry.title)); + changelog_content.append(tr("• %0: %1").arg(entry.version.isEmpty() ? tr("N/A") : entry.version, entry.title.isEmpty() ? tr("N/A") : entry.title)); } - QMessageBox mb(QMessageBox::Icon::Question, tr("Update Available"), m_update_message, QMessageBox::Yes | QMessageBox::No, m_downloader->get_progress_dialog() ? m_downloader->get_progress_dialog() : m_parent); + QMessageBox mb(QMessageBox::Icon::Question, tr("Update Available"), update_message, QMessageBox::Yes | QMessageBox::No, m_downloader->get_progress_dialog() ? m_downloader->get_progress_dialog() : m_parent); mb.setTextFormat(Qt::RichText); mb.setCheckBox(new QCheckBox(tr("Don't show again for this version"))); @@ -380,16 +380,16 @@ void update_manager::update(bool auto_accept) // Smartass hack to make the unresizeable message box wide enough for the changelog const int changelog_width = QLabel(changelog_content).sizeHint().width(); - if (QLabel(m_update_message).sizeHint().width() < changelog_width) + if (QLabel(update_message).sizeHint().width() < changelog_width) { - m_update_message += "  "; - while (QLabel(m_update_message).sizeHint().width() < changelog_width) + update_message += "  "; + while (QLabel(update_message).sizeHint().width() < changelog_width) { - m_update_message += " "; + update_message += " "; } } - mb.setText(m_update_message); + mb.setText(update_message); } update_log.notice("Asking user for permission to update..."); @@ -400,8 +400,8 @@ void update_manager::update(bool auto_accept) if (mb.checkBox()->isChecked()) { - update_log.notice("User requested to skip further automatic update notifications for version '%s'", m_new_version); - m_gui_settings->SetValue(gui::ib_skip_version, QString::fromStdString(m_new_version)); + update_log.notice("User requested to skip further automatic update notifications for version '%s'", m_update_info.new_version); + m_gui_settings->SetValue(gui::ib_skip_version, m_update_info.new_version); } m_downloader->close_progress_dialog(); @@ -751,7 +751,7 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept) if (fs::file update_file{fs::get_config_dir() + "update_history.log", fs::create + fs::write + fs::append}) { const std::string update_time = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss").toStdString(); - const std::string entry = fmt::format("%s: Updated from \"%s\" to \"%s\"", update_time, m_old_version, m_new_version); + const std::string entry = fmt::format("%s: Updated from \"%s\" to \"%s\"", update_time, m_update_info.old_version, m_update_info.new_version); update_file.write(fmt::format("%s\n", entry)); update_log.notice("Added entry '%s' to update_history.log", entry); } diff --git a/rpcs3/rpcs3qt/update_manager.h b/rpcs3/rpcs3qt/update_manager.h index f6a534b0b2..8e7d9e8eb0 100644 --- a/rpcs3/rpcs3qt/update_manager.h +++ b/rpcs3/rpcs3qt/update_manager.h @@ -19,20 +19,28 @@ private: std::shared_ptr m_gui_settings; - // This message is empty if there is no download available - QString m_update_message; - struct changelog_data { QString version; QString title; }; - std::vector m_changelog; + + struct update_info + { + bool update_found = false; + bool hash_found = false; + qint64 diff_msec = 0; + QString cur_date; + QString lts_date; + QString old_version; + QString new_version; + std::vector changelog; + }; + + update_info m_update_info {}; std::string m_request_url; std::string m_expected_hash; - std::string m_old_version; - std::string m_new_version; u64 m_expected_size = 0; bool handle_json(bool automatic, bool check_only, bool auto_accept, const QByteArray& data); From 6489fca16b81e4517a71ae7773819b1bbadd312f Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 30 Nov 2025 21:44:03 +0100 Subject: [PATCH 21/35] YoRHa: Fix the height of QSpinBox --- bin/GuiConfigs/YoRHa by Ani.qss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/GuiConfigs/YoRHa by Ani.qss b/bin/GuiConfigs/YoRHa by Ani.qss index bcfd059c2e..c772f25196 100644 --- a/bin/GuiConfigs/YoRHa by Ani.qss +++ b/bin/GuiConfigs/YoRHa by Ani.qss @@ -201,7 +201,7 @@ QPushButton::disabled { /* QSpinBox (Settings -> Emulator -> width/height) */ /* QDoubleSpinBox (Pads -> Mouse Acceleration -> x/y) */ QSpinBox, QDoubleSpinBox { - height: 0.1em; + height: 1.50em; background-color: #b3ac98; } QSpinBox::disabled, QDoubleSpinBox::disabled { From 613d428ced78c345fd5c0077b0e72d484bce10d5 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 00:38:35 +0100 Subject: [PATCH 22/35] Qt: fix update note visiblity on linux/macOs --- rpcs3/rpcs3qt/main_window.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index fac0ea5b92..e2c2aea78b 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -243,21 +243,19 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) ui->menuBar->setCornerWidget(corner_bar); ui->menuBar->cornerWidget()->setVisible(false); ui->menuBar->removeAction(ui->menuUpdate_Available->menuAction()); -#endif - - ui->menuUpdate_Available->setVisible(false); connect(&m_updater, &update_manager::signal_update_available, this, [this](bool update_available) { - if (ui->menuUpdate_Available) - { - ui->menuUpdate_Available->setVisible(update_available); - } - if (ui->menuBar && ui->menuBar->cornerWidget()) - { - ui->menuBar->cornerWidget()->setVisible(update_available); - } + ui->menuBar->cornerWidget()->setVisible(update_available); }); +#else + ui->menuUpdate_Available->menuAction()->setVisible(false); + + connect(&m_updater, &update_manager::signal_update_available, this, [this](bool update_available) + { + ui->menuUpdate_Available->menuAction()->setVisible(update_available); + }); +#endif #ifdef RPCS3_UPDATE_SUPPORTED if (const auto update_value = m_gui_settings->GetValue(gui::m_check_upd_start).toString(); update_value != gui::update_off) From cbba687ffab9b0831116a4926bbf343546669880 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 00:08:13 +0100 Subject: [PATCH 23/35] Qt: Relax game_list deselection checks --- rpcs3/rpcs3qt/game_list.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/game_list.cpp b/rpcs3/rpcs3qt/game_list.cpp index c5eba4efd3..70089d650e 100644 --- a/rpcs3/rpcs3qt/game_list.cpp +++ b/rpcs3/rpcs3qt/game_list.cpp @@ -117,7 +117,8 @@ void game_list::fix_narrow_columns() void game_list::mousePressEvent(QMouseEvent* event) { - if (QTableWidgetItem* item = itemAt(event->pos()); !item || !item->data(Qt::UserRole).isValid()) + // Handle deselction when clicking on empty space in the table + if (!itemAt(event->pos())) { clearSelection(); setCurrentItem(nullptr); // Needed for currentItemChanged From 23ffa4ccdbd7075e051b6c8136f1c70cb91b8d1a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 09:01:37 +0100 Subject: [PATCH 24/35] Update FAudio to 25.12 --- 3rdparty/FAudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/FAudio b/3rdparty/FAudio index 8de3616b5b..4ea8afea6b 160000 --- a/3rdparty/FAudio +++ b/3rdparty/FAudio @@ -1 +1 @@ -Subproject commit 8de3616b5b204260fe639e76587731d8a73b8d2c +Subproject commit 4ea8afea6ba857c24e40877f487d000d559b196d From b86b4d15c638644e3ef42ea6fe915733adb7a1bc Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 09:02:03 +0100 Subject: [PATCH 25/35] Update SDL to 3.2.28 --- 3rdparty/libsdl-org/SDL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index badbf8da4e..7f3ae3d574 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit badbf8da4ee72b3ef599c721ffc9899e8d7c8d90 +Subproject commit 7f3ae3d57459e59943a4ecfefc8f6277ec6bf540 From 2a6f1cf35c00e1edb5e8bed78b1ce7fa334b583e Mon Sep 17 00:00:00 2001 From: kd-11 <15904127+kd-11@users.noreply.github.com> Date: Wed, 3 Dec 2025 02:25:17 +0300 Subject: [PATCH 26/35] rsx/common - work around macos posix_memalign quirks --- rpcs3/Emu/RSX/Common/simple_array.hpp | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 4f8d2a5100..00dd6e7d95 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -12,28 +12,45 @@ namespace rsx namespace aligned_allocator { template + requires (Align != 0) && ((Align & (Align - 1)) == 0) + size_t align_up(size_t size) + { + return (size + (Align - 1)) & ~(Align - 1); + } + + template + requires (Align != 0) && ((Align & (Align - 1)) == 0) void* malloc(size_t size) { -#ifdef _WIN32 +#if defined(_WIN32) return _aligned_malloc(size, Align); +#elif defined(__APPLE__) + constexpr size_t NativeAlign = std::max(Align, sizeof(void*)); + return std::aligned_alloc(NativeAlign, align_up(size)); #else - return std::aligned_alloc(Align, size); + return std::aligned_alloc(Align, align_up(size)); #endif } template + requires (Align != 0) && ((Align & (Align - 1)) == 0) void* realloc(void* prev_ptr, [[maybe_unused]] size_t prev_size, size_t new_size) { - if (prev_size >= new_size) + if (align_up(prev_size) >= new_size) { return prev_ptr; } ensure(reinterpret_cast(prev_ptr) % Align == 0, "Pointer not aligned to Align"); -#ifdef _WIN32 +#if defined(_WIN32) return _aligned_realloc(prev_ptr, new_size, Align); #else - void* ret = std::aligned_alloc(Align, new_size); +#if defined(__APPLE__) + constexpr size_t NativeAlign = std::max(Align, sizeof(void*)); + void* ret = std::aligned_alloc(NativeAlign, align_up(new_size)); +#else + void* ret = std::aligned_alloc(Align, align_up(new_size)); +#endif std::memcpy(ret, prev_ptr, std::min(prev_size, new_size)); std::free(prev_ptr); return ret; From ff9401303b387cb11b97cf5984a9ab7672f487fc Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 09:39:22 +0100 Subject: [PATCH 27/35] hidapi: switch to official libusb remote --- .gitmodules | 2 +- 3rdparty/hidapi/hidapi | 2 +- rpcs3/Input/ds3_pad_handler.cpp | 8 -------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.gitmodules b/.gitmodules index 6f0cd78a5b..77fae5cf55 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,7 +21,7 @@ ignore = dirty [submodule "3rdparty/hidapi"] path = 3rdparty/hidapi/hidapi - url = ../../RPCS3/hidapi.git + url = ../../libusb/hidapi.git branch = master ignore = dirty [submodule "3rdparty/pugixml"] diff --git a/3rdparty/hidapi/hidapi b/3rdparty/hidapi/hidapi index f42423643e..d6b2a97460 160000 --- a/3rdparty/hidapi/hidapi +++ b/3rdparty/hidapi/hidapi @@ -1 +1 @@ -Subproject commit f42423643ec9011c98cccc0bb790722bbbd3f30b +Subproject commit d6b2a974608dec3b76fb1e36c189f22b9cf3650c diff --git a/rpcs3/Input/ds3_pad_handler.cpp b/rpcs3/Input/ds3_pad_handler.cpp index 0495ef1c05..8d8318ce45 100644 --- a/rpcs3/Input/ds3_pad_handler.cpp +++ b/rpcs3/Input/ds3_pad_handler.cpp @@ -266,14 +266,6 @@ void ds3_pad_handler::check_add_device(hid_device* hidDevice, hid_enumerated_dev } device->report_id = buf[0]; -#elif defined (__APPLE__) - int res = hid_init_sixaxis_usb(hidDevice); - if (res < 0) - { - ds3_log.error("check_add_device: hid_init_sixaxis_usb failed! (result=%d, error=%s)", res, hid_error(hidDevice)); - HidDevice::close(hidDevice); - return; - } #endif for (wchar_t ch : wide_serial) From e3f5f2d14e44a44eec9f8c0f79f53893ff04fdbc Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 30 Nov 2025 13:39:32 +0100 Subject: [PATCH 28/35] cellPad: fix pad mode setters --- rpcs3/Emu/Cell/Modules/cellPad.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellPad.cpp b/rpcs3/Emu/Cell/Modules/cellPad.cpp index a217a37313..9c8e05f74f 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPad.cpp @@ -1051,7 +1051,15 @@ error_code cellPadSetPortSetting(u32 port_no, u32 port_setting) if (port_no >= CELL_PAD_MAX_PORT_NUM) return CELL_OK; - config.port_setting[port_no] = port_setting; + if (port_setting & CELL_PAD_SETTING_PRESS_ON) + config.port_setting[port_no] |= CELL_PAD_SETTING_PRESS_ON; + else + config.port_setting[port_no] &= ~CELL_PAD_SETTING_PRESS_ON; + + if (port_setting & CELL_PAD_SETTING_SENSOR_ON) + config.port_setting[port_no] |= CELL_PAD_SETTING_SENSOR_ON; + else + config.port_setting[port_no] &= ~CELL_PAD_SETTING_SENSOR_ON; // can also return CELL_PAD_ERROR_UNSUPPORTED_GAMEPAD <- Update: seems to be just internal and ignored @@ -1123,7 +1131,7 @@ error_code cellPadSetPressMode(u32 port_no, u32 mode) if (!config.max_connect) return CELL_PAD_ERROR_UNINITIALIZED; - if (port_no >= CELL_PAD_MAX_PORT_NUM) + if (port_no >= CELL_MAX_PADS || mode > 1) return CELL_PAD_ERROR_INVALID_PARAMETER; // CELL_PAD_ERROR_NO_DEVICE is not returned in this case. @@ -1157,7 +1165,7 @@ error_code cellPadSetSensorMode(u32 port_no, u32 mode) if (!config.max_connect) return CELL_PAD_ERROR_UNINITIALIZED; - if (port_no >= CELL_MAX_PADS) + if (port_no >= CELL_MAX_PADS || mode > 1) return CELL_PAD_ERROR_INVALID_PARAMETER; // CELL_PAD_ERROR_NO_DEVICE is not returned in this case. From b9a9c1af07e85d3d542416039accaef3e1020fc8 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 20:38:33 +0100 Subject: [PATCH 29/35] Qt: revert setting UserRole. this doesn't seem to have been the culprit for multiselection --- rpcs3/rpcs3qt/movie_item.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/rpcs3/rpcs3qt/movie_item.cpp b/rpcs3/rpcs3qt/movie_item.cpp index eee10cc3ba..3cfb269709 100644 --- a/rpcs3/rpcs3qt/movie_item.cpp +++ b/rpcs3/rpcs3qt/movie_item.cpp @@ -3,15 +3,12 @@ movie_item::movie_item() : QTableWidgetItem(), movie_item_base() { - setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } movie_item::movie_item(const QString& text, int type) : QTableWidgetItem(text, type), movie_item_base() { - setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } movie_item::movie_item(const QIcon& icon, const QString& text, int type) : QTableWidgetItem(icon, text, type), movie_item_base() { - setData(Qt::UserRole, {}); // Set any value to UserRole for proper indexing (e.g. for multiselection) } From dc27047ed44b5d61c8c9eee58ec4b839e6f3ced9 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 20:39:22 +0100 Subject: [PATCH 30/35] Qt: fix game list refresh order when changing the language --- rpcs3/rpcs3qt/main_window.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index e2c2aea78b..5509a6f69c 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2340,6 +2340,12 @@ void main_window::RetranslateUI(const QStringList& language_codes, const QString ui->retranslateUi(this); + // Refresh game list first to prevent localization mismatches in further Refresh calls + if (m_game_list_frame) + { + m_game_list_frame->Refresh(true); + } + // Update menu bar size (needed if the corner widget changes its size) ui->menuBar->adjustSize(); @@ -2354,11 +2360,6 @@ void main_window::RetranslateUI(const QStringList& language_codes, const QString default: break; } - if (m_game_list_frame) - { - m_game_list_frame->Refresh(true); - } - Q_EMIT RequestDialogRepaint(); } From 7d3cf831d542f7a919cb5f82cb3ee2fb09174374 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 20:50:59 +0100 Subject: [PATCH 31/35] Qt/macOS: Fix GUI freezes on Qt 6.8+ --- rpcs3/rpcs3.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index f7efb7728b..e7257577dc 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -435,6 +435,9 @@ QCoreApplication* create_application(std::span qt_argv) { qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0"); } +#elif __APPLE__ + // set the QT_MTL_NO_TRANSACTION variable in order to prevent Qt GUI freeze + qputenv("QT_MTL_NO_TRANSACTION", "1"); #endif bool use_high_dpi = true; From fce393024a1ba71414210909c859e050740fa2ac Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 21:17:31 +0100 Subject: [PATCH 32/35] Update Qt for macOs to 6.9.3 --- .ci/build-mac-arm64.sh | 11 ++++------- .ci/build-mac.sh | 11 ++++------- .github/workflows/rpcs3.yml | 2 +- rpcs3/rpcs3qt/qt_camera_video_sink.cpp | 4 ---- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index 0efe2fda87..42e0f61fb3 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -38,17 +38,14 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then git clone https://github.com/engnr/qt-downloader.git cd qt-downloader git checkout f52efee0f18668c6d6de2dec0234b8c4bc54c597 - # nested Qt 6.10.1 URL workaround - # sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader - # sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader - # archived Qt 6.7.3 URL workaround - sed -i '' "s/official_releases/archive/g" qt-downloader + sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader + sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader cd "/tmp/Qt" "$BREW_PATH/bin/pipenv" run pip3 uninstall py7zr requests semantic_version lxml "$BREW_PATH/bin/pipenv" run pip3 install py7zr requests semantic_version lxml --no-cache mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" - # sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" # Qt 6.10.1 workaround - "$BREW_PATH/bin/pipenv" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats # -o "$QT_VER/clang_64" + sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" + "$BREW_PATH/bin/pipenv" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" fi cd "$WORKDIR" diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 6993c7ec9c..afd687061c 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -38,16 +38,13 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then git clone https://github.com/engnr/qt-downloader.git cd qt-downloader git checkout f52efee0f18668c6d6de2dec0234b8c4bc54c597 - # nested Qt 6.10.1 URL workaround - # sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader - # sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader - # archived Qt 6.7.3 URL workaround - sed -i '' "s/official_releases/archive/g" qt-downloader + sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader + sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader cd "/tmp/Qt" "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run pip3 install py7zr requests semantic_version lxml mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" - # sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" # Qt 6.10.1 workaround - "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats # -o "$QT_VER/clang_64" + sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" + "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" fi cd "$WORKDIR" diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 4a906c024d..9913b632f3 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -134,7 +134,7 @@ jobs: runs-on: macos-14 env: CCACHE_DIR: /tmp/ccache_dir - QT_VER: '6.7.3' + QT_VER: '6.9.3' QT_VER_MAIN: '6' LLVM_COMPILER_VER: '21' RELEASE_MESSAGE: ../GitHubReleaseMessage.txt diff --git a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp index c13fcb781d..de63d80b5d 100644 --- a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp +++ b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp @@ -71,14 +71,10 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) // Flip image if necessary if (flip_horizontally || flip_vertically) { -#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) Qt::Orientations orientation {}; orientation.setFlag(Qt::Orientation::Horizontal, flip_horizontally); orientation.setFlag(Qt::Orientation::Vertical, flip_vertically); image.flip(orientation); -#else - image.mirror(flip_horizontally, flip_vertically); -#endif } if (image.format() != QImage::Format_RGBA8888) From 133b19f2059a8c8bf2a79f8ab04bffd1ad87a89e Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 3 Dec 2025 22:17:52 +0100 Subject: [PATCH 33/35] Update Qt for macOs to 6.10.1 --- .github/workflows/rpcs3.yml | 2 +- rpcs3/rpcs3.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 9913b632f3..1edd6aa9b8 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -134,7 +134,7 @@ jobs: runs-on: macos-14 env: CCACHE_DIR: /tmp/ccache_dir - QT_VER: '6.9.3' + QT_VER: '6.10.1' QT_VER_MAIN: '6' LLVM_COMPILER_VER: '21' RELEASE_MESSAGE: ../GitHubReleaseMessage.txt diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index e7257577dc..007de26bdb 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -438,6 +438,9 @@ QCoreApplication* create_application(std::span qt_argv) #elif __APPLE__ // set the QT_MTL_NO_TRANSACTION variable in order to prevent Qt GUI freeze qputenv("QT_MTL_NO_TRANSACTION", "1"); + + // set the QT_MAC_NO_CONTAINER_LAYER variable in order to prevent swapchain crash + qputenv("QT_MAC_NO_CONTAINER_LAYER", "1"); #endif bool use_high_dpi = true; From 67f7119717fae25b8b2ecc5469c5fb5db45af1b6 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:17:39 +0200 Subject: [PATCH 34/35] Make RSX FIFO Atomic fetching default (#17810) --- rpcs3/Emu/system_config.h | 2 +- rpcs3/rpcs3qt/emu_settings.cpp | 8 ++++---- rpcs3/rpcs3qt/emu_settings_type.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 18f11da896..2b45ce82a0 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -53,7 +53,7 @@ struct cfg_root : cfg::node } }; - fifo_setting rsx_fifo_accuracy{this, "RSX FIFO Accuracy", rsx_fifo_mode::fast }; + fifo_setting rsx_fifo_accuracy{this, "RSX FIFO Fetch Accuracy", rsx_fifo_mode::atomic }; cfg::_bool spu_verification{ this, "SPU Verification", true }; // Should be enabled cfg::_bool spu_cache{ this, "SPU Cache", true }; cfg::_bool spu_prof{ this, "SPU Profiler", false }; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index e9aca513a7..37692423a3 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1198,10 +1198,10 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case emu_settings_type::FIFOAccuracy: switch (static_cast(index)) { - case rsx_fifo_mode::fast: return tr("Fast", "RSX FIFO Accuracy"); - case rsx_fifo_mode::atomic: return tr("Atomic", "RSX FIFO Accuracy"); - case rsx_fifo_mode::atomic_ordered: return tr("Ordered & Atomic", "RSX FIFO Accuracy"); - case rsx_fifo_mode::as_ps3: return tr("PS3", "RSX FIFO Accuracy"); + case rsx_fifo_mode::fast: return tr("Fast", "RSX FIFO Fetch Accuracy"); + case rsx_fifo_mode::atomic: return tr("Atomic", "RSX FIFO Fetch Accuracy"); + case rsx_fifo_mode::atomic_ordered: return tr("Ordered & Atomic", "RSX FIFO Fetch Accuracy"); + case rsx_fifo_mode::as_ps3: return tr("PS3", "RSX FIFO Fetch Accuracy"); } break; case emu_settings_type::PerfOverlayDetailLevel: diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index d90aa7c862..7e69656eac 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -235,7 +235,7 @@ inline static const std::map settings_location { emu_settings_type::AccurateSpuDMA, { "Core", "Accurate SPU DMA"}}, { emu_settings_type::AccurateClineStores, { "Core", "Accurate Cache Line Stores"}}, { emu_settings_type::AccurateRSXAccess, { "Core", "Accurate RSX reservation access"}}, - { emu_settings_type::FIFOAccuracy, { "Core", "RSX FIFO Accuracy"}}, + { emu_settings_type::FIFOAccuracy, { "Core", "RSX FIFO Fetch Accuracy"}}, { emu_settings_type::XFloatAccuracy, { "Core", "XFloat Accuracy"}}, { emu_settings_type::MFCCommandsShuffling, { "Core", "MFC Commands Shuffling Limit"}}, { emu_settings_type::SetDAZandFTZ, { "Core", "Set DAZ and FTZ"}}, From 54206c62b3968273d927664876adfbd8c1bedfe5 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Thu, 4 Dec 2025 21:46:59 +0000 Subject: [PATCH 35/35] Add OpenCV to macOS builds --- .ci/build-mac-arm64.sh | 3 ++- .ci/build-mac.sh | 3 ++- .ci/deploy-mac-arm64.sh | 1 + .ci/deploy-mac.sh | 5 +++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index 42e0f61fb3..b4e255c083 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -6,7 +6,8 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet pipenv googletest ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 # moltenvk based on commit for 1.4.0 release diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index afd687061c..98034c458e 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -12,7 +12,8 @@ brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \ rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config} arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 # moltenvk based on commit for 1.4.0 release diff --git a/.ci/deploy-mac-arm64.sh b/.ci/deploy-mac-arm64.sh index 6e067bde9f..57cd72dafb 100755 --- a/.ci/deploy-mac-arm64.sh +++ b/.ci/deploy-mac-arm64.sh @@ -17,6 +17,7 @@ cd bin mkdir "rpcs3.app/Contents/lib/" || true cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" +cp "$(realpath /opt/homebrew/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib" cp "$(realpath /opt/homebrew/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib" cp "$(realpath /opt/homebrew/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib" diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 4b54267539..92acb70003 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -16,8 +16,9 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin mkdir "rpcs3.app/Contents/lib/" -cp "/usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" -cp "/usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib" +cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" +cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib)" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib" +cp "$(realpath /usr/local/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib" cp "$(realpath /usr/local/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib" cp "$(realpath /usr/local/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"