From 649e49c9e23a6c2ee4d10958c7cbaed034ced018 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Tue, 5 Nov 2024 19:22:30 -0600 Subject: [PATCH 01/32] ControllerEmu: Remove nunchuk stick data hax. --- .../Core/HW/WiimoteEmu/Extension/Nunchuk.cpp | 22 +------------------ .../ControlGroup/AnalogStick.cpp | 9 -------- .../ControllerEmu/ControlGroup/AnalogStick.h | 1 - 3 files changed, 1 insertion(+), 31 deletions(-) diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp index 14ba12b01f..741b061f49 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp @@ -67,31 +67,11 @@ void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state) DataFormat nc_data = {}; // stick - bool override_occurred = false; const ControllerEmu::AnalogStick::StateData stick_state = - m_stick->GetState(m_input_override_function, &override_occurred); + m_stick->GetState(m_input_override_function); nc_data.jx = MapFloat(stick_state.x, STICK_CENTER, 0, STICK_RANGE); nc_data.jy = MapFloat(stick_state.y, STICK_CENTER, 0, STICK_RANGE); - if (!override_occurred) - { - // Some terribly coded games check whether to move with a check like - // - // if (x != 0 && y != 0) - // do_movement(x, y); - // - // With keyboard controls, these games break if you simply hit one - // of the axes. Adjust this if you're hitting one of the axes so that - // we slightly tweak the other axis. - if (nc_data.jx != STICK_CENTER || nc_data.jy != STICK_CENTER) - { - if (nc_data.jx == STICK_CENTER) - ++nc_data.jx; - if (nc_data.jy == STICK_CENTER) - ++nc_data.jy; - } - } - // buttons u8 buttons = 0; m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data(), m_input_override_function); diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp index c26f143f1c..7cc204ab10 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp @@ -50,13 +50,6 @@ AnalogStick::StateData AnalogStick::GetState() const } AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& override_func) const -{ - bool override_occurred = false; - return GetState(override_func, &override_occurred); -} - -AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& override_func, - bool* override_occurred) const { StateData state = GetState(); if (!override_func) @@ -65,13 +58,11 @@ AnalogStick::StateData AnalogStick::GetState(const InputOverrideFunction& overri if (const std::optional x_override = override_func(name, X_INPUT_OVERRIDE, state.x)) { state.x = *x_override; - *override_occurred = true; } if (const std::optional y_override = override_func(name, Y_INPUT_OVERRIDE, state.y)) { state.y = *y_override; - *override_occurred = true; } return state; diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h index 377e63251d..5e913029ad 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h @@ -22,7 +22,6 @@ public: StateData GetState() const; StateData GetState(const InputOverrideFunction& override_func) const; - StateData GetState(const InputOverrideFunction& override_func, bool* override_occurred) const; private: Control* GetModifierInput() const override; From 303366b1ce2e93ff33bac1afc42251f281073f9b Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 22 Mar 2025 12:13:48 +0100 Subject: [PATCH 02/32] PowerPC: Add RAII handling for breakpoint updates bbf72e7 made a change where you can pass `false` to certain MemChecks functions to get them to skip performing an "update" step. It was then up to the caller to call the Update function later. This commit changes the implementation so that, instead of the caller passing in a boolean that controls whether a function calls Update, the function now returns an object that on destruction will call Update. Callers that are fine with Update being called right away can skip storing the object in a variable and thereby call Update immediately, and callers that want to call Update later can keep the object around. This new design reduces the risk that someone will forget calling Update. --- Source/Core/Core/PowerPC/BreakPoints.cpp | 19 ++++----- Source/Core/Core/PowerPC/BreakPoints.h | 42 ++++++++++++++++++- Source/Core/Core/PowerPC/GDBStub.cpp | 4 +- .../DolphinQt/Debugger/MemoryViewWidget.cpp | 7 ++-- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/Source/Core/Core/PowerPC/BreakPoints.cpp b/Source/Core/Core/PowerPC/BreakPoints.cpp index bb3c683fb4..10e037ca7b 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.cpp +++ b/Source/Core/Core/PowerPC/BreakPoints.cpp @@ -256,6 +256,7 @@ MemChecks::TMemChecksStr MemChecks::GetStrings() const void MemChecks::AddFromStrings(const TMemChecksStr& mc_strings) { const Core::CPUThreadGuard guard(m_system); + DelayedMemCheckUpdate delayed_update(this); for (const std::string& mc_string : mc_strings) { @@ -283,13 +284,11 @@ void MemChecks::AddFromStrings(const TMemChecksStr& mc_strings) mc.condition = Expression::TryParse(condition); } - Add(std::move(mc), false); + delayed_update |= Add(std::move(mc)); } - - Update(); } -void MemChecks::Add(TMemCheck memory_check, bool update) +DelayedMemCheckUpdate MemChecks::Add(TMemCheck memory_check) { const Core::CPUThreadGuard guard(m_system); @@ -310,8 +309,7 @@ void MemChecks::Add(TMemCheck memory_check, bool update) m_mem_checks.emplace_back(std::move(memory_check)); } - if (update) - Update(); + return DelayedMemCheckUpdate(this, true); } bool MemChecks::ToggleEnable(u32 address) @@ -326,22 +324,19 @@ bool MemChecks::ToggleEnable(u32 address) return true; } -bool MemChecks::Remove(u32 address, bool update) +DelayedMemCheckUpdate MemChecks::Remove(u32 address) { const auto iter = std::find_if(m_mem_checks.cbegin(), m_mem_checks.cend(), [address](const auto& check) { return check.start_address == address; }); if (iter == m_mem_checks.cend()) - return false; + return DelayedMemCheckUpdate(this, false); const Core::CPUThreadGuard guard(m_system); m_mem_checks.erase(iter); - if (update) - Update(); - - return true; + return DelayedMemCheckUpdate(this, true); } void MemChecks::Clear() diff --git a/Source/Core/Core/PowerPC/BreakPoints.h b/Source/Core/Core/PowerPC/BreakPoints.h index 6b08c25c8d..6408a54252 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.h +++ b/Source/Core/Core/PowerPC/BreakPoints.h @@ -97,6 +97,8 @@ private: Core::System& m_system; }; +class DelayedMemCheckUpdate; + // Memory breakpoints class MemChecks { @@ -115,13 +117,13 @@ public: TMemChecksStr GetStrings() const; void AddFromStrings(const TMemChecksStr& mc_strings); - void Add(TMemCheck memory_check, bool update = true); + DelayedMemCheckUpdate Add(TMemCheck memory_check); bool ToggleEnable(u32 address); TMemCheck* GetMemCheck(u32 address, size_t size = 1); bool OverlapsMemcheck(u32 address, u32 length) const; - bool Remove(u32 address, bool update = true); + DelayedMemCheckUpdate Remove(u32 address); void Update(); void Clear(); @@ -132,3 +134,39 @@ private: Core::System& m_system; bool m_mem_breakpoints_set = false; }; + +class DelayedMemCheckUpdate final +{ +public: + DelayedMemCheckUpdate(MemChecks* memchecks, bool update_needed = false) + : m_memchecks(memchecks), m_update_needed(update_needed) + { + } + + DelayedMemCheckUpdate(const DelayedMemCheckUpdate&) = delete; + DelayedMemCheckUpdate(DelayedMemCheckUpdate&& other) = delete; + DelayedMemCheckUpdate& operator=(const DelayedMemCheckUpdate&) = delete; + DelayedMemCheckUpdate& operator=(DelayedMemCheckUpdate&& other) = delete; + + ~DelayedMemCheckUpdate() + { + if (m_update_needed) + m_memchecks->Update(); + } + + DelayedMemCheckUpdate& operator|=(DelayedMemCheckUpdate&& other) + { + if (m_memchecks == other.m_memchecks) + { + m_update_needed |= other.m_update_needed; + other.m_update_needed = false; + } + return *this; + } + + operator bool() const { return m_update_needed; } + +private: + MemChecks* m_memchecks; + bool m_update_needed; +}; diff --git a/Source/Core/Core/PowerPC/GDBStub.cpp b/Source/Core/Core/PowerPC/GDBStub.cpp index c9875a99db..08b9f09c20 100644 --- a/Source/Core/Core/PowerPC/GDBStub.cpp +++ b/Source/Core/Core/PowerPC/GDBStub.cpp @@ -172,12 +172,12 @@ static void RemoveBreakpoint(BreakpointType type, u32 addr, u32 len) else { auto& memchecks = Core::System::GetInstance().GetPowerPC().GetMemChecks(); + DelayedMemCheckUpdate delayed_update(&memchecks); while (memchecks.GetMemCheck(addr, len) != nullptr) { - memchecks.Remove(addr, false); + delayed_update |= memchecks.Remove(addr); INFO_LOG_FMT(GDB_STUB, "gdb: removed a memcheck: {:08x} bytes at {:08x}", len, addr); } - memchecks.Update(); } Host_PPCBreakpointsChanged(); } diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 75cafdd814..7d8c506ecc 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -996,6 +996,7 @@ void MemoryViewWidget::ToggleBreakpoint(u32 addr, bool row) { const Core::CPUThreadGuard guard(m_system); + DelayedMemCheckUpdate delayed_update(&memchecks); for (int i = 0; i < breaks; i++) { @@ -1014,16 +1015,14 @@ void MemoryViewWidget::ToggleBreakpoint(u32 addr, bool row) check.log_on_hit = m_do_log; check.break_on_hit = true; - memchecks.Add(std::move(check), false); + delayed_update |= memchecks.Add(std::move(check)); } else if (check_ptr != nullptr) { // Using the pointer fixes misaligned breakpoints (0x11 breakpoint in 0x10 aligned view). - memchecks.Remove(check_ptr->start_address, false); + delayed_update |= memchecks.Remove(check_ptr->start_address); } } - - memchecks.Update(); } emit Host::GetInstance()->PPCBreakpointsChanged(); From 6ab83bdd05d6785d084a3d2e58a9aa182c8d4aa7 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Mon, 9 Jun 2025 18:36:40 -0500 Subject: [PATCH 03/32] VideoCommon: fix regression where scenes with expanded lines or points would cause issues if doing per-vertex transformations in the vertex shader --- Source/Core/VideoCommon/VertexShaderGen.cpp | 61 ++++++++++++++++----- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/Source/Core/VideoCommon/VertexShaderGen.cpp b/Source/Core/VideoCommon/VertexShaderGen.cpp index bf313daa5c..4dec9a3b21 100644 --- a/Source/Core/VideoCommon/VertexShaderGen.cpp +++ b/Source/Core/VideoCommon/VertexShaderGen.cpp @@ -74,6 +74,28 @@ VertexShaderUid GetVertexShaderUid() return out; } +static void WritePrimitiveExpand(APIType api_type, const ShaderHostConfig& host_config, + const vertex_shader_uid_data* uid_data, ShaderCode& out) +{ + if (uid_data->vs_expand == VSExpand::None) + return; + + out.Write("InputData dolphin_primitive_expand_data(int index_offset)\n"); + out.Write("{{\n"); + if (api_type == APIType::D3D) + { + // D3D doesn't include the base vertex in SV_VertexID + // See comment in UberShaderVertex for details + out.Write("\tuint vertex_id = (gl_VertexID >> 2) + base_vertex;\n"); + } + else + { + out.Write("\tuint vertex_id = uint(gl_VertexID) >> 2u;\n"); + } + out.Write("\treturn input_buffer[vertex_id + index_offset];\n"); + out.Write("}}\n\n"); +} + static void WriteTransformMatrices(APIType api_type, const ShaderHostConfig& host_config, const vertex_shader_uid_data* uid_data, ShaderCode& out) { @@ -82,6 +104,11 @@ static void WriteTransformMatrices(APIType api_type, const ShaderHostConfig& hos out.Write("\tmat3x4 result;\n"); if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) { + if (uid_data->vs_expand != VSExpand::None) + { + out.Write("\tInputData i = dolphin_primitive_expand_data(0);\n"); + out.Write("\tuvec4 posmtx = unpack_ubyte4(i.posmtx);\n"); + } // Vertex format has a per-vertex matrix out.Write("\tint posidx = int(posmtx.r);\n" "\tresult[0] = " I_TRANSFORMMATRICES "[posidx];\n" @@ -108,6 +135,11 @@ static void WriteTransformMatrices(APIType api_type, const ShaderHostConfig& hos out.Write("\tmat3 result;\n"); if ((uid_data->components & VB_HAS_POSMTXIDX) != 0) { + if (uid_data->vs_expand != VSExpand::None) + { + out.Write("\tInputData i = dolphin_primitive_expand_data(0);\n"); + out.Write("\tuvec4 posmtx = unpack_ubyte4(i.posmtx);\n"); + } // Vertex format has a per-vertex matrix out.Write("\tint posidx = int(posmtx.r);\n"); out.Write("\tint normidx = posidx & 31;\n" @@ -401,7 +433,8 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho if (uid_data->components & VB_HAS_POSMTXIDX) { out.Write(" uint posmtx;\n"); - input_extract.Write("uint4 posmtx = unpack_ubyte4(i.posmtx);\n"); + // Note: posmtx is handled in the matrix transform functions and + // doesn't need to be added to 'input_extract' } if (uid_data->position_has_3_elems) { @@ -507,6 +540,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho } // Note: this is done after to ensure above global variables are accessible + WritePrimitiveExpand(api_type, host_config, uid_data, out); WriteTransformMatrices(api_type, host_config, uid_data, out); WriteTexCoordTransforms(api_type, host_config, uid_data, out); WriteVertexDefines(api_type, host_config, uid_data, out); @@ -532,15 +566,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho if (uid_data->vs_expand != VSExpand::None) { - out.Write("bool is_bottom = (gl_VertexID & 2) != 0;\n" - "bool is_right = (gl_VertexID & 1) != 0;\n"); - // D3D doesn't include the base vertex in SV_VertexID - // See comment in UberShaderVertex for details - if (api_type == APIType::D3D) - out.Write("uint vertex_id = (gl_VertexID >> 2) + base_vertex;\n"); - else - out.Write("uint vertex_id = uint(gl_VertexID) >> 2u;\n"); - out.Write("InputData i = input_buffer[vertex_id];\n" + out.Write("InputData i = dolphin_primitive_expand_data(0);\n" "{}", input_extract.GetBuffer()); } @@ -691,14 +717,15 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho if (uid_data->vs_expand == VSExpand::Line) { + out.Write("bool is_bottom = (gl_VertexID & 2) != 0;\n"); out.Write("// Line expansion\n" - "uint other_id = vertex_id;\n" + "int id_offset = 0;\n" "if (is_bottom) {{\n" - " other_id -= 1u;\n" + " id_offset -= 1;\n" "}} else {{\n" - " other_id += 1u;\n" + " id_offset += 1;\n" "}}\n" - "InputData other = input_buffer[other_id];\n"); + "InputData other = dolphin_primitive_expand_data(id_offset);\n"); if (uid_data->position_has_3_elems) out.Write("float4 other_pos = float4(other.pos0, other.pos1, other.pos2, 1.0f);\n"); else @@ -716,10 +743,16 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho { out.Write("other_pos = vec4(other_pos * dolphin_position_matrix(), 1.0);\n"); } + + // Variable needed by GenerateVSLineExpansion + out.Write("bool is_right = (gl_VertexID & 1) != 0;\n"); GenerateVSLineExpansion(out, "", uid_data->numTexGens); } else if (uid_data->vs_expand == VSExpand::Point) { + // Variables needed by GenerateVSPointExpansion + out.Write("bool is_bottom = (gl_VertexID & 2) != 0;\n"); + out.Write("bool is_right = (gl_VertexID & 1) != 0;\n"); out.Write("// Point expansion\n"); GenerateVSPointExpansion(out, "", uid_data->numTexGens); } From 5f6cc197cefc9232fdb7e407b69cdae707379381 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sun, 15 Jun 2025 20:37:50 -0500 Subject: [PATCH 04/32] DolphinQt: Always enable the "Skip Main Menu" checkbox when "SkipIPL" is disabled. --- Source/Core/DolphinQt/Settings/GameCubePane.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 8c5d306988..568230b3e0 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -726,7 +726,7 @@ void GameCubePane::LoadSettings() } } - m_skip_main_menu->setEnabled(have_menu); + m_skip_main_menu->setEnabled(have_menu || !m_skip_main_menu->isChecked()); m_skip_main_menu->setToolTip(have_menu ? QString{} : tr("Put IPL ROMs in User/GC/.")); // Device Settings From 4af2e2782190f36da4ceff0f43c6877750386370 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 25 Jun 2025 21:53:04 -0500 Subject: [PATCH 05/32] DolphinQt: Fix Windows dark theme when using external stylesheets. --- Source/Core/DolphinQt/Settings.cpp | 56 +++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index 993f53b621..7375f3e0ad 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -175,40 +175,40 @@ void Settings::ApplyStyle() } #ifdef _WIN32 - if (stylesheet_contents.isEmpty()) + // Unlike other OSes we don't automatically get a default dark theme on Windows. + // We manually load a dark palette for our included "(Dark)" style, + // and for *any* external style when the system is in "Dark" mode. + // Unfortunately it doesn't seem trivial to load a palette based on the stylesheet itself. + if (style_type == StyleType::Dark || (style_type != StyleType::Light && IsSystemDark())) { - // No theme selected or found. Usually we would just fallthrough and set an empty stylesheet - // which would select Qt's default theme, but unlike other OSes we don't automatically get a - // default dark theme on Windows when the user has selected dark mode in the Windows settings. - // So manually check if the user wants dark mode and, if yes, load our embedded dark theme. - if (style_type == StyleType::Dark || (style_type != StyleType::Light && IsSystemDark())) + if (stylesheet_contents.isEmpty()) { QFile file(QStringLiteral(":/dolphin_dark_win/dark.qss")); if (file.open(QFile::ReadOnly)) stylesheet_contents = QString::fromUtf8(file.readAll().data()); + } - QPalette palette = qApp->style()->standardPalette(); - palette.setColor(QPalette::Window, QColor(32, 32, 32)); - palette.setColor(QPalette::WindowText, QColor(220, 220, 220)); - palette.setColor(QPalette::Base, QColor(32, 32, 32)); - palette.setColor(QPalette::AlternateBase, QColor(48, 48, 48)); - palette.setColor(QPalette::PlaceholderText, QColor(126, 126, 126)); - palette.setColor(QPalette::Text, QColor(220, 220, 220)); - palette.setColor(QPalette::Button, QColor(48, 48, 48)); - palette.setColor(QPalette::ButtonText, QColor(220, 220, 220)); - palette.setColor(QPalette::BrightText, QColor(255, 255, 255)); - palette.setColor(QPalette::Highlight, QColor(0, 120, 215)); - palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); - palette.setColor(QPalette::Link, QColor(100, 160, 220)); - palette.setColor(QPalette::LinkVisited, QColor(100, 160, 220)); - qApp->setPalette(palette); - } - else - { - // reset any palette changes that may exist from a previously set dark mode - if (s_default_palette) - qApp->setPalette(*s_default_palette); - } + QPalette palette = qApp->style()->standardPalette(); + palette.setColor(QPalette::Window, QColor(32, 32, 32)); + palette.setColor(QPalette::WindowText, QColor(220, 220, 220)); + palette.setColor(QPalette::Base, QColor(32, 32, 32)); + palette.setColor(QPalette::AlternateBase, QColor(48, 48, 48)); + palette.setColor(QPalette::PlaceholderText, QColor(126, 126, 126)); + palette.setColor(QPalette::Text, QColor(220, 220, 220)); + palette.setColor(QPalette::Button, QColor(48, 48, 48)); + palette.setColor(QPalette::ButtonText, QColor(220, 220, 220)); + palette.setColor(QPalette::BrightText, QColor(255, 255, 255)); + palette.setColor(QPalette::Highlight, QColor(0, 120, 215)); + palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); + palette.setColor(QPalette::Link, QColor(100, 160, 220)); + palette.setColor(QPalette::LinkVisited, QColor(100, 160, 220)); + qApp->setPalette(palette); + } + else + { + // reset any palette changes that may exist from a previously set dark mode + if (s_default_palette) + qApp->setPalette(*s_default_palette); } #endif From 60156f4f94f46a4a708a5fd5d37d4789a777b7b9 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Sat, 28 Jun 2025 15:52:27 -0500 Subject: [PATCH 06/32] VideoCommon: Fix "Force Nearest" texture filter setting. --- Source/Core/VideoCommon/TextureCacheBase.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index f58460e9ad..ad8583ae46 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -1029,6 +1029,11 @@ SamplerState TextureCacheBase::GetSamplerState(u32 index, float custom_tex_scale // Anisotropic filtering option. if (g_ActiveConfig.iMaxAnisotropy != AnisotropicFilteringMode::Default && IsAnisostropicEnhancementSafe(tm0)) + { + state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy); + } + + if (state.tm0.anisotropic_filtering != 0) { // https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt // For predictable results on all hardware/drivers, only use one of: @@ -1041,7 +1046,6 @@ SamplerState TextureCacheBase::GetSamplerState(u32 index, float custom_tex_scale state.tm0.mag_filter = FilterMode::Linear; if (tm0.mipmap_filter != MipMode::None) state.tm0.mipmap_filter = FilterMode::Linear; - state.tm0.anisotropic_filtering = Common::ToUnderlying(g_ActiveConfig.iMaxAnisotropy); } if (has_arbitrary_mips && tm0.mipmap_filter != MipMode::None) From c613d3ca109a437e5001aa86c7839f494345818f Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Sat, 28 Jun 2025 12:33:34 -0700 Subject: [PATCH 07/32] ControllerInterface: Fix Windows deadlock Remove the redundant s_populate_mutex and only use ControllerInterface::m_devices_population_mutex instead to prevent a deadlock caused by locking them in opposite orders. The device population functions in the win32 InputBackend previously locked s_populate_mutex first before calling various functions that locked m_devices_population_mutex. This normally worked but ControllerInterface::RefreshDevices locks m_devices_population_mutex first and then calls HandleWindowChange which then locked s_populate_mutex, potentially causing the deadlock. Fix this by using PlatformPopulateDevices to lock m_devices_population_mutex before running the code that was previously protected by s_populate_mutex. The functions in question lock m_devices_population_mutex anyway, so this shouldn't meaningfully increase contention on the lock. Reproduction steps: * Let Dolphin finish startup. * In Win32.cpp::OnDevicesChanged set a breakpoint on the call to PlatformPopulateDevices. When the breakpoint is triggered the function will have locked s_populate_mutex, but since PlatformPopulateDevices won't have run yet m_devices_population_mutex will still be unlocked. * Unplug a device from your computer. * Wait for the breakpoint to trigger. (At this point you can plug the device back in). * Freeze the ntdll.dll!TppWorkerThread() that triggered the breakpoint. * Resume Dolphin and start a game. * Core::EmuThread will call ControllerInterface::ChangeWindow which calls RefreshDevices. It locks m_devices_population_mutex, then calls InputBackend::HandleWindowChange, which tries to lock s_populate_mutex. * Unfreeze ntdll.dll!TppWorkerThread(). At this point EmuThread and TppWorkerThread are deadlocked. The UI is still responsive since the Host thread is unaffected, but trying to stop the game or close Dolphin normally will fail since EmuThread is unable to stop. --- .../ControllerInterface/Win32/Win32.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp index 2e69fc80e6..79584913cf 100644 --- a/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Win32/Win32.cpp @@ -10,8 +10,6 @@ #include -#include - #include "Common/Flag.h" #include "Common/Logging/Log.h" #include "InputCommon/ControllerInterface/DInput/DInput.h" @@ -20,7 +18,6 @@ #pragma comment(lib, "OneCoreUAP.Lib") -static std::mutex s_populate_mutex; // TODO is this really needed? static Common::Flag s_first_populate_devices_asked; static HCMNOTIFICATION s_notify_handle; @@ -52,7 +49,6 @@ _Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALL // listen for it. if (s_first_populate_devices_asked.IsSet()) { - std::lock_guard lk_population(s_populate_mutex); // TODO: we could easily use the message passed alongside this event, which tells // whether a device was added or removed, to avoid removing old, still connected, devices g_controller_interface.PlatformPopulateDevices([&] { @@ -96,17 +92,18 @@ InputBackend::InputBackend(ControllerInterface* controller_interface) void InputBackend::PopulateDevices() { - std::lock_guard lk_population(s_populate_mutex); - s_first_populate_devices_asked.Set(); - ciface::DInput::PopulateDevices(GetHWND()); - ciface::XInput::PopulateDevices(); - ciface::WGInput::PopulateDevices(); + g_controller_interface.PlatformPopulateDevices([this] { + s_first_populate_devices_asked.Set(); + ciface::DInput::PopulateDevices(GetHWND()); + ciface::XInput::PopulateDevices(); + ciface::WGInput::PopulateDevices(); + }); } void InputBackend::HandleWindowChange() { - std::lock_guard lk_population(s_populate_mutex); - ciface::DInput::ChangeWindow(GetHWND()); + g_controller_interface.PlatformPopulateDevices( + [this] { ciface::DInput::ChangeWindow(GetHWND()); }); } InputBackend::~InputBackend() From f2392e4048ce977bad2227d08f3a042526f7c4da Mon Sep 17 00:00:00 2001 From: Sintendo <3380580+Sintendo@users.noreply.github.com> Date: Sun, 6 Jul 2025 08:41:12 +0200 Subject: [PATCH 08/32] Avoid map/set double lookups Fix some common anti-patterns with these data structures. - You can dereference the iterator returned by `find` to access the underlying value directly, without an extra `operator[]`/`at`. - Rather than checking for an element before insertion/deletion, you can just do the operation and if needed check the return value to determine if the insertion/deletion succeeded. --- Source/Core/Common/Assembler/GekkoIRGen.cpp | 6 ++-- Source/Core/Core/AchievementManager.cpp | 4 +-- Source/Core/Core/IOS/Network/Socket.cpp | 5 ++-- .../Emulated/Skylanders/SkylanderFigure.cpp | 5 ++-- Source/Core/Core/IOS/USB/OH0/OH0.cpp | 4 +-- Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp | 16 ++++++---- Source/Core/Core/NetPlayClient.cpp | 4 +-- Source/Core/Core/NetPlayServer.cpp | 16 ++++++---- .../Core/PowerPC/Jit64Common/ConstantPool.cpp | 8 ++--- Source/Core/Core/State.cpp | 4 +-- .../Core/DolphinQt/GameList/GameTracker.cpp | 30 +++++++++---------- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 5 ++-- .../Runtime/GraphicsModManager.cpp | 3 +- Source/Core/VideoCommon/ShaderCache.cpp | 29 ++++++------------ Source/Core/VideoCommon/TextureCacheBase.cpp | 4 +-- 15 files changed, 69 insertions(+), 74 deletions(-) diff --git a/Source/Core/Common/Assembler/GekkoIRGen.cpp b/Source/Core/Common/Assembler/GekkoIRGen.cpp index 7ec7c476ba..51cbbb4208 100644 --- a/Source/Core/Common/Assembler/GekkoIRGen.cpp +++ b/Source/Core/Common/Assembler/GekkoIRGen.cpp @@ -356,14 +356,13 @@ void GekkoIRPlugin::OnCloseParen(ParenType type) void GekkoIRPlugin::OnLabelDecl(std::string_view name) { const std::string name_str(name); - if (m_symset.contains(name_str)) + if (const bool inserted = m_symset.insert(name_str).second; !inserted) { m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name)); return; } m_labels[name_str] = m_active_block->BlockEndAddress(); - m_symset.insert(name_str); } void GekkoIRPlugin::OnNumericLabelDecl(std::string_view, u32 num) @@ -374,14 +373,13 @@ void GekkoIRPlugin::OnNumericLabelDecl(std::string_view, u32 num) void GekkoIRPlugin::OnVarDecl(std::string_view name) { const std::string name_str(name); - if (m_symset.contains(name_str)) + if (const bool inserted = m_symset.insert(name_str).second; !inserted) { m_owner->EmitErrorHere(fmt::format("Label/Constant {} is already defined", name)); return; } m_active_var = &m_constants[name_str]; - m_symset.insert(name_str); } void GekkoIRPlugin::PostParseAction() diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 0ed5628c43..44b5fbe5b0 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -192,8 +192,8 @@ void AchievementManager::LoadGame(const DiscIO::Volume* volume) std::lock_guard lg{m_lock}; #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION const auto& names = volume->GetLongNames(); - if (names.contains(DiscIO::Language::English)) - m_title_estimate = names.at(DiscIO::Language::English); + if (const auto it = names.find(DiscIO::Language::English); it != names.end()) + m_title_estimate = it->second; else if (!names.empty()) m_title_estimate = names.begin()->second; else diff --git a/Source/Core/Core/IOS/Network/Socket.cpp b/Source/Core/Core/IOS/Network/Socket.cpp index bae1d48778..46ee116311 100644 --- a/Source/Core/Core/IOS/Network/Socket.cpp +++ b/Source/Core/Core/IOS/Network/Socket.cpp @@ -964,8 +964,9 @@ s32 WiiSockMan::NewSocket(s32 af, s32 type, s32 protocol) s32 WiiSockMan::GetHostSocket(s32 wii_fd) const { - if (WiiSockets.contains(wii_fd)) - return WiiSockets.at(wii_fd).fd; + auto socket_entry = WiiSockets.find(wii_fd); + if (socket_entry != WiiSockets.end()) + return socket_entry->second.fd; return -EBADF; } diff --git a/Source/Core/Core/IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp b/Source/Core/Core/IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp index f4483ba359..9bfa31c928 100644 --- a/Source/Core/Core/IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp +++ b/Source/Core/Core/IOS/USB/Emulated/Skylanders/SkylanderFigure.cpp @@ -186,9 +186,10 @@ FigureData SkylanderFigure::GetData() const auto filter = std::make_pair(figure_data.figure_id, figure_data.variant_id); Type type = Type::Item; - if (IOS::HLE::USB::list_skylanders.contains(filter)) + if (const auto it = IOS::HLE::USB::list_skylanders.find(filter); + it != IOS::HLE::USB::list_skylanders.end()) { - auto found = IOS::HLE::USB::list_skylanders.at(filter); + auto found = it->second; type = found.type; } diff --git a/Source/Core/Core/IOS/USB/OH0/OH0.cpp b/Source/Core/Core/IOS/USB/OH0/OH0.cpp index 37d5202448..f1b500fc09 100644 --- a/Source/Core/Core/IOS/USB/OH0/OH0.cpp +++ b/Source/Core/Core/IOS/USB/OH0/OH0.cpp @@ -176,9 +176,9 @@ std::optional OH0::RegisterRemovalHook(const u64 device_id, const IOCt { std::lock_guard lock{m_hooks_mutex}; // IOS only allows a single device removal hook. - if (m_removal_hooks.contains(device_id)) + const bool inserted = m_removal_hooks.try_emplace(device_id, request.address).second; + if (!inserted) return IPCReply(IPC_EEXIST); - m_removal_hooks.insert({device_id, request.address}); return std::nullopt; } diff --git a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp index aa70c0f523..eb9c50a85a 100644 --- a/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp +++ b/Source/Core/Core/IOS/USB/USB_HID/HIDv4.cpp @@ -171,16 +171,20 @@ void USB_HIDv4::OnDeviceChange(ChangeEvent event, std::shared_ptr d std::lock_guard id_map_lock{m_id_map_mutex}; if (event == ChangeEvent::Inserted) { + const auto id = device->GetId(); s32 new_id = 0; - while (m_ios_ids.contains(new_id)) + while (!m_ios_ids.emplace(new_id, id).second) ++new_id; - m_ios_ids[new_id] = device->GetId(); - m_device_ids[device->GetId()] = new_id; + + m_device_ids[id] = new_id; } - else if (event == ChangeEvent::Removed && m_device_ids.contains(device->GetId())) + else if (event == ChangeEvent::Removed) { - m_ios_ids.erase(m_device_ids.at(device->GetId())); - m_device_ids.erase(device->GetId()); + if (const auto it = m_device_ids.find(device->GetId()); it != m_device_ids.end()) + { + m_ios_ids.erase(it->second); + m_device_ids.erase(it); + } } } diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index ee784ce771..3f4344ad88 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -2392,8 +2392,8 @@ void NetPlayClient::RequestGolfControl() std::string NetPlayClient::GetCurrentGolfer() { std::lock_guard lkp(m_crit.players); - if (m_players.contains(m_current_golfer)) - return m_players[m_current_golfer].name; + if (const auto it = m_players.find(m_current_golfer); it != m_players.end()) + return it->second.name; return ""; } diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 51b3f1916b..948dabebcd 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -290,8 +290,8 @@ void NetPlayServer::ThreadFunc() auto& e = m_async_queue.Front(); if (e.target_mode == TargetMode::Only) { - if (m_players.contains(e.target_pid)) - Send(m_players.at(e.target_pid).socket, e.packet, e.channel_id); + if (const auto it = m_players.find(e.target_pid); it != m_players.end()) + Send(it->second.socket, e.packet, e.channel_id); } else { @@ -794,9 +794,10 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) u32 cid; packet >> cid; - if (m_chunked_data_complete_count.contains(cid)) + if (const auto it = m_chunked_data_complete_count.find(cid); + it != m_chunked_data_complete_count.end()) { - m_chunked_data_complete_count[cid]++; + it->second++; m_chunked_data_complete_event.Set(); } } @@ -839,8 +840,11 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) if (m_host_input_authority) { // Prevent crash before game stop if the golfer disconnects - if (m_current_golfer != 0 && m_players.contains(m_current_golfer)) - Send(m_players.at(m_current_golfer).socket, spac); + if (m_current_golfer != 0) + { + if (const auto it = m_players.find(m_current_golfer); it != m_players.end()) + Send(it->second.socket, spac); + } } else { diff --git a/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp b/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp index dd556c7486..8aaa164f43 100644 --- a/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp +++ b/Source/Core/Core/PowerPC/Jit64Common/ConstantPool.cpp @@ -40,9 +40,10 @@ const void* ConstantPool::GetConstant(const void* value, size_t element_size, si size_t index) { const size_t value_size = element_size * num_elements; - auto iter = m_const_info.find(value); + const auto [iter, inserted] = m_const_info.emplace(value, ConstantInfo{}); + ConstantInfo& info = iter->second; - if (iter == m_const_info.end()) + if (inserted) { void* ptr = std::align(ALIGNMENT, value_size, m_current_ptr, m_remaining_size); ASSERT_MSG(DYNA_REC, ptr, "Constant pool has run out of space."); @@ -51,10 +52,9 @@ const void* ConstantPool::GetConstant(const void* value, size_t element_size, si m_remaining_size -= value_size; std::memcpy(ptr, value, value_size); - iter = m_const_info.emplace(std::make_pair(value, ConstantInfo{ptr, value_size})).first; + info = ConstantInfo{ptr, value_size}; } - const ConstantInfo& info = iter->second; ASSERT_MSG(DYNA_REC, info.m_size == value_size, "Constant has incorrect size in constant pool."); u8* location = static_cast(info.m_location); return location + element_size * index; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index bbfb92ebe5..a947ad19a3 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -727,12 +727,12 @@ static bool ValidateHeaders(const StateHeader& header) std::string loaded_str = header.version_string; const u32 loaded_version = header.version_header.version_cookie - COOKIE_BASE; - if (s_old_versions.contains(loaded_version)) + if (const auto it = s_old_versions.find(loaded_version); it != s_old_versions.end()) { // This is a REALLY old version, before we started writing the version string to file success = false; - std::pair version_range = s_old_versions.find(loaded_version)->second; + std::pair version_range = it->second; std::string oldest_version = version_range.first; std::string newest_version = version_range.second; diff --git a/Source/Core/DolphinQt/GameList/GameTracker.cpp b/Source/Core/DolphinQt/GameList/GameTracker.cpp index 90dfe5d75c..8f6f76e39a 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt/GameList/GameTracker.cpp @@ -251,17 +251,18 @@ static std::unique_ptr GetIterator(const QString& dir) void GameTracker::RemoveDirectoryInternal(const QString& dir) { RemovePath(dir); - auto it = GetIterator(dir); - while (it->hasNext()) + const auto dir_it = GetIterator(dir); + while (dir_it->hasNext()) { - QString path = QFileInfo(it->next()).canonicalFilePath(); - if (m_tracked_files.contains(path)) + QString path = QFileInfo(dir_it->next()).canonicalFilePath(); + if (const auto it = m_tracked_files.find(path); it != m_tracked_files.end()) { - m_tracked_files[path].remove(dir); - if (m_tracked_files[path].empty()) + auto& set = *it; + set.remove(dir); + if (set.isEmpty()) { RemovePath(path); - m_tracked_files.remove(path); + m_tracked_files.erase(it); if (m_started) emit GameRemoved(path.toStdString()); } @@ -271,16 +272,14 @@ void GameTracker::RemoveDirectoryInternal(const QString& dir) void GameTracker::UpdateDirectoryInternal(const QString& dir) { - auto it = GetIterator(dir); - while (it->hasNext() && !m_processing_halted) + const auto dir_it = GetIterator(dir); + while (dir_it->hasNext() && !m_processing_halted) { - QString path = QFileInfo(it->next()).canonicalFilePath(); + QString path = QFileInfo(dir_it->next()).canonicalFilePath(); - if (m_tracked_files.contains(path)) + if (const auto it = m_tracked_files.find(path); it != m_tracked_files.end()) { - auto& tracked_file = m_tracked_files[path]; - if (!tracked_file.contains(dir)) - tracked_file.insert(dir); + it->insert(dir); } else { @@ -339,8 +338,7 @@ QSet GameTracker::FindMissingFiles(const QString& dir) while (it->hasNext()) { QString path = QFileInfo(it->next()).canonicalFilePath(); - if (m_tracked_files.contains(path)) - missing_files.remove(path); + m_tracked_files.remove(path); } return missing_files; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 0677666416..621e0113c7 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -655,8 +655,9 @@ void NetPlayDialog::UpdateGUI() auto* name_item = new QTableWidgetItem(QString::fromStdString(p->name)); name_item->setToolTip(name_item->text()); - const auto& status_info = player_status.contains(p->game_status) ? - player_status.at(p->game_status) : + const auto it = player_status.find(p->game_status); + const auto& status_info = it != player_status.end() ? + it->second : std::make_pair(QStringLiteral("?"), QStringLiteral("?")); auto* status_item = new QTableWidgetItem(status_info.first); status_item->setToolTip(status_info.second); diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp index 0d070a4310..70d3a9a5ca 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.cpp @@ -197,14 +197,13 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config) { for (const GraphicsTargetGroupConfig& group : mod.m_groups) { - if (m_groups.contains(group.m_name)) + if (const bool inserted = m_groups.insert(group.m_name).second; !inserted) { WARN_LOG_FMT( VIDEO, "Specified graphics mod group '{}' for mod '{}' is already specified by another mod.", group.m_name, mod.m_title); } - m_groups.insert(group.m_name); const auto internal_group = fmt::format("{}.{}", mod.m_title, group.m_name); for (const GraphicsTargetConfig& target : group.m_targets) diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 0e5cc141a1..84b21f8e3d 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -1535,26 +1535,21 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat TextureFormat to_format) { const auto key = std::make_pair(from_format, to_format); - auto iter = m_texture_reinterpret_pipelines.find(key); - if (iter != m_texture_reinterpret_pipelines.end()) + const auto [iter, inserted] = m_texture_reinterpret_pipelines.emplace(key, nullptr); + + if (!inserted) return iter->second.get(); std::string shader_source = FramebufferShaderGen::GenerateTextureReinterpretShader(from_format, to_format); if (shader_source.empty()) - { - m_texture_reinterpret_pipelines.emplace(key, nullptr); return nullptr; - } std::unique_ptr shader = g_gfx->CreateShaderFromSource( ShaderStage::Pixel, shader_source, fmt::format("Texture reinterpret pixel shader: {} to {}", from_format, to_format)); if (!shader) - { - m_texture_reinterpret_pipelines.emplace(key, nullptr); return nullptr; - } AbstractPipelineConfig config; config.vertex_format = nullptr; @@ -1566,8 +1561,8 @@ const AbstractPipeline* ShaderCache::GetTextureReinterpretPipeline(TextureFormat config.blending_state = RenderState::GetNoBlendingBlendState(); config.framebuffer_state = RenderState::GetRGBA8FramebufferState(); config.usage = AbstractPipelineUsage::Utility; - auto iiter = m_texture_reinterpret_pipelines.emplace(key, g_gfx->CreatePipeline(config)); - return iiter.first->second.get(); + iter->second = g_gfx->CreatePipeline(config); + return iter->second.get(); } const AbstractShader* @@ -1576,17 +1571,14 @@ ShaderCache::GetTextureDecodingShader(TextureFormat format, { const auto key = std::make_pair(static_cast(format), static_cast(palette_format.value_or(TLUTFormat::IA8))); - const auto iter = m_texture_decoding_shaders.find(key); - if (iter != m_texture_decoding_shaders.end()) + const auto [iter, inserted] = m_texture_decoding_shaders.emplace(key, nullptr); + if (!inserted) return iter->second.get(); const std::string shader_source = TextureConversionShaderTiled::GenerateDecodingShader(format, palette_format, APIType::OpenGL); if (shader_source.empty()) - { - m_texture_decoding_shaders.emplace(key, nullptr); return nullptr; - } const std::string name = palette_format.has_value() ? @@ -1596,12 +1588,9 @@ ShaderCache::GetTextureDecodingShader(TextureFormat format, std::unique_ptr shader = g_gfx->CreateShaderFromSource(ShaderStage::Compute, shader_source, name); if (!shader) - { - m_texture_decoding_shaders.emplace(key, nullptr); return nullptr; - } - const auto iiter = m_texture_decoding_shaders.emplace(key, std::move(shader)); - return iiter.first->second.get(); + iter->second = std::move(shader); + return iter->second.get(); } } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index f58460e9ad..dc47eac86e 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -652,8 +652,8 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) auto refpair1 = std::make_pair(*id1, *id2); auto refpair2 = std::make_pair(*id2, *id1); - if (!reference_pairs.contains(refpair1) && !reference_pairs.contains(refpair2)) - reference_pairs.insert(refpair1); + if (!reference_pairs.contains(refpair2)) + reference_pairs.insert(std::move(refpair1)); } } From 74814b32039885d07b18a93d2e64efe299b0b339 Mon Sep 17 00:00:00 2001 From: Maxim Cournoyer Date: Tue, 22 Jul 2025 16:58:57 +0900 Subject: [PATCH 09/32] fix gc-font-tool.cpp with GCC 14 --- docs/gc-font-tool.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/gc-font-tool.cpp b/docs/gc-font-tool.cpp index f09964609a..c43496d2fa 100644 --- a/docs/gc-font-tool.cpp +++ b/docs/gc-font-tool.cpp @@ -76,6 +76,7 @@ // Font data is encoded in 2 bit greyscale and in 8x8 blocks. #include +#include #include #include #include From 7871b24b597090a2885f63a0fc55b266c84ca8f8 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 23 Jul 2025 21:13:21 -0500 Subject: [PATCH 10/32] LibusbUtils: Change libusb_init failure from an ASSERT_MSG to an ERROR_LOG_FMT. --- Source/Core/Core/LibusbUtils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/LibusbUtils.cpp b/Source/Core/Core/LibusbUtils.cpp index ba3d0fd8e6..3cd9949566 100644 --- a/Source/Core/Core/LibusbUtils.cpp +++ b/Source/Core/Core/LibusbUtils.cpp @@ -10,8 +10,8 @@ #include #endif -#include "Common/Assert.h" #include "Common/Flag.h" +#include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Common/Thread.h" @@ -24,9 +24,11 @@ public: Impl() { const int ret = libusb_init(&m_context); - ASSERT_MSG(IOS_USB, ret == LIBUSB_SUCCESS, "Failed to init libusb: {}", ErrorWrap(ret)); if (ret != LIBUSB_SUCCESS) + { + ERROR_LOG_FMT(IOS_USB, "Failed to init libusb: {}", ErrorWrap(ret)); return; + } #ifdef _WIN32 const int usbdk_ret = libusb_set_option(m_context, LIBUSB_OPTION_USE_USBDK); From e2a8e7da489de6d72c5d0ff9e1b74486c611d2e0 Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Thu, 24 Jul 2025 11:24:32 -0700 Subject: [PATCH 11/32] GameConfigWidget: Unify tooltips Use ToolTipWidget::SetDescription insead of QWidget::setTooltip to put the description in the BalloonTip with the title, instead of having the description be in a separate standard tooltip. --- .../DolphinQt/Config/GameConfigWidget.cpp | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Source/Core/DolphinQt/Config/GameConfigWidget.cpp b/Source/Core/DolphinQt/Config/GameConfigWidget.cpp index 1fa2a50292..efaae69c62 100644 --- a/Source/Core/DolphinQt/Config/GameConfigWidget.cpp +++ b/Source/Core/DolphinQt/Config/GameConfigWidget.cpp @@ -112,14 +112,16 @@ void GameConfigWidget::CreateWidgets() m_deterministic_dual_core = new ConfigStringChoice(choice, Config::MAIN_GPU_DETERMINISM_MODE, layer); - m_enable_mmu->setToolTip(tr( + m_enable_mmu->SetDescription(tr( "Enables the Memory Management Unit, needed for some games. (ON = Compatible, OFF = Fast)")); - m_enable_fprf->setToolTip(tr("Enables Floating Point Result Flag calculation, needed for a few " - "games. (ON = Compatible, OFF = Fast)")); - m_sync_gpu->setToolTip(tr("Synchronizes the GPU and CPU threads to help prevent random freezes " - "in Dual core mode. (ON = Compatible, OFF = Fast)")); - m_emulate_disc_speed->setToolTip( + m_enable_fprf->SetDescription( + tr("Enables Floating Point Result Flag calculation, needed for a few " + "games. (ON = Compatible, OFF = Fast)")); + m_sync_gpu->SetDescription( + tr("Synchronizes the GPU and CPU threads to help prevent random freezes " + "in Dual core mode. (ON = Compatible, OFF = Fast)")); + m_emulate_disc_speed->SetDescription( tr("Enable emulated disc speed. Disabling this can cause crashes " "and other problems in some games. " "(ON = Compatible, OFF = Unlocked)")); @@ -143,11 +145,11 @@ void GameConfigWidget::CreateWidgets() m_use_monoscopic_shadows = new ConfigBool(tr("Monoscopic Shadows"), Config::GFX_STEREO_EFB_MONO_DEPTH, layer); - m_depth_slider->setToolTip( + m_depth_slider->SetDescription( tr("This value is multiplied with the depth set in the graphics configuration.")); - m_convergence_spin->setToolTip( + m_convergence_spin->SetDescription( tr("This value is added to the convergence value set in the graphics configuration.")); - m_use_monoscopic_shadows->setToolTip( + m_use_monoscopic_shadows->SetDescription( tr("Use a single depth buffer for both eyes. Needed for a few games.")); stereoscopy_layout->addWidget(new ConfigSliderLabel(tr("Depth Percentage:"), m_depth_slider), 0, From 9d9b6d870534abec259763c7867ca7eae5b17b3a Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 1 Jul 2025 17:39:16 -0400 Subject: [PATCH 12/32] Android: Bump SDK to 36 and Gradle to 8.13 Starting August 31, 2025, targeting at least Android 15 (API level 35) will be required: https://support.google.com/googleplay/android-developer/answer/11926878 This commit sets the target SDK to API level 36 (Android 16), the latest available version. --- Source/Android/app/build.gradle.kts | 4 ++-- Source/Android/build.gradle.kts | 6 +++--- Source/Android/gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Android/app/build.gradle.kts b/Source/Android/app/build.gradle.kts index 00db541705..7924c8b281 100644 --- a/Source/Android/app/build.gradle.kts +++ b/Source/Android/app/build.gradle.kts @@ -7,7 +7,7 @@ plugins { @Suppress("UnstableApiUsage") android { - compileSdkVersion = "android-34" + compileSdkVersion = "android-36" ndkVersion = "27.0.12077973" buildFeatures { @@ -40,7 +40,7 @@ android { defaultConfig { applicationId = "org.dolphinemu.dolphinemu" minSdk = 21 - targetSdk = 34 + targetSdk = 36 versionCode = getBuildVersionCode() diff --git a/Source/Android/build.gradle.kts b/Source/Android/build.gradle.kts index 7aeb75bd81..ed91cc6f4f 100644 --- a/Source/Android/build.gradle.kts +++ b/Source/Android/build.gradle.kts @@ -1,9 +1,9 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.9.0" apply false - id("com.android.library") version "8.9.0" apply false + id("com.android.application") version "8.11.0" apply false + id("com.android.library") version "8.11.0" apply false id("org.jetbrains.kotlin.android") version "1.8.21" apply false - id("com.android.test") version "8.9.0" apply false + id("com.android.test") version "8.11.0" apply false id("androidx.baselineprofile") version "1.3.3" apply false } diff --git a/Source/Android/gradle/wrapper/gradle-wrapper.properties b/Source/Android/gradle/wrapper/gradle-wrapper.properties index e2847c8200..37f853b1c8 100644 --- a/Source/Android/gradle/wrapper/gradle-wrapper.properties +++ b/Source/Android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From dcb289be8f859f1547e6652430230a070c16ee47 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 24 Jul 2025 18:14:06 -0500 Subject: [PATCH 13/32] InputCommon/ControllerEmu: Fix saving of Wii Remote "Attach MotionPlus" setting. --- .../Core/InputCommon/ControllerEmu/ControlGroup/Attachments.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.cpp b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.cpp index 45ce337d6c..27304907c5 100644 --- a/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.cpp +++ b/Source/Core/InputCommon/ControllerEmu/ControlGroup/Attachments.cpp @@ -77,6 +77,8 @@ void Attachments::LoadConfig(Common::IniFile::Section* sec, const std::string& b void Attachments::SaveConfig(Common::IniFile::Section* sec, const std::string& base) { + ControlGroup::SaveConfig(sec, base); + if (GetSelectionSetting().IsSimpleValue()) { sec->Set(base + name, GetAttachmentList()[GetSelectedAttachment()]->GetName(), "None"); From 0fa15342ec43759d3e71cacb6c038600d9249aa8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 26 Jul 2025 18:08:19 +0200 Subject: [PATCH 14/32] Boot: Fix RetroAchievements for GameCube games launched with IPL The SetDisc function calls AchievementManager::LoadGame with the game's volume. Calling AchievementManager::LoadGame again afterwards with nullptr prevents RetroAchievements from working. --- Source/Core/Core/Boot/Boot.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 1b796952f8..e43ffc52d4 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -632,8 +632,10 @@ bool CBoot::BootUp(Core::System& system, const Core::CPUThreadGuard& guard, SetDisc(system.GetDVDInterface(), DiscIO::CreateDisc(ipl.disc->path), ipl.disc->auto_disc_change_paths); } - - AchievementManager::GetInstance().LoadGame(nullptr); + else + { + AchievementManager::GetInstance().LoadGame(nullptr); + } SConfig::OnTitleDirectlyBooted(guard); return true; From c3be0495719104fa0bc7a450aac1a6ae5d4711b9 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 26 Jul 2025 19:48:52 +0200 Subject: [PATCH 15/32] Android: Don't let RetroAchievements override onPause When Android sends Dolphin to the background, emulation *must* pause, otherwise emulation continues running and continues outputting audio to the user. RetroAchievements mustn't be allowed to override it. --- .../main/java/org/dolphinemu/dolphinemu/NativeLibrary.java | 2 +- .../dolphinemu/dolphinemu/activities/EmulationActivity.kt | 2 +- .../dolphinemu/dolphinemu/fragments/EmulationFragment.kt | 2 +- Source/Android/jni/MainAndroid.cpp | 6 ++++-- Source/Core/Core/Core.cpp | 4 ++-- Source/Core/Core/Core.h | 2 +- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index e9ab837619..69b9a94595 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -362,7 +362,7 @@ public final class NativeLibrary /** * Pauses emulation. */ - public static native void PauseEmulation(); + public static native void PauseEmulation(boolean overrideAchievementRestrictions); /** * Stops emulation. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt index 112b780df0..b743328920 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt @@ -455,7 +455,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { MENU_ACTION_REFRESH_WIIMOTES -> NativeLibrary.RefreshWiimotes() MENU_ACTION_PAUSE_EMULATION -> { hasUserPausedEmulation = true - NativeLibrary.PauseEmulation() + NativeLibrary.PauseEmulation(false) } MENU_ACTION_UNPAUSE_EMULATION -> { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt index eeb223c3db..04db24c03a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt @@ -109,7 +109,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { override fun onPause() { if (NativeLibrary.IsRunningAndUnpaused() && !NativeLibrary.IsShowingAlertMessage()) { Log.debug("[EmulationFragment] Pausing emulation.") - NativeLibrary.PauseEmulation() + NativeLibrary.PauseEmulation(true) } super.onPause() } diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index be1d168730..7f4daf54dd 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -271,10 +271,12 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_UnPauseEmula Core::SetState(Core::System::GetInstance(), Core::State::Running); } -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation(JNIEnv*, jclass) +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_PauseEmulation( + JNIEnv*, jclass, bool override_achievement_restrictions) { HostThreadLock guard; - Core::SetState(Core::System::GetInstance(), Core::State::Paused); + Core::SetState(Core::System::GetInstance(), Core::State::Paused, true, + override_achievement_restrictions); } JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_StopEmulation(JNIEnv*, jclass) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index a122a467e9..ccb1ad68bd 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -672,7 +672,7 @@ static void EmuThread(Core::System& system, std::unique_ptr boot // Set or get the running state void SetState(Core::System& system, State state, bool report_state_change, - bool initial_execution_state) + bool override_achievement_restrictions) { // State cannot be controlled until the CPU Thread is operational if (s_state.load() != State::Running) @@ -682,7 +682,7 @@ void SetState(Core::System& system, State state, bool report_state_change, { case State::Paused: #ifdef USE_RETRO_ACHIEVEMENTS - if (!initial_execution_state && !AchievementManager::GetInstance().CanPause()) + if (!override_achievement_restrictions && !AchievementManager::GetInstance().CanPause()) return; #endif // USE_RETRO_ACHIEVEMENTS // NOTE: GetState() will return State::Paused immediately, even before anything has diff --git a/Source/Core/Core/Core.h b/Source/Core/Core/Core.h index 8ff3b32626..68a402772a 100644 --- a/Source/Core/Core/Core.h +++ b/Source/Core/Core/Core.h @@ -146,7 +146,7 @@ bool WantsDeterminism(); // [NOT THREADSAFE] For use by Host only void SetState(Core::System& system, State state, bool report_state_change = true, - bool initial_execution_state = false); + bool override_achievement_restrictions = false); State GetState(Core::System& system); void SaveScreenShot(); From e2ecafcaf3b4af8459a32dc67bb4c42019969f0b Mon Sep 17 00:00:00 2001 From: Martino Fontana Date: Thu, 3 Jul 2025 18:03:04 +0200 Subject: [PATCH 16/32] GameSettings: Minor approved cheats update --- Data/Sys/ApprovedInis.json | 2 +- Data/Sys/GameSettings/G8ME01.ini | 3 +++ Data/Sys/GameSettings/G8MP01.ini | 3 +++ Data/Sys/GameSettings/GC6.ini | 4 +-- Data/Sys/GameSettings/GC6E01.ini | 3 +++ Data/Sys/GameSettings/GC6J01.ini | 22 +++------------- Data/Sys/GameSettings/GC6P01.ini | 3 +++ Data/Sys/GameSettings/GD7PB2.ini | 17 ++++++++++++ Data/Sys/GameSettings/GPVE01.ini | 37 +++++++++++++++------------ Data/Sys/GameSettings/GUNE5Dr0.ini | 4 +-- Data/Sys/GameSettings/GUNE5Dr1.ini | 4 +-- Data/Sys/GameSettings/GUNP5D.ini | 4 +-- Data/Sys/GameSettings/GXX.ini | 4 +-- Data/Sys/GameSettings/GXXE01.ini | 16 +++++------- Data/Sys/GameSettings/GXXJ01.ini | 11 ++++++++ Data/Sys/GameSettings/GXXP01.ini | 8 ++++-- Source/Core/Core/AchievementManager.h | 4 +-- 17 files changed, 89 insertions(+), 60 deletions(-) create mode 100644 Data/Sys/GameSettings/GD7PB2.ini diff --git a/Data/Sys/ApprovedInis.json b/Data/Sys/ApprovedInis.json index abcc1133b6..3940c7ac4a 100644 --- a/Data/Sys/ApprovedInis.json +++ b/Data/Sys/ApprovedInis.json @@ -1 +1 @@ -{"D43J01":{"loophack":"CAB9CED2D904F12CCB21F5B1DE9B5433620C3E13"},"DPOJ8P":{"Bypass Modem Detection":"90BA66E25640A538FEFA6693715718139079FA7B"},"G2BE5G":{"Disable interlaced rendering":"7FFF6BDD93713BEDFD23739C32B86153FA19AEA0"},"G2BP7D":{"Disable interlaced rendering":"56E85D7285F10348E1E5354E379918D07E79EDA9"},"G2VE08":{"16:9 Widescreen":"3ED033396382424533A263A39687A0961EC994A6"},"G2VP08":{"16:9 Widescreen":"EEBC3C5F04BB70AFCA1A4C3D638335CDC9142BA2"},"G3RP52":{"16:9 Widescreen":"7F6B7743F0DDD3704B15882330E490103AE24E36","60fps":"4C9A8572A35F1DB85BBB2F32ED34288A343F1FC0"},"G3YP52":{"16:9 Widescreen":"3D102AFEE2F4945894E74BB702ED3ED2294C6FEC","Force PAL60":"FD61581C593EA34DD358AE33819FAB310F2CE961"},"G4AEE9":{"16:9 Aspect Ratio Fix":"8C2564CF47BB6A9B57D712C90E17F459B0BB6CCD"},"G4ME69":{"16:9 Widescreen":"6FC6E5D91693FAABE25CC721F2121CB0B5CA9D12"},"G5SE7D":{"16:9 Widescreen":"16119398BFE241A84151C046E0BC5BCF89D22049","Remove Bloom":"A5F2FB2B3BFF61F34F8AB1DD5A912A0FD8DFA720"},"G5SP7D":{"16:9 Widescreen":"718AA8BD831961C33114852FDA75BB58F768F46A"},"G8ME01":{"16:9 Aspect Ratio Fix - Centered HUD":"8C2FC3FC6F7766AA8E87425BF3BDD2E46E73DE71","16:9 Aspect Ratio Fix - Centered HUD with letterboxing":"AE3DBE2604CA0660165CA870C4B80E2B523C9475","16:9 Aspect Ratio Fix - Normal HUD":"F6E8264CF14D0189219C1E2D20C4182F190F4B88","16:9 Aspect Ratio Fix - Stretched HUD":"8EA4C7B80ADE3E8FEC28EEFA3A046B6B04162E81"},"G8MJ01":{"16:9 Aspect Ratio Fix - Centered HUD":"B3181CA1AAB11EAE9132184DA48D76E775AC0A24","16:9 Aspect Ratio Fix - Centered HUD with letterboxing":"904C0EEEF16D761F7BDF7099DEB384DD9AB26E00","16:9 Aspect Ratio Fix - Normal HUD":"631CE99BDE3F1475303C00C67E3C130D4EB2547B","16:9 Aspect Ratio Fix - Stretched HUD":"81F7AD591F12A01DE82850457ECF147998D1238F"},"G8MP01":{"16:9 Aspect Ratio Fix - Centered HUD":"75AF5D7FA49BE9C3C73EA1482B49CD9B36CE817E","16:9 Aspect Ratio Fix - Centered HUD with letterboxing":"C2E5003E74551DF693A7CB814850CA51D77549EF","16:9 Aspect Ratio Fix - Normal HUD":"FB26AA84B0CB787705E84E869B649307776A2839","16:9 Aspect Ratio Fix - Stretched HUD":"C5F8A81520193418C07C7BC4881F02BE9C4DC9BC"},"G9SE8P":{"16:9 Widescreen":"CD043229AC47AC93D537B6A0725A2D0BB6FEF4E8"},"G9SJ8P":{"16:9 Widescreen":"C55EF90B542AA3973352A232629A828BC22A8509"},"G9SP8P":{"16:9 Widescreen":"9521774ADEE769CFCCE86F8170FE626387E73019"},"GAFE01":{"16:9 Widescreen":"D24F6D9BE5D79D3C795C2844634A0214BB68B5DA","Make Game Save Copyable (donny2112)":"F9A812FF62A20D5440CAB79DB6AFAE068457A10E"},"GAL":{"Trophy Viewer":"DBDB50216B22CDFFF77BDD80261CD0FF75803D3B"},"GALE01r0":{"Normal C Stick Functionality in Singleplayer Modes":"9DC8D90D986FAC175F45006AFF20CF00EDFBE053","Proper 16:9 Widescreen Support":"18B4159E77013EDFE499A33281B771C442970E3B","Properly Display in 4:3":"DC2BCA7D99EBDE1DC7C68CA6AC7F543B93FD6A67"},"GALE01r1":{"Normal C Stick Functionality in Singleplayer Modes":"F6030959C1CAC92F37287D556F18B3C5298FAD5B"},"GALE01r2":{"Disable Rumble":"CFDEEA8AD2A99394BFFEA7211EF03394E7471FA9","Normal C Stick Functionality in Singleplayer Modes":"BCD918A72368B2A30E797C76994E7539A1C0B5D9","PAL Stock Icons":"6B92D95F0EEA165EA84FD753F74B0A9E2079C2D7","Proper 16:9 Widescreen Support":"CEDF18354CDCFC49398551C629E51B053331A55B","Widescreen Support v1.2 [Dan Salvato]":"D32ED4CD685F3FF649F7E749E8C182C657013918"},"GALP01":{"Normal C Stick Functionality in Singleplayer Modes":"4817F202678559291F9F76B46DC7561A45D00B77","Proper 16:9 Widescreen Support":"BFCBE098FE8679B9140A48EA1D8620D5A369697C","Properly Display in 4:3":"5BA2933B8365E21553AA273B91954A4C722B1035"},"GAZE69":{"60FPS":"BC281053C5266B21FDDC6C8996A6021DEA03D459"},"GBLE52":{"16:9 Widescreen":"C0F3AB3BBC9B21F4BF25F44E48CF1CD36D2B3F45"},"GBLP52":{"16:9 Widescreen":"797040CB097BFD369490A1BE29659929D76CE6F7"},"GBRJ18":{"16:9 Widescreen":"B3C867AB34D90E2E9C5B799D800F4C191DE279FA"},"GBSE8P":{"16:9 Widescreen":"4148D453F61A5B81E53669335BC667E651865C76"},"GBSP8P":{"16:9 Widescreen":"6CB029AE768BA5B9995D5F12C1EBB37B3D1FE242"},"GC6E01":{"16:9 Widescreen":"DE932CCAD1B5EB1FAA8D08733BD0D3940C78382A","Allow Memory Card saving with Savestates":"2F64F98686E62B60E466E931A9EBCD19A750FF4E"},"GC6J01":{"16:9 Widescreen":"5CB4934396ABD43F7147EA13521C952728933874","Allow Memory Card saving with Savestates":"D8F327304A88FBC717BB1F775494C5F864B9E8D2"},"GC6P01":{"16:9 Widescreen [Ralf]":"05C9016BF58A628A3FD7B16E1B9BB0769A14846F","Allow Memory Card saving with Savestates":"EDEE0E28EEA1834868F2865336290FFBDFB9C6DA"},"GC8JA4":{"Fix C4 texture tiling (used for buttons and some character icons)":"843297019804192AF1FC660BC85F4B10891D0BA3"},"GCBE7D":{"16:9 Widescreen":"478DA1D596D8513C343850719155813CB61A56DD"},"GCBP7D":{"16:9 Widescreen":"478DA1D596D8513C343850719155813CB61A56DD","60Hz":"DF2C4DDF082C9E67919443C5ACC485AB2CF7F8D0"},"GCCE01":{"16:9 Widescreen":"BA0066518E7EA8599A81F8496BD39DE20CC369D6","Fix GBA connections":"483BDB94615C690045C3759795AF13CE76552286","Fix buffer overrun bug (crash at Goblin Wall)":"6C107FEC15C76201233CA2645EB5FAB4FF9751CE"},"GCCJGC":{"Fix GBA connections":"4C104D24329172F5D0F8649DE9423B931FE72CA3"},"GCCP01":{"16:9 Widescreen":"2580E7379633CF4DE13B9EC945DB17543477828A","Fix GBA connections":"2EAA60A8A115AD68A795109FB59E4A726D29016D"},"GCN":{"Fix C4 texture tiling (used for buttons and some character icons)":"601FE183C9524ACCF068874DABD73921C86769CF"},"GCVEEB":{"16:9 Widescreen":"ADA686C90189D1D7D23E23E525EFC96EFA104BB9"},"GD7E70":{"Deinterlacing Fix":"95A17AFAAFD83E96CD1CC6242B8667F6A3DC8416"},"GDREAF":{"Fix audio issues":"F8EAE60FEB0CFB4477FDC4B9E136B63F68DFA63A"},"GDRP69":{"Fix audio issues":"E23D98B2CE185C3993A40F2495D37E41B971BF91"},"GEDE01":{"Fix startup hang":"21068C3CE905FB0CFFAA7408A93154AF8A5295A2"},"GEDJ01":{"Fix startup hang":"7061F3CF11BF64D3BA7F32CCF2BAC42FF3614AB6"},"GEDP01":{"Fix startup hang":"6F1B00517CBA30BEB738EAA90E71221378CD570D"},"GEME7F":{"Force Progressive Scan":"CB04E00918C9C0F161715D21D046ED6620F7ADEF"},"GEMJ28":{"Force Progressive Scan":"CC2057185BB10DAD4A361412D024DFD586EE0130"},"GEZE8P":{"16:9 Widescreen [gamemasterplc]":"5CEBCFBEA4E444495D2FD6D8B6607DFB2349CC1B"},"GF7E01":{"16:9 Widescreen [gamemasterplc]":"C59CCA3EF8A5E97B32EB64DB9AE80E652ED281C6"},"GF7P01":{"16:9 Widescreen [gamemasterplc]":"1230053B5C347B62E432EFD6635433A183D18619"},"GFQEA4":{"16:9 Widescreen":"5CD9568CE58EF82EB371594667015C9B799454B9"},"GFZJ01":{"Make Save Copyable":"F659D22CB1DFF15C3915D7630D838EED8DB0BA48"},"GFZP01":{"Make Save Copyable":"F659D22CB1DFF15C3915D7630D838EED8DB0BA48"},"GGTE01":{"16:9 Widescreen [darkludx]":"EF128E7A9C22676834F558BA0F0F7FD8B9028727","Experimental 60fps":"D40344111D989EA009901F8B1C45B5AC8D39E6D2","Simple 60FPS":"5232A937D1D813FF58DD71D716284ED6AB535689"},"GGTJ01":{"Experimental 60fps":"BEC2310911003AF9E4B984A4051E0F885B8CC825","Simple 60FPS":"D62E777A2F019D7BD0AFFBCE876BD9AE408F1667"},"GGTP01":{"16:9 Widescreen":"FA9CD330ECDA01275DA88BD0803DE47757D16A4E","Experimental 60fps":"EB1ACD613BB432A3391CFFF1F0145A6ACE66B210","Simple 60FPS":"43FAD0DDD130BE1E4C8C7603EF6CA7DAA0DF5EE9"},"GGVD78":{"16:9 Widescreen":"9D07DBB5EC2FAA47F2E4587FBD75FD6F3E7E91B0","EFB Copy Fix":"FE52240DF6D132C15A8324E8A477F2BF2250D208"},"GGVE78":{"16:9 Widescreen":"86E561452235BF88D41884558EF34F54CE0FEB48","EFB Copy Fix":"5E38E10829D5F77243C95E9E41518BB3ADE24139"},"GGVP78":{"16:9 Widescreen":"F9B2108D833084FA36A53E00F1647A9579F847D3","EFB Copy Fix":"5E38E10829D5F77243C95E9E41518BB3ADE24139"},"GGVX78":{"EFB Copy Fix":"740F2D1C01DA39D1760D96B03974A48E6F74578D"},"GHAE08":{"Fix audio issues":"9799AFF8463EC86C9230E31E2627E141F0C129D3"},"GHAJ08":{"Fix audio issues":"B45A8FC32D14567B8D6C95F303E00A72C0E1D344"},"GHAP08":{"Fix audio issues":"BC7F3CFC97593AA2055C370C175950DC478D2709"},"GHLE69":{"16:9 Widescreen":"742BFC79D8F0BA5D6215772B58F5A0EADD7BFAFF"},"GHQE7D":{"16:9 Widescreen":"520E5F718D3BD1291C55021776091E0DE0FB4822"},"GHQP7D":{"16:9 Widescreen":"AAE14CDC13A0C082A850DED85417100A8C8A0687","60Hz":"884A33613AE8D916128E3FF39B3AA9F63DAADC58"},"GHSE69":{"16:9 Widescreen":"3538CDBD13FA939B7BA5F0F82253401AFDD1E2CD"},"GHSP69":{"16:9 Widescreen":"409754E2EBB6F05DEE1AFC647E25B15D462638FF"},"GICD78":{"EFB Copy Fix":"3A94591A149AE88C150AB3320BBC909FE54BAEA5"},"GICE78":{"EFB Copy Fix":"5BF55685B8867A85EAA9C86571309B17BF7DED32"},"GICF78":{"EFB Copy Fix":"85AABAEB9A59C4F96D9330A3B884F6D757DA1683"},"GICH78":{"EFB Copy Fix":"3A94591A149AE88C150AB3320BBC909FE54BAEA5"},"GICJG9":{"EFB Copy Fix":"969134EA21A160EBDA91C0870266E7D1707FDC43"},"GICP78":{"EFB Copy Fix":"13B158CF41F5412BC637F50644193D43CC3DA49A"},"GIQE78":{"EFB Copy Fix":"E15AA1E30D26E5735D68AAADE436E7B7E4A33A35"},"GIQJ8P":{"EFB Copy Fix":"FFFCB76E98DDB06A7BBBC0AA73C869C87EB787D6"},"GIQX78":{"EFB Copy Fix":"485DA99FAB35646DAA2A138B0315361495ABE778"},"GIQY78":{"EFB Copy Fix":"485DA99FAB35646DAA2A138B0315361495ABE778"},"GK2":{"16:9 Widescreen [Vague Rant]":"6B4C59C2A2C5D71C7A38C8513A76E6467E307A26"},"GK2E52":{"60 FPS":"8F2397930A50C3C176188526EB9612578F9CBCE5"},"GK4":{"16:9 Widescreen":"4652969A4DA869FD28F2CAA3869A38F5C2AAFEB1"},"GKB":{"16:9 Widescreen Region-Free":"4652969A4DA869FD28F2CAA3869A38F5C2AAFEB1"},"GKBPAF":{"60Hz":"8E4E0ABA0E6D102A33206F34F4DEE63159B3CB4F"},"GKDP01":{"16:9 Widescreen":"DF4657937DE730107B636C73E959277EB963D210","60Hz":"6740D7B914CC897AF50798C0AB391965294FD1E1"},"GKRPB2":{"16:9 Widescreen":"69C01A4F91D991CF866237FED94A138308FC104F","60Hz":"40D06A5C7A3D873ABC73DEDF573738C2E3E37FF0"},"GKWJ18":{"16:9 Widescreen":"696570101FA040778EAD310377C484C846D87430"},"GKYE01":{"16:9 Widescreen":"6520EF1B7D88F00747B120A3A010458602ED989D"},"GKYJ01":{"16:9 Widescreen":"47B8EF7D02831AA5C375C698797BF2D5475FEFF2"},"GKYP01":{"16:9 Widescreen":"69413C75036D2975716066E6574461B981FF0124"},"GLEE08":{"Fix audio issues":"7355F358CAC6F418D37E4C23E64F7867D46E4FC9"},"GLEJ08":{"Fix audio issues":"12B24A6D7389A2AC5AB75FC0BF8493E7661F2A73"},"GLEP08":{"Fix audio issues":"81BD39F5527552DE89E3B59BA86298900F0A3168"},"GLSD64":{"Fix freeze in opening cutscene":"5E2A73717BD66EF647846DD64C33BC80AD9B5227"},"GLSE64":{"Fix freeze in opening cutscene":"1CE78E7954415A44DF693C0BB879AA5A4FF059A3"},"GLSF64":{"Fix freeze in opening cutscene":"009B0C4AD80A9C28C987934D254C2C4AACC9A07A"},"GLSP64":{"16:9 Widescreen":"8E7A544C10E7A5E2F0304A0D2586879627EF6586","60Hz":"B67144E87B54246137142992A3BC83DC13BE68A7","Fix freeze in opening cutscene":"3D0894616C9A7FA5ED91C1D2F461BF14DF47ECEC"},"GM4E01":{"16:9 Menu Backgrounds Add-On":"87D1A6A3E29ADA03B0D29C2F1841C18E62DB0A15","16:9 Widescreen v2":"78BE50F93E81A1972CA31ABC318949E366E82BD0"},"GM4P01":{"16:9 Menu Backgrounds Add-On":"87D1A6A3E29ADA03B0D29C2F1841C18E62DB0A15","16:9 Widescreen v2":"6D950B6EEFD6D304E1A424355B74A595D32BDF2F"},"GMBE8P":{"16:9 Widescreen":"D12DE9E3941BCE6EB50B8DA92140E09AFC0104C8","NA Stretched HUD":"0151A6683E1FBFD20096807C54A6952C4A4BAD40"},"GMBP8P":{"16:9 Widescreen":"8A688981F33A2C53882EF08FCF20B88AD43EF417"},"GMOP70":{"16:9 Widescreen":"CE4D298616BD42E4D8F8936B325CD1FD7F5B956B","60Hz":"0CD2CC787A5FF551901E41C85C8AAC02017ECB89"},"GMPE01":{"16:9 Widescreen":"4F98592DB3DEE3857469A8D8605FAF4BD6F7C76D","QOL - Allow Digital Presses for Map Screen":"E232B27564E9AA0C32DE163C9C056317A7B2B12E","QOL - Automatically Advance Text Boxes":"59607671BFC4717ACAF9807BB7EB0D9F982866D4","QOL - Disable Advance on Results":"CAEA37F3FEF89400513353EB85875F2A3AF4C03E","QOL - Faster Boot Time":"F75FBBD838C5B84FF687962FBA9195B217DE132C","QOL - Increased Board Speed":"19F264DE7F07EAC9433CA4B591BEBA1EC976C9F1","QOL - Increased Taunt Capabilities":"C70391D4961A0E820DE40141C89341369A9F021B","QOL - Instant Text Display":"F4E6913CF034E5778B9F9CA5FF448FF1B78B3333","QOL - Rumble Always Off":"93621947019532F02C25937FD3EEEE21A788CB07","QOL - Show Controller Port Number of Who Paused":"D7249AC3C949FEB860CF4350C5B7B79AC16569F1"},"GMPJ01":{"16:9 Widescreen":"A795811F2F0C92D9DCABDE97D9E39B47B1DBCEFD"},"GMPP01":{"16:9 Widescreen":"A795811F2F0C92D9DCABDE97D9E39B47B1DBCEFD"},"GMSE01":{"60FPS":"2805B1A0DD53EB64877D375D10F667700955720F","Widescreen":"BD718F961DBA5372B1D0257D454D535746C453A0"},"GMSJ01":{"16:9 Widescreen":"E8112A01040A06460E368F142C5D1FD0B4085D94"},"GMSJ01r0":{"60FPS":"9894B5B6B5215844D48411021FF8EECFE851D79D"},"GMSJ01r1":{"60FPS":"2DB69DB7A3753D543CD85CA4C77B4EF0AED7486E"},"GMSP01":{"60FPS":"D07009B710B3BBC6B12E54ED6A7969D58197EDAF","Widescreen":"0B7B89BF0868393076EF5F9DBD689DD0EDBCB84C"},"GNHE5d":{"Nop Hack":"89393A24E2336841AA4CD0AD3BE1C9A66B89E9EF"},"GOCE5D":{"16:9 Widescreen":"E4D800B90F16A15D9252EED1B2A23AEAF5CD230A"},"GOME01":{"16:9 Widescreen":"C33CBBF6F22195BF626104B4827200B3EE1CF30C"},"GOMP01":{"16:9 Widescreen":"874784CCD4AC3DC9C69654E471C18AD0F5E4D9AE"},"GONE69":{"Widescreen Hack Culling":"6412A67E1C26E146A9526AFC9F0EE517B214069D"},"GP4J18":{"16:9 Widescreen":"BF4D2D6AF66F285E81173B3B20D5369DB6FB351B"},"GP5E01":{"16:9 Widescreen":"5ED053787332DB5EEB8B963BA5DF197B58C6BB75","QOL - Automatically Advance Text Boxes":"41EF84663008668682BB75DC8B874E38669AE041","QOL - Disable Advance on Results":"6C36BEFE751131298BC8B39012892F6A6903CE60","QOL - Faster Boot Time":"8E952E6E12639AC20F6F9FDD150C12F0C294DF43","QOL - Increased Board Speed":"4BD72B8ED75ED8EFB3329C104BAD114FDDB8AD2C","QOL - Increased Capsule Throwing Speed":"C5BA3C9D386D09DD6CB9242A12A0D041BEB93160","QOL - Increased Taunt Capabilities":"C360EBDDCB0237076052C983BD9719411CA67CDE","QOL - Instant Text Display":"2CD4A06A3B9C18CC9EA2A2DEBD96E066CD1D700A","QOL - Show Controller Port Number of Who Paused":"5BA3648D18346EDC97B23475D4748C637E6095A2"},"GP5J01":{"16:9 Widescreen":"81E69B6BEE85E06805EC24E1CFEA9405BF459DF8"},"GP5P01":{"16:9 Widescreen":"FD4FCCB97C06F0173A30D4CC9A99422B2C0ABA9B"},"GP6E01":{"16:9 Widescreen":"B502AF0887792E9B140516D14BA75F99772C17BE","QOL - Auto Advance Text Boxes":"E645DF70CE0266D174D917A817A7FBEEE974A9F0","QOL - Disable Advance on Results":"798E566B621F6A32BF23C588E76EAC0776E2A8A3","QOL - Faster Boot Time":"B089A3C80D5DA86ABF6258F8BFB1DC78BB54A973","QOL - Increased Board Speed":"501C52ACF117950888A66DB2BEDBDFD7D9B20235","QOL - Increased Orb Throwing Speed":"845F439278DA8CBC225658FF4E2F82D707422BFB","QOL - Increased Taunt Capabilities":"0AED27341A06186AD92D5725C0B9693CA8F85B7B","QOL - Instant Text Display":"5AAC4E21927D4AEE7AC329CE793D4CDAEDDF7A3F","QOL - Show Controller Port Number of Who Paused":"DF5D2F068D76ABF2B1C294D413B9154B8C356929"},"GP6J01":{"16:9 Widescreen":"88F50F8298F82E3E5C161277BD4A985CE893A9D0"},"GP6P01":{"16:9 Widescreen":"DCCE59CA987624C753F0A86BEBA4287635901C93","Remove Black Bars":"138A86D43B5830BFE1926F58C0A4FBE2971BB02D"},"GP7E01":{"16:9 Widescreen":"87EBF72D90503EBC5CE9BC994795669E48A92A4A","QOL - Auto Advance Text Boxes":"82338AB5D82A7F398B4EB674CA4866DD1F4A1E65","QOL - Controller Options Always Acesssible":"D2F9CC0B9E52136E77187395FB7DA9B64B2C3B30","QOL - Disable Advance on Results":"A644551162A61DE7997B2A84F5BFDEFFD3FC2FB5","QOL - Faster Boot Time":"1939B5C575AE8FF6890BDDDD1B3FEA472D8CABA0","QOL - Increased Board Speed":"7E06A2EBCFECADB8074152B489984237A6FE9F21","QOL - Increased Orb Throwing Speed":"6671BEE71920D6B845AE1257AFE3AB3B95CD6FBD","QOL - Increased Taunt Capabilities":"9F60923F95FFE709CCD06966CD2DF743263934D1","QOL - Instant Text Display":"48FA6002E42DA9BB83ABC980A32C60CE3B21C4FC","QOL - Show Controller Port Number of Who Paused":"5E6F8C6C87D5944DB18A4E4F9A45F61EE87A4163"},"GP7J01":{"16:9 Widescreen":"A081F0729068D233E99DC00C64E36CA33C945640"},"GP7P01":{"16:9 Widescreen":"75D6CDA4EA301A71F7701A0487544E79136850B9","60Hz":"6D0D5B7CA58C38A3147F8ED981B5B21CC6C519CD"},"GPIE01":{"No Blur":"A25CDDB79991F090BD7F12840AD06F13BD5CCF95"},"GPIE01r0":{"16:9 Widescreen":"2B58166A66644F984E64077A6245C94C79B51063"},"GPIE01r1":{"16:9 Widescreen":"9A1545AB4E2B9216B95AFFA83420D723512DA8C8","60FPS":"3337C767EAA21D30C6CFEAE9985FFCABFC63E2E3","Disable Pikmin optimizations":"CBD15E9104929F0631713AE482475CB2B90E71F2"},"GPIP01":{"16:9 Widescreen":"5AE44D5B0E0D4C412B453B95CF5A41DBA4D685E6","60Hz":"AE59FF751E167632AE283F38CC4B96BDF7B9B81F","Turn off blur":"4ED79A548DBD7DBD35928A2F1138315FF103E260"},"GPNE08":{"16:9 Widescreen [Ralf, darkludx]":"ED3D9BB08C5F1D21BC8D5C73155C00E3C25C466F"},"GPNP08":{"16:9 Widescreen [Ralf]":"469B18FE8219031D355557B836EF02B625040A4F"},"GPOE8P":{"Make Save Copyable NTSC Port - 1.0\/1.1":"7A4B3E596BA4FDA8F3FCF984079B3F44CB2D6AB3","Make Save Copyable NTSC Port - PLUS\/1.2":"304DB311F2107E88EBCE058535B266F9263AA8E2","Save Validation Code v 1.0\/v1.1":"9F6259132453DCC0607EAB35DB182C76818F2F9F","Save Validation Code v1.2\/Plus":"17EB6CFB408EF27D44C053A1336C3B87B6A05018"},"GPOE8Pr0":{"16:9 Widescreen":"99986BB1D4ABE45C429D48B01BFB578FEA69C14F"},"GPOE8Pr1":{"16:9 Widescreen (Plus) [Ralf]":"5563671A6702785FFE106DAB7EF3F4FE6D9F705B"},"GPOP8P":{"16:9 Widescreen":"DF133C61C29D5CFA87A2A785C962C205F80E6D04","Game Save Valid":"40EC1590DB26C7C58D00E3065F1E404341EF0E73","Game Save copyable":"2F5FB98927DAFF141D1CCD1EACA8AF626397E284"},"GPVE01":{"16:9 Widescreen":"E5B51080CFD518FE584CF3B61A1099CD032479D6","60 FPS":"271C40F5FC113896717DB0EEFED42E788CA3AC81"},"GPVP01":{"16:9 Widescreen":"78F3C9A789827B063A6701987262276A66ABD82B"},"GQPE78":{"EFB Copy Fix":"880B114E9A308084CAB92C004A9EE067B371C310"},"GQPP78":{"EFB Copy Fix":"5D9A14954AE8D639C9B254F3BA73A70F284BBC8D"},"GQSDAF":{"16:9 Widescreen":"CA82C44B2E8FC5C184E3FF935BC89661B7DB55D6","60Hz":"952165FD78543EB6D2F5230F7570B0513773D332","No Blur":"DEB7DF358423F0EF30C9FD49F4F14590836D52A6"},"GQSEAF":{"16:9 Widescreen":"48AFE1D32843C6B5AD4337A49F15FD447DF2A752","Remove Blur":"EF448CF1FA6A15EB2661EA5338703C13D52EF65B"},"GQSFAF":{"16:9 Widescreen":"76C30E1737CC5C3B5DE32532B5F9A5CD50B690F8","No Blur":"986BAE5103CDE9286D2D34FD28FE2540D0759AAB"},"GR2E52":{"16:9 Widescreen":"5F55CE7DA7E1E3261CF2FF88F34CDA63BA511003"},"GR2J52":{"16:9 Widescreen":"D3C6FA690BED07BFA65C2E1FCDE2FFA5AA0EEEE4"},"GR2P52":{"16:9 Widescreen":"D7BCADD4E76B5E3F7D9D463F55A2777528E37E94"},"GRNE52":{"16:9 Widescreen":"483D74C47AD1012606D591A226AB3144C7FB201C"},"GRNJ52":{"16:9 Widescreen":"949281B7221B20680B7BC29E4754B73A326EBE85"},"GRNP52":{"16:9 Widescreen":"11051B094FE5A8B2E73060BFF786C1588E36979A"},"GROE5Z":{"16:9 Widescreen":"FADBAB5EC280CF8ED4C9536A33F4184BA210C9B0"},"GRYE41":{"Disable Culling to Fix Rise and Shrine Hang":"AF0A575EB6071EAC0D2EC3D2EA30A23EB05A4192"},"GSAE01r0":{"viWidth 704 Aspect Ratio Fix":"0D529180B9B28BB06E21EDBDBA61C17EEF0FFB7F"},"GSAE01r1":{"viWidth 704 Aspect Ratio Fix":"CB15ED22625690A5DBE4607FE30160125421461D"},"GSAP01":{"viWidth 704 Aspect Ratio Fix":"749EDA62B1B73354599929F1B50E992E111C5493"},"GSNE8P":{"16:9 Widescreen":"CFDF3FA5B48B347E49E8E521D7BE47FCC9A5CBF4","60Hz Aspect Ratio Fix":"D90F66C0D46D9BEF27C8E13BC8804DB1F382C8B1"},"GSNJ8P":{"60Hz Aspect Ratio Fix":"98DF9D67DE77F66A4B09F25C5621805DEB40865B"},"GSNP8P":{"60Hz Aspect Ratio Fix":"D90F66C0D46D9BEF27C8E13BC8804DB1F382C8B1"},"GSO":{"16:9 Widescreen Region Free":"0D60D0593F6DA28B1236381B22082506B9533F0E"},"GSPE69":{"16:9 Widescreen":"80E18A8963F4EFD3C03494C95934452FCB6E521B"},"GSPP69":{"16:9 Widescreen":"162E87E9F5511CD82216811055836C11B374C45B","60Hz":"E5DF2DF34D2F0BDEE4205C891576511EC572C5DC"},"GSWE64":{"16:9 Widescreen":"6480326CCA8FF64A4868EC6E9FDE9F38819A75A1","Disable Dithering":"85F65E4B627641862C1A6091D29A3BB0626B04C3"},"GTEE01":{"16:9 Widescreen":"6B57EBCB9CC0ACE2EDA0F13E3F1F55F3FABC3A26"},"GTEP01":{"16:9 Widescreen":"72785784363C46180AF8E388348FD77667E7D382"},"GTOJAF":{"Remove Blur":"53A2EDF113F2ED7E54A16AA0E73FDB2C44C79DE5"},"GTZE41":{"16:9 Widescreen":"ECCE87E0006475C73E7D936AA0150BD20166F9EF"},"GTZP41":{"16:9 Widescreen":"62D8F159B93167171E0860D96C4BB32A5BF5648B"},"GU2D78":{"EFB Copy Fix":"CFF4C3F932B08732627572EDA1A0CD2D9C71AE0C"},"GU2F78":{"EFB Copy Fix":"CFF4C3F932B08732627572EDA1A0CD2D9C71AE0C"},"GU3D78":{"EFB Copy Fix":"8A0E3114862ADFE421874211BD6F5220AA425BF5"},"GU3X78":{"EFB Copy Fix":"E3303FDAE7ECA17A72EDC440C32D94648A6453A0"},"GU4Y78":{"EFB Copy Fix":"D54767785E139A8BC8C4B75573FBD5A0B686D8E3"},"GUNE5Dr0":{"16:9 Widescreen":"C942EBCBE8A487C25E296EC1FAC2358DA1487DBD","60Hz":"A441630EC5FF0EB74D2243A3092D22E69C6BEA02"},"GUNE5Dr1":{"16:9 Widescreen":"39BD84CBE2AFACFEDEC5E9020D1A8B0D36DD53F9","60Hz":"05779F1A3D0C43305D52A95447D612CB424942C0"},"GUNP5D":{"16:9 Widescreen":"FE3BA1DAA1AF278A7839E27BF466B0BA0B390EC2","50Hz":"3B20499A3F8D1D7CCA5B0015F2D80ECED25842A6"},"GV4E69":{"Fix 2D Rendering":"8679891FCAA250FCFF670B26E0CB9875900D17FD"},"GVJE08":{"16:9 Widescreen":"09EFDA75D876A675121C8344D8FDD09A70A1A846"},"GVJP08":{"16:9 Widescreen":"93938F4C6112C91549F2B52E6259170F7501EC07"},"GVPE69":{"Fix 2D Rendering":"3159CA79B0A890131763EA6CB163684BEE886E3F"},"GVSE8P":{"16:9 Widescreen":"4CE9C491160A4B631142EE9CE802C694163F1CA3"},"GWRE01":{"16:9 Widescreen":"AC42770B06662BE1DC863EC80F44B5E034C63664"},"GWRP01":{"16:9 Widescreen":"9DDDFAB28C4BD35CF64050E1EF684DC042B1AFFA"},"GWWE01":{"16:9 Widescreen":"98B2E75D8E1CED4A964D3129A5DC10E30538CAA6"},"GWWJ01":{"16:9 Widescreen":"2DAD9A5E2A140F02CCBA727C4BE7C74BAC156778"},"GWWP01":{"16:9 Widescreen":"2DAD9A5E2A140F02CCBA727C4BE7C74BAC156778"},"GWZE01":{"16:9 Widescreen":"9EFC191DE6D21A1681FE241AB2EE4A131259F317"},"GXCE01":{"16:9 Widescreen":"901A1E78A3A0124F55548507D3B3707125C64A8A"},"GXSE8P":{"16:9 Widescreen":"F50BBA440184FC77A4DFFAA58FF2BB888E2E072F","Aspect Ratio Fix":"4214C74DFB8A74FC3AA4A643E869BC4D9A38EDA0"},"GXSJ8P":{"Aspect Ratio Fix":"227909607984BBC3D36AAC7DB9DFE385F3363C49"},"GXXE01":{"16:9 Widescreen":"2CA66DFAB3CC67D76AE06A378B1F0E9CBA5F3D68","Allow Memory Card saving with Savestates":"64FAA15062F0D0C319F904BBDE9C4489A25D6369"},"GXXJ01":{"Allow Memory Card saving with Savestates":"8293802260536FA2EF2EFDAB5266DE36BB88DE1B"},"GXXP01":{"16:9 Widescreen":"02716B2585BEE74F8FCDD97A78F6A0D3DC7F331B","Allow Memory Card saving with Savestates":"3CAFBC4AE6FC5CE9F53377F86AB5BD8F1BC8861A"},"GYFPA4":{"60Hz":"402ED10AC842041AB4B39AE8F2D81B2D7AEF9CB4"},"GZ2E01":{"16:9 Widescreen v2":"E7521ED27BFC972628906CBE8D5403ED57253BB8","Hyrule Field Speed Hack":"FCB673D46E716C7F63C618B8D8BF83AEE0B501F0"},"GZ2J01":{"16:9 Widescreen":"F985A0A58D8E2B23E8A557FAFF8D367AFFEADD07","Hyrule Field Speed Hack":"FCB673D46E716C7F63C618B8D8BF83AEE0B501F0"},"GZ2P01":{"16:9 Widescreen v2":"CAC38B0D334B925A57AA3118D35932B8A185137E","Hyrule Field Speed Hack":"0F63623D4D984B7706F718F57C0ABDB6DBADCF8D"},"GZ3E70":{"16:9 Widescreen":"167F9E9A9B372CB3A01F308B46FF1403F8599C51","Deinterlacing Fix":"2E163CB5FE724EB49B31CF607E5B9AFDA031DDD5"},"GZDP70":{"16:9 Widescreen":"2E424E0BBAE6EF5D6A8FB4224EA1D0746BAC37D3"},"GZLE01":{"16:9 Widescreen":"9FA864EE7DD8CE7FF538EB4E0243F20137430BD8","Remove Distance Blur":"78EA34CEF8E01701491C280F155F1C12EC9BF1A2"},"GZLJ01":{"16:9 Widescreen":"E03B61989025CA33937E63A057E6E40A403811BC"},"GZLP01":{"16:9 Widescreen":"3EFFF6C52B5633A1729FAA6883D8579E77F7D057","Remove Distance Blur":"14BECBEA4DD281EBD0F7FE7DEE8020B6F2418ACD"},"GZPE70":{"16:9 Widescreen":"7142F2495507AC7136992128ED0FCA6BC72B61F4"},"GZPP70":{"16:9 Widescreen":"591FD6C9668C79FF1CE8558EBED2486A7A327F05","60Hz":"A59B84DB5486521AEE1C23B6C741ECD35216E5AD"},"GZSE70":{"16:9 Widescreen":"8E86EAC7EA4F4D2854DD9020CD795630CA64C4EE"},"HAF":{"BufferPatch":"181195871F63B89B1CF09AFA4420CF89B9883108"},"HAL":{"RSAPatch":"AD12237401ABE9FE4A545AADB5C5AE10355E2076"},"RGQE70":{"crashfix":"5F4CF8D4DA19A0FF74FF9EB925AC0236069BFD59"},"RLEEFS":{"Fix crash on main menu":"793642AC6862C2F3412035A9E3D7172CC4A1D5C7"},"RM8E01":{"16:9 Widescreen":"E61344EB1542A78D497981C307B6549985C7A05A","Extra - Disable Music":"25223F9EFAABF601CAC7810004F124E4056598B1","QOL - Faster Boot Time":"BCC4279F8B28636AD773F01540E78DF40EAD6087","QOL - Increased Board Speed":"B65AF1819966CD3435D88801E8C79704E3A52DB5","QOL - Increased Taunt Capabilities":"66495D7CB532FAE778AFC22CF45D17D0FFDE5310","QOL - Increased Text Display":"B4186DDC54F33F4D6A22188EF50CEB43FB205673","QOL - Invert IR Stick for GameCube Mod":"9D90A9C66AE8AD91B201B40C4145D1323B701A77","QOL - Remove Explanations":"330DD53AB993A99576564FEFD222D7BD211B878F"},"RM8J01":{"16:9 Widescreen":"A140BDCB8E1721CB6B4CD878E412113322258B57"},"RM8P01":{"16:9 Widescreen":"F664C32AFD3D785FC6E04D8990A3FA1C72A18C5C"},"RMHE08":{"Bloom OFF":"CCF233DA57B3E75221870DE502955114B0D4E7FA"},"RMHJ08":{"Bloom OFF":"29D3625B7ED577587E56AA07CB0EB8C47C97E823"},"RMHP08":{"Bloom OFF":"1720C1173D4698167080DBFC4232F21757C4DA08"},"RO2P7N":{"Hangfix":"EEE9C8DE4671C18DD7F81DD08D39B64C57600DEA"},"RPBE01":{"Fix black screen effects":"775ABECA6073E02C5C68CF4D644194D966A418F5"},"RPBJ01r0":{"Fix black screen effects":"0EAB5D8DE827894AFEF97C10ACB67378E6983323"},"RPBJ01r1":{"Fix black screen effects":"4905E08643E9D00136F7EAF51978CF2F54D10D07"},"RPBJ01r2":{"Fix black screen effects":"4905E08643E9D00136F7EAF51978CF2F54D10D07"},"RPBP01":{"Fix black screen effects":"82AEB60F9A9083F93060531A970FFAABE0833A40"},"RTH":{"Disable blur":"812EE46AC967BFCD239335B10A664D71A93E8175"},"RX4E4Z":{"Fix file reads (dcache bypass)":"9E4E0F1465A9A1E85349DBA3B1278AC215A97DBB"},"RX4PMT":{"Fix file reads (dcache bypass)":"EE85907C03F0295794821383B93F8D5B91D2697A"},"RZDE01r0":{"Hyrule Field Speed Hack":"15EAD073414C9903D6CAE5229DCE582BD17A9162"},"RZDE01r2":{"Hyrule Field Speed Hack":"27395CC8BC2C51201D566657D31A471A850482FB"},"RZDJ01":{"Hyrule Field Speed Hack":"B3F7473F8C911A32F1D616491C9E78EBBD7A6309"},"RZDK01":{"Hyrule Field Speed Hack":"A280C0114B800D7DC056ECFB5E482229DA0B1550"},"RZDP01":{"Hyrule Field Speed Hack":"2A83ADFB760F9498841ED0ED68B0C0438232472C"},"SAOE78":{"Fix crash on boot":"EA11FA4908FB20B61876ACD360EC7657A6D39FB2"},"SAOEVZ":{"Fix crash on boot":"AA55C214DE7545DE0E203CC39F06BF3D31451BE9"},"SGLEA4":{"Fix black screen":"258378187ACF475A55EFEAF8A703681252E014C3"},"SGLPA4":{"Fix black screen":"6F8CD59D897338CA90939149E1A62588620C6D88"}} \ No newline at end of file +{"D43J01":{"loophack":"CAB9CED2D904F12CCB21F5B1DE9B5433620C3E13"},"DPOJ8P":{"Bypass Modem Detection":"90BA66E25640A538FEFA6693715718139079FA7B"},"G2BE5G":{"Disable interlaced rendering":"7FFF6BDD93713BEDFD23739C32B86153FA19AEA0"},"G2BP7D":{"Disable interlaced rendering":"56E85D7285F10348E1E5354E379918D07E79EDA9"},"G2VE08":{"16:9 Widescreen":"3ED033396382424533A263A39687A0961EC994A6"},"G2VP08":{"16:9 Widescreen":"EEBC3C5F04BB70AFCA1A4C3D638335CDC9142BA2"},"G3RP52":{"16:9 Widescreen":"7F6B7743F0DDD3704B15882330E490103AE24E36","60fps":"4C9A8572A35F1DB85BBB2F32ED34288A343F1FC0"},"G3YP52":{"16:9 Widescreen":"3D102AFEE2F4945894E74BB702ED3ED2294C6FEC","Force PAL60":"FD61581C593EA34DD358AE33819FAB310F2CE961"},"G4AEE9":{"16:9 Aspect Ratio Fix":"8C2564CF47BB6A9B57D712C90E17F459B0BB6CCD"},"G4ME69":{"16:9 Widescreen":"6FC6E5D91693FAABE25CC721F2121CB0B5CA9D12"},"G5SE7D":{"16:9 Widescreen":"16119398BFE241A84151C046E0BC5BCF89D22049","Remove Bloom":"A5F2FB2B3BFF61F34F8AB1DD5A912A0FD8DFA720"},"G5SP7D":{"16:9 Widescreen":"718AA8BD831961C33114852FDA75BB58F768F46A"},"G8ME01":{"16:9 Aspect Ratio Fix - Centered HUD":"8C2FC3FC6F7766AA8E87425BF3BDD2E46E73DE71","16:9 Aspect Ratio Fix - Centered HUD with letterboxing":"AE3DBE2604CA0660165CA870C4B80E2B523C9475","16:9 Aspect Ratio Fix - Normal HUD":"F6E8264CF14D0189219C1E2D20C4182F190F4B88","16:9 Aspect Ratio Fix - Stretched HUD":"8EA4C7B80ADE3E8FEC28EEFA3A046B6B04162E81","No Letterbox":"B1DE2A3526F2E4511F2B94A5171BF7B3C2C52B9B"},"G8MJ01":{"16:9 Aspect Ratio Fix - Centered HUD":"B3181CA1AAB11EAE9132184DA48D76E775AC0A24","16:9 Aspect Ratio Fix - Centered HUD with letterboxing":"904C0EEEF16D761F7BDF7099DEB384DD9AB26E00","16:9 Aspect Ratio Fix - Normal HUD":"631CE99BDE3F1475303C00C67E3C130D4EB2547B","16:9 Aspect Ratio Fix - Stretched HUD":"81F7AD591F12A01DE82850457ECF147998D1238F"},"G8MP01":{"16:9 Aspect Ratio Fix - Centered HUD":"75AF5D7FA49BE9C3C73EA1482B49CD9B36CE817E","16:9 Aspect Ratio Fix - Centered HUD with letterboxing":"C2E5003E74551DF693A7CB814850CA51D77549EF","16:9 Aspect Ratio Fix - Normal HUD":"FB26AA84B0CB787705E84E869B649307776A2839","16:9 Aspect Ratio Fix - Stretched HUD":"C5F8A81520193418C07C7BC4881F02BE9C4DC9BC","No Letterbox":"7828F5B0C8B470620755C16C60A5A4554F92640C"},"G9SE8P":{"16:9 Widescreen":"CD043229AC47AC93D537B6A0725A2D0BB6FEF4E8"},"G9SJ8P":{"16:9 Widescreen":"C55EF90B542AA3973352A232629A828BC22A8509"},"G9SP8P":{"16:9 Widescreen":"9521774ADEE769CFCCE86F8170FE626387E73019"},"GAFE01":{"16:9 Widescreen":"D24F6D9BE5D79D3C795C2844634A0214BB68B5DA","Make Game Save Copyable (donny2112)":"F9A812FF62A20D5440CAB79DB6AFAE068457A10E"},"GAL":{"Trophy Viewer":"DBDB50216B22CDFFF77BDD80261CD0FF75803D3B"},"GALE01r0":{"Normal C Stick Functionality in Singleplayer Modes":"9DC8D90D986FAC175F45006AFF20CF00EDFBE053","Proper 16:9 Widescreen Support":"18B4159E77013EDFE499A33281B771C442970E3B","Properly Display in 4:3":"DC2BCA7D99EBDE1DC7C68CA6AC7F543B93FD6A67"},"GALE01r1":{"Normal C Stick Functionality in Singleplayer Modes":"F6030959C1CAC92F37287D556F18B3C5298FAD5B"},"GALE01r2":{"Disable Rumble":"CFDEEA8AD2A99394BFFEA7211EF03394E7471FA9","Normal C Stick Functionality in Singleplayer Modes":"BCD918A72368B2A30E797C76994E7539A1C0B5D9","PAL Stock Icons":"6B92D95F0EEA165EA84FD753F74B0A9E2079C2D7","Proper 16:9 Widescreen Support":"CEDF18354CDCFC49398551C629E51B053331A55B","Widescreen Support v1.2 [Dan Salvato]":"D32ED4CD685F3FF649F7E749E8C182C657013918"},"GALP01":{"Normal C Stick Functionality in Singleplayer Modes":"4817F202678559291F9F76B46DC7561A45D00B77","Proper 16:9 Widescreen Support":"BFCBE098FE8679B9140A48EA1D8620D5A369697C","Properly Display in 4:3":"5BA2933B8365E21553AA273B91954A4C722B1035"},"GAZE69":{"60FPS":"BC281053C5266B21FDDC6C8996A6021DEA03D459"},"GBLE52":{"16:9 Widescreen":"C0F3AB3BBC9B21F4BF25F44E48CF1CD36D2B3F45"},"GBLP52":{"16:9 Widescreen":"797040CB097BFD369490A1BE29659929D76CE6F7"},"GBRJ18":{"16:9 Widescreen":"B3C867AB34D90E2E9C5B799D800F4C191DE279FA"},"GBSE8P":{"16:9 Widescreen":"4148D453F61A5B81E53669335BC667E651865C76"},"GBSP8P":{"16:9 Widescreen":"6CB029AE768BA5B9995D5F12C1EBB37B3D1FE242"},"GC6E01":{"16:9 Widescreen":"DE932CCAD1B5EB1FAA8D08733BD0D3940C78382A","60 FPS [Nerdzilla]":"85F98064F17AFE0FB02A99A182AF3FEB7644DCAA","Allow Memory Card saving with Savestates":"2F64F98686E62B60E466E931A9EBCD19A750FF4E"},"GC6J01":{"60 FPS [Nerdzilla]":"A6146772E37970EA5564F51A622A04792158B321","Allow Memory Card saving with Savestates":"D8F327304A88FBC717BB1F775494C5F864B9E8D2"},"GC6P01":{"16:9 Widescreen [Ralf]":"05C9016BF58A628A3FD7B16E1B9BB0769A14846F","60 FPS [Nerdzilla]":"1D3FB017C0D20754E48A8BA54EEE7DC4851BE84B","Allow Memory Card saving with Savestates":"EDEE0E28EEA1834868F2865336290FFBDFB9C6DA"},"GC8JA4":{"Fix C4 texture tiling (used for buttons and some character icons)":"843297019804192AF1FC660BC85F4B10891D0BA3"},"GCBE7D":{"16:9 Widescreen":"478DA1D596D8513C343850719155813CB61A56DD"},"GCBP7D":{"16:9 Widescreen":"478DA1D596D8513C343850719155813CB61A56DD","60Hz":"DF2C4DDF082C9E67919443C5ACC485AB2CF7F8D0"},"GCCE01":{"16:9 Widescreen":"BA0066518E7EA8599A81F8496BD39DE20CC369D6","Fix GBA connections":"483BDB94615C690045C3759795AF13CE76552286","Fix buffer overrun bug (crash at Goblin Wall)":"6C107FEC15C76201233CA2645EB5FAB4FF9751CE"},"GCCJGC":{"Fix GBA connections":"4C104D24329172F5D0F8649DE9423B931FE72CA3"},"GCCP01":{"16:9 Widescreen":"2580E7379633CF4DE13B9EC945DB17543477828A","Fix GBA connections":"2EAA60A8A115AD68A795109FB59E4A726D29016D"},"GCN":{"Fix C4 texture tiling (used for buttons and some character icons)":"601FE183C9524ACCF068874DABD73921C86769CF"},"GCVEEB":{"16:9 Widescreen":"ADA686C90189D1D7D23E23E525EFC96EFA104BB9"},"GD7E70":{"Deinterlacing Fix":"95A17AFAAFD83E96CD1CC6242B8667F6A3DC8416"},"GD7PB2":{"Deinterlacing Fix":"847264D37B4822FCBE30082590D936A45BB327D6"},"GDREAF":{"Fix audio issues":"F8EAE60FEB0CFB4477FDC4B9E136B63F68DFA63A"},"GDRP69":{"Fix audio issues":"E23D98B2CE185C3993A40F2495D37E41B971BF91"},"GEDE01":{"Fix startup hang":"21068C3CE905FB0CFFAA7408A93154AF8A5295A2"},"GEDJ01":{"Fix startup hang":"7061F3CF11BF64D3BA7F32CCF2BAC42FF3614AB6"},"GEDP01":{"Fix startup hang":"6F1B00517CBA30BEB738EAA90E71221378CD570D"},"GEME7F":{"Force Progressive Scan":"CB04E00918C9C0F161715D21D046ED6620F7ADEF"},"GEMJ28":{"Force Progressive Scan":"CC2057185BB10DAD4A361412D024DFD586EE0130"},"GEZE8P":{"16:9 Widescreen [gamemasterplc]":"5CEBCFBEA4E444495D2FD6D8B6607DFB2349CC1B"},"GF7E01":{"16:9 Widescreen [gamemasterplc]":"C59CCA3EF8A5E97B32EB64DB9AE80E652ED281C6"},"GF7P01":{"16:9 Widescreen [gamemasterplc]":"1230053B5C347B62E432EFD6635433A183D18619"},"GFQEA4":{"16:9 Widescreen":"5CD9568CE58EF82EB371594667015C9B799454B9"},"GFZJ01":{"Make Save Copyable":"F659D22CB1DFF15C3915D7630D838EED8DB0BA48"},"GFZP01":{"Make Save Copyable":"F659D22CB1DFF15C3915D7630D838EED8DB0BA48"},"GGTE01":{"16:9 Widescreen [darkludx]":"EF128E7A9C22676834F558BA0F0F7FD8B9028727","Experimental 60fps":"D40344111D989EA009901F8B1C45B5AC8D39E6D2","Simple 60FPS":"5232A937D1D813FF58DD71D716284ED6AB535689"},"GGTJ01":{"Experimental 60fps":"BEC2310911003AF9E4B984A4051E0F885B8CC825","Simple 60FPS":"D62E777A2F019D7BD0AFFBCE876BD9AE408F1667"},"GGTP01":{"16:9 Widescreen":"FA9CD330ECDA01275DA88BD0803DE47757D16A4E","Experimental 60fps":"EB1ACD613BB432A3391CFFF1F0145A6ACE66B210","Simple 60FPS":"43FAD0DDD130BE1E4C8C7603EF6CA7DAA0DF5EE9"},"GGVD78":{"16:9 Widescreen":"9D07DBB5EC2FAA47F2E4587FBD75FD6F3E7E91B0","EFB Copy Fix":"FE52240DF6D132C15A8324E8A477F2BF2250D208"},"GGVE78":{"16:9 Widescreen":"86E561452235BF88D41884558EF34F54CE0FEB48","EFB Copy Fix":"5E38E10829D5F77243C95E9E41518BB3ADE24139"},"GGVP78":{"16:9 Widescreen":"F9B2108D833084FA36A53E00F1647A9579F847D3","EFB Copy Fix":"5E38E10829D5F77243C95E9E41518BB3ADE24139"},"GGVX78":{"EFB Copy Fix":"740F2D1C01DA39D1760D96B03974A48E6F74578D"},"GHAE08":{"Fix audio issues":"9799AFF8463EC86C9230E31E2627E141F0C129D3"},"GHAJ08":{"Fix audio issues":"B45A8FC32D14567B8D6C95F303E00A72C0E1D344"},"GHAP08":{"Fix audio issues":"BC7F3CFC97593AA2055C370C175950DC478D2709"},"GHLE69":{"16:9 Widescreen":"742BFC79D8F0BA5D6215772B58F5A0EADD7BFAFF"},"GHQE7D":{"16:9 Widescreen":"520E5F718D3BD1291C55021776091E0DE0FB4822"},"GHQP7D":{"16:9 Widescreen":"AAE14CDC13A0C082A850DED85417100A8C8A0687","60Hz":"884A33613AE8D916128E3FF39B3AA9F63DAADC58"},"GHSE69":{"16:9 Widescreen":"3538CDBD13FA939B7BA5F0F82253401AFDD1E2CD"},"GHSP69":{"16:9 Widescreen":"409754E2EBB6F05DEE1AFC647E25B15D462638FF"},"GICD78":{"EFB Copy Fix":"3A94591A149AE88C150AB3320BBC909FE54BAEA5"},"GICE78":{"EFB Copy Fix":"5BF55685B8867A85EAA9C86571309B17BF7DED32"},"GICF78":{"EFB Copy Fix":"85AABAEB9A59C4F96D9330A3B884F6D757DA1683"},"GICH78":{"EFB Copy Fix":"3A94591A149AE88C150AB3320BBC909FE54BAEA5"},"GICJG9":{"EFB Copy Fix":"969134EA21A160EBDA91C0870266E7D1707FDC43"},"GICP78":{"EFB Copy Fix":"13B158CF41F5412BC637F50644193D43CC3DA49A"},"GIQE78":{"EFB Copy Fix":"E15AA1E30D26E5735D68AAADE436E7B7E4A33A35"},"GIQJ8P":{"EFB Copy Fix":"FFFCB76E98DDB06A7BBBC0AA73C869C87EB787D6"},"GIQX78":{"EFB Copy Fix":"485DA99FAB35646DAA2A138B0315361495ABE778"},"GIQY78":{"EFB Copy Fix":"485DA99FAB35646DAA2A138B0315361495ABE778"},"GK2":{"16:9 Widescreen [Vague Rant]":"6B4C59C2A2C5D71C7A38C8513A76E6467E307A26"},"GK2E52":{"60 FPS":"8F2397930A50C3C176188526EB9612578F9CBCE5"},"GK4":{"16:9 Widescreen":"4652969A4DA869FD28F2CAA3869A38F5C2AAFEB1"},"GKB":{"16:9 Widescreen Region-Free":"4652969A4DA869FD28F2CAA3869A38F5C2AAFEB1"},"GKBPAF":{"60Hz":"8E4E0ABA0E6D102A33206F34F4DEE63159B3CB4F"},"GKDP01":{"16:9 Widescreen":"DF4657937DE730107B636C73E959277EB963D210","60Hz":"6740D7B914CC897AF50798C0AB391965294FD1E1"},"GKRPB2":{"16:9 Widescreen":"69C01A4F91D991CF866237FED94A138308FC104F","60Hz":"40D06A5C7A3D873ABC73DEDF573738C2E3E37FF0"},"GKWJ18":{"16:9 Widescreen":"696570101FA040778EAD310377C484C846D87430"},"GKYE01":{"16:9 Widescreen":"6520EF1B7D88F00747B120A3A010458602ED989D"},"GKYJ01":{"16:9 Widescreen":"47B8EF7D02831AA5C375C698797BF2D5475FEFF2"},"GKYP01":{"16:9 Widescreen":"69413C75036D2975716066E6574461B981FF0124"},"GLEE08":{"Fix audio issues":"7355F358CAC6F418D37E4C23E64F7867D46E4FC9"},"GLEJ08":{"Fix audio issues":"12B24A6D7389A2AC5AB75FC0BF8493E7661F2A73"},"GLEP08":{"Fix audio issues":"81BD39F5527552DE89E3B59BA86298900F0A3168"},"GLSD64":{"Fix freeze in opening cutscene":"5E2A73717BD66EF647846DD64C33BC80AD9B5227"},"GLSE64":{"Fix freeze in opening cutscene":"1CE78E7954415A44DF693C0BB879AA5A4FF059A3"},"GLSF64":{"Fix freeze in opening cutscene":"009B0C4AD80A9C28C987934D254C2C4AACC9A07A"},"GLSP64":{"16:9 Widescreen":"8E7A544C10E7A5E2F0304A0D2586879627EF6586","60Hz":"B67144E87B54246137142992A3BC83DC13BE68A7","Fix freeze in opening cutscene":"3D0894616C9A7FA5ED91C1D2F461BF14DF47ECEC"},"GM4E01":{"16:9 Menu Backgrounds Add-On":"87D1A6A3E29ADA03B0D29C2F1841C18E62DB0A15","16:9 Widescreen v2":"78BE50F93E81A1972CA31ABC318949E366E82BD0"},"GM4P01":{"16:9 Menu Backgrounds Add-On":"87D1A6A3E29ADA03B0D29C2F1841C18E62DB0A15","16:9 Widescreen v2":"6D950B6EEFD6D304E1A424355B74A595D32BDF2F"},"GMBE8P":{"16:9 Widescreen":"D12DE9E3941BCE6EB50B8DA92140E09AFC0104C8","NA Stretched HUD":"0151A6683E1FBFD20096807C54A6952C4A4BAD40"},"GMBP8P":{"16:9 Widescreen":"8A688981F33A2C53882EF08FCF20B88AD43EF417"},"GMOP70":{"16:9 Widescreen":"CE4D298616BD42E4D8F8936B325CD1FD7F5B956B","60Hz":"0CD2CC787A5FF551901E41C85C8AAC02017ECB89"},"GMPE01":{"16:9 Widescreen":"4F98592DB3DEE3857469A8D8605FAF4BD6F7C76D","QOL - Allow Digital Presses for Map Screen":"E232B27564E9AA0C32DE163C9C056317A7B2B12E","QOL - Automatically Advance Text Boxes":"59607671BFC4717ACAF9807BB7EB0D9F982866D4","QOL - Disable Advance on Results":"CAEA37F3FEF89400513353EB85875F2A3AF4C03E","QOL - Faster Boot Time":"F75FBBD838C5B84FF687962FBA9195B217DE132C","QOL - Increased Board Speed":"19F264DE7F07EAC9433CA4B591BEBA1EC976C9F1","QOL - Increased Taunt Capabilities":"C70391D4961A0E820DE40141C89341369A9F021B","QOL - Instant Text Display":"F4E6913CF034E5778B9F9CA5FF448FF1B78B3333","QOL - Rumble Always Off":"93621947019532F02C25937FD3EEEE21A788CB07","QOL - Show Controller Port Number of Who Paused":"D7249AC3C949FEB860CF4350C5B7B79AC16569F1"},"GMPJ01":{"16:9 Widescreen":"A795811F2F0C92D9DCABDE97D9E39B47B1DBCEFD"},"GMPP01":{"16:9 Widescreen":"A795811F2F0C92D9DCABDE97D9E39B47B1DBCEFD"},"GMSE01":{"60FPS":"2805B1A0DD53EB64877D375D10F667700955720F","Widescreen":"BD718F961DBA5372B1D0257D454D535746C453A0"},"GMSJ01":{"16:9 Widescreen":"E8112A01040A06460E368F142C5D1FD0B4085D94"},"GMSJ01r0":{"60FPS":"9894B5B6B5215844D48411021FF8EECFE851D79D"},"GMSJ01r1":{"60FPS":"2DB69DB7A3753D543CD85CA4C77B4EF0AED7486E"},"GMSP01":{"60FPS":"D07009B710B3BBC6B12E54ED6A7969D58197EDAF","Widescreen":"0B7B89BF0868393076EF5F9DBD689DD0EDBCB84C"},"GNHE5d":{"Nop Hack":"89393A24E2336841AA4CD0AD3BE1C9A66B89E9EF"},"GOCE5D":{"16:9 Widescreen":"E4D800B90F16A15D9252EED1B2A23AEAF5CD230A"},"GOME01":{"16:9 Widescreen":"C33CBBF6F22195BF626104B4827200B3EE1CF30C"},"GOMP01":{"16:9 Widescreen":"874784CCD4AC3DC9C69654E471C18AD0F5E4D9AE"},"GONE69":{"Widescreen Hack Culling":"6412A67E1C26E146A9526AFC9F0EE517B214069D"},"GP4J18":{"16:9 Widescreen":"BF4D2D6AF66F285E81173B3B20D5369DB6FB351B"},"GP5E01":{"16:9 Widescreen":"5ED053787332DB5EEB8B963BA5DF197B58C6BB75","QOL - Automatically Advance Text Boxes":"41EF84663008668682BB75DC8B874E38669AE041","QOL - Disable Advance on Results":"6C36BEFE751131298BC8B39012892F6A6903CE60","QOL - Faster Boot Time":"8E952E6E12639AC20F6F9FDD150C12F0C294DF43","QOL - Increased Board Speed":"4BD72B8ED75ED8EFB3329C104BAD114FDDB8AD2C","QOL - Increased Capsule Throwing Speed":"C5BA3C9D386D09DD6CB9242A12A0D041BEB93160","QOL - Increased Taunt Capabilities":"C360EBDDCB0237076052C983BD9719411CA67CDE","QOL - Instant Text Display":"2CD4A06A3B9C18CC9EA2A2DEBD96E066CD1D700A","QOL - Show Controller Port Number of Who Paused":"5BA3648D18346EDC97B23475D4748C637E6095A2"},"GP5J01":{"16:9 Widescreen":"81E69B6BEE85E06805EC24E1CFEA9405BF459DF8"},"GP5P01":{"16:9 Widescreen":"FD4FCCB97C06F0173A30D4CC9A99422B2C0ABA9B"},"GP6E01":{"16:9 Widescreen":"B502AF0887792E9B140516D14BA75F99772C17BE","QOL - Auto Advance Text Boxes":"E645DF70CE0266D174D917A817A7FBEEE974A9F0","QOL - Disable Advance on Results":"798E566B621F6A32BF23C588E76EAC0776E2A8A3","QOL - Faster Boot Time":"B089A3C80D5DA86ABF6258F8BFB1DC78BB54A973","QOL - Increased Board Speed":"501C52ACF117950888A66DB2BEDBDFD7D9B20235","QOL - Increased Orb Throwing Speed":"845F439278DA8CBC225658FF4E2F82D707422BFB","QOL - Increased Taunt Capabilities":"0AED27341A06186AD92D5725C0B9693CA8F85B7B","QOL - Instant Text Display":"5AAC4E21927D4AEE7AC329CE793D4CDAEDDF7A3F","QOL - Show Controller Port Number of Who Paused":"DF5D2F068D76ABF2B1C294D413B9154B8C356929"},"GP6J01":{"16:9 Widescreen":"88F50F8298F82E3E5C161277BD4A985CE893A9D0"},"GP6P01":{"16:9 Widescreen":"DCCE59CA987624C753F0A86BEBA4287635901C93","Remove Black Bars":"138A86D43B5830BFE1926F58C0A4FBE2971BB02D"},"GP7E01":{"16:9 Widescreen":"87EBF72D90503EBC5CE9BC994795669E48A92A4A","QOL - Auto Advance Text Boxes":"82338AB5D82A7F398B4EB674CA4866DD1F4A1E65","QOL - Controller Options Always Acesssible":"D2F9CC0B9E52136E77187395FB7DA9B64B2C3B30","QOL - Disable Advance on Results":"A644551162A61DE7997B2A84F5BFDEFFD3FC2FB5","QOL - Faster Boot Time":"1939B5C575AE8FF6890BDDDD1B3FEA472D8CABA0","QOL - Increased Board Speed":"7E06A2EBCFECADB8074152B489984237A6FE9F21","QOL - Increased Orb Throwing Speed":"6671BEE71920D6B845AE1257AFE3AB3B95CD6FBD","QOL - Increased Taunt Capabilities":"9F60923F95FFE709CCD06966CD2DF743263934D1","QOL - Instant Text Display":"48FA6002E42DA9BB83ABC980A32C60CE3B21C4FC","QOL - Show Controller Port Number of Who Paused":"5E6F8C6C87D5944DB18A4E4F9A45F61EE87A4163"},"GP7J01":{"16:9 Widescreen":"A081F0729068D233E99DC00C64E36CA33C945640"},"GP7P01":{"16:9 Widescreen":"75D6CDA4EA301A71F7701A0487544E79136850B9","60Hz":"6D0D5B7CA58C38A3147F8ED981B5B21CC6C519CD"},"GPIE01":{"No Blur":"A25CDDB79991F090BD7F12840AD06F13BD5CCF95"},"GPIE01r0":{"16:9 Widescreen":"2B58166A66644F984E64077A6245C94C79B51063"},"GPIE01r1":{"16:9 Widescreen":"9A1545AB4E2B9216B95AFFA83420D723512DA8C8","60FPS":"3337C767EAA21D30C6CFEAE9985FFCABFC63E2E3","Disable Pikmin optimizations":"CBD15E9104929F0631713AE482475CB2B90E71F2"},"GPIP01":{"16:9 Widescreen":"5AE44D5B0E0D4C412B453B95CF5A41DBA4D685E6","60Hz":"AE59FF751E167632AE283F38CC4B96BDF7B9B81F","Turn off blur":"4ED79A548DBD7DBD35928A2F1138315FF103E260"},"GPNE08":{"16:9 Widescreen [Ralf, darkludx]":"ED3D9BB08C5F1D21BC8D5C73155C00E3C25C466F"},"GPNP08":{"16:9 Widescreen [Ralf]":"469B18FE8219031D355557B836EF02B625040A4F"},"GPOE8P":{"Make Save Copyable NTSC Port - 1.0\/1.1":"7A4B3E596BA4FDA8F3FCF984079B3F44CB2D6AB3","Make Save Copyable NTSC Port - PLUS\/1.2":"304DB311F2107E88EBCE058535B266F9263AA8E2","Save Validation Code v 1.0\/v1.1":"9F6259132453DCC0607EAB35DB182C76818F2F9F","Save Validation Code v1.2\/Plus":"17EB6CFB408EF27D44C053A1336C3B87B6A05018"},"GPOE8Pr0":{"16:9 Widescreen":"99986BB1D4ABE45C429D48B01BFB578FEA69C14F"},"GPOE8Pr1":{"16:9 Widescreen (Plus) [Ralf]":"5563671A6702785FFE106DAB7EF3F4FE6D9F705B"},"GPOP8P":{"16:9 Widescreen":"DF133C61C29D5CFA87A2A785C962C205F80E6D04","Game Save Valid":"40EC1590DB26C7C58D00E3065F1E404341EF0E73","Game Save copyable":"2F5FB98927DAFF141D1CCD1EACA8AF626397E284"},"GPVE01":{"16:9 Widescreen":"E5B51080CFD518FE584CF3B61A1099CD032479D6","60 FPS":"ADF5E3BBF0002A76949D0C11C51E58089BACFEED"},"GPVP01":{"16:9 Widescreen":"78F3C9A789827B063A6701987262276A66ABD82B"},"GQPE78":{"EFB Copy Fix":"880B114E9A308084CAB92C004A9EE067B371C310"},"GQPP78":{"EFB Copy Fix":"5D9A14954AE8D639C9B254F3BA73A70F284BBC8D"},"GQSDAF":{"16:9 Widescreen":"CA82C44B2E8FC5C184E3FF935BC89661B7DB55D6","60Hz":"952165FD78543EB6D2F5230F7570B0513773D332","No Blur":"DEB7DF358423F0EF30C9FD49F4F14590836D52A6"},"GQSEAF":{"16:9 Widescreen":"48AFE1D32843C6B5AD4337A49F15FD447DF2A752","Remove Blur":"EF448CF1FA6A15EB2661EA5338703C13D52EF65B"},"GQSFAF":{"16:9 Widescreen":"76C30E1737CC5C3B5DE32532B5F9A5CD50B690F8","No Blur":"986BAE5103CDE9286D2D34FD28FE2540D0759AAB"},"GR2E52":{"16:9 Widescreen":"5F55CE7DA7E1E3261CF2FF88F34CDA63BA511003"},"GR2J52":{"16:9 Widescreen":"D3C6FA690BED07BFA65C2E1FCDE2FFA5AA0EEEE4"},"GR2P52":{"16:9 Widescreen":"D7BCADD4E76B5E3F7D9D463F55A2777528E37E94"},"GRNE52":{"16:9 Widescreen":"483D74C47AD1012606D591A226AB3144C7FB201C"},"GRNJ52":{"16:9 Widescreen":"949281B7221B20680B7BC29E4754B73A326EBE85"},"GRNP52":{"16:9 Widescreen":"11051B094FE5A8B2E73060BFF786C1588E36979A"},"GROE5Z":{"16:9 Widescreen":"FADBAB5EC280CF8ED4C9536A33F4184BA210C9B0"},"GRYE41":{"Disable Culling to Fix Rise and Shrine Hang":"AF0A575EB6071EAC0D2EC3D2EA30A23EB05A4192"},"GSAE01r0":{"viWidth 704 Aspect Ratio Fix":"0D529180B9B28BB06E21EDBDBA61C17EEF0FFB7F"},"GSAE01r1":{"viWidth 704 Aspect Ratio Fix":"CB15ED22625690A5DBE4607FE30160125421461D"},"GSAP01":{"viWidth 704 Aspect Ratio Fix":"749EDA62B1B73354599929F1B50E992E111C5493"},"GSNE8P":{"16:9 Widescreen":"CFDF3FA5B48B347E49E8E521D7BE47FCC9A5CBF4","60Hz Aspect Ratio Fix":"D90F66C0D46D9BEF27C8E13BC8804DB1F382C8B1"},"GSNJ8P":{"60Hz Aspect Ratio Fix":"98DF9D67DE77F66A4B09F25C5621805DEB40865B"},"GSNP8P":{"60Hz Aspect Ratio Fix":"D90F66C0D46D9BEF27C8E13BC8804DB1F382C8B1"},"GSO":{"16:9 Widescreen Region Free":"0D60D0593F6DA28B1236381B22082506B9533F0E"},"GSPE69":{"16:9 Widescreen":"80E18A8963F4EFD3C03494C95934452FCB6E521B"},"GSPP69":{"16:9 Widescreen":"162E87E9F5511CD82216811055836C11B374C45B","60Hz":"E5DF2DF34D2F0BDEE4205C891576511EC572C5DC"},"GSWE64":{"16:9 Widescreen":"6480326CCA8FF64A4868EC6E9FDE9F38819A75A1","Disable Dithering":"85F65E4B627641862C1A6091D29A3BB0626B04C3"},"GTEE01":{"16:9 Widescreen":"6B57EBCB9CC0ACE2EDA0F13E3F1F55F3FABC3A26"},"GTEP01":{"16:9 Widescreen":"72785784363C46180AF8E388348FD77667E7D382"},"GTOJAF":{"Remove Blur":"53A2EDF113F2ED7E54A16AA0E73FDB2C44C79DE5"},"GTZE41":{"16:9 Widescreen":"ECCE87E0006475C73E7D936AA0150BD20166F9EF"},"GTZP41":{"16:9 Widescreen":"62D8F159B93167171E0860D96C4BB32A5BF5648B"},"GU2D78":{"EFB Copy Fix":"CFF4C3F932B08732627572EDA1A0CD2D9C71AE0C"},"GU2F78":{"EFB Copy Fix":"CFF4C3F932B08732627572EDA1A0CD2D9C71AE0C"},"GU3D78":{"EFB Copy Fix":"8A0E3114862ADFE421874211BD6F5220AA425BF5"},"GU3X78":{"EFB Copy Fix":"E3303FDAE7ECA17A72EDC440C32D94648A6453A0"},"GU4Y78":{"EFB Copy Fix":"D54767785E139A8BC8C4B75573FBD5A0B686D8E3"},"GUNE5Dr0":{"16:9 Widescreen":"C942EBCBE8A487C25E296EC1FAC2358DA1487DBD","60 FPS":"A441630EC5FF0EB74D2243A3092D22E69C6BEA02"},"GUNE5Dr1":{"16:9 Widescreen":"39BD84CBE2AFACFEDEC5E9020D1A8B0D36DD53F9","60 FPS":"05779F1A3D0C43305D52A95447D612CB424942C0"},"GUNP5D":{"16:9 Widescreen":"FE3BA1DAA1AF278A7839E27BF466B0BA0B390EC2","50 FPS":"3B20499A3F8D1D7CCA5B0015F2D80ECED25842A6"},"GV4E69":{"Fix 2D Rendering":"8679891FCAA250FCFF670B26E0CB9875900D17FD"},"GVJE08":{"16:9 Widescreen":"09EFDA75D876A675121C8344D8FDD09A70A1A846"},"GVJP08":{"16:9 Widescreen":"93938F4C6112C91549F2B52E6259170F7501EC07"},"GVPE69":{"Fix 2D Rendering":"3159CA79B0A890131763EA6CB163684BEE886E3F"},"GVSE8P":{"16:9 Widescreen":"4CE9C491160A4B631142EE9CE802C694163F1CA3"},"GWRE01":{"16:9 Widescreen":"AC42770B06662BE1DC863EC80F44B5E034C63664"},"GWRP01":{"16:9 Widescreen":"9DDDFAB28C4BD35CF64050E1EF684DC042B1AFFA"},"GWWE01":{"16:9 Widescreen":"98B2E75D8E1CED4A964D3129A5DC10E30538CAA6"},"GWWJ01":{"16:9 Widescreen":"2DAD9A5E2A140F02CCBA727C4BE7C74BAC156778"},"GWWP01":{"16:9 Widescreen":"2DAD9A5E2A140F02CCBA727C4BE7C74BAC156778"},"GWZE01":{"16:9 Widescreen":"9EFC191DE6D21A1681FE241AB2EE4A131259F317"},"GXCE01":{"16:9 Widescreen":"901A1E78A3A0124F55548507D3B3707125C64A8A"},"GXSE8P":{"16:9 Widescreen":"F50BBA440184FC77A4DFFAA58FF2BB888E2E072F","Aspect Ratio Fix":"4214C74DFB8A74FC3AA4A643E869BC4D9A38EDA0"},"GXSJ8P":{"Aspect Ratio Fix":"227909607984BBC3D36AAC7DB9DFE385F3363C49"},"GXXE01":{"16:9 Widescreen":"7CAFDAD7B5E7459CCC1BB209D439DDCBB5BE4E4E","60 FPS":"7E2829049003FC9DC8BC28044B33CAFF3768B54C","Allow Memory Card saving with Savestates":"64FAA15062F0D0C319F904BBDE9C4489A25D6369"},"GXXJ01":{"16:9 Widescreen":"AF704A8C6838FDF47054D9EDD9106B3F46781FA9","60 FPS":"4F5FD45B4EC1B33FE076FA06F8FF863C46A06BBA","Allow Memory Card saving with Savestates":"8293802260536FA2EF2EFDAB5266DE36BB88DE1B"},"GXXP01":{"16:9 Widescreen":"02716B2585BEE74F8FCDD97A78F6A0D3DC7F331B","60 FPS":"BF6171EA5CCF4B57AA596372589DDB5E6904DD7A","Allow Memory Card saving with Savestates":"3CAFBC4AE6FC5CE9F53377F86AB5BD8F1BC8861A"},"GYFPA4":{"60Hz":"402ED10AC842041AB4B39AE8F2D81B2D7AEF9CB4"},"GZ2E01":{"16:9 Widescreen v2":"E7521ED27BFC972628906CBE8D5403ED57253BB8","Hyrule Field Speed Hack":"FCB673D46E716C7F63C618B8D8BF83AEE0B501F0"},"GZ2J01":{"16:9 Widescreen":"F985A0A58D8E2B23E8A557FAFF8D367AFFEADD07","Hyrule Field Speed Hack":"FCB673D46E716C7F63C618B8D8BF83AEE0B501F0"},"GZ2P01":{"16:9 Widescreen v2":"CAC38B0D334B925A57AA3118D35932B8A185137E","Hyrule Field Speed Hack":"0F63623D4D984B7706F718F57C0ABDB6DBADCF8D"},"GZ3E70":{"16:9 Widescreen":"167F9E9A9B372CB3A01F308B46FF1403F8599C51","Deinterlacing Fix":"2E163CB5FE724EB49B31CF607E5B9AFDA031DDD5"},"GZDP70":{"16:9 Widescreen":"2E424E0BBAE6EF5D6A8FB4224EA1D0746BAC37D3"},"GZLE01":{"16:9 Widescreen":"9FA864EE7DD8CE7FF538EB4E0243F20137430BD8","Remove Distance Blur":"78EA34CEF8E01701491C280F155F1C12EC9BF1A2"},"GZLJ01":{"16:9 Widescreen":"E03B61989025CA33937E63A057E6E40A403811BC"},"GZLP01":{"16:9 Widescreen":"3EFFF6C52B5633A1729FAA6883D8579E77F7D057","Remove Distance Blur":"14BECBEA4DD281EBD0F7FE7DEE8020B6F2418ACD"},"GZPE70":{"16:9 Widescreen":"7142F2495507AC7136992128ED0FCA6BC72B61F4"},"GZPP70":{"16:9 Widescreen":"591FD6C9668C79FF1CE8558EBED2486A7A327F05","60Hz":"A59B84DB5486521AEE1C23B6C741ECD35216E5AD"},"GZSE70":{"16:9 Widescreen":"8E86EAC7EA4F4D2854DD9020CD795630CA64C4EE"},"HAF":{"BufferPatch":"181195871F63B89B1CF09AFA4420CF89B9883108"},"HAL":{"RSAPatch":"AD12237401ABE9FE4A545AADB5C5AE10355E2076"},"RGQE70":{"crashfix":"5F4CF8D4DA19A0FF74FF9EB925AC0236069BFD59"},"RLEEFS":{"Fix crash on main menu":"793642AC6862C2F3412035A9E3D7172CC4A1D5C7"},"RM8E01":{"16:9 Widescreen":"E61344EB1542A78D497981C307B6549985C7A05A","Extra - Disable Music":"25223F9EFAABF601CAC7810004F124E4056598B1","QOL - Faster Boot Time":"BCC4279F8B28636AD773F01540E78DF40EAD6087","QOL - Increased Board Speed":"B65AF1819966CD3435D88801E8C79704E3A52DB5","QOL - Increased Taunt Capabilities":"66495D7CB532FAE778AFC22CF45D17D0FFDE5310","QOL - Increased Text Display":"B4186DDC54F33F4D6A22188EF50CEB43FB205673","QOL - Invert IR Stick for GameCube Mod":"9D90A9C66AE8AD91B201B40C4145D1323B701A77","QOL - Remove Explanations":"330DD53AB993A99576564FEFD222D7BD211B878F"},"RM8J01":{"16:9 Widescreen":"A140BDCB8E1721CB6B4CD878E412113322258B57"},"RM8P01":{"16:9 Widescreen":"F664C32AFD3D785FC6E04D8990A3FA1C72A18C5C"},"RMHE08":{"Bloom OFF":"CCF233DA57B3E75221870DE502955114B0D4E7FA"},"RMHJ08":{"Bloom OFF":"29D3625B7ED577587E56AA07CB0EB8C47C97E823"},"RMHP08":{"Bloom OFF":"1720C1173D4698167080DBFC4232F21757C4DA08"},"RO2P7N":{"Hangfix":"EEE9C8DE4671C18DD7F81DD08D39B64C57600DEA"},"RPBE01":{"Fix black screen effects":"775ABECA6073E02C5C68CF4D644194D966A418F5"},"RPBJ01r0":{"Fix black screen effects":"0EAB5D8DE827894AFEF97C10ACB67378E6983323"},"RPBJ01r1":{"Fix black screen effects":"4905E08643E9D00136F7EAF51978CF2F54D10D07"},"RPBJ01r2":{"Fix black screen effects":"4905E08643E9D00136F7EAF51978CF2F54D10D07"},"RPBP01":{"Fix black screen effects":"82AEB60F9A9083F93060531A970FFAABE0833A40"},"RTH":{"Disable blur":"812EE46AC967BFCD239335B10A664D71A93E8175"},"RX4E4Z":{"Fix file reads (dcache bypass)":"9E4E0F1465A9A1E85349DBA3B1278AC215A97DBB"},"RX4PMT":{"Fix file reads (dcache bypass)":"EE85907C03F0295794821383B93F8D5B91D2697A"},"RZDE01r0":{"Hyrule Field Speed Hack":"15EAD073414C9903D6CAE5229DCE582BD17A9162"},"RZDE01r2":{"Hyrule Field Speed Hack":"27395CC8BC2C51201D566657D31A471A850482FB"},"RZDJ01":{"Hyrule Field Speed Hack":"B3F7473F8C911A32F1D616491C9E78EBBD7A6309"},"RZDK01":{"Hyrule Field Speed Hack":"A280C0114B800D7DC056ECFB5E482229DA0B1550"},"RZDP01":{"Hyrule Field Speed Hack":"2A83ADFB760F9498841ED0ED68B0C0438232472C"},"SAOE78":{"Fix crash on boot":"EA11FA4908FB20B61876ACD360EC7657A6D39FB2"},"SAOEVZ":{"Fix crash on boot":"AA55C214DE7545DE0E203CC39F06BF3D31451BE9"},"SGLEA4":{"Fix black screen":"258378187ACF475A55EFEAF8A703681252E014C3"},"SGLPA4":{"Fix black screen":"6F8CD59D897338CA90939149E1A62588620C6D88"}} \ No newline at end of file diff --git a/Data/Sys/GameSettings/G8ME01.ini b/Data/Sys/GameSettings/G8ME01.ini index b46363f7c4..c7abda1cc5 100644 --- a/Data/Sys/GameSettings/G8ME01.ini +++ b/Data/Sys/GameSettings/G8ME01.ini @@ -59,9 +59,12 @@ C202F310 00000003 $16:9 Aspect Ratio Fix - Centered HUD with letterboxing 04199598 4E800020 0441F6D8 3FE38E39 +$No Letterbox [Ralf] +0400E3D4 48000070 [Gecko_RetroAchievements_Verified] $16:9 Aspect Ratio Fix - Normal HUD $16:9 Aspect Ratio Fix - Centered HUD $16:9 Aspect Ratio Fix - Stretched HUD $16:9 Aspect Ratio Fix - Centered HUD with letterboxing +$No Letterbox diff --git a/Data/Sys/GameSettings/G8MP01.ini b/Data/Sys/GameSettings/G8MP01.ini index 0fd467d4ef..e410a2ff64 100644 --- a/Data/Sys/GameSettings/G8MP01.ini +++ b/Data/Sys/GameSettings/G8MP01.ini @@ -32,9 +32,12 @@ C202F3F8 00000003 $16:9 Aspect Ratio Fix - Centered HUD with letterboxing 0419B274 4E800020 0442C158 3FE38E39 +$No Letterbox [Ralf] +0400E598 48000070 [Gecko_RetroAchievements_Verified] $16:9 Aspect Ratio Fix - Normal HUD $16:9 Aspect Ratio Fix - Centered HUD $16:9 Aspect Ratio Fix - Stretched HUD $16:9 Aspect Ratio Fix - Centered HUD with letterboxing +$No Letterbox diff --git a/Data/Sys/GameSettings/GC6.ini b/Data/Sys/GameSettings/GC6.ini index 070fea6961..b1d4979fba 100644 --- a/Data/Sys/GameSettings/GC6.ini +++ b/Data/Sys/GameSettings/GC6.ini @@ -1,4 +1,4 @@ -# GC6E01, GC6P01 - Pokemon Colosseum +# GC6E01, GC6P01, GC6J01 - Pokemon Colosseum [Core] # Values set here will override the main Dolphin settings. @@ -13,5 +13,5 @@ SafeTextureCacheColorSamples = 0 # Many areas of the game have unused vertexes, especially with cutscenes # involving Shadow Pokémon, such as the purification cutscene. -# CPU Cull ends up greatly boosting performance for these cases. +# CPU Cull ends up greatly boosting performance for these cases. CPUCull = True diff --git a/Data/Sys/GameSettings/GC6E01.ini b/Data/Sys/GameSettings/GC6E01.ini index 6227fabc23..b42eac6ccc 100644 --- a/Data/Sys/GameSettings/GC6E01.ini +++ b/Data/Sys/GameSettings/GC6E01.ini @@ -36,9 +36,12 @@ $16:9 Widescreen 04005308 4809E62C 040A3930 4BF619D0 0447E724 3FAAAAAB +$60 FPS [Nerdzilla] +04005D98 38600000 [Patches_RetroAchievements_Verified] $Allow Memory Card saving with Savestates [AR_RetroAchievements_Verified] $16:9 Widescreen +$60 FPS [Nerdzilla] diff --git a/Data/Sys/GameSettings/GC6J01.ini b/Data/Sys/GameSettings/GC6J01.ini index b75e516310..7111923c0c 100644 --- a/Data/Sys/GameSettings/GC6J01.ini +++ b/Data/Sys/GameSettings/GC6J01.ini @@ -30,27 +30,11 @@ $Allow Memory Card saving with Savestates $Allow Memory Card saving with Savestates [ActionReplay] -$16:9 Widescreen -04261AC0 000034E0 -04261AC4 000034E4 -F6000001 80008180 -FFA01090 93E10024 -D2000000 00000003 -3DC03FAA 61CEAAAB -91C20000 C2620000 -EFB300B2 00000000 -E0000000 80008000 -F6000001 80008180 -FF601090 7C7F1B78 -D2000000 00000004 -3DC03FAA 61CEAAAB -91C20004 C2220004 -EF7100B2 39C00000 -60000000 00000000 -E0000000 80008000 +$60 FPS [Nerdzilla] +04005CFC 38600000 [Patches_RetroAchievements_Verified] $Allow Memory Card saving with Savestates [AR_RetroAchievements_Verified] -$16:9 Widescreen +$60 FPS [Nerdzilla] diff --git a/Data/Sys/GameSettings/GC6P01.ini b/Data/Sys/GameSettings/GC6P01.ini index 803d2e3f1d..1d22faa89a 100644 --- a/Data/Sys/GameSettings/GC6P01.ini +++ b/Data/Sys/GameSettings/GC6P01.ini @@ -36,9 +36,12 @@ $16:9 Widescreen [Ralf] 04005308 480A1C68 040A6F6C 4BF5E394 044CBCA4 3FAAAAAB +$60 FPS [Nerdzilla] +04005E90 38600000 [Patches_RetroAchievements_Verified] $Allow Memory Card saving with Savestates [AR_RetroAchievements_Verified] $16:9 Widescreen [Ralf] +$60 FPS [Nerdzilla] diff --git a/Data/Sys/GameSettings/GD7PB2.ini b/Data/Sys/GameSettings/GD7PB2.ini new file mode 100644 index 0000000000..556c2eefcf --- /dev/null +++ b/Data/Sys/GameSettings/GD7PB2.ini @@ -0,0 +1,17 @@ +# GD7PB2 - Dragon Ball Z: Budokai + +[Core] +# Values set here will override the main Dolphin settings. + +[OnFrame] +# Add memory patches to be applied every frame here. + +[ActionReplay] +# Add action replay cheats here. + +[Gecko] +$Deinterlacing Fix +0044E9A8 00000000 + +[Gecko_RetroAchievements_Verified] +$Deinterlacing Fix diff --git a/Data/Sys/GameSettings/GPVE01.ini b/Data/Sys/GameSettings/GPVE01.ini index dfc7956d88..30907737e5 100644 --- a/Data/Sys/GameSettings/GPVE01.ini +++ b/Data/Sys/GameSettings/GPVE01.ini @@ -101,23 +101,28 @@ C2434EBC 00000002 3DC03F36 91DC00CC 60000000 00000000 $60 FPS -04423540 3BC00001 # `setFramerate`: force 60 FPS -0451D804 40100000 # Map translation speed 1 -C2312B04 00000002 # Map translation speed 2 -3C003F80 9001FFFC -C121FFFC 00000000 +C242DF28 00000004 # Set to 30 FPS when cutscene starts +3C608042 6063352C +7C6903A6 806D9AEC +38800002 4E800421 +801F01F0 00000000 +C242E040 00000004 # Set to 60 FPS when cutscene ends +3C608042 6063352C +7C6903A6 806D9AEC +38800001 4E800421 +801F01F0 00000000 +0414B778 38800001 # `BaseGameSection::init`, force `setFramerate` argument to 60 FPS (2P mode doesn't start with a cutscene) +04513C5C 40100000 # Map translation speed 1 +04513C60 3F800000 # Map translation speed 2 0451D7B0 3C75C28F # Map scale speed -04520628 3F911111 # Cutscene delta time -0452062C 20000000 -C2010654 00000002 # Double cutscene sections' "frame duration" -5484083C 909F002C -60000000 00000000 -C242ED28 00000002 # Cutscene: objects spawn/despawn timing -3C003F00 9001FFFC -C001FFFC 00000000 -C24374DC 00000002 # Cutscene: trigger `startFadeblack` a frame earlier (fixes black screen in Day 1 cutscene) -806D9AEC C0230054 -FC020840 00000000 +04514128 3ECCCCCD # Final floor animation +04516140 3F000000 # Pay dept animation +04520078 3EB33333 # Ready-go animation +04410FA4 388000C8 # Win/Lose reason duration 1 +04410FAC 386000FA # Win/Lose reason duration 2 +04520224 3F000000 # Win/Lose animation +044106DC 38800078 # Win/Lose duration 1 +044106E4 38600168 # Win/Lose duration 2 [Gecko_RetroAchievements_Verified] $16:9 Widescreen diff --git a/Data/Sys/GameSettings/GUNE5Dr0.ini b/Data/Sys/GameSettings/GUNE5Dr0.ini index 2bd82b3e38..060d123894 100644 --- a/Data/Sys/GameSettings/GUNE5Dr0.ini +++ b/Data/Sys/GameSettings/GUNE5Dr0.ini @@ -8,7 +8,7 @@ $16:9 Widescreen 043438A8 3FAF0000 043486A8 406CB800 -$60Hz +$60 FPS 040B66F8 60000000 0402F0E4 38600002 0402F1CC 60000000 @@ -16,4 +16,4 @@ $60Hz [AR_RetroAchievements_Verified] $16:9 Widescreen -$60Hz +$60 FPS diff --git a/Data/Sys/GameSettings/GUNE5Dr1.ini b/Data/Sys/GameSettings/GUNE5Dr1.ini index 71efea513e..c426e94bb0 100644 --- a/Data/Sys/GameSettings/GUNE5Dr1.ini +++ b/Data/Sys/GameSettings/GUNE5Dr1.ini @@ -8,7 +8,7 @@ $16:9 Widescreen 04343EC8 3FAF0000 04348D30 406CB800 -$60Hz +$60 FPS 040B6F08 60000000 0402F130 38600002 0402F218 60000000 @@ -16,4 +16,4 @@ $60Hz [AR_RetroAchievements_Verified] $16:9 Widescreen -$60Hz +$60 FPS diff --git a/Data/Sys/GameSettings/GUNP5D.ini b/Data/Sys/GameSettings/GUNP5D.ini index 070b132b70..17eabc17ed 100644 --- a/Data/Sys/GameSettings/GUNP5D.ini +++ b/Data/Sys/GameSettings/GUNP5D.ini @@ -8,7 +8,7 @@ $16:9 Widescreen 04336CE8 3FABA000 0433B920 406C4F00 -$50Hz +$50 FPS 040B6408 60000000 0402F214 38600001 0402F300 60000000 @@ -16,4 +16,4 @@ $50Hz [AR_RetroAchievements_Verified] $16:9 Widescreen -$50Hz +$50 FPS diff --git a/Data/Sys/GameSettings/GXX.ini b/Data/Sys/GameSettings/GXX.ini index d65b341f53..f831d90915 100644 --- a/Data/Sys/GameSettings/GXX.ini +++ b/Data/Sys/GameSettings/GXX.ini @@ -1,4 +1,4 @@ -# GXXE01, GXXP01, GXXJ01 - POKeMON XD +# GXXE01, GXXP01, GXXJ01 - Pokemon XD: Gale of Darkness [Core] # Prevents crash when Greevil's henchman and Zook show up @@ -18,5 +18,5 @@ MMU = True SafeTextureCacheColorSamples = 0 # Many areas of the game have unused vertexes, especially with cutscenes # involving Shadow Pokémon, such as the purification cutscene. -# CPU Cull ends up greatly boosting performance for these cases. +# CPU Cull ends up greatly boosting performance for these cases. CPUCull = True diff --git a/Data/Sys/GameSettings/GXXE01.ini b/Data/Sys/GameSettings/GXXE01.ini index 7654007cee..b6b61dc8cf 100644 --- a/Data/Sys/GameSettings/GXXE01.ini +++ b/Data/Sys/GameSettings/GXXE01.ini @@ -12,17 +12,13 @@ $Allow Memory Card saving with Savestates [Patches_RetroAchievements_Verified] $Allow Memory Card saving with Savestates -[Gecko] +[ActionReplay] $16:9 Widescreen -042EB168 00001783 -042EB16C 000017EA -0400F614 60000000 -0405C984 60000000 -040875BC 60000000 -0405C8AC 60000000 -04086A34 60000000 -04086930 60000000 044ED860 3FC962F9 +$60 FPS +042AF894 38000000 +042AEBEC 38000000 -[Gecko_RetroAchievements_Verified] +[AR_RetroAchievements_Verified] $16:9 Widescreen +$60 FPS diff --git a/Data/Sys/GameSettings/GXXJ01.ini b/Data/Sys/GameSettings/GXXJ01.ini index 0bf33c8824..153fe6e104 100644 --- a/Data/Sys/GameSettings/GXXJ01.ini +++ b/Data/Sys/GameSettings/GXXJ01.ini @@ -11,3 +11,14 @@ $Allow Memory Card saving with Savestates [Patches_RetroAchievements_Verified] $Allow Memory Card saving with Savestates + +[ActionReplay] +$16:9 Widescreen +044CACE8 3FC962F9 +$60 FPS +042AA2BC 38000000 +042A9614 38000000 + +[AR_RetroAchievements_Verified] +$16:9 Widescreen +$60 FPS diff --git a/Data/Sys/GameSettings/GXXP01.ini b/Data/Sys/GameSettings/GXXP01.ini index c11b6ddb12..16d3a1d2e8 100644 --- a/Data/Sys/GameSettings/GXXP01.ini +++ b/Data/Sys/GameSettings/GXXP01.ini @@ -12,13 +12,17 @@ $Allow Memory Card saving with Savestates [Patches_RetroAchievements_Verified] $Allow Memory Card saving with Savestates -[Gecko] +[ActionReplay] $16:9 Widescreen 04005300 C3A2BFBC 04005304 EFBD00B2 04005308 480AF7A8 040B4AAC 4BF50854 0452A35C 3FAAAAAB +$60 FPS +042B17F8 38000000 +042B0B50 38000000 -[Gecko_RetroAchievements_Verified] +[AR_RetroAchievements_Verified] $16:9 Widescreen +$60 FPS diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index a891fec15d..ad69f03de6 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -89,8 +89,8 @@ public: static constexpr std::string_view BLUE = "#0B71C1"; static constexpr std::string_view APPROVED_LIST_FILENAME = "ApprovedInis.json"; static const inline Common::SHA1::Digest APPROVED_LIST_HASH = { - 0x6D, 0x91, 0xF5, 0xC1, 0xE2, 0x4C, 0xC3, 0x39, 0xF5, 0x7F, - 0xEC, 0xA9, 0x8C, 0xA9, 0xBD, 0x61, 0x28, 0x54, 0x11, 0x62}; + 0x29, 0x4C, 0xBD, 0x08, 0xF0, 0x5F, 0x47, 0x94, 0xC9, 0xB8, + 0x05, 0x2E, 0x5C, 0xD6, 0x14, 0x48, 0xFA, 0x07, 0xE8, 0x53}; struct LeaderboardEntry { From 288da38dc33fb977279289926d20177292f35b1c Mon Sep 17 00:00:00 2001 From: Tillmann Karras Date: Sun, 27 Jul 2025 14:14:59 +0100 Subject: [PATCH 17/32] docs/DSP: fix some bit pattern inconsistencies Thanks to @Oaisus who reported this. --- .../GameCube_DSP_Users_Manual.tex | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex b/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex index aec339c55e..4fb858fa94 100644 --- a/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex +++ b/docs/DSP/GameCube_DSP_Users_Manual/GameCube_DSP_Users_Manual.tex @@ -46,7 +46,7 @@ % Document front page material \title{\textbf{\Huge GameCube DSP User's Manual}} \author{Reverse-engineered and documented by Duddie \\ \href{mailto:duddie@walla.com}{duddie@walla.com}} -\date{\today\\v0.1.7} +\date{\today\\v0.1.8} % Title formatting commands \newcommand{\OpcodeTitle}[1]{\subsection{#1}\label{instruction:#1}} @@ -265,6 +265,7 @@ The purpose of this documentation is purely academic and it aims at understandin 0.1.5 & 2022.09.29 & vpelletier & Fixed \texttt{BLOOP} and \texttt{BLOOPI} suboperation order \\ \hline 0.1.6 & 2022.06.20 & xperia64 & Accelerator documentation updates, fix register typo in ANDC and ORC descriptions \\ \hline 0.1.7 & 2025.04.21 & Tilka & Fixed typos and complained about GFDL \\ \hline +0.1.8 & 2025.07.27 & Tilka & Fixed some bit pattern inconsistencies in the 'LDAX* opcodes \\ \hline \end{tabular} \end{table} @@ -4615,7 +4616,7 @@ When the main and extension opcodes write to the same register, the register is \begin{DSPOpcode}{'LDAXM} \begin{DSPOpcodeBytefield}{16} - \monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0011} + \monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{1011} \end{DSPOpcodeBytefield} \begin{DSPOpcodeFormat} @@ -4642,7 +4643,7 @@ When the main and extension opcodes write to the same register, the register is \begin{DSPOpcode}{'LDAXNM} \begin{DSPOpcodeBytefield}{16} - \monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0011} + \monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{1111} \end{DSPOpcodeBytefield} \begin{DSPOpcodeFormat} @@ -4670,7 +4671,7 @@ When the main and extension opcodes write to the same register, the register is \begin{DSPOpcode}{'LDAXN} \begin{DSPOpcodeBytefield}{16} - \monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0011} + \monobitbox{4}{xxxx} & \monobitbox{4}{xxxx} & \monobitbox{4}{11sr} & \monobitbox{4}{0111} \end{DSPOpcodeBytefield} \begin{DSPOpcodeFormat} @@ -5214,7 +5215,7 @@ Instruction & Opcode & Page \\ \hline \OpcodeRow{xxxx xxxx 11dr 10ss}{'LDM} \OpcodeRow{xxxx xxxx 11sr 1011}{'LDAXM} \OpcodeRow{xxxx xxxx 11dr 11ss}{'LDNM} -\OpcodeRow{xxxx xxxx 11dr 1111}{'LDAXNM} +\OpcodeRow{xxxx xxxx 11sr 1111}{'LDAXNM} \end{longtable} \end{center} From d62e21e7b6f47c4ae3fdf97ac0b111c9a584dd32 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 29 Jun 2025 23:24:08 +0200 Subject: [PATCH 18/32] PPCSymbolDB: Wait for locking to succeed 9395238 added a mutex to PPCSymbolDB, and made functions return with an "empty" result if called while the mutex is locked. This new behavior has the potential to affect not only less important call sites like the symbol printing mentioned in a comment, but also the JIT deciding if it should HLE a function. A later commit in this pull request decreases the amount of lock contention, reducing the performance impact of this commit. --- Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index e6077f6174..dcef06ed29 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -136,11 +136,8 @@ void PPCSymbolDB::DetermineNoteLayers() Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) { - // If m_functions is changing, there should be a PPCSymbolsChanged signal afterward. The signal - // will re-update persistent symbol displays by calling this function. Only one-off calls to this - // function, such as printing the symbol to console, should be affected by leaving early. - std::unique_lock lock(m_write_lock, std::try_to_lock); - if (!lock.owns_lock() || m_functions.empty()) + std::unique_lock lock(m_write_lock); + if (m_functions.empty()) return nullptr; auto it = m_functions.lower_bound(addr); @@ -164,8 +161,8 @@ Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) { - std::unique_lock lock(m_write_lock, std::try_to_lock); - if (!lock.owns_lock() || m_notes.empty()) + std::unique_lock lock(m_write_lock); + if (m_notes.empty()) return nullptr; auto itn = m_notes.lower_bound(addr); @@ -212,9 +209,7 @@ std::string_view PPCSymbolDB::GetDescription(u32 addr) void PPCSymbolDB::FillInCallers() { - std::unique_lock lock(m_write_lock, std::try_to_lock); - if (!lock.owns_lock()) - return; + std::unique_lock lock(m_write_lock); for (auto& p : m_functions) { @@ -314,10 +309,9 @@ bool PPCSymbolDB::FindMapFile(std::string* existing_map_file, std::string* writa return false; } +// Returns true if m_functions was changed. bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard) { - // Loads from emuthread and can crash with main thread accessing the map. Any other loads will be - // done on the main thread and should be safe. Returns true if m_functions was changed. std::lock_guard lock(m_write_lock); std::string existing_map_file; From 59d126d21582d2fd2df05dc33d5eeebaf097204a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 1 Jul 2025 20:16:36 +0200 Subject: [PATCH 19/32] PPCSymbolDB: Rename m_write_lock to m_mutex This mutex needs to be locked both when reading and writing, not just when writing. --- Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 8 ++++---- Source/Core/Core/PowerPC/PPCSymbolDB.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index dcef06ed29..e7bf9bd923 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -136,7 +136,7 @@ void PPCSymbolDB::DetermineNoteLayers() Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) { - std::unique_lock lock(m_write_lock); + std::lock_guard lock(m_mutex); if (m_functions.empty()) return nullptr; @@ -161,7 +161,7 @@ Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) { - std::unique_lock lock(m_write_lock); + std::lock_guard lock(m_mutex); if (m_notes.empty()) return nullptr; @@ -209,7 +209,7 @@ std::string_view PPCSymbolDB::GetDescription(u32 addr) void PPCSymbolDB::FillInCallers() { - std::unique_lock lock(m_write_lock); + std::lock_guard lock(m_mutex); for (auto& p : m_functions) { @@ -312,7 +312,7 @@ bool PPCSymbolDB::FindMapFile(std::string* existing_map_file, std::string* writa // Returns true if m_functions was changed. bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard) { - std::lock_guard lock(m_write_lock); + std::lock_guard lock(m_mutex); std::string existing_map_file; if (!PPCSymbolDB::FindMapFile(&existing_map_file, nullptr)) diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.h b/Source/Core/Core/PowerPC/PPCSymbolDB.h index 6e51392195..b182023a54 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.h +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.h @@ -51,5 +51,5 @@ public: static bool FindMapFile(std::string* existing_map_file, std::string* writable_map_file); private: - std::mutex m_write_lock; + std::mutex m_mutex; }; From 803e6b017bf8bb82ee65b48fa244eb1e4c89034d Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 30 Jun 2025 19:12:00 +0200 Subject: [PATCH 20/32] PPCSymbolDB: Reduce lock contention in LoadMap/LoadMapOnBoot By building the map in a local variable and then swapping it with the member variable, we avoid the need to hold a lock while building the map. --- Source/Core/Common/SymbolDB.cpp | 10 ++- Source/Core/Common/SymbolDB.h | 4 + Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 98 +++++++++++++++--------- Source/Core/Core/PowerPC/PPCSymbolDB.h | 11 ++- 4 files changed, 84 insertions(+), 39 deletions(-) diff --git a/Source/Core/Common/SymbolDB.cpp b/Source/Core/Common/SymbolDB.cpp index 88f281e497..ad438b8277 100644 --- a/Source/Core/Common/SymbolDB.cpp +++ b/Source/Core/Common/SymbolDB.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -49,6 +50,8 @@ bool SymbolDB::IsEmpty() const bool SymbolDB::Clear(const char* prefix) { + std::lock_guard lock(m_mutex); + // TODO: honor prefix m_map_name.clear(); if (IsEmpty()) @@ -61,9 +64,14 @@ bool SymbolDB::Clear(const char* prefix) } void SymbolDB::Index() +{ + Index(&m_functions); +} + +void SymbolDB::Index(XFuncMap* functions) { int i = 0; - for (auto& func : m_functions) + for (auto& func : *functions) { func.second.index = i++; } diff --git a/Source/Core/Common/SymbolDB.h b/Source/Core/Common/SymbolDB.h index 7763999453..69515532b1 100644 --- a/Source/Core/Common/SymbolDB.h +++ b/Source/Core/Common/SymbolDB.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -105,9 +106,12 @@ public: void Index(); protected: + static void Index(XFuncMap* functions); + XFuncMap m_functions; XNoteMap m_notes; XFuncPtrMap m_checksum_to_function; std::string m_map_name; + std::mutex m_mutex; }; } // namespace Common diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index e7bf9bd923..51fb3eaa62 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -56,8 +56,17 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd const std::string& name, const std::string& object_name, Common::Symbol::Type type) { - auto iter = m_functions.find(startAddr); - if (iter != m_functions.end()) + AddKnownSymbol(guard, startAddr, size, name, object_name, type, &m_functions, + &m_checksum_to_function); +} + +void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size, + const std::string& name, const std::string& object_name, + Common::Symbol::Type type, XFuncMap* functions, + XFuncPtrMap* checksum_to_function) +{ + auto iter = functions->find(startAddr); + if (iter != functions->end()) { // already got it, let's just update name, checksum & size to be sure. Common::Symbol* tempfunc = &iter->second; @@ -70,7 +79,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd else { // new symbol. run analyze. - auto& new_symbol = m_functions.emplace(startAddr, name).first->second; + auto& new_symbol = functions->emplace(startAddr, name).first->second; new_symbol.object_name = object_name; new_symbol.type = type; new_symbol.address = startAddr; @@ -85,7 +94,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd name, size, new_symbol.size); new_symbol.size = size; } - m_checksum_to_function[new_symbol.hash].insert(&new_symbol); + (*checksum_to_function)[new_symbol.hash].insert(&new_symbol); } else { @@ -96,9 +105,14 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name) { - auto iter = m_notes.find(start_addr); + AddKnownNote(start_addr, size, name, &m_notes); +} - if (iter != m_notes.end()) +void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name, XNoteMap* notes) +{ + auto iter = notes->find(start_addr); + + if (iter != notes->end()) { // Already got it, just update the name and size. Common::Note* tempfunc = &iter->second; @@ -112,22 +126,27 @@ void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name tf.address = start_addr; tf.size = size; - m_notes[start_addr] = tf; + (*notes)[start_addr] = tf; } } void PPCSymbolDB::DetermineNoteLayers() { - if (m_notes.empty()) + DetermineNoteLayers(&m_notes); +} + +void PPCSymbolDB::DetermineNoteLayers(XNoteMap* notes) +{ + if (notes->empty()) return; - for (auto& note : m_notes) + for (auto& note : *notes) note.second.layer = 0; - for (auto iter = m_notes.begin(); iter != m_notes.end(); ++iter) + for (auto iter = notes->begin(); iter != notes->end(); ++iter) { const u32 range = iter->second.address + iter->second.size; - auto search = m_notes.lower_bound(range); + auto search = notes->lower_bound(range); while (--search != iter) search->second.layer += 1; @@ -312,21 +331,19 @@ bool PPCSymbolDB::FindMapFile(std::string* existing_map_file, std::string* writa // Returns true if m_functions was changed. bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard) { - std::lock_guard lock(m_mutex); - std::string existing_map_file; if (!PPCSymbolDB::FindMapFile(&existing_map_file, nullptr)) return Clear(); - // If the map is already loaded (such as restarting the same game), skip reloading. - if (!IsEmpty() && existing_map_file == m_map_name) - return false; + { + std::lock_guard lock(m_mutex); + // If the map is already loaded (such as restarting the same game), skip reloading. + if (!IsEmpty() && existing_map_file == m_map_name) + return false; + } - // Load map into cleared m_functions. - bool changed = Clear(); - - if (!LoadMap(guard, existing_map_file)) - return changed; + if (!LoadMap(guard, std::move(existing_map_file))) + return Clear(); return true; } @@ -338,14 +355,11 @@ bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard) // function names and addresses that have a BLR before the start and at the end, but ignore any that // don't, and then tell you how many were good and how many it ignored. That way you either find out // it is all good and use it, find out it is partly good and use the good part, or find out that -// only -// a handful of functions lined up by coincidence and then you can clear the symbols. In the future -// I -// want to make it smarter, so it checks that there are no BLRs in the middle of the function -// (by checking the code length), and also make it cope with added functions in the middle or work -// based on the order of the functions and their approximate length. Currently that process has to -// be -// done manually and is very tedious. +// only a handful of functions lined up by coincidence and then you can clear the symbols. In the +// future I want to make it smarter, so it checks that there are no BLRs in the middle of the +// function (by checking the code length), and also make it cope with added functions in the middle +// or work based on the order of the functions and their approximate length. Currently that process +// has to be done manually and is very tedious. // The use case for separate handling of map files that aren't bad is that you usually want to also // load names that aren't functions(if included in the map file) without them being rejected as // invalid. @@ -356,12 +370,16 @@ bool PPCSymbolDB::LoadMapOnBoot(const Core::CPUThreadGuard& guard) // This one can load both leftover map files on game discs (like Zelda), and mapfiles // produced by SaveSymbolMap below. // bad=true means carefully load map files that might not be from exactly the right version -bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& filename, bool bad) +bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, std::string filename, bool bad) { File::IOFile f(filename, "r"); if (!f) return false; + XFuncMap new_functions; + XNoteMap new_notes; + XFuncPtrMap checksum_to_function; + // Two columns are used by Super Smash Bros. Brawl Korean map file // Three columns are commonly used // Four columns are used in American Mensa Academy map files and perhaps other games @@ -573,9 +591,14 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& ++good_count; if (section_name == ".note") - AddKnownNote(vaddress, size, name); + { + AddKnownNote(vaddress, size, name, &new_notes); + } else - AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type); + { + AddKnownSymbol(guard, vaddress, size, name_string, object_filename_string, type, + &new_functions, &checksum_to_function); + } } else { @@ -584,10 +607,15 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string& } } - m_map_name = filename; + Index(&new_functions); + DetermineNoteLayers(&new_notes); + + std::lock_guard lock(m_mutex); + std::swap(m_functions, new_functions); + std::swap(m_notes, new_notes); + std::swap(m_checksum_to_function, checksum_to_function); + std::swap(m_map_name, filename); - Index(); - DetermineNoteLayers(); NOTICE_LOG_FMT(SYMBOLS, "{} symbols loaded, {} symbols ignored.", good_count, bad_count); return true; } diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.h b/Source/Core/Core/PowerPC/PPCSymbolDB.h index b182023a54..079328147d 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.h +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.h @@ -3,7 +3,6 @@ #pragma once -#include #include #include @@ -40,7 +39,7 @@ public: void FillInCallers(); bool LoadMapOnBoot(const Core::CPUThreadGuard& guard); - bool LoadMap(const Core::CPUThreadGuard& guard, const std::string& filename, bool bad = false); + bool LoadMap(const Core::CPUThreadGuard& guard, std::string filename, bool bad = false); bool SaveSymbolMap(const std::string& filename) const; bool SaveCodeMap(const Core::CPUThreadGuard& guard, const std::string& filename) const; @@ -51,5 +50,11 @@ public: static bool FindMapFile(std::string* existing_map_file, std::string* writable_map_file); private: - std::mutex m_mutex; + static void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size, + const std::string& name, const std::string& object_name, + Common::Symbol::Type type, XFuncMap* functions, + XFuncPtrMap* checksum_to_function); + static void AddKnownNote(u32 start_addr, u32 size, const std::string& name, XNoteMap* notes); + + static void DetermineNoteLayers(XNoteMap* notes); }; From fef77a5f20d03f22315b2d77f7970c3fed895b13 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 30 Jun 2025 19:26:24 +0200 Subject: [PATCH 21/32] PPCSymbolDB: Add missing locking 9395238 added locking in some PPCSymbolDB functions that access member variables, but far from all. To ensure thread safety, this commit adds the missing locking. --- Source/Core/Common/SymbolDB.cpp | 11 +++++++++++ Source/Core/Common/SymbolDB.h | 2 +- Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 19 ++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/SymbolDB.cpp b/Source/Core/Common/SymbolDB.cpp index ad438b8277..0784256f0b 100644 --- a/Source/Core/Common/SymbolDB.cpp +++ b/Source/Core/Common/SymbolDB.cpp @@ -35,6 +35,7 @@ void Symbol::Rename(const std::string& symbol_name) void SymbolDB::List() { + std::lock_guard lock(m_mutex); for (const auto& func : m_functions) { DEBUG_LOG_FMT(OSHLE, "{} @ {:08x}: {} bytes (hash {:08x}) : {} calls", func.second.name, @@ -45,6 +46,7 @@ void SymbolDB::List() bool SymbolDB::IsEmpty() const { + std::lock_guard lock(m_mutex); return m_functions.empty() && m_notes.empty(); } @@ -65,6 +67,7 @@ bool SymbolDB::Clear(const char* prefix) void SymbolDB::Index() { + std::lock_guard lock(m_mutex); Index(&m_functions); } @@ -79,6 +82,8 @@ void SymbolDB::Index(XFuncMap* functions) Symbol* SymbolDB::GetSymbolFromName(std::string_view name) { + std::lock_guard lock(m_mutex); + for (auto& func : m_functions) { if (func.second.function_name == name) @@ -90,6 +95,7 @@ Symbol* SymbolDB::GetSymbolFromName(std::string_view name) std::vector SymbolDB::GetSymbolsFromName(std::string_view name) { + std::lock_guard lock(m_mutex); std::vector symbols; for (auto& func : m_functions) @@ -103,6 +109,8 @@ std::vector SymbolDB::GetSymbolsFromName(std::string_view name) Symbol* SymbolDB::GetSymbolFromHash(u32 hash) { + std::lock_guard lock(m_mutex); + auto iter = m_checksum_to_function.find(hash); if (iter == m_checksum_to_function.end()) return nullptr; @@ -112,6 +120,8 @@ Symbol* SymbolDB::GetSymbolFromHash(u32 hash) std::vector SymbolDB::GetSymbolsFromHash(u32 hash) { + std::lock_guard lock(m_mutex); + const auto iter = m_checksum_to_function.find(hash); if (iter == m_checksum_to_function.cend()) @@ -122,6 +132,7 @@ std::vector SymbolDB::GetSymbolsFromHash(u32 hash) void SymbolDB::AddCompleteSymbol(const Symbol& symbol) { + std::lock_guard lock(m_mutex); m_functions.emplace(symbol.address, symbol); } } // namespace Common diff --git a/Source/Core/Common/SymbolDB.h b/Source/Core/Common/SymbolDB.h index 69515532b1..b035b20c53 100644 --- a/Source/Core/Common/SymbolDB.h +++ b/Source/Core/Common/SymbolDB.h @@ -112,6 +112,6 @@ protected: XNoteMap m_notes; XFuncPtrMap m_checksum_to_function; std::string m_map_name; - std::mutex m_mutex; + std::recursive_mutex m_mutex; }; } // namespace Common diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index 51fb3eaa62..362d0ad6ee 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -37,6 +37,8 @@ PPCSymbolDB::~PPCSymbolDB() = default; // Adds the function to the list, unless it's already there Common::Symbol* PPCSymbolDB::AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) { + std::lock_guard lock(m_mutex); + // It's already in the list if (m_functions.contains(start_addr)) return nullptr; @@ -56,6 +58,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd const std::string& name, const std::string& object_name, Common::Symbol::Type type) { + std::lock_guard lock(m_mutex); AddKnownSymbol(guard, startAddr, size, name, object_name, type, &m_functions, &m_checksum_to_function); } @@ -105,6 +108,7 @@ void PPCSymbolDB::AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAdd void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name) { + std::lock_guard lock(m_mutex); AddKnownNote(start_addr, size, name, &m_notes); } @@ -132,6 +136,7 @@ void PPCSymbolDB::AddKnownNote(u32 start_addr, u32 size, const std::string& name void PPCSymbolDB::DetermineNoteLayers() { + std::lock_guard lock(m_mutex); DetermineNoteLayers(&m_notes); } @@ -211,11 +216,13 @@ Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) void PPCSymbolDB::DeleteFunction(u32 start_address) { + std::lock_guard lock(m_mutex); m_functions.erase(start_address); } void PPCSymbolDB::DeleteNote(u32 start_address) { + std::lock_guard lock(m_mutex); m_notes.erase(start_address); } @@ -261,6 +268,8 @@ void PPCSymbolDB::FillInCallers() void PPCSymbolDB::PrintCalls(u32 funcAddr) const { + std::lock_guard lock(m_mutex); + const auto iter = m_functions.find(funcAddr); if (iter == m_functions.end()) { @@ -282,6 +291,8 @@ void PPCSymbolDB::PrintCalls(u32 funcAddr) const void PPCSymbolDB::PrintCallers(u32 funcAddr) const { + std::lock_guard lock(m_mutex); + const auto iter = m_functions.find(funcAddr); if (iter == m_functions.end()) return; @@ -300,6 +311,8 @@ void PPCSymbolDB::PrintCallers(u32 funcAddr) const void PPCSymbolDB::LogFunctionCall(u32 addr) { + std::lock_guard lock(m_mutex); + auto iter = m_functions.find(addr); if (iter == m_functions.end()) return; @@ -627,6 +640,8 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const if (!file) return false; + std::lock_guard lock(m_mutex); + // Write .text section auto function_symbols = m_functions | @@ -677,7 +692,7 @@ bool PPCSymbolDB::SaveSymbolMap(const std::string& filename) const return true; } -// Save code map (won't work if Core is running) +// Save code map // // Notes: // - Dolphin doesn't load back code maps @@ -692,6 +707,8 @@ bool PPCSymbolDB::SaveCodeMap(const Core::CPUThreadGuard& guard, const std::stri // Write ".text" at the top f.WriteString(".text\n"); + std::lock_guard lock(m_mutex); + const auto& ppc_debug_interface = guard.GetSystem().GetPowerPC().GetDebugInterface(); u32 next_address = 0; From 9f32562e3633b62526918cbf0cfb920b96a263da Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 30 Jun 2025 21:31:06 +0200 Subject: [PATCH 22/32] PPCSymbolDB: Don't return non-const pointers For thread safety, we shouldn't return any pointers or references that can be used to mutate the state of the PPCSymbolDB. This should be the final part of making PPCSymbolDB thread safe unless I've missed something. --- Source/Core/Common/SymbolDB.cpp | 39 ++++++++-- Source/Core/Common/SymbolDB.h | 62 ++++++++++++--- Source/Core/Core/Debugger/DebugInterface.h | 3 +- .../Core/Core/Debugger/Debugger_SymbolMap.cpp | 6 +- .../Core/Core/Debugger/PPCDebugInterface.cpp | 2 +- Source/Core/Core/Debugger/PPCDebugInterface.h | 2 +- Source/Core/Core/Debugger/RSO.cpp | 5 +- .../Core/Core/PowerPC/JitCommon/JitCache.cpp | 2 +- Source/Core/Core/PowerPC/PPCAnalyst.cpp | 76 +++++++++---------- Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 8 +- Source/Core/Core/PowerPC/PPCSymbolDB.h | 8 +- .../PowerPC/SignatureDB/MEGASignatureDB.cpp | 8 +- .../Core/PowerPC/SignatureDB/SignatureDB.cpp | 19 +++-- .../DolphinQt/Debugger/CodeViewWidget.cpp | 16 ++-- Source/Core/DolphinQt/Debugger/CodeWidget.cpp | 24 +++--- 15 files changed, 172 insertions(+), 108 deletions(-) diff --git a/Source/Core/Common/SymbolDB.cpp b/Source/Core/Common/SymbolDB.cpp index 0784256f0b..2fa514ba86 100644 --- a/Source/Core/Common/SymbolDB.cpp +++ b/Source/Core/Common/SymbolDB.cpp @@ -80,7 +80,7 @@ void SymbolDB::Index(XFuncMap* functions) } } -Symbol* SymbolDB::GetSymbolFromName(std::string_view name) +const Symbol* SymbolDB::GetSymbolFromName(std::string_view name) const { std::lock_guard lock(m_mutex); @@ -93,10 +93,10 @@ Symbol* SymbolDB::GetSymbolFromName(std::string_view name) return nullptr; } -std::vector SymbolDB::GetSymbolsFromName(std::string_view name) +std::vector SymbolDB::GetSymbolsFromName(std::string_view name) const { std::lock_guard lock(m_mutex); - std::vector symbols; + std::vector symbols; for (auto& func : m_functions) { @@ -107,7 +107,7 @@ std::vector SymbolDB::GetSymbolsFromName(std::string_view name) return symbols; } -Symbol* SymbolDB::GetSymbolFromHash(u32 hash) +const Symbol* SymbolDB::GetSymbolFromHash(u32 hash) const { std::lock_guard lock(m_mutex); @@ -118,7 +118,7 @@ Symbol* SymbolDB::GetSymbolFromHash(u32 hash) return *iter->second.begin(); } -std::vector SymbolDB::GetSymbolsFromHash(u32 hash) +std::vector SymbolDB::GetSymbolsFromHash(u32 hash) const { std::lock_guard lock(m_mutex); @@ -133,6 +133,33 @@ std::vector SymbolDB::GetSymbolsFromHash(u32 hash) void SymbolDB::AddCompleteSymbol(const Symbol& symbol) { std::lock_guard lock(m_mutex); - m_functions.emplace(symbol.address, symbol); + m_functions[symbol.address] = symbol; } + +bool SymbolDB::RenameSymbol(const Symbol& symbol, const std::string& symbol_name) +{ + std::lock_guard lock(m_mutex); + + auto it = m_functions.find(symbol.address); + if (it == m_functions.end()) + return false; + + it->second.Rename(symbol_name); + return true; +} + +bool SymbolDB::RenameSymbol(const Symbol& symbol, const std::string& symbol_name, + const std::string& object_name) +{ + std::lock_guard lock(m_mutex); + + auto it = m_functions.find(symbol.address); + if (it == m_functions.end()) + return false; + + it->second.Rename(symbol_name); + it->second.object_name = object_name; + return true; +} + } // namespace Common diff --git a/Source/Core/Common/SymbolDB.h b/Source/Core/Common/SymbolDB.h index b035b20c53..8a1767d696 100644 --- a/Source/Core/Common/SymbolDB.h +++ b/Source/Core/Common/SymbolDB.h @@ -14,6 +14,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonTypes.h" namespace Core @@ -88,18 +89,59 @@ public: SymbolDB(); virtual ~SymbolDB(); - virtual Symbol* GetSymbolFromAddr(u32 addr) { return nullptr; } - virtual Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) { return nullptr; } + virtual const Symbol* GetSymbolFromAddr(u32 addr) const { return nullptr; } + virtual const Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) + { + return nullptr; + } void AddCompleteSymbol(const Symbol& symbol); + bool RenameSymbol(const Symbol& symbol, const std::string& symbol_name); + bool RenameSymbol(const Symbol& symbol, const std::string& symbol_name, + const std::string& object_name); - Symbol* GetSymbolFromName(std::string_view name); - std::vector GetSymbolsFromName(std::string_view name); - Symbol* GetSymbolFromHash(u32 hash); - std::vector GetSymbolsFromHash(u32 hash); + const Symbol* GetSymbolFromName(std::string_view name) const; + std::vector GetSymbolsFromName(std::string_view name) const; + const Symbol* GetSymbolFromHash(u32 hash) const; + std::vector GetSymbolsFromHash(u32 hash) const; + + template + void ForEachSymbol(F f) const + { + std::lock_guard lock(m_mutex); + for (const auto& [addr, symbol] : m_functions) + f(symbol); + } + + template + void ForEachSymbolWithMutation(F f) + { + std::lock_guard lock(m_mutex); + for (auto& [addr, symbol] : m_functions) + { + f(symbol); + ASSERT_MSG(COMMON, addr == symbol.address, "Symbol address was unexpectedly changed"); + } + } + + template + void ForEachNote(F f) const + { + std::lock_guard lock(m_mutex); + for (const auto& [addr, note] : m_notes) + f(note); + } + + template + void ForEachNoteWithMutation(F f) + { + std::lock_guard lock(m_mutex); + for (auto& [addr, note] : m_notes) + { + f(note); + ASSERT_MSG(COMMON, addr == note.address, "Note address was unexpectedly changed"); + } + } - const XFuncMap& Symbols() const { return m_functions; } - const XNoteMap& Notes() const { return m_notes; } - XFuncMap& AccessSymbols() { return m_functions; } bool IsEmpty() const; bool Clear(const char* prefix = ""); void List(); @@ -112,6 +154,6 @@ protected: XNoteMap m_notes; XFuncPtrMap m_checksum_to_function; std::string m_map_name; - std::recursive_mutex m_mutex; + mutable std::recursive_mutex m_mutex; }; } // namespace Common diff --git a/Source/Core/Core/Debugger/DebugInterface.h b/Source/Core/Core/Debugger/DebugInterface.h index cc07d06d3b..607cbe8bba 100644 --- a/Source/Core/Core/Debugger/DebugInterface.h +++ b/Source/Core/Core/Debugger/DebugInterface.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include "Common/CommonTypes.h" @@ -104,7 +103,7 @@ public: { return 0xFFFFFFFF; } - virtual std::string_view GetDescription(u32 /*address*/) const = 0; + virtual std::string GetDescription(u32 /*address*/) const = 0; virtual void Clear(const CPUThreadGuard& guard) = 0; }; } // namespace Core diff --git a/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp b/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp index d0abe78be0..7ee5868fce 100644 --- a/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp +++ b/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp @@ -80,7 +80,7 @@ bool GetCallstack(const Core::CPUThreadGuard& guard, std::vector }); WalkTheStack(guard, [&output, &ppc_symbol_db](u32 func_addr) { - std::string_view func_desc = ppc_symbol_db.GetDescription(func_addr); + std::string func_desc = ppc_symbol_db.GetDescription(func_addr); if (func_desc.empty() || func_desc == "Invalid") func_desc = "(unknown)"; @@ -107,14 +107,14 @@ void PrintCallstack(const Core::CPUThreadGuard& guard, Common::Log::LogType type GENERIC_LOG_FMT(type, level, " LR = 0 - this is bad"); } - if (const std::string_view lr_desc = ppc_symbol_db.GetDescription(LR(ppc_state)); + if (const std::string lr_desc = ppc_symbol_db.GetDescription(LR(ppc_state)); lr_desc != ppc_symbol_db.GetDescription(ppc_state.pc)) { GENERIC_LOG_FMT(type, level, " * {} [ LR = {:08x} ]", lr_desc, LR(ppc_state)); } WalkTheStack(guard, [type, level, &ppc_symbol_db](u32 func_addr) { - std::string_view func_desc = ppc_symbol_db.GetDescription(func_addr); + std::string func_desc = ppc_symbol_db.GetDescription(func_addr); if (func_desc.empty() || func_desc == "Invalid") func_desc = "(unknown)"; GENERIC_LOG_FMT(type, level, " * {} [ addr = {:08x} ]", func_desc, func_addr); diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp index 2c840d0ae5..dea3aa25a9 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp +++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp @@ -439,7 +439,7 @@ u32 PPCDebugInterface::GetColor(const Core::CPUThreadGuard* guard, u32 address) } // ============= -std::string_view PPCDebugInterface::GetDescription(u32 address) const +std::string PPCDebugInterface::GetDescription(u32 address) const { return m_ppc_symbol_db.GetDescription(address); } diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.h b/Source/Core/Core/Debugger/PPCDebugInterface.h index 797f2d5254..456b987ab9 100644 --- a/Source/Core/Core/Debugger/PPCDebugInterface.h +++ b/Source/Core/Core/Debugger/PPCDebugInterface.h @@ -102,7 +102,7 @@ public: void Step() override {} void RunTo(u32 address) override; u32 GetColor(const Core::CPUThreadGuard* guard, u32 address) const override; - std::string_view GetDescription(u32 address) const override; + std::string GetDescription(u32 address) const override; std::shared_ptr NetworkLogger(); diff --git a/Source/Core/Core/Debugger/RSO.cpp b/Source/Core/Core/Debugger/RSO.cpp index f793096967..7ee9481df3 100644 --- a/Source/Core/Core/Debugger/RSO.cpp +++ b/Source/Core/Core/Debugger/RSO.cpp @@ -381,7 +381,7 @@ void RSOView::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) c u32 address = GetExportAddress(rso_export); if (address != 0) { - Common::Symbol* symbol = symbol_db->AddFunction(guard, address); + const Common::Symbol* symbol = symbol_db->AddFunction(guard, address); if (!symbol) symbol = symbol_db->GetSymbolFromAddr(address); @@ -389,8 +389,7 @@ void RSOView::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) c if (symbol) { // Function symbol - symbol->Rename(export_name); - symbol->object_name = rso_name; + symbol_db->RenameSymbol(*symbol, export_name, rso_name); } else { diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index 06cbcbb9c4..c9050be730 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -187,7 +187,7 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, LinkBlock(block); } - Common::Symbol* symbol = nullptr; + const Common::Symbol* symbol = nullptr; if (Common::JitRegister::IsEnabled() && (symbol = m_jit.m_ppc_symbol_db.GetSymbolFromAddr(block.effectiveAddress)) != nullptr) { diff --git a/Source/Core/Core/PowerPC/PPCAnalyst.cpp b/Source/Core/Core/PowerPC/PPCAnalyst.cpp index c8ab26f884..49338af10b 100644 --- a/Source/Core/Core/PowerPC/PPCAnalyst.cpp +++ b/Source/Core/Core/PowerPC/PPCAnalyst.cpp @@ -346,10 +346,10 @@ static void FindFunctionsFromHandlers(const Core::CPUThreadGuard& guard, PPCSymb if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex, entry.first)) { // Check if this function is already mapped - Common::Symbol* f = func_db->AddFunction(guard, entry.first); + const Common::Symbol* f = func_db->AddFunction(guard, entry.first); if (!f) continue; - f->Rename(entry.second); + func_db->RenameSymbol(*f, entry.second); } } } @@ -359,8 +359,8 @@ static void FindFunctionsAfterReturnInstruction(const Core::CPUThreadGuard& guar { std::vector funcAddrs; - for (const auto& func : func_db->Symbols()) - funcAddrs.push_back(func.second.address + func.second.size); + func_db->ForEachSymbol( + [&](const Common::Symbol& symbol) { funcAddrs.push_back(symbol.address + symbol.size); }); auto& mmu = guard.GetSystem().GetMMU(); for (u32& location : funcAddrs) @@ -380,7 +380,7 @@ static void FindFunctionsAfterReturnInstruction(const Core::CPUThreadGuard& guar if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex, location)) { // check if this function is already mapped - Common::Symbol* f = func_db->AddFunction(guard, location); + const Common::Symbol* f = func_db->AddFunction(guard, location); if (!f) break; else @@ -407,45 +407,45 @@ void FindFunctions(const Core::CPUThreadGuard& guard, u32 startAddr, u32 endAddr int numLeafs = 0, numNice = 0, numUnNice = 0; int numTimer = 0, numRFI = 0, numStraightLeaf = 0; int leafSize = 0, niceSize = 0, unniceSize = 0; - for (auto& func : func_db->AccessSymbols()) - { - if (func.second.address == 4) + func_db->ForEachSymbolWithMutation([&](Common::Symbol& f) { + if (f.address == 4) { WARN_LOG_FMT(SYMBOLS, "Weird function"); - continue; - } - AnalyzeFunction2(func_db, &(func.second)); - Common::Symbol& f = func.second; - if (f.name.substr(0, 3) == "zzz") - { - if (f.flags & Common::FFLAG_LEAF) - f.Rename(f.name + "_leaf"); - if (f.flags & Common::FFLAG_STRAIGHT) - f.Rename(f.name + "_straight"); - } - if (f.flags & Common::FFLAG_LEAF) - { - numLeafs++; - leafSize += f.size; - } - else if (f.flags & Common::FFLAG_ONLYCALLSNICELEAFS) - { - numNice++; - niceSize += f.size; } else { - numUnNice++; - unniceSize += f.size; - } + AnalyzeFunction2(func_db, &f); + if (f.name.substr(0, 3) == "zzz") + { + if (f.flags & Common::FFLAG_LEAF) + f.Rename(f.name + "_leaf"); + if (f.flags & Common::FFLAG_STRAIGHT) + f.Rename(f.name + "_straight"); + } + if (f.flags & Common::FFLAG_LEAF) + { + numLeafs++; + leafSize += f.size; + } + else if (f.flags & Common::FFLAG_ONLYCALLSNICELEAFS) + { + numNice++; + niceSize += f.size; + } + else + { + numUnNice++; + unniceSize += f.size; + } - if (f.flags & Common::FFLAG_TIMERINSTRUCTIONS) - numTimer++; - if (f.flags & Common::FFLAG_RFI) - numRFI++; - if ((f.flags & Common::FFLAG_STRAIGHT) && (f.flags & Common::FFLAG_LEAF)) - numStraightLeaf++; - } + if (f.flags & Common::FFLAG_TIMERINSTRUCTIONS) + numTimer++; + if (f.flags & Common::FFLAG_RFI) + numRFI++; + if ((f.flags & Common::FFLAG_STRAIGHT) && (f.flags & Common::FFLAG_LEAF)) + numStraightLeaf++; + } + }); if (numLeafs == 0) leafSize = 0; else diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index 362d0ad6ee..02da35f94b 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -35,7 +35,7 @@ PPCSymbolDB::PPCSymbolDB() = default; PPCSymbolDB::~PPCSymbolDB() = default; // Adds the function to the list, unless it's already there -Common::Symbol* PPCSymbolDB::AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) +const Common::Symbol* PPCSymbolDB::AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) { std::lock_guard lock(m_mutex); @@ -158,7 +158,7 @@ void PPCSymbolDB::DetermineNoteLayers(XNoteMap* notes) } } -Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) +const Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) const { std::lock_guard lock(m_mutex); if (m_functions.empty()) @@ -183,7 +183,7 @@ Common::Symbol* PPCSymbolDB::GetSymbolFromAddr(u32 addr) return nullptr; } -Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) +const Common::Note* PPCSymbolDB::GetNoteFromAddr(u32 addr) const { std::lock_guard lock(m_mutex); if (m_notes.empty()) @@ -226,7 +226,7 @@ void PPCSymbolDB::DeleteNote(u32 start_address) m_notes.erase(start_address); } -std::string_view PPCSymbolDB::GetDescription(u32 addr) +std::string PPCSymbolDB::GetDescription(u32 addr) const { if (const Common::Symbol* const symbol = GetSymbolFromAddr(addr)) return symbol->name; diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.h b/Source/Core/Core/PowerPC/PPCSymbolDB.h index 079328147d..d106f68aa4 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.h +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.h @@ -21,20 +21,20 @@ public: PPCSymbolDB(); ~PPCSymbolDB() override; - Common::Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) override; + const Common::Symbol* AddFunction(const Core::CPUThreadGuard& guard, u32 start_addr) override; void AddKnownSymbol(const Core::CPUThreadGuard& guard, u32 startAddr, u32 size, const std::string& name, const std::string& object_name, Common::Symbol::Type type = Common::Symbol::Type::Function); void AddKnownNote(u32 start_addr, u32 size, const std::string& name); - Common::Symbol* GetSymbolFromAddr(u32 addr) override; + const Common::Symbol* GetSymbolFromAddr(u32 addr) const override; bool NoteExists() const { return !m_notes.empty(); } - Common::Note* GetNoteFromAddr(u32 addr); + const Common::Note* GetNoteFromAddr(u32 addr) const; void DetermineNoteLayers(); void DeleteFunction(u32 start_address); void DeleteNote(u32 start_address); - std::string_view GetDescription(u32 addr); + std::string GetDescription(u32 addr) const; void FillInCallers(); diff --git a/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp b/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp index a6aefc088b..6b0f573b27 100644 --- a/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp +++ b/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp @@ -160,20 +160,18 @@ bool MEGASignatureDB::Save(const std::string& file_path) const void MEGASignatureDB::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symbol_db) const { - for (auto& it : symbol_db->AccessSymbols()) - { - auto& symbol = it.second; + symbol_db->ForEachSymbol([&](const Common::Symbol& symbol) { for (const auto& sig : m_signatures) { if (Compare(guard, symbol.address, symbol.size, sig)) { - symbol.name = sig.name; + symbol_db->RenameSymbol(symbol, sig.name); INFO_LOG_FMT(SYMBOLS, "Found {} at {:08x} (size: {:08x})!", sig.name, symbol.address, symbol.size); break; } } - } + }); symbol_db->Index(); } diff --git a/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp b/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp index b9cec4a2be..c67000b9bb 100644 --- a/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp +++ b/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp @@ -128,7 +128,7 @@ void HashSignatureDB::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symb for (const auto& function : symbol_db->GetSymbolsFromHash(entry.first)) { // Found the function. Let's rename it according to the symbol file. - function->Rename(entry.second.name); + symbol_db->RenameSymbol(*function, entry.second.name); if (entry.second.size == static_cast(function->size)) { INFO_LOG_FMT(SYMBOLS, "Found {} at {:08x} (size: {:08x})!", entry.second.name, @@ -146,18 +146,17 @@ void HashSignatureDB::Apply(const Core::CPUThreadGuard& guard, PPCSymbolDB* symb void HashSignatureDB::Populate(const PPCSymbolDB* symbol_db, const std::string& filter) { - for (const auto& symbol : symbol_db->Symbols()) - { - if ((filter.empty() && (!symbol.second.name.empty()) && - symbol.second.name.substr(0, 3) != "zz_" && symbol.second.name.substr(0, 1) != ".") || - ((!filter.empty()) && symbol.second.name.substr(0, filter.size()) == filter)) + symbol_db->ForEachSymbol([&](const Common::Symbol& symbol) { + if ((filter.empty() && (!symbol.name.empty()) && symbol.name.substr(0, 3) != "zz_" && + symbol.name.substr(0, 1) != ".") || + ((!filter.empty()) && symbol.name.substr(0, filter.size()) == filter)) { DBFunc temp_dbfunc; - temp_dbfunc.name = symbol.second.name; - temp_dbfunc.size = symbol.second.size; - m_database[symbol.second.hash] = temp_dbfunc; + temp_dbfunc.name = symbol.name; + temp_dbfunc.size = symbol.size; + m_database[symbol.hash] = temp_dbfunc; } - } + }); } u32 HashSignatureDB::ComputeCodeChecksum(const Core::CPUThreadGuard& guard, u32 offsetStart, diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index a430be15a3..f94b640c41 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -335,7 +335,7 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) std::string ins = (split == std::string::npos ? disas : disas.substr(0, split)); std::string param = (split == std::string::npos ? "" : disas.substr(split + 1)); - const std::string_view desc = debug_interface.GetDescription(addr); + const std::string desc = debug_interface.GetDescription(addr); const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr); std::string note_string; @@ -950,7 +950,7 @@ void CodeViewWidget::OnFollowBranch() void CodeViewWidget::OnEditSymbol() { const u32 addr = GetContextAddress(); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); + const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); if (symbol == nullptr) { @@ -974,12 +974,14 @@ void CodeViewWidget::OnEditSymbol() } if (symbol->name != name) - symbol->Rename(name); + m_ppc_symbol_db.RenameSymbol(*symbol, name); if (symbol->size != size) { Core::CPUThreadGuard guard(m_system); - PPCAnalyst::ReanalyzeFunction(guard, symbol->address, *symbol, size); + Common::Symbol new_symbol = *symbol; + PPCAnalyst::ReanalyzeFunction(guard, symbol->address, new_symbol, size); + m_ppc_symbol_db.AddCompleteSymbol(new_symbol); } emit Host::GetInstance()->PPCSymbolsChanged(); @@ -988,7 +990,7 @@ void CodeViewWidget::OnEditSymbol() void CodeViewWidget::OnDeleteSymbol() { const u32 addr = GetContextAddress(); - Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); + const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); if (symbol == nullptr) return; @@ -1039,7 +1041,7 @@ void CodeViewWidget::OnSelectionChanged() void CodeViewWidget::OnEditNote() { const u32 context_address = GetContextAddress(); - Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); + const Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); if (note == nullptr) return; @@ -1071,7 +1073,7 @@ void CodeViewWidget::OnEditNote() void CodeViewWidget::OnDeleteNote() { const u32 context_address = GetContextAddress(); - Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); + const Common::Note* const note = m_ppc_symbol_db.GetNoteFromAddr(context_address); if (note == nullptr) return; diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp index e5aaa1c2cd..3e4eedf49d 100644 --- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp @@ -423,14 +423,13 @@ void CodeWidget::UpdateSymbols() m_symbols_list->selectedItems()[0]->text(); m_symbols_list->clear(); - for (const auto& symbol : m_ppc_symbol_db.Symbols()) - { - QString name = QString::fromStdString(symbol.second.name); + m_ppc_symbol_db.ForEachSymbol([&](const Common::Symbol& symbol) { + QString name = QString::fromStdString(symbol.name); // If the symbol has an object name, add it to the entry name. - if (!symbol.second.object_name.empty()) + if (!symbol.object_name.empty()) { - name += QString::fromStdString(fmt::format(" ({})", symbol.second.object_name)); + name += QString::fromStdString(fmt::format(" ({})", symbol.object_name)); } auto* item = new QListWidgetItem(name); @@ -438,14 +437,14 @@ void CodeWidget::UpdateSymbols() item->setSelected(true); // Disable non-function symbols as you can't do anything with them. - if (symbol.second.type != Common::Symbol::Type::Function) + if (symbol.type != Common::Symbol::Type::Function) item->setFlags(Qt::NoItemFlags); - item->setData(Qt::UserRole, symbol.second.address); + item->setData(Qt::UserRole, symbol.address); if (name.contains(m_symbol_filter, Qt::CaseInsensitive)) m_symbols_list->addItem(item); - } + }); m_symbols_list->sortItems(); } @@ -457,19 +456,18 @@ void CodeWidget::UpdateNotes() m_note_list->selectedItems()[0]->text(); m_note_list->clear(); - for (const auto& note : m_ppc_symbol_db.Notes()) - { - const QString name = QString::fromStdString(note.second.name); + m_ppc_symbol_db.ForEachNote([&](const Common::Note& note) { + const QString name = QString::fromStdString(note.name); auto* item = new QListWidgetItem(name); if (name == selection) item->setSelected(true); - item->setData(Qt::UserRole, note.second.address); + item->setData(Qt::UserRole, note.address); if (name.toUpper().indexOf(m_symbol_filter.toUpper()) != -1) m_note_list->addItem(item); - } + }); m_note_list->sortItems(); } From 4e0d41627ee25bdcb4512b25a892eae1fcb6f6fa Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 28 Jul 2025 18:30:46 -0500 Subject: [PATCH 23/32] HW/EXI_DeviceEthernet: Make interrupt state atomic. --- Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h index 71849a5222..21852f341c 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceEthernet.h @@ -269,7 +269,7 @@ private: u8 revision_id = 0; // 0xf0 u8 interrupt_mask = 0; - u8 interrupt = 0; + std::atomic interrupt = 0; u16 device_id = 0xD107; u8 acstart = 0x4E; u32 hash_challenge = 0; From 59e9267e3d33cb29f06d8a4f583e7633360ef259 Mon Sep 17 00:00:00 2001 From: CrossVR Date: Sun, 27 Jul 2025 05:43:17 +0900 Subject: [PATCH 24/32] DriverDetails: Disable depth_clamp_control on AMD official drivers --- Source/Core/VideoBackends/Vulkan/VulkanContext.cpp | 2 +- Source/Core/VideoCommon/DriverDetails.cpp | 3 +++ Source/Core/VideoCommon/DriverDetails.h | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index 60580aff40..93cd5a969b 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -664,7 +664,7 @@ bool VulkanContext::SelectDeviceExtensions(bool enable_surface) AddExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false); AddExtension(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME, false); - if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_D32F_CLEAR)) + if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_DEPTH_CLAMP_CONTROL)) { // Unrestricted depth range is one of the few extensions that changes the behavior // of Vulkan just by being enabled, so we rely on lazy evaluation to ensure it is diff --git a/Source/Core/VideoCommon/DriverDetails.cpp b/Source/Core/VideoCommon/DriverDetails.cpp index 037c5c0439..41f49f490a 100644 --- a/Source/Core/VideoCommon/DriverDetails.cpp +++ b/Source/Core/VideoCommon/DriverDetails.cpp @@ -158,6 +158,8 @@ constexpr BugInfo m_known_bugs[] = { BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING, -1.0, -1.0, true}, {API_VULKAN, OS_ANDROID, VENDOR_QUALCOMM, DRIVER_QUALCOMM, Family::UNKNOWN, BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY, -1.0, -1.0, true}, + {API_VULKAN, OS_WINDOWS, VENDOR_ATI, DRIVER_ATI, Family::UNKNOWN, + BUG_BROKEN_DEPTH_CLAMP_CONTROL, -1.0, -1.0, true}, }; static std::map m_bugs; @@ -297,6 +299,7 @@ static const char* to_string(Bug bug) case BUG_BROKEN_DISCARD_WITH_EARLY_Z: return "broken-discard-with-early-z"; case BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING: return "broken-dynamic-sampler-indexing"; case BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY: return "slow-optimal-image-to-buffer-copy"; + case BUG_BROKEN_DEPTH_CLAMP_CONTROL: return "broken-depth-clamp-control"; } return "Unknown"; } diff --git a/Source/Core/VideoCommon/DriverDetails.h b/Source/Core/VideoCommon/DriverDetails.h index 5d45da43a3..fbf36928e1 100644 --- a/Source/Core/VideoCommon/DriverDetails.h +++ b/Source/Core/VideoCommon/DriverDetails.h @@ -342,7 +342,14 @@ enum Bug // Affected devices: Adreno // Started Version: -1 // Ended Version: -1 - BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY + BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY, + + // BUG: Incorrect implementation of VK_EXT_depth_clamp_control causes incorrect depth values to + // be written to the depth buffer. + // Affected devices: AMD (Windows) + // Started Version: -1 + // Ended Version: -1 + BUG_BROKEN_DEPTH_CLAMP_CONTROL }; // Initializes our internal vendor, device family, and driver version From 0d87f835e2321992159d4b2f8eac923e2275c436 Mon Sep 17 00:00:00 2001 From: CrossVR Date: Sun, 27 Jul 2025 15:18:22 +0900 Subject: [PATCH 25/32] VKPipeline: Don't include depth clamp control struct when not supported This should not be needed --- Source/Core/VideoBackends/Vulkan/VKPipeline.cpp | 2 +- Source/Core/VideoCommon/DriverDetails.cpp | 4 ++-- Source/Core/VideoCommon/DriverDetails.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp b/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp index 1b8dd0eb9a..1e6eb68b7c 100644 --- a/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKPipeline.cpp @@ -380,7 +380,7 @@ std::unique_ptr VKPipeline::Create(const AbstractPipelineConfig& con static const VkRect2D scissor = {{0, 0}, {1, 1}}; static const VkPipelineViewportStateCreateInfo viewport_state = { VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, - &depth_clamp_state, + g_backend_info.bSupportsUnrestrictedDepthRange ? &depth_clamp_state : nullptr, 0, // VkPipelineViewportStateCreateFlags flags; 1, // uint32_t viewportCount &viewport, // const VkViewport* pViewports diff --git a/Source/Core/VideoCommon/DriverDetails.cpp b/Source/Core/VideoCommon/DriverDetails.cpp index 41f49f490a..22d6fff2d0 100644 --- a/Source/Core/VideoCommon/DriverDetails.cpp +++ b/Source/Core/VideoCommon/DriverDetails.cpp @@ -158,8 +158,8 @@ constexpr BugInfo m_known_bugs[] = { BUG_BROKEN_DYNAMIC_SAMPLER_INDEXING, -1.0, -1.0, true}, {API_VULKAN, OS_ANDROID, VENDOR_QUALCOMM, DRIVER_QUALCOMM, Family::UNKNOWN, BUG_SLOW_OPTIMAL_IMAGE_TO_BUFFER_COPY, -1.0, -1.0, true}, - {API_VULKAN, OS_WINDOWS, VENDOR_ATI, DRIVER_ATI, Family::UNKNOWN, - BUG_BROKEN_DEPTH_CLAMP_CONTROL, -1.0, -1.0, true}, + {API_VULKAN, OS_ALL, VENDOR_ATI, DRIVER_ATI, Family::UNKNOWN, BUG_BROKEN_DEPTH_CLAMP_CONTROL, + -1.0, -1.0, true}, }; static std::map m_bugs; diff --git a/Source/Core/VideoCommon/DriverDetails.h b/Source/Core/VideoCommon/DriverDetails.h index fbf36928e1..597b2848d4 100644 --- a/Source/Core/VideoCommon/DriverDetails.h +++ b/Source/Core/VideoCommon/DriverDetails.h @@ -346,7 +346,7 @@ enum Bug // BUG: Incorrect implementation of VK_EXT_depth_clamp_control causes incorrect depth values to // be written to the depth buffer. - // Affected devices: AMD (Windows) + // Affected devices: Official AMD (RADV is unaffected) // Started Version: -1 // Ended Version: -1 BUG_BROKEN_DEPTH_CLAMP_CONTROL From 6105d025ca4223a156a9fce70cd717639c219e57 Mon Sep 17 00:00:00 2001 From: AGuy27 <91433739+AGuy27@users.noreply.github.com> Date: Tue, 29 Jul 2025 20:05:23 -0400 Subject: [PATCH 26/32] Create SP4.ini --- Data/Sys/GameSettings/SP4.ini | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Data/Sys/GameSettings/SP4.ini diff --git a/Data/Sys/GameSettings/SP4.ini b/Data/Sys/GameSettings/SP4.ini new file mode 100644 index 0000000000..848437e359 --- /dev/null +++ b/Data/Sys/GameSettings/SP4.ini @@ -0,0 +1,5 @@ +# SP4PJW - Pétanque Master + +[Video_Hacks] +# Immediate XFB causes seizure-inducing flickering +ImmediateXFBEnable = False From cc71401e29c629551349d3986607c5e326289afe Mon Sep 17 00:00:00 2001 From: Tillmann Karras Date: Mon, 28 Jul 2025 01:49:54 +0100 Subject: [PATCH 27/32] VideoCommon: drop unused XF enumerators We handle all six registers (scale/offset * xyz) via the viewport name. Keeping around unused enumerators only for the z component is confusing. --- Source/Core/VideoCommon/XFMemory.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Core/VideoCommon/XFMemory.h b/Source/Core/VideoCommon/XFMemory.h index e0a2696317..4077e50859 100644 --- a/Source/Core/VideoCommon/XFMemory.h +++ b/Source/Core/VideoCommon/XFMemory.h @@ -223,8 +223,6 @@ enum XFMEM_SETMATRIXINDA = 0x1018, XFMEM_SETMATRIXINDB = 0x1019, XFMEM_SETVIEWPORT = 0x101a, - XFMEM_SETZSCALE = 0x101c, - XFMEM_SETZOFFSET = 0x101f, XFMEM_SETPROJECTION = 0x1020, // XFMEM_SETPROJECTIONB = 0x1021, // XFMEM_SETPROJECTIONC = 0x1022, From 6cb78682868fd28bfbe55ec1ee55eeb861addc40 Mon Sep 17 00:00:00 2001 From: Alex Harrison Date: Tue, 22 Jul 2025 20:37:01 -0600 Subject: [PATCH 28/32] CodeViewWidget: Improve performance by removing unnecessary FillInCallers computation during update --- Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp index f94b640c41..abbf59c4ce 100644 --- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp @@ -433,8 +433,6 @@ void CodeViewWidget::Update(const Core::CPUThreadGuard* guard) CalculateBranchIndentation(); - m_ppc_symbol_db.FillInCallers(); - repaint(); m_updating = false; } From 56f04b5406fb78859a62efff55cde361c2ac4451 Mon Sep 17 00:00:00 2001 From: Alex Harrison Date: Wed, 23 Jul 2025 12:58:52 -0600 Subject: [PATCH 29/32] PPCSymbolDB: Fill in callers after loading map files --- Source/Core/Core/PowerPC/PPCSymbolDB.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp index 02da35f94b..4bfe2abd3d 100644 --- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp +++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp @@ -622,6 +622,7 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, std::string filenam Index(&new_functions); DetermineNoteLayers(&new_notes); + FillInCallers(); std::lock_guard lock(m_mutex); std::swap(m_functions, new_functions); From f7e7b0f6b0e2b1aa895eaeec2ed37ada1bf4a21e Mon Sep 17 00:00:00 2001 From: TryTwo Date: Fri, 18 Jul 2025 22:14:08 -0700 Subject: [PATCH 30/32] MemoryWidget: Add symbols and Notes. Add option to hide them. Add box to search. Add ability to edit data symbols and notes in MemoryViewWidget. --- .../DolphinQt/Debugger/MemoryViewWidget.cpp | 138 ++++++++++++++++- .../DolphinQt/Debugger/MemoryViewWidget.h | 16 ++ .../Core/DolphinQt/Debugger/MemoryWidget.cpp | 141 +++++++++++++++++- Source/Core/DolphinQt/Debugger/MemoryWidget.h | 20 +++ 4 files changed, 309 insertions(+), 6 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 2cd85627b2..9977f3f38b 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -29,8 +29,10 @@ #include "Core/Core.h" #include "Core/HW/AddressSpace.h" #include "Core/PowerPC/BreakPoints.h" +#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" +#include "DolphinQt/Debugger/EditSymbolDialog.h" #include "DolphinQt/Host.h" #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" @@ -196,7 +198,7 @@ private: }; MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent) - : QWidget(parent), m_system(system) + : QWidget(parent), m_system(system), m_ppc_symbol_db(m_system.GetPPCSymbolDB()) { auto* layout = new QHBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); @@ -220,6 +222,8 @@ MemoryViewWidget::MemoryViewWidget(Core::System& system, QWidget* parent) this->setLayout(layout); connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont); + connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, + [this] { UpdateDispatcher(UpdateType::Symbols); }); connect(Host::GetInstance(), &Host::PPCBreakpointsChanged, this, &MemoryViewWidget::UpdateBreakpointTags); connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { @@ -347,6 +351,9 @@ void MemoryViewWidget::UpdateDispatcher(UpdateType type) if (Core::GetState(m_system) == Core::State::Paused) GetValues(); UpdateColumns(); + [[fallthrough]]; + case UpdateType::Symbols: + UpdateSymbols(); break; case UpdateType::Auto: // Values were captured on CPU thread while doing a callback. @@ -371,7 +378,7 @@ void MemoryViewWidget::CreateTable() // Span is the number of unique memory values covered in one row. const int data_span = m_bytes_per_row / GetTypeSize(m_type); m_data_columns = m_dual_view ? data_span * 2 : data_span; - const int total_columns = MISC_COLUMNS + m_data_columns; + const int total_columns = MISC_COLUMNS + m_data_columns + (m_show_symbols ? 1 : 0); const int rows = std::round((m_table->height() / static_cast(m_table->rowHeight(0))) - 0.25); @@ -440,6 +447,15 @@ void MemoryViewWidget::CreateTable() m_table->setItem(i, c + MISC_COLUMNS, item.clone()); } + + if (!m_show_symbols) + continue; + + // Symbols + auto* description_item = new QTableWidgetItem(QStringLiteral("-")); + description_item->setFlags(Qt::ItemIsEnabled); + + m_table->setItem(i, m_table->columnCount() - 1, description_item); } // Update column width @@ -500,6 +516,9 @@ void MemoryViewWidget::Update() item->setBackground(Qt::transparent); item->setData(USER_ROLE_VALID_ADDRESS, false); } + + if (m_show_symbols) + m_table->item(i, m_table->columnCount() - 1)->setData(USER_ROLE_CELL_ADDRESS, row_address); } UpdateBreakpointTags(); @@ -576,6 +595,34 @@ void MemoryViewWidget::UpdateColumns() } } +void MemoryViewWidget::UpdateSymbols() +{ + if (!m_show_symbols) + return; + + // Update symbols + for (int i = 0; i < m_table->rowCount(); i++) + { + auto* item = m_table->item(i, m_table->columnCount() - 1); + if (!item) + continue; + + const u32 address = item->data(USER_ROLE_CELL_ADDRESS).toUInt(); + const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(address); + + std::string desc; + if (note == nullptr) + desc = m_ppc_symbol_db.GetDescription(address); + else + desc = note->name; + + item->setText(QString::fromStdString(" " + desc)); + } + + if (m_show_symbols) + m_table->resizeColumnToContents(m_table->columnCount() - 1); +} + // Always runs on CPU thread from a callback. void MemoryViewWidget::UpdateOnFrameEnd() { @@ -1059,6 +1106,78 @@ void MemoryViewWidget::OnCopyHex(u32 addr) QStringLiteral("%1").arg(value, sizeof(u64) * 2, 16, QLatin1Char('0')).left(length * 2)); } +void MemoryViewWidget::ShowSymbols(bool enable) +{ + m_show_symbols = enable; + UpdateDispatcher(UpdateType::Full); +} + +void MemoryViewWidget::OnEditSymbol(EditSymbolType type, u32 addr) +{ + // Add Note and Add Region use these values. + std::string name = ""; + std::string object_name = ""; + u32 size = GetTypeSize(m_type); + u32 address = addr; + EditSymbolDialog::Type dialog_type = EditSymbolDialog::Type::Note; + + // Add and edit region are tied to the same context menu action. + if (type == EditSymbolType::EditRegion) + { + // If symbol doesn't exist, it's safe to add a new region. + const Common::Symbol* const symbol = m_ppc_symbol_db.GetSymbolFromAddr(addr); + dialog_type = EditSymbolDialog::Type::Symbol; + + if (symbol != nullptr) + { + // Leave the more specialized function editing to code widget. + if (symbol->type != Common::Symbol::Type::Data) + return; + + // Edit data region. + name = symbol->name; + object_name = symbol->object_name; + size = symbol->size; + address = symbol->address; + } + } + else if (type == EditSymbolType::EditNote) + { + const Common::Note* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + if (note == nullptr) + return; + + name = note->name; + size = note->size; + address = note->address; + } + + EditSymbolDialog dialog(this, address, &size, &name, dialog_type); + + if (dialog.exec() != QDialog::Accepted) + return; + + if (dialog.DeleteRequested()) + { + if (type == EditSymbolType::EditRegion) + m_ppc_symbol_db.DeleteFunction(address); + else + m_ppc_symbol_db.DeleteNote(address); + } + else if (type == EditSymbolType::EditRegion) + { + m_ppc_symbol_db.AddKnownSymbol(Core::CPUThreadGuard{m_system}, address, size, name, object_name, + Common::Symbol::Type::Data); + } + else + { + m_ppc_symbol_db.AddKnownNote(address, size, name); + m_ppc_symbol_db.DetermineNoteLayers(); + } + + emit Host::GetInstance()->PPCSymbolsChanged(); +} + void MemoryViewWidget::OnContextMenu(const QPoint& pos) { auto* item_selected = m_table->itemAt(pos); @@ -1089,6 +1208,21 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos) menu->addSeparator(); + auto* note_add_action = menu->addAction( + tr("Add Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::AddNote, addr); }); + auto* note_edit_action = menu->addAction( + tr("Edit Note"), this, [this, addr] { OnEditSymbol(EditSymbolType::EditNote, addr); }); + menu->addAction(tr("Add or edit region label"), this, + [this, addr] { OnEditSymbol(EditSymbolType::EditRegion, addr); }); + + auto* note = m_ppc_symbol_db.GetNoteFromAddr(addr); + note_edit_action->setEnabled(note != nullptr); + // A note cannot be added ontop of the starting address of another note. + if (note != nullptr && note->address == addr) + note_add_action->setEnabled(false); + + menu->addSeparator(); + menu->addAction(tr("Show in code"), this, [this, addr] { emit ShowCode(addr); }); menu->addSeparator(); diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h index 46643e39a4..102d6e0aed 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h @@ -24,6 +24,8 @@ class CPUThreadGuard; class System; } // namespace Core +class PPCSymbolDB; + // Captures direct editing of the table. class TableEditDelegate : public QStyledItemDelegate { @@ -76,14 +78,23 @@ public: Full, Addresses, Values, + Symbols, Auto, }; + enum class EditSymbolType + { + AddNote, + EditNote, + EditRegion, + }; + explicit MemoryViewWidget(Core::System& system, QWidget* parent = nullptr); void CreateTable(); void UpdateDispatcher(UpdateType type = UpdateType::Addresses); void Update(); + void UpdateSymbols(); void UpdateOnFrameEnd(); void GetValues(); void UpdateFont(const QFont& font); @@ -98,6 +109,7 @@ public: void SetBPType(BPType type); void SetAddress(u32 address); void SetFocus() const; + void ShowSymbols(bool enable); void SetBPLoggingEnabled(bool enabled); @@ -108,6 +120,7 @@ signals: void ActivateSearch(); private: + void OnEditSymbol(EditSymbolType type, u32 addr); void OnContextMenu(const QPoint& pos); void OnCopyAddress(u32 addr); void OnCopyHex(u32 addr); @@ -116,9 +129,11 @@ private: void UpdateColumns(); void ScrollbarActionTriggered(int action); void ScrollbarSliderReleased(); + std::optional ValueToString(const Core::CPUThreadGuard& guard, u32 address, Type type); Core::System& m_system; + PPCSymbolDB& m_ppc_symbol_db; MemoryViewTable* m_table; QScrollBar* m_scrollbar; @@ -137,6 +152,7 @@ private: int m_alignment = 16; int m_data_columns; bool m_dual_view = false; + bool m_show_symbols = true; std::mutex m_updating; QColor m_highlight_color = QColor(120, 255, 255, 100); diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp index 50ecbf5dd9..7e68df9c7a 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +33,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/HW/AddressSpace.h" +#include "Core/PowerPC/PPCSymbolDB.h" #include "Core/System.h" #include "DolphinQt/Debugger/MemoryViewWidget.h" #include "DolphinQt/Host.h" @@ -41,7 +43,7 @@ using Type = MemoryViewWidget::Type; MemoryWidget::MemoryWidget(Core::System& system, QWidget* parent) - : QDockWidget(parent), m_system(system) + : QDockWidget(parent), m_system(system), m_ppc_symbol_db(system.GetPPCSymbolDB()) { setWindowTitle(tr("Memory")); setObjectName(QStringLiteral("memory")); @@ -247,6 +249,25 @@ void MemoryWidget::CreateWidgets() bp_layout->addWidget(m_bp_log_check); bp_layout->setSpacing(1); + // Notes + m_labels_group = new QGroupBox(tr("Labels")); + auto* symbols_box = new QTabWidget; + m_note_list = new QListWidget; + m_data_list = new QListWidget; + m_symbols_list = new QListWidget; + symbols_box->addTab(m_note_list, tr("Notes")); + symbols_box->addTab(m_data_list, tr("Data")); + symbols_box->addTab(m_symbols_list, tr("Symbols")); + m_symbols_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + auto* labels_layout = new QVBoxLayout; + m_search_labels = new QLineEdit; + m_search_labels->setPlaceholderText(tr("Filter Label List")); + + m_labels_group->setLayout(labels_layout); + labels_layout->addWidget(symbols_box); + labels_layout->addWidget(m_search_labels); + // Sidebar auto* sidebar = new QWidget; auto* sidebar_layout = new QVBoxLayout; @@ -262,8 +283,9 @@ void MemoryWidget::CreateWidgets() &MemoryWidget::OnSetValueFromFile); menubar->addMenu(menu_import); + // View Menu auto* auto_update_action = - menu_views->addAction(tr("Auto update memory values"), this, [this](bool checked) { + menu_views->addAction(tr("&Auto update memory values"), this, [this](bool checked) { m_auto_update_enabled = checked; if (checked) RegisterAfterFrameEventCallback(); @@ -274,14 +296,23 @@ void MemoryWidget::CreateWidgets() auto_update_action->setChecked(true); auto* highlight_update_action = - menu_views->addAction(tr("Highlight recently changed values"), this, + menu_views->addAction(tr("&Highlight recently changed values"), this, [this](bool checked) { m_memory_view->ToggleHighlights(checked); }); highlight_update_action->setCheckable(true); highlight_update_action->setChecked(true); - menu_views->addAction(tr("Highlight color"), this, + menu_views->addAction(tr("Highlight &color"), this, [this] { m_memory_view->SetHighlightColor(); }); + auto* show_notes = + menu_views->addAction(tr("&Show symbols and notes"), this, [this](bool checked) { + m_labels_visible = checked; + m_memory_view->ShowSymbols(checked); + UpdateNotes(); + }); + show_notes->setCheckable(true); + show_notes->setChecked(true); + QMenu* menu_export = new QMenu(tr("&Export"), menubar); menu_export->addAction(tr("Dump &MRAM"), this, &MemoryWidget::OnDumpMRAM); menu_export->addAction(tr("Dump &ExRAM"), this, &MemoryWidget::OnDumpExRAM); @@ -306,6 +337,7 @@ void MemoryWidget::CreateWidgets() sidebar_layout->addWidget(address_space_group); sidebar_layout->addItem(new QSpacerItem(1, 10)); sidebar_layout->addWidget(bp_group); + sidebar_layout->addWidget(m_labels_group); sidebar_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); // Splitter @@ -327,6 +359,7 @@ void MemoryWidget::CreateWidgets() auto* widget = new QWidget; widget->setLayout(layout); setWidget(widget); + UpdateNotes(); } void MemoryWidget::ConnectWidgets() @@ -359,6 +392,12 @@ void MemoryWidget::ConnectWidgets() connect(m_base_check, &QCheckBox::toggled, this, &MemoryWidget::ValidateAndPreviewInputValue); connect(m_bp_log_check, &QCheckBox::toggled, this, &MemoryWidget::OnBPLogChanged); + + for (auto* list : {m_symbols_list, m_data_list, m_note_list}) + connect(list, &QListWidget::itemClicked, this, &MemoryWidget::OnSelectLabel); + + connect(Host::GetInstance(), &Host::PPCSymbolsChanged, this, &MemoryWidget::RefreshLabelBox); + connect(m_search_labels, &QLineEdit::textChanged, this, &MemoryWidget::RefreshLabelBox); connect(m_memory_view, &MemoryViewWidget::ShowCode, this, &MemoryWidget::ShowCode); connect(m_memory_view, &MemoryViewWidget::RequestWatch, this, &MemoryWidget::RequestWatch); connect(m_memory_view, &MemoryViewWidget::ActivateSearch, this, @@ -795,6 +834,100 @@ void MemoryWidget::OnSetValueFromFile() Update(); } +void MemoryWidget::RefreshLabelBox() +{ + if (!m_labels_visible || (m_ppc_symbol_db.Notes().empty() && m_ppc_symbol_db.IsEmpty())) + { + m_labels_group->hide(); + return; + } + + m_labels_group->show(); + + UpdateSymbols(); + UpdateNotes(); +} + +void MemoryWidget::OnSelectLabel() +{ + QList items; + if (m_note_list->isVisible()) + items = m_note_list->selectedItems(); + else if (m_symbols_list->isVisible()) + items = m_symbols_list->selectedItems(); + else if (m_data_list->isVisible()) + items = m_data_list->selectedItems(); + + if (items.isEmpty()) + return; + + const u32 address = items[0]->data(Qt::UserRole).toUInt(); + + SetAddress(address); +} + +void MemoryWidget::UpdateSymbols() +{ + const QString selection = m_symbols_list->selectedItems().isEmpty() ? + QString{} : + m_symbols_list->selectedItems()[0]->text(); + m_symbols_list->clear(); + m_data_list->clear(); + + for (const auto& symbol : m_ppc_symbol_db.Symbols()) + { + QString name = QString::fromStdString(symbol.second.name); + + // If the symbol has an object name, add it to the entry name. + if (!symbol.second.object_name.empty()) + { + name += QString::fromStdString(fmt::format(" ({})", symbol.second.object_name)); + } + + auto* item = new QListWidgetItem(name); + if (name == selection) + item->setSelected(true); + + item->setData(Qt::UserRole, symbol.second.address); + + if (!name.contains(m_search_labels->text(), Qt::CaseInsensitive)) + continue; + + if (symbol.second.type != Common::Symbol::Type::Function) + m_data_list->addItem(item); + else + m_symbols_list->addItem(item); + } + + m_symbols_list->sortItems(); +} + +void MemoryWidget::UpdateNotes() +{ + // Save selection to re-apply. + const QString selection = m_note_list->selectedItems().isEmpty() ? + QStringLiteral("") : + m_note_list->selectedItems()[0]->text(); + m_note_list->clear(); + + for (const auto& note : m_ppc_symbol_db.Notes()) + { + const QString name = QString::fromStdString(note.second.name); + + auto* item = new QListWidgetItem(name); + if (name == selection) + item->setSelected(true); + + item->setData(Qt::UserRole, note.second.address); + + // Filter notes based on the search text. + if (name.contains(m_search_labels->text(), Qt::CaseInsensitive)) + m_note_list->addItem(item); + } + + m_note_list->sortItems(); +} + static void DumpArray(const std::string& filename, const u8* data, size_t length) { if (!data) diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.h b/Source/Core/DolphinQt/Debugger/MemoryWidget.h index 6aa1f01aa5..e1b0118844 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.h @@ -14,8 +14,11 @@ class MemoryViewWidget; class QCheckBox; class QComboBox; +class QGroupBox; +class QHideEvent; class QLabel; class QLineEdit; +class QListWidget; class QPushButton; class QRadioButton; class QShowEvent; @@ -27,6 +30,8 @@ class System; class CPUThreadGuard; } // namespace Core +class PPCSymbolDB; + class MemoryWidget : public QDockWidget { Q_OBJECT @@ -66,6 +71,11 @@ private: void OnSetValue(); void OnSetValueFromFile(); + void OnSelectLabel(); + void RefreshLabelBox(); + void UpdateSymbols(); + void UpdateNotes(); + void OnDumpMRAM(); void OnDumpExRAM(); void OnDumpARAM(); @@ -85,6 +95,7 @@ private: void ActivateSearchAddress(); Core::System& m_system; + PPCSymbolDB& m_ppc_symbol_db; MemoryViewWidget* m_memory_view; QSplitter* m_splitter; @@ -115,6 +126,15 @@ private: QRadioButton* m_bp_read_only; QRadioButton* m_bp_write_only; QCheckBox* m_bp_log_check; + + QGroupBox* m_labels_group; + QLineEdit* m_search_labels; + QListWidget* m_symbols_list; + QListWidget* m_data_list; + QListWidget* m_note_list; + QString m_note_filter; + bool m_labels_visible = true; + Common::EventHook m_vi_end_field_event; bool m_auto_update_enabled = true; From c880210ec1d9aa832f56d1d4f106ff939bd6f568 Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Fri, 1 Aug 2025 15:31:38 -0700 Subject: [PATCH 31/32] MemoryWidget: Fix build breakage from conflicting PRs PRs https://github.com/dolphin-emu/dolphin/pull/13786 and https://github.com/dolphin-emu/dolphin/pull/13797 had incompatible changes that resulted in the build being broken when they were both merged. --- .../Core/DolphinQt/Debugger/MemoryWidget.cpp | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp index 7e68df9c7a..c4bf8f26f4 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryWidget.cpp @@ -836,7 +836,7 @@ void MemoryWidget::OnSetValueFromFile() void MemoryWidget::RefreshLabelBox() { - if (!m_labels_visible || (m_ppc_symbol_db.Notes().empty() && m_ppc_symbol_db.IsEmpty())) + if (!m_labels_visible || m_ppc_symbol_db.IsEmpty()) { m_labels_group->hide(); return; @@ -874,30 +874,29 @@ void MemoryWidget::UpdateSymbols() m_symbols_list->clear(); m_data_list->clear(); - for (const auto& symbol : m_ppc_symbol_db.Symbols()) - { - QString name = QString::fromStdString(symbol.second.name); + m_ppc_symbol_db.ForEachSymbol([&](const Common::Symbol& symbol) { + QString name = QString::fromStdString(symbol.name); // If the symbol has an object name, add it to the entry name. - if (!symbol.second.object_name.empty()) + if (!symbol.object_name.empty()) { - name += QString::fromStdString(fmt::format(" ({})", symbol.second.object_name)); + name += QString::fromStdString(fmt::format(" ({})", symbol.object_name)); } auto* item = new QListWidgetItem(name); if (name == selection) item->setSelected(true); - item->setData(Qt::UserRole, symbol.second.address); + item->setData(Qt::UserRole, symbol.address); if (!name.contains(m_search_labels->text(), Qt::CaseInsensitive)) - continue; + return; - if (symbol.second.type != Common::Symbol::Type::Function) + if (symbol.type != Common::Symbol::Type::Function) m_data_list->addItem(item); else m_symbols_list->addItem(item); - } + }); m_symbols_list->sortItems(); } @@ -910,20 +909,19 @@ void MemoryWidget::UpdateNotes() m_note_list->selectedItems()[0]->text(); m_note_list->clear(); - for (const auto& note : m_ppc_symbol_db.Notes()) - { - const QString name = QString::fromStdString(note.second.name); + m_ppc_symbol_db.ForEachNote([&](const Common::Note& note) { + const QString name = QString::fromStdString(note.name); auto* item = new QListWidgetItem(name); if (name == selection) item->setSelected(true); - item->setData(Qt::UserRole, note.second.address); + item->setData(Qt::UserRole, note.address); // Filter notes based on the search text. if (name.contains(m_search_labels->text(), Qt::CaseInsensitive)) m_note_list->addItem(item); - } + }); m_note_list->sortItems(); } From 38accd7fc3b77e301218296ce4523dd9f6e9d809 Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Fri, 1 Aug 2025 15:43:56 -0700 Subject: [PATCH 32/32] GameTracker: Fix games not being displayed --- Source/Core/DolphinQt/GameList/GameTracker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Core/DolphinQt/GameList/GameTracker.cpp b/Source/Core/DolphinQt/GameList/GameTracker.cpp index 8f6f76e39a..5b15bfc53c 100644 --- a/Source/Core/DolphinQt/GameList/GameTracker.cpp +++ b/Source/Core/DolphinQt/GameList/GameTracker.cpp @@ -338,7 +338,8 @@ QSet GameTracker::FindMissingFiles(const QString& dir) while (it->hasNext()) { QString path = QFileInfo(it->next()).canonicalFilePath(); - m_tracked_files.remove(path); + if (m_tracked_files.contains(path)) + missing_files.remove(path); } return missing_files;