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 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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 06/15] 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 07/15] 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 08/15] 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 09/15] 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 10/15] 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 11/15] 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 12/15] 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 13/15] 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 14/15] 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 15/15] 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);