From 0bb84b374e59ec212a3c66be73e41702298a4692 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 15 Nov 2025 10:28:11 +0200 Subject: [PATCH 01/13] sys_ppu_thread: Fix u64 stack size argument --- rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp | 7 ++++--- rpcs3/Emu/Cell/lv2/sys_ppu_thread.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index 1aa9409f46..250cd99431 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -468,11 +468,11 @@ error_code sys_ppu_thread_restart(ppu_thread& ppu) return CELL_OK; } -error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::ptr param, u64 arg, u64 unk, s32 prio, u32 _stacksz, u64 flags, vm::cptr threadname) +error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::ptr param, u64 arg, u64 unk, s32 prio, u64 _stacksz, u64 flags, vm::cptr threadname) { ppu.state += cpu_flag::wait; - sys_ppu_thread.warning("_sys_ppu_thread_create(thread_id=*0x%x, param=*0x%x, arg=0x%llx, unk=0x%llx, prio=%d, stacksize=0x%x, flags=0x%llx, threadname=*0x%x)", + sys_ppu_thread.warning("_sys_ppu_thread_create(thread_id=*0x%x, param=*0x%x, arg=0x%llx, unk=0x%llx, prio=%d, stacksize=0x%llx, flags=0x%llx, threadname=*0x%x)", thread_id, param, arg, unk, prio, _stacksz, flags, threadname); // thread_id is checked for null in stub -> CELL_ENOMEM @@ -497,7 +497,8 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::p const u32 tls = param->tls; // Compute actual stack size and allocate - const u32 stack_size = utils::align(std::max(_stacksz, 4096), 4096); + // 0 and UINT64_MAX both convert to 4096 + const u64 stack_size = FN(x ? x : 4096)(utils::align(_stacksz, 4096)); auto& dct = g_fxo->get(); diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h index 81598288ea..2e3f8c524e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h @@ -53,7 +53,7 @@ error_code sys_ppu_thread_get_priority(ppu_thread& ppu, u32 thread_id, vm::ptr sp); error_code sys_ppu_thread_stop(ppu_thread& ppu, u32 thread_id); error_code sys_ppu_thread_restart(ppu_thread& ppu); -error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::ptr param, u64 arg, u64 arg4, s32 prio, u32 stacksize, u64 flags, vm::cptr threadname); +error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::ptr param, u64 arg, u64 arg4, s32 prio, u64 stacksize, u64 flags, vm::cptr threadname); error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id); error_code sys_ppu_thread_rename(ppu_thread& ppu, u32 thread_id, vm::cptr name); error_code sys_ppu_thread_recover_page_fault(ppu_thread& ppu, u32 thread_id); From 7e209a7275a13a00eedec818ba2f7a859f7a778e Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 15 Nov 2025 10:27:12 +0200 Subject: [PATCH 02/13] sys_spu: Improve arguments accuracy --- rpcs3/Emu/Cell/Modules/cellSpurs.cpp | 2 +- rpcs3/Emu/Cell/lv2/sys_spu.cpp | 32 ++++++++++++++++++++++++---- rpcs3/Emu/Cell/lv2/sys_spu.h | 11 ++++++++-- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellSpurs.cpp b/rpcs3/Emu/Cell/Modules/cellSpurs.cpp index c09c808e92..c5defbd048 100644 --- a/rpcs3/Emu/Cell/Modules/cellSpurs.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSpurs.cpp @@ -1230,7 +1230,7 @@ s32 _spurs::initialize(ppu_thread& ppu, vm::ptr spurs, u32 revision, if (flags & SAF_UNKNOWN_FLAG_9) spuTgAttr->type |= 0x0800; if (flags & SAF_SYSTEM_WORKLOAD_ENABLED) spuTgAttr->type |= SYS_SPU_THREAD_GROUP_TYPE_COOPERATE_WITH_SYSTEM; - if (s32 rc = sys_spu_thread_group_create(ppu, spurs.ptr(&CellSpurs::spuTG), nSpus, spuPriority, spuTgAttr)) + if (s32 rc = sys_spu_thread_group_create(ppu, spurs.ptr(&CellSpurs::spuTG), nSpus, spuPriority, vm::unsafe_ptr_cast(spuTgAttr))) { ppu_execute<&sys_spu_image_close>(ppu, spurs.ptr(&CellSpurs::spuImg)); return rollback(), rc; diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index c3328eb604..3665efcd0a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -767,7 +767,12 @@ error_code sys_spu_thread_initialize(ppu_thread& ppu, vm::ptr thread, u32 g } // Read thread name - const std::string thread_name(attr_data.name.get_ptr(), std::max(attr_data.name_len, 1) - 1); + std::string thread_name; + + if (attr_data.name_len && !vm::read_string(attr_data.name.addr(), attr_data.name_len - 1, thread_name, true)) + { + return { CELL_EFAULT, attr_data.name.addr() }; + } const auto group = idm::get_unlocked(group_id); @@ -906,7 +911,7 @@ error_code sys_spu_thread_get_exit_status(ppu_thread& ppu, u32 id, vm::ptr return CELL_ESTAT; } -error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr id, u32 num, s32 prio, vm::ptr attr) +error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr id, u32 num, s32 prio, vm::ptr attr) { ppu.state += cpu_flag::wait; @@ -914,13 +919,32 @@ error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr id, u32 num const s32 min_prio = g_ps3_process_info.has_root_perm() ? 0 : 16; - const sys_spu_thread_group_attribute attr_data = *attr; + sys_spu_thread_group_attribute attr_data{}; + { + const reduced_sys_spu_thread_group_attribute attr_reduced = *attr; + attr_data.name = attr_reduced.name; + attr_data.nsize = attr_reduced.nsize; + attr_data.type = attr_reduced.type; + + // Read container-id member at offset 12 bytes conditionally (that's what LV2 does) + if (attr_data.type & SYS_SPU_THREAD_GROUP_TYPE_MEMORY_FROM_CONTAINER) + { + attr_data.ct = vm::unsafe_ptr_cast(attr)->ct; + } + } if (attr_data.nsize > 0x80 || !num) { return CELL_EINVAL; } + std::string group_name; + + if (attr_data.nsize && !vm::read_string(attr_data.name.addr(), attr_data.nsize - 1, group_name, true)) + { + return { CELL_EFAULT, attr_data.name.addr() }; + } + const s32 type = attr_data.type; bool use_scheduler = true; @@ -1075,7 +1099,7 @@ error_code sys_spu_thread_group_create(ppu_thread& ppu, vm::ptr id, u32 num return CELL_EBUSY; } - const auto group = idm::make_ptr(std::string(attr_data.name.get_ptr(), std::max(attr_data.nsize, 1) - 1), num, prio, type, ct, use_scheduler, mem_size); + const auto group = idm::make_ptr(std::move(group_name), num, prio, type, ct, use_scheduler, mem_size); if (!group) { diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.h b/rpcs3/Emu/Cell/lv2/sys_spu.h index 06e21e067b..3a0012b760 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.h +++ b/rpcs3/Emu/Cell/lv2/sys_spu.h @@ -82,11 +82,18 @@ enum spu_stop_syscall : u32 SYS_SPU_THREAD_STOP_SWITCH_SYSTEM_MODULE = 0x0120, }; -struct sys_spu_thread_group_attribute +struct reduced_sys_spu_thread_group_attribute { be_t nsize; // name length including NULL terminator vm::bcptr name; be_t type; +}; + +struct sys_spu_thread_group_attribute +{ + be_t nsize; + vm::bcptr name; + be_t type; be_t ct; // memory container id }; @@ -360,7 +367,7 @@ error_code _sys_spu_image_close(ppu_thread&, vm::ptr img); error_code _sys_spu_image_get_segments(ppu_thread&, vm::ptr img, vm::ptr segments, s32 nseg); error_code sys_spu_thread_initialize(ppu_thread&, vm::ptr thread, u32 group, u32 spu_num, vm::ptr, vm::ptr, vm::ptr); error_code sys_spu_thread_set_argument(ppu_thread&, u32 id, vm::ptr arg); -error_code sys_spu_thread_group_create(ppu_thread&, vm::ptr id, u32 num, s32 prio, vm::ptr attr); +error_code sys_spu_thread_group_create(ppu_thread&, vm::ptr id, u32 num, s32 prio, vm::ptr attr); error_code sys_spu_thread_group_destroy(ppu_thread&, u32 id); error_code sys_spu_thread_group_start(ppu_thread&, u32 id); error_code sys_spu_thread_group_suspend(ppu_thread&, u32 id); From 871dd729b2b2cdf87c85a27bba39b70c4bc7839b Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 15 Nov 2025 11:46:56 +0100 Subject: [PATCH 03/13] cheats: fix cheat type on apply --- rpcs3/rpcs3qt/cheat_manager.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rpcs3/rpcs3qt/cheat_manager.cpp b/rpcs3/rpcs3qt/cheat_manager.cpp index 8f4d82d9c9..e6b760a0a2 100644 --- a/rpcs3/rpcs3qt/cheat_manager.cpp +++ b/rpcs3/rpcs3qt/cheat_manager.cpp @@ -769,7 +769,7 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) } // TODO: better way to do this? - switch (static_cast(cbx_cheat_search_type->currentIndex())) + switch (cheat->type) { case cheat_type::unsigned_8_cheat: results = convert_and_set(final_offset); break; case cheat_type::unsigned_16_cheat: results = convert_and_set(final_offset); break; @@ -1062,7 +1062,5 @@ QString cheat_manager_dialog::get_localized_cheat_type(cheat_type type) case cheat_type::float_32_cheat: return tr("Float 32 bits"); case cheat_type::max: break; } - std::string type_formatted; - fmt::append(type_formatted, "%s", type); - return QString::fromStdString(type_formatted); + return QString::fromStdString(fmt::format("%s", type)); } From fa06fed86f01c62c129aaaed78a8bf0a6ce0e7e7 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 15 Nov 2025 14:21:10 +0100 Subject: [PATCH 04/13] overlays: take screenshot after exiting the home menu --- rpcs3/Emu/RSX/GL/GLPresent.cpp | 6 ++++-- rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu.cpp | 11 ++++++++++- .../Overlays/HomeMenu/overlay_home_menu_components.h | 3 ++- .../Overlays/HomeMenu/overlay_home_menu_main_menu.cpp | 4 +--- rpcs3/Emu/RSX/VK/VKPresent.cpp | 6 ++++-- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index e8e92a04dc..e7c03dfcf5 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -323,7 +323,9 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) if (image_to_flip) { - if (g_user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) + const bool user_asked_for_screenshot = g_user_asked_for_screenshot.exchange(false); + + if (user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) { static const gl::pixel_pack_settings pack_settings{}; @@ -374,7 +376,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) { screenshot_log.error("Failed to capture image: 0x%x", err); } - else if (g_user_asked_for_screenshot.exchange(false)) + else if (user_asked_for_screenshot) { m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, false); } diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu.cpp index 1938404c36..497ce12031 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu.cpp @@ -4,6 +4,8 @@ #include "Emu/system_config.h" #include "Utilities/date_time.h" +extern atomic_t g_user_asked_for_screenshot; + namespace rsx { namespace overlays @@ -94,12 +96,13 @@ namespace rsx break; } case page_navigation::exit: + case page_navigation::exit_for_screenshot: { fade_animation.current = color4f(1.f); fade_animation.end = color4f(0.f); fade_animation.active = true; - fade_animation.on_finish = [this] + fade_animation.on_finish = [this, navigation] { close(true, true); @@ -110,6 +113,12 @@ namespace rsx Emu.Resume(); }); } + + if (navigation == page_navigation::exit_for_screenshot) + { + rsx_log.notice("Taking screenshot after exiting home menu"); + g_user_asked_for_screenshot = true; + } }; break; } diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_components.h b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_components.h index 7a54acddd8..be25e6ae28 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_components.h +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_components.h @@ -18,7 +18,8 @@ namespace rsx stay, back, next, - exit + exit, + exit_for_screenshot }; struct home_menu_entry : horizontal_layout diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp index acd9f196f4..7a6a5a6b47 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp @@ -11,7 +11,6 @@ #include "Emu/Cell/Modules/sceNpTrophy.h" extern atomic_t g_user_asked_for_recording; -extern atomic_t g_user_asked_for_screenshot; namespace rsx { @@ -98,8 +97,7 @@ namespace rsx if (btn != pad_button::cross) return page_navigation::stay; rsx_log.notice("User selected screenshot in home menu"); - g_user_asked_for_screenshot = true; - return page_navigation::exit; + return page_navigation::exit_for_screenshot; }); std::unique_ptr recording = std::make_unique(get_localized_string(localized_string_id::HOME_MENU_RECORDING)); diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 908b6c301d..3eac2821de 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -694,7 +694,9 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) m_upscaler->scale_output(*m_current_command_buffer, image_to_flip, target_image, target_layout, rgn, UPSCALE_AND_COMMIT | UPSCALE_DEFAULT_VIEW); } - if (g_user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) + const bool user_asked_for_screenshot = g_user_asked_for_screenshot.exchange(false); + + if (user_asked_for_screenshot || (g_recording_mode != recording_mode::stopped && m_frame->can_consume_frame())) { const usz sshot_size = buffer_height * buffer_width * 4; @@ -766,7 +768,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) const bool is_bgra = image_to_copy->format() == VK_FORMAT_B8G8R8A8_UNORM; - if (g_user_asked_for_screenshot.exchange(false)) + if (user_asked_for_screenshot) { m_frame->take_screenshot(std::move(sshot_frame), buffer_width, buffer_height, is_bgra); } From 510ed00b11cd3dd544ab43df553aafdaf6c5cbc5 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 11 Nov 2025 08:09:41 +0100 Subject: [PATCH 05/13] rsx: fix some warnings --- rpcs3/Emu/RSX/Common/simple_array.hpp | 23 ++++++++++++++++++----- rpcs3/Emu/RSX/Overlays/overlay_utils.cpp | 2 +- rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 7d3d331686..633cae62c9 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -178,11 +178,24 @@ namespace rsx return; } - // Use memcpy to allow compiler optimizations - Ty _stack_alloc[_local_capacity]; - std::memcpy(_stack_alloc, that._data, that.size_bytes()); - std::memcpy(that._data, _data, size_bytes()); - std::memcpy(_data, _stack_alloc, that.size_bytes()); + if constexpr (std::is_trivially_copyable_v) + { + // Use memcpy to allow compiler optimizations + Ty _stack_alloc[_local_capacity]; + std::memcpy(_stack_alloc, that._data, that.size_bytes()); + std::memcpy(that._data, _data, size_bytes()); + std::memcpy(_data, _stack_alloc, that.size_bytes()); + } + else + { + // Safe path: use element-wise std::swap + const usz count = std::max(size(), that.size()); + for (usz i = 0; i < count; ++i) + { + std::swap(_data[i], that._data[i]); + } + } + std::swap(_size, that._size); } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp b/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp index 65dde9a8a7..18eac492e9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp @@ -201,7 +201,7 @@ std::u32string utf16_to_u32string(const std::u16string& utf16_string) for (const auto& code : utf16_string) { - result.push_back(code); + result.push_back(static_cast(code)); } return result; diff --git a/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp b/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp index 02d0f0f407..ba7e4add41 100644 --- a/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp +++ b/rpcs3/Emu/RSX/VK/VKProgramPipeline.cpp @@ -453,7 +453,7 @@ namespace vk } m_descriptor_slots.resize(bind_slots_count); - std::memset(m_descriptor_slots.data(), 0, sizeof(descriptor_slot_t) * bind_slots_count); + std::fill(m_descriptor_slots.begin(), m_descriptor_slots.end(), descriptor_slot_t{}); m_descriptors_dirty.resize(bind_slots_count); std::fill(m_descriptors_dirty.begin(), m_descriptors_dirty.end(), false); @@ -534,7 +534,7 @@ namespace vk auto update_descriptor_slot = [this](unsigned idx) { const auto& slot = m_descriptor_slots[idx]; - const VkDescriptorType type = m_descriptor_types[idx]; + if (auto ptr = std::get_if(&slot)) { m_descriptor_template[idx].pImageInfo = m_descriptor_set.store(*ptr); From 5a761c7184c8a622eda0d02a853d94b2dcf84f60 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 11 Nov 2025 13:11:41 +0100 Subject: [PATCH 06/13] rsx: only allow trivially copyable types in simple_array --- rpcs3/Emu/RSX/Common/simple_array.hpp | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 633cae62c9..6fd18ddd1d 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -16,7 +16,7 @@ namespace rsx }; template - requires std::is_trivially_destructible_v + requires std::is_trivially_destructible_v && std::is_trivially_copyable_v struct simple_array { public: @@ -178,24 +178,11 @@ namespace rsx return; } - if constexpr (std::is_trivially_copyable_v) - { - // Use memcpy to allow compiler optimizations - Ty _stack_alloc[_local_capacity]; - std::memcpy(_stack_alloc, that._data, that.size_bytes()); - std::memcpy(that._data, _data, size_bytes()); - std::memcpy(_data, _stack_alloc, that.size_bytes()); - } - else - { - // Safe path: use element-wise std::swap - const usz count = std::max(size(), that.size()); - for (usz i = 0; i < count; ++i) - { - std::swap(_data[i], that._data[i]); - } - } - + // Use memcpy to allow compiler optimizations + Ty _stack_alloc[_local_capacity]; + std::memcpy(_stack_alloc, that._data, that.size_bytes()); + std::memcpy(that._data, _data, size_bytes()); + std::memcpy(_data, _stack_alloc, that.size_bytes()); std::swap(_size, that._size); } From 18111ac8bc18dc4ce43357bb7c0b2d485e125f3f Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 12 Nov 2025 17:16:38 +0100 Subject: [PATCH 07/13] rsx: add simple pair and tuple types --- rpcs3/CMakeLists.txt | 2 + rpcs3/Emu/RSX/Common/simple_array.hpp | 2 +- rpcs3/Emu/RSX/Common/surface_store.h | 10 +- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 2 +- rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 ++ rpcs3/tests/rpcs3_test.vcxproj | 4 +- rpcs3/tests/test_pair.cpp | 46 +++++++++ rpcs3/tests/test_simple_array.cpp | 27 ++++++ rpcs3/tests/test_tuple.cpp | 114 +++++++++++++++++++++++ rpcs3/util/pair.hpp | 15 +++ rpcs3/util/tuple.hpp | 63 +++++++++++++ 12 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 rpcs3/tests/test_pair.cpp create mode 100644 rpcs3/tests/test_tuple.cpp create mode 100644 rpcs3/util/pair.hpp create mode 100644 rpcs3/util/tuple.hpp diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 16147bb8d1..3217cc38f5 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -188,6 +188,8 @@ if(BUILD_RPCS3_TESTS) PRIVATE tests/test.cpp tests/test_fmt.cpp + tests/test_pair.cpp + tests/test_tuple.cpp tests/test_simple_array.cpp tests/test_address_range.cpp ) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 6fd18ddd1d..090c51d674 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -196,7 +196,7 @@ namespace rsx if (is_local_storage()) { // Switch to heap storage - _data = static_cast(std::malloc(sizeof(Ty) * size)); + ensure(_data = static_cast(std::malloc(sizeof(Ty) * size))); std::memcpy(static_cast(_data), _local_storage, size_bytes()); } else diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index 3a80793f77..5fa595a80b 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -9,6 +9,7 @@ #include #include "util/asm.hpp" +#include "util/pair.hpp" namespace rsx { @@ -244,10 +245,9 @@ namespace rsx template void intersect_surface_region(command_list_type cmd, u32 address, surface_type new_surface, surface_type prev_surface) { - auto scan_list = [&new_surface, address](const rsx::address_range32& mem_range, - surface_ranged_map& data) -> rsx::simple_array> + auto scan_list = [&new_surface, address](const rsx::address_range32& mem_range, surface_ranged_map& data) { - rsx::simple_array> result; + rsx::simple_array> result; for (auto it = data.begin_range(mem_range); it != data.end(); ++it) { auto surface = Traits::get(it->second); @@ -314,7 +314,7 @@ namespace rsx } } - rsx::simple_array> surface_info; + rsx::simple_array> surface_info; if (list1.empty()) { surface_info = std::move(list2); @@ -1091,7 +1091,7 @@ namespace rsx rsx::simple_array get_merged_texture_memory_region(commandbuffer_type& cmd, u32 texaddr, u32 required_width, u32 required_height, u32 required_pitch, u8 required_bpp, rsx::surface_access access) { rsx::simple_array result; - rsx::simple_array> dirty; + rsx::simple_array> dirty; const auto surface_internal_pitch = (required_width * required_bpp); diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 7383c0d7fa..9606ca7b16 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -387,7 +387,7 @@ namespace gl allocator.pools[i].flags = 0; } - rsx::simple_array> replacement_map; + rsx::simple_array> replacement_map; for (int i = 0; i < rsx::limits::fragment_textures_count; ++i) { if (reference_mask & (1 << i)) diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index c5f6673723..0bbea41832 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -736,6 +736,8 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 9c09dd791a..65cd509f85 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -2758,6 +2758,12 @@ Utilities + + Utilities + + + Utilities + diff --git a/rpcs3/tests/rpcs3_test.vcxproj b/rpcs3/tests/rpcs3_test.vcxproj index c458653fda..4f0d136a9a 100644 --- a/rpcs3/tests/rpcs3_test.vcxproj +++ b/rpcs3/tests/rpcs3_test.vcxproj @@ -90,6 +90,8 @@ + + @@ -102,4 +104,4 @@ - + \ No newline at end of file diff --git a/rpcs3/tests/test_pair.cpp b/rpcs3/tests/test_pair.cpp new file mode 100644 index 0000000000..086f7102b6 --- /dev/null +++ b/rpcs3/tests/test_pair.cpp @@ -0,0 +1,46 @@ +#include + +#include "util/types.hpp" +#include "util/pair.hpp" + +struct some_struct +{ + u64 v {}; + char s[12] = "Hello World"; + + bool operator == (const some_struct& r) const + { + return v == r.v && std::memcmp(s, r.s, sizeof(s)) == 0; + } +}; + +TEST(Utils, Pair) +{ + some_struct s {}; + s.v = 1234; + + utils::pair p; + EXPECT_EQ(sizeof(p), 32); + EXPECT_EQ(p.first, 0); + EXPECT_EQ(p.second, some_struct{}); + + p = { 666, s }; + EXPECT_EQ(p.first, 666); + EXPECT_EQ(p.second, s); + + const utils::pair p1 = p; + EXPECT_EQ(p.first, 666); + EXPECT_EQ(p.second, s); + EXPECT_EQ(p1.first, 666); + EXPECT_EQ(p1.second, s); + + utils::pair p2 = p1; + EXPECT_EQ(p1.first, 666); + EXPECT_EQ(p1.second, s); + EXPECT_EQ(p2.first, 666); + EXPECT_EQ(p2.second, s); + + utils::pair p3 = std::move(p); + EXPECT_EQ(p3.first, 666); + EXPECT_EQ(p3.second, s); +} diff --git a/rpcs3/tests/test_simple_array.cpp b/rpcs3/tests/test_simple_array.cpp index 6fc9107e48..c581ab2277 100644 --- a/rpcs3/tests/test_simple_array.cpp +++ b/rpcs3/tests/test_simple_array.cpp @@ -1,5 +1,7 @@ #include +#include "util/pair.hpp" + #define private public #include "Emu/RSX/Common/simple_array.hpp" #undef private @@ -240,4 +242,29 @@ namespace rsx EXPECT_EQ(sum, 15); } + + TEST(SimpleArray, SimplePair) + { + struct some_struct + { + u64 v {}; + char s[12] = "Hello World"; + }; + some_struct s {}; + + rsx::simple_array> arr; + for (int i = 0; i < 5; ++i) + { + s.v = i; + arr.push_back(utils::pair(i, s)); + } + + EXPECT_EQ(arr.size(), 5); + for (int i = 0; i < 5; ++i) + { + EXPECT_EQ(arr[i].first, i); + EXPECT_EQ(arr[i].second.v, i); + EXPECT_EQ(std::memcmp(arr[i].second.s, "Hello World", sizeof(arr[i].second.s)), 0); + } + } } diff --git a/rpcs3/tests/test_tuple.cpp b/rpcs3/tests/test_tuple.cpp new file mode 100644 index 0000000000..2a174d85d3 --- /dev/null +++ b/rpcs3/tests/test_tuple.cpp @@ -0,0 +1,114 @@ +#include + +#include "util/tuple.hpp" + +struct some_struct +{ + u64 v {}; + char s[12] = "Hello World"; + + bool operator == (const some_struct& r) const + { + return v == r.v && std::memcmp(s, r.s, sizeof(s)) == 0; + } +}; + +TEST(Utils, Tuple) +{ + some_struct s {}; + s.v = 1234; + + utils::tuple t0 = {}; + EXPECT_EQ(t0.size(), 0); + + utils::tuple t; + EXPECT_EQ(sizeof(t), sizeof(int)); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_EQ(t.size(), 1); + EXPECT_EQ(t.get<0>(), 0); + + utils::tuple t1 = 2; + EXPECT_EQ(sizeof(t1), sizeof(int)); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_EQ(t1.size(), 1); + EXPECT_EQ(t1.get<0>(), 2); + t1 = {}; + EXPECT_EQ(t1.size(), 1); + EXPECT_EQ(t1.get<0>(), 0); + + utils::tuple t2 = { 2, s }; + EXPECT_EQ(sizeof(t2), 32); + EXPECT_EQ(t2.size(), 2); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_TRUE((std::is_same_v()), some_struct&>)); + EXPECT_EQ(t2.get<0>(), 2); + EXPECT_EQ(t2.get<1>(), s); + t2 = {}; + EXPECT_EQ(t2.size(), 2); + EXPECT_EQ(t2.get<0>(), 0); + EXPECT_EQ(t2.get<1>(), some_struct{}); + + t2.get<0>() = 666; + t2.get<1>() = s; + EXPECT_EQ(t2.get<0>(), 666); + EXPECT_EQ(t2.get<1>(), s); + + utils::tuple t3 = { 2, s, 1234.0 }; + EXPECT_EQ(sizeof(t3), 40); + EXPECT_EQ(t3.size(), 3); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_TRUE((std::is_same_v()), some_struct&>)); + EXPECT_TRUE((std::is_same_v()), double&>)); + EXPECT_EQ(t3.get<0>(), 2); + EXPECT_EQ(t3.get<1>(), s); + EXPECT_EQ(t3.get<2>(), 1234.0); + t3 = {}; + EXPECT_EQ(t3.size(), 3); + EXPECT_EQ(t3.get<0>(), 0); + EXPECT_EQ(t3.get<1>(), some_struct{}); + EXPECT_EQ(t3.get<2>(), 0.0); + + t3.get<0>() = 666; + t3.get<1>() = s; + t3.get<2>() = 7.0; + EXPECT_EQ(t3.get<0>(), 666); + EXPECT_EQ(t3.get<1>(), s); + EXPECT_EQ(t3.get<2>(), 7.0); + + // const + const utils::tuple tc = { 2, s }; + EXPECT_EQ(tc.size(), 2); + EXPECT_TRUE((std::is_same_v()), const int&>)); + EXPECT_TRUE((std::is_same_v()), const some_struct&>)); + EXPECT_EQ(tc.get<0>(), 2); + EXPECT_EQ(tc.get<1>(), s); + + // assignment + const utils::tuple ta1 = { 2, s }; + utils::tuple ta = ta1; + EXPECT_EQ(ta.size(), 2); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_TRUE((std::is_same_v()), some_struct&>)); + EXPECT_EQ(ta.get<0>(), 2); + EXPECT_EQ(ta.get<1>(), s); + + utils::tuple ta2 = { 2, s }; + ta = ta2; + EXPECT_EQ(ta.size(), 2); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_TRUE((std::is_same_v()), some_struct&>)); + EXPECT_EQ(ta.get<0>(), 2); + EXPECT_EQ(ta.get<1>(), s); + EXPECT_EQ(ta2.size(), 2); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_TRUE((std::is_same_v()), some_struct&>)); + EXPECT_EQ(ta2.get<0>(), 2); + EXPECT_EQ(ta2.get<1>(), s); + + ta = std::move(ta2); + EXPECT_EQ(ta.size(), 2); + EXPECT_TRUE((std::is_same_v()), int&>)); + EXPECT_TRUE((std::is_same_v()), some_struct&>)); + EXPECT_EQ(ta.get<0>(), 2); + EXPECT_EQ(ta.get<1>(), s); +} diff --git a/rpcs3/util/pair.hpp b/rpcs3/util/pair.hpp new file mode 100644 index 0000000000..454054f3ff --- /dev/null +++ b/rpcs3/util/pair.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace utils +{ + template + requires std::is_trivially_copyable_v && std::is_trivially_destructible_v && + std::is_trivially_copyable_v && std::is_trivially_destructible_v + struct pair + { + T1 first {}; + T2 second {}; + }; +} diff --git a/rpcs3/util/tuple.hpp b/rpcs3/util/tuple.hpp new file mode 100644 index 0000000000..7488f7cba4 --- /dev/null +++ b/rpcs3/util/tuple.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include "types.hpp" +#include +#include + +namespace utils +{ + template + requires ((std::is_trivially_copyable_v && std::is_trivially_destructible_v) && ...) + struct tuple; + + template <> + struct tuple<> + { + constexpr tuple() = default; + + static constexpr usz size() noexcept { return 0; } + }; + + template + struct tuple : tuple + { + private: + Head head; + + public: + constexpr tuple() + : tuple() + , head{} + {} + + constexpr tuple(Head h, Tail... t) + : tuple(std::forward(t)...) + , head(std::move(h)) + {} + + static constexpr usz size() noexcept + { + return 1 + sizeof...(Tail); + } + + template + requires (N < size()) + constexpr auto& get() + { + if constexpr (N == 0) + return head; + else + return tuple::template get(); + } + + template + requires (N < size()) + constexpr const auto& get() const + { + if constexpr (N == 0) + return head; + else + return tuple::template get(); + } + }; +} From ec70c9691fd29c36f2841af9cb2b8053907dc21e Mon Sep 17 00:00:00 2001 From: qurious-pixel <62252937+qurious-pixel@users.noreply.github.com> Date: Sat, 15 Nov 2025 08:45:33 -0800 Subject: [PATCH 08/13] [Mac Arm64] Use runner ccache Remove HomeBrew ccache in favor of Mac runner --- .ci/build-mac-arm64.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index 4d46cac7ef..b69c634f48 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -6,7 +6,7 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet ccache pipenv googletest ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +brew install -f --overwrite --quiet pipenv googletest ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 # moltenvk based on commit for 1.4.0 release @@ -27,6 +27,11 @@ export CMAKE_EXTRA_OPTS='-DLLVM_TARGETS_TO_BUILD=arm64' export WORKDIR; WORKDIR="$(pwd)" +# Setup ccache +if [ ! -d "$CCACHE_DIR" ]; then + mkdir -p "$CCACHE_DIR" +fi + # Get Qt if [ ! -d "/tmp/Qt/$QT_VER" ]; then mkdir -p "/tmp/Qt" From d61f4101ad894014e2b1537ce79dd5227ac0ff05 Mon Sep 17 00:00:00 2001 From: oltolm Date: Fri, 14 Nov 2025 22:22:01 +0100 Subject: [PATCH 09/13] utils: replace hex_to_u64 with std::from_chars --- rpcs3/Crypto/key_vault.cpp | 8 +++---- rpcs3/Crypto/utils.cpp | 48 ++++++++++---------------------------- rpcs3/Crypto/utils.h | 6 ++--- 3 files changed, 19 insertions(+), 43 deletions(-) diff --git a/rpcs3/Crypto/key_vault.cpp b/rpcs3/Crypto/key_vault.cpp index 2533b4d7fc..6fa9aa0684 100644 --- a/rpcs3/Crypto/key_vault.cpp +++ b/rpcs3/Crypto/key_vault.cpp @@ -11,10 +11,10 @@ SELF_KEY::SELF_KEY(u64 ver_start, u64 ver_end, u16 rev, u32 type, const std::str version_end = ver_end; revision = rev; self_type = type; - hex_to_bytes(erk, e.c_str(), 0); - hex_to_bytes(riv, r.c_str(), 0); - hex_to_bytes(pub, pb.c_str(), 0); - hex_to_bytes(priv, pr.c_str(), 0); + hex_to_bytes(erk, e, 0); + hex_to_bytes(riv, r, 0); + hex_to_bytes(pub, pb, 0); + hex_to_bytes(priv, pr, 0); curve_type = ct; } diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 1858fb1fa0..7432acbf62 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -7,9 +7,11 @@ #include "sha1.h" #include "sha256.h" #include "key_vault.h" +#include +#include #include -#include -#include +#include +#include #include "Utilities/StrUtil.h" #include "Utilities/File.h" @@ -21,50 +23,24 @@ // Auxiliary functions (endian swap, xor). // Hex string conversion auxiliary functions. -u64 hex_to_u64(const char* hex_str) +void hex_to_bytes(unsigned char* data, std::string_view hex_str, unsigned int str_length) { - auto length = std::strlen(hex_str); - u64 tmp = 0; - u64 result = 0; - char c; - - while (length--) - { - c = *hex_str++; - if((c >= '0') && (c <= '9')) - tmp = c - '0'; - else if((c >= 'a') && (c <= 'f')) - tmp = c - 'a' + 10; - else if((c >= 'A') && (c <= 'F')) - tmp = c - 'A' + 10; - else - tmp = 0; - result |= (tmp << (length * 4)); - } - - return result; -} - -void hex_to_bytes(unsigned char* data, const char* hex_str, unsigned int str_length) -{ - const auto strn_length = (str_length > 0) ? str_length : std::strlen(hex_str); - auto data_length = strn_length / 2; - char tmp_buf[3] = {0, 0, 0}; + const auto strn_length = (str_length > 0) ? str_length : hex_str.size(); // Don't convert if the string length is odd. if ((strn_length % 2) == 0) { - while (data_length--) + for (size_t i = 0; i < strn_length; i += 2) { - tmp_buf[0] = *hex_str++; - tmp_buf[1] = *hex_str++; - - *data++ = static_cast(hex_to_u64(tmp_buf) & 0xFF); + const auto [ptr, err] = std::from_chars(hex_str.data() + i, hex_str.data() + i + 2, *data++, 16); + if (err != std::errc()) + { + fmt::throw_exception("Failed to read hex string: %s", std::make_error_code(err).message()); + } } } } - // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len) { diff --git a/rpcs3/Crypto/utils.h b/rpcs3/Crypto/utils.h index 25cdeb7193..6d9bb8092b 100644 --- a/rpcs3/Crypto/utils.h +++ b/rpcs3/Crypto/utils.h @@ -6,7 +6,8 @@ #include "util/types.hpp" -#include +#include +#include enum { CRYPTO_MAX_PATH = 4096 }; @@ -15,8 +16,7 @@ char* extract_file_name(const char* file_path, char real_file_name[CRYPTO_MAX_PA std::string sha256_get_hash(const char* data, usz size, bool lower_case); // Hex string conversion auxiliary functions. -u64 hex_to_u64(const char* hex_str); -void hex_to_bytes(unsigned char *data, const char *hex_str, unsigned int str_length); +void hex_to_bytes(unsigned char* data, std::string_view hex_str, unsigned int str_length); // Crypto functions (AES128-CBC, AES128-ECB, SHA1-HMAC and AES-CMAC). void aescbc128_decrypt(unsigned char *key, unsigned char *iv, unsigned char *in, unsigned char *out, usz len); From c7ae97f8eac220b25d097b44efc877ba04915a69 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 15 Nov 2025 15:05:53 +0100 Subject: [PATCH 10/13] Qt: add button to remove gamepad config --- rpcs3/rpcs3qt/pad_settings_dialog.cpp | 119 +++++++++++++++++++------- rpcs3/rpcs3qt/pad_settings_dialog.h | 7 +- rpcs3/rpcs3qt/pad_settings_dialog.ui | 12 ++- 3 files changed, 107 insertions(+), 31 deletions(-) diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index da3712660b..8075577b95 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -16,6 +16,7 @@ #include "Emu/System.h" #include "Emu/system_utils.hpp" +#include "Utilities/File.h" #include "Input/pad_thread.h" #include "Input/gui_pad_thread.h" @@ -86,21 +87,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti if (m_title_id.empty()) { - const QString input_config_dir = QString::fromStdString(rpcs3::utils::get_input_config_dir(m_title_id)); - QStringList config_files = gui::utils::get_dir_entries(QDir(input_config_dir), QStringList() << "*.yml"); - QString active_config_file = QString::fromStdString(g_cfg_input_configs.active_configs.get_value(g_cfg_input_configs.global_key)); - - if (!config_files.contains(active_config_file)) - { - const QString default_config_file = QString::fromStdString(g_cfg_input_configs.default_config); - - if (!config_files.contains(default_config_file) && CreateConfigFile(input_config_dir, default_config_file)) - { - config_files.prepend(default_config_file); - } - - active_config_file = default_config_file; - } + const auto [config_files, active_config_file] = get_config_files(); for (const QString& profile : config_files) { @@ -145,6 +132,9 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti // Pushbutton: Add config file connect(ui->b_addConfig, &QAbstractButton::clicked, this, &pad_settings_dialog::AddConfigFile); + // Pushbutton: Remove config file + connect(ui->b_remConfig, &QAbstractButton::clicked, this, &pad_settings_dialog::RemoveConfigFile); + ui->buttonBox->button(QDialogButtonBox::Reset)->setText(tr("Filter Noise")); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) @@ -272,6 +262,27 @@ void pad_settings_dialog::showEvent(QShowEvent* event) QDialog::showEvent(event); } +std::pair pad_settings_dialog::get_config_files() +{ + const QString input_config_dir = QString::fromStdString(rpcs3::utils::get_input_config_dir(m_title_id)); + QStringList config_files = gui::utils::get_dir_entries(QDir(input_config_dir), QStringList() << "*.yml"); + QString active_config_file = QString::fromStdString(g_cfg_input_configs.active_configs.get_value(g_cfg_input_configs.global_key)); + + if (!config_files.contains(active_config_file)) + { + const QString default_config_file = QString::fromStdString(g_cfg_input_configs.default_config); + + if (!config_files.contains(default_config_file) && CreateConfigFile(input_config_dir, default_config_file)) + { + config_files.prepend(default_config_file); + } + + active_config_file = default_config_file; + } + + return std::make_pair(std::move(config_files), std::move(active_config_file)); +} + void pad_settings_dialog::InitButtons() { m_pad_buttons = new QButtonGroup(this); @@ -321,6 +332,7 @@ void pad_settings_dialog::InitButtons() m_pad_buttons->addButton(ui->b_refresh, button_ids::id_refresh); m_pad_buttons->addButton(ui->b_addConfig, button_ids::id_add_config_file); + m_pad_buttons->addButton(ui->b_remConfig, button_ids::id_remove_config_file); connect(m_pad_buttons, &QButtonGroup::idClicked, this, &pad_settings_dialog::OnPadButtonClicked); @@ -1320,6 +1332,7 @@ void pad_settings_dialog::OnPadButtonClicked(int id) case button_ids::id_pad_begin: case button_ids::id_pad_end: case button_ids::id_add_config_file: + case button_ids::id_remove_config_file: case button_ids::id_refresh: return; case button_ids::id_reset_parameters: @@ -1636,6 +1649,8 @@ void pad_settings_dialog::ChangeConfig(const QString& config_file) m_config_file = config_file.toStdString(); + ui->b_remConfig->setEnabled(m_title_id.empty() && m_config_file != g_cfg_input_configs.default_config); + // Load in order to get the pad handlers if (!g_cfg_input.load(m_title_id, m_config_file, true)) { @@ -1810,6 +1825,44 @@ void pad_settings_dialog::AddConfigFile() } } +void pad_settings_dialog::RemoveConfigFile() +{ + const std::string config_to_remove = m_config_file; + const QString q_config_to_remove = QString::fromStdString(config_to_remove); + + if (config_to_remove == g_cfg_input_configs.default_config) + { + QMessageBox::warning(this, tr("Warning!"), tr("Can't remove default configuration '%0'.").arg(q_config_to_remove)); + return; + } + + if (QMessageBox::question(this, tr("Remove Configuration?"), tr("Do you really want to remove the configuration '%0'?").arg(q_config_to_remove)) != QMessageBox::StandardButton::Yes) + { + return; + } + + const std::string filepath = fmt::format("%s%s.yml", rpcs3::utils::get_input_config_dir(m_title_id), config_to_remove); + + if (!fs::remove_file(filepath)) + { + QMessageBox::warning(this, tr("Warning!"), tr("Failed to remove '%0'.").arg(QString::fromStdString(filepath))); + return; + } + + const auto [config_files, active_config_file] = get_config_files(); + + ui->chooseConfig->setCurrentText(active_config_file); + ui->chooseConfig->removeItem(ui->chooseConfig->findText(q_config_to_remove)); + + // Save new config if we removed the currently saved config + if (active_config_file == q_config_to_remove) + { + save(false); + } + + QMessageBox::information(this, tr("Removed Configuration"), tr("Removed configuration '%0'.\nThe selected configuration is now '%1'.").arg(q_config_to_remove).arg(active_config_file)); +} + void pad_settings_dialog::RefreshHandlers() { const u32 player_id = GetPlayerIndex(); @@ -1949,27 +2002,30 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) cfg.product_id.set(info.product_id); } -void pad_settings_dialog::SaveExit() +void pad_settings_dialog::save(bool check_duplicates) { ApplyCurrentPlayerConfig(m_last_player_id); - for (const auto& [player_id, key] : m_duplicate_buttons) + if (check_duplicates) { - if (!key.empty()) + for (const auto& [player_id, key] : m_duplicate_buttons) { - int result = QMessageBox::Yes; - m_gui_settings->ShowConfirmationBox( - tr("Warning!"), - tr("The %0 button %1 of Player %2 was assigned at least twice.
Please consider adjusting the configuration.

Continue anyway?
") - .arg(QString::fromStdString(g_cfg_input.player[player_id]->handler.to_string())) - .arg(QString::fromStdString(key)) - .arg(player_id + 1), - gui::ib_same_buttons, &result, this); + if (!key.empty()) + { + int result = QMessageBox::Yes; + m_gui_settings->ShowConfirmationBox( + tr("Warning!"), + tr("The %0 button %1 of Player %2 was assigned at least twice.
Please consider adjusting the configuration.

Continue anyway?
") + .arg(QString::fromStdString(g_cfg_input.player[player_id]->handler.to_string())) + .arg(QString::fromStdString(key)) + .arg(player_id + 1), + gui::ib_same_buttons, &result, this); - if (result == QMessageBox::No) - return; + if (result == QMessageBox::No) + return; - break; + break; + } } } @@ -1979,6 +2035,11 @@ void pad_settings_dialog::SaveExit() g_cfg_input_configs.save(); g_cfg_input.save(m_title_id, m_config_file); +} + +void pad_settings_dialog::SaveExit() +{ + save(true); QDialog::accept(); } diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index 467ba537be..e9e56903df 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -74,7 +74,8 @@ class pad_settings_dialog : public QDialog id_reset_parameters, id_blacklist, id_refresh, - id_add_config_file + id_add_config_file, + id_remove_config_file }; struct pad_button @@ -101,6 +102,7 @@ private Q_SLOTS: void ChangeDevice(int index); void HandleDeviceClassChange(u32 class_id) const; void AddConfigFile(); + void RemoveConfigFile(); /** Update the current player config with the GUI values. */ void ApplyCurrentPlayerConfig(int new_player_id); void RefreshPads(); @@ -192,6 +194,9 @@ private: void start_input_thread(); void pause_input_thread(); + std::pair get_config_files(); + + void save(bool check_duplicates); void SaveExit(); void CancelExit(); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.ui b/rpcs3/rpcs3qt/pad_settings_dialog.ui index f41fe42971..62f0bcf4f5 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.ui +++ b/rpcs3/rpcs3qt/pad_settings_dialog.ui @@ -170,7 +170,17 @@ - Add Configuration + Add New + + + false + + + + + + + Remove false From 109abea454aef49a9bccbc479088940fe8c07288 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:22:36 +0200 Subject: [PATCH 11/13] sys_ppu_thread: Fix warning --- rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index 250cd99431..e2463ca101 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -508,7 +508,7 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::p return {CELL_ENOMEM, dct.size - dct.used}; } - const vm::addr_t stack_base{vm::alloc(stack_size, vm::stack, 4096)}; + const vm::addr_t stack_base{vm::alloc(static_cast(stack_size), vm::stack, 4096)}; if (!stack_base) { @@ -533,7 +533,7 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr thread_id, vm::p { ppu_thread_params p; p.stack_addr = stack_base; - p.stack_size = stack_size; + p.stack_size = static_cast(stack_size); p.tls_addr = tls; p.entry = entry; p.arg0 = arg; From 63f7bcf652e9f8244a1688e70566dad3f6895b7d Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 15 Nov 2025 14:11:47 +0200 Subject: [PATCH 12/13] SPU: Use CMPXCHG16B for atomic store if possible --- rpcs3/Emu/Cell/SPUThread.cpp | 47 ++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index d5ad515a1a..d8ab460121 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -303,6 +303,33 @@ static FORCE_INLINE void mov_rdata_avx(__m256i* dst, const __m256i* src) } #endif +// Check if only a single 16-bytes block has changed +// Returning its position, or -1 if that is not the situation +static inline usz scan16_rdata(const decltype(spu_thread::rdata)& _lhs, const decltype(spu_thread::rdata)& _rhs) +{ + const auto lhs = reinterpret_cast(_lhs); + const auto rhs = reinterpret_cast(_rhs); + + u32 mask = 0; + + for (usz i = 0; i < 8; i += 4) + { + const u32 a = (lhs[i + 0] != rhs[i + 0]) ? 1 : 0; + const u32 b = (lhs[i + 1] != rhs[i + 1]) ? 1 : 0; + const u32 c = (lhs[i + 2] != rhs[i + 2]) ? 1 : 0; + const u32 d = (lhs[i + 3] != rhs[i + 3]) ? 1 : 0; + + mask |= ((a << 0) + (b << 1) + (c << 2) + (c << 3)) << i; + } + + if (mask && (mask & (mask - 1)) == 0) + { + return std::countr_zero(mask); + } + + return umax; +} + #ifdef _MSC_VER __forceinline #endif @@ -3854,6 +3881,11 @@ bool spu_thread::do_putllc(const spu_mfc_cmd& args) return false; } + static const auto cast_as = [](void* ptr, usz pos){ return reinterpret_cast(ptr) + pos; }; + static const auto cast_as_const = [](const void* ptr, usz pos){ return reinterpret_cast(ptr) + pos; }; + + const usz diff16_pos = scan16_rdata(to_write, rdata); + auto [_oldd, _ok] = res.fetch_op([&](u64& r) { if ((r & -128) != rtime || (r & 127)) @@ -3975,8 +4007,19 @@ bool spu_thread::do_putllc(const spu_mfc_cmd& args) if (cmp_rdata(rdata, super_data)) { - mov_rdata(super_data, to_write); - return true; + if (diff16_pos != umax) + { + // Do it with CMPXCHG16B if possible, this allows to improve accuracy whenever "RSX Accurate Reservations" is off + if (atomic_storage::compare_exchange(*cast_as(super_data, diff16_pos), *cast_as(rdata, diff16_pos), *cast_as_const(to_write, diff16_pos))) + { + return true; + } + } + else + { + mov_rdata(super_data, to_write); + return true; + } } return false; From db8437b01cf24ba1146cf7d22e1be02202cc98f4 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 15 Nov 2025 17:11:27 +0200 Subject: [PATCH 13/13] Fix mask calculation in SPUThread.cpp --- rpcs3/Emu/Cell/SPUThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index d8ab460121..0ae55091c6 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -319,7 +319,7 @@ static inline usz scan16_rdata(const decltype(spu_thread::rdata)& _lhs, const de const u32 c = (lhs[i + 2] != rhs[i + 2]) ? 1 : 0; const u32 d = (lhs[i + 3] != rhs[i + 3]) ? 1 : 0; - mask |= ((a << 0) + (b << 1) + (c << 2) + (c << 3)) << i; + mask |= ((a << 0) + (b << 1) + (c << 2) + (d << 3)) << i; } if (mask && (mask & (mask - 1)) == 0)