From cf8ac5c09c8719db3c88b34b6d7ffc4084800f75 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Thu, 15 Sep 2016 02:38:48 +0000 Subject: [PATCH 01/10] GeckoCode: Don't run PPC code in a CoreTiming callback Executing PPC code inside an external events callback is a bad idea. CoreTiming::Advance does not support recursion properly which will cause timing glitches. The interpreter has a slice length hack that counters this but without it this would cause a 20000 cycles time skip. It isn't clear what this was supposed to accomplish that just changing the current PC would not. Changing the PC works fine. --- Source/Core/Core/GeckoCode.cpp | 36 ++++++++++++---------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 6983eb6224..b4d896a3f2 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -156,34 +156,24 @@ static bool InstallCodeHandler() void RunCodeHandler() { - if (SConfig::GetInstance().bEnableCheats && active_codes.size() > 0) + if (!SConfig::GetInstance().bEnableCheats || active_codes.empty()) + return; + + if (!code_handler_installed || PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS) - 0xd01f1bad > 5) { - if (!code_handler_installed || PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS) - 0xd01f1bad > 5) - code_handler_installed = InstallCodeHandler(); + code_handler_installed = InstallCodeHandler(); + // A warning was already issued for the install failing if (!code_handler_installed) - { - // A warning was already issued. return; - } + } - if (PC == LR) - { - u32 oldLR = LR; - PowerPC::CoreMode oldMode = PowerPC::GetMode(); - - PC = INSTALLER_BASE_ADDRESS + 0xA8; - LR = 0; - - // Execute the code handler in interpreter mode to track when it exits - PowerPC::SetMode(PowerPC::MODE_INTERPRETER); - - while (PC != 0) - PowerPC::SingleStep(); - - PowerPC::SetMode(oldMode); - PC = LR = oldLR; - } + // If the last block that just executed ended with a BLR instruction then we can intercept it and + // redirect control into the Gecko Code Handler. The Code Handler will automatically BLR back to + // the original return address (which will still be in the link register) at the end. + if (PC == LR) + { + PC = NPC = INSTALLER_BASE_ADDRESS + 0xA8; } } From e91c0222b4a223a8917c70a444aba0c787efb7f9 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Fri, 23 Sep 2016 13:20:28 +0000 Subject: [PATCH 02/10] GeckoCode: Cleanup The active codes vector cannot safely be used outside the mutex, move the lock out into RunCodeHandler. s_code_handler_installed was also racing against SetActiveCodes since it's being written both inside and outside the lock. General cleanup. Add s_ prefixes, use constexpr, remove C casts. --- Source/Core/Core/GeckoCode.cpp | 130 ++++++++++++++++----------------- Source/Core/Core/GeckoCode.h | 1 - 2 files changed, 61 insertions(+), 70 deletions(-) diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index b4d896a3f2..2845e9d778 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include +#include #include #include @@ -16,128 +18,118 @@ namespace Gecko { -static const u32 INSTALLER_BASE_ADDRESS = 0x80001800; -static const u32 INSTALLER_END_ADDRESS = 0x80003000; +static constexpr u32 INSTALLER_BASE_ADDRESS = 0x80001800; +static constexpr u32 INSTALLER_END_ADDRESS = 0x80003000; +static constexpr u32 CODE_SIZE = 8; +static constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; // return true if a code exists bool GeckoCode::Exist(u32 address, u32 data) const { - for (const GeckoCode::Code& code : codes) - { - if (code.address == address && code.data == data) - return true; - } - - return false; + return std::find_if(codes.begin(), codes.end(), [&](const Code& code) { + return code.address == address && code.data == data; + }) != codes.end(); } // return true if the code is identical bool GeckoCode::Compare(const GeckoCode& compare) const { - if (codes.size() != compare.codes.size()) - return false; - - unsigned int exist = 0; - - for (const GeckoCode::Code& code : codes) - { - if (compare.Exist(code.address, code.data)) - exist++; - } - - return exist == codes.size(); + return codes.size() == compare.codes.size() && + std::equal(codes.begin(), codes.end(), compare.codes.begin(), + [](const Code& a, const Code& b) { + return a.address == b.address && a.data == b.data; + }); } -static bool code_handler_installed = false; +static bool s_code_handler_installed = false; // the currently active codes -static std::vector active_codes; -static std::mutex active_codes_lock; +static std::vector s_active_codes; +static std::mutex s_active_codes_lock; void SetActiveCodes(const std::vector& gcodes) { - std::lock_guard lk(active_codes_lock); + std::lock_guard lk(s_active_codes_lock); - active_codes.clear(); + s_active_codes.clear(); + s_active_codes.reserve(gcodes.size()); + std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), + [](const GeckoCode& code) { return code.enabled; }); + s_active_codes.shrink_to_fit(); - // add enabled codes - for (const GeckoCode& gecko_code : gcodes) - { - if (gecko_code.enabled) - { - // TODO: apply modifiers - // TODO: don't need description or creator string, just takin up memory - active_codes.push_back(gecko_code); - } - } - - code_handler_installed = false; + s_code_handler_installed = false; } -static bool InstallCodeHandler() +// Requires s_active_codes_lock +// NOTE: Refer to "codehandleronly.s" from Gecko OS. +static bool InstallCodeHandlerLocked() { std::string data; - std::string _rCodeHandlerFilename = File::GetSysDirectory() + GECKO_CODE_HANDLER; - if (!File::ReadFileToString(_rCodeHandlerFilename, data)) + if (!File::ReadFileToString(File::GetSysDirectory() + GECKO_CODE_HANDLER, data)) { - WARN_LOG(ACTIONREPLAY, "Could not enable cheats because codehandler.bin was missing."); + ERROR_LOG(ACTIONREPLAY, "Could not enable cheats because " GECKO_CODE_HANDLER " was missing."); return false; } - u8 mmioAddr = 0xCC; + if (data.size() > INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS - CODE_SIZE) + { + ERROR_LOG(ACTIONREPLAY, GECKO_CODE_HANDLER " is too big. The file may be corrupt."); + return false; + } + u8 mmio_addr = 0xCC; if (SConfig::GetInstance().bWii) { - mmioAddr = 0xCD; + mmio_addr = 0xCD; } // Install code handler - for (size_t i = 0, e = data.length(); i < e; ++i) - PowerPC::HostWrite_U8(data[i], (u32)(INSTALLER_BASE_ADDRESS + i)); + for (u32 i = 0; i < data.size(); ++i) + PowerPC::HostWrite_U8(data[i], INSTALLER_BASE_ADDRESS + i); - // Patch the code handler to the system starting up + // Patch the code handler to the current system type (Gamecube/Wii) for (unsigned int h = 0; h < data.length(); h += 4) { // Patch MMIO address - if (PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS + h) == (0x3f000000u | ((mmioAddr ^ 1) << 8))) + if (PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS + h) == (0x3f000000u | ((mmio_addr ^ 1) << 8))) { NOTICE_LOG(ACTIONREPLAY, "Patching MMIO access at %08x", INSTALLER_BASE_ADDRESS + h); - PowerPC::HostWrite_U32(0x3f000000u | mmioAddr << 8, INSTALLER_BASE_ADDRESS + h); + PowerPC::HostWrite_U32(0x3f000000u | mmio_addr << 8, INSTALLER_BASE_ADDRESS + h); } } - u32 codelist_base_address = INSTALLER_BASE_ADDRESS + (u32)data.length() - 8; - u32 codelist_end_address = INSTALLER_END_ADDRESS; + const u32 codelist_base_address = + INSTALLER_BASE_ADDRESS + static_cast(data.size()) - CODE_SIZE; + const u32 codelist_end_address = INSTALLER_END_ADDRESS; // Write a magic value to 'gameid' (codehandleronly does not actually read this). - PowerPC::HostWrite_U32(0xd01f1bad, INSTALLER_BASE_ADDRESS); + PowerPC::HostWrite_U32(MAGIC_GAMEID, INSTALLER_BASE_ADDRESS); // Create GCT in memory PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address); PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address + 4); - std::lock_guard lk(active_codes_lock); - int i = 0; - for (const GeckoCode& active_code : active_codes) + for (const GeckoCode& active_code : s_active_codes) { if (active_code.enabled) { for (const GeckoCode::Code& code : active_code.codes) { // Make sure we have enough memory to hold the code list - if ((codelist_base_address + 24 + i) < codelist_end_address) + if ((codelist_base_address + CODE_SIZE * 3 + i) < codelist_end_address) { - PowerPC::HostWrite_U32(code.address, codelist_base_address + 8 + i); - PowerPC::HostWrite_U32(code.data, codelist_base_address + 12 + i); - i += 8; + PowerPC::HostWrite_U32(code.address, codelist_base_address + CODE_SIZE + i); + PowerPC::HostWrite_U32(code.data, codelist_base_address + CODE_SIZE + 4 + i); + i += CODE_SIZE; } } } } - PowerPC::HostWrite_U32(0xff000000, codelist_base_address + 8 + i); - PowerPC::HostWrite_U32(0x00000000, codelist_base_address + 12 + i); + // Stop code. Tells the handler that this is the end of the list. + PowerPC::HostWrite_U32(0xF0000000, codelist_base_address + CODE_SIZE + i); + PowerPC::HostWrite_U32(0x00000000, codelist_base_address + CODE_SIZE + 4 + i); // Turn on codes PowerPC::HostWrite_U8(1, INSTALLER_BASE_ADDRESS + 7); @@ -147,24 +139,24 @@ static bool InstallCodeHandler() { PowerPC::ppcState.iCache.Invalidate(INSTALLER_BASE_ADDRESS + j); } - for (unsigned int k = codelist_base_address; k < codelist_end_address; k += 32) - { - PowerPC::ppcState.iCache.Invalidate(k); - } return true; } void RunCodeHandler() { - if (!SConfig::GetInstance().bEnableCheats || active_codes.empty()) + if (!SConfig::GetInstance().bEnableCheats) return; - if (!code_handler_installed || PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS) - 0xd01f1bad > 5) + std::lock_guard codes_lock(s_active_codes_lock); + if (s_active_codes.empty()) + return; + + if (!s_code_handler_installed || PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS) - MAGIC_GAMEID > 5) { - code_handler_installed = InstallCodeHandler(); + s_code_handler_installed = InstallCodeHandlerLocked(); // A warning was already issued for the install failing - if (!code_handler_installed) + if (!s_code_handler_installed) return; } diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 37cc05f768..a54fa22149 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -34,7 +34,6 @@ public: }; void SetActiveCodes(const std::vector& gcodes); -bool RunActiveCodes(); void RunCodeHandler(); } // namespace Gecko From 4fef9d8d64c49dae52ffb6f5b3c923e67c21c59f Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Thu, 15 Sep 2016 02:39:11 +0000 Subject: [PATCH 03/10] GeckoCode: Don't truncate codes that won't fit The code table builder cuts off the end of codes that won't fit after already writing part of it. That seems quite unlikely to work the way anyone would find useful since the codes can contain actual PPC instructions. --- Source/Core/Core/GeckoCode.cpp | 38 +++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 2845e9d778..9ad15b94f6 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -108,28 +108,38 @@ static bool InstallCodeHandlerLocked() PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address); PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address + 4); - int i = 0; + // Each code is 8 bytes (2 words) wide. There is a starter code and an end code. + const u32 start_address = codelist_base_address + CODE_SIZE; + const u32 end_address = codelist_end_address - CODE_SIZE; + u32 next_address = start_address; + // NOTE: Only active codes are in the list for (const GeckoCode& active_code : s_active_codes) { - if (active_code.enabled) + // If the code is not going to fit in the space we have left then we have to skip it + if (next_address + active_code.codes.size() * CODE_SIZE > end_address) { - for (const GeckoCode::Code& code : active_code.codes) - { - // Make sure we have enough memory to hold the code list - if ((codelist_base_address + CODE_SIZE * 3 + i) < codelist_end_address) - { - PowerPC::HostWrite_U32(code.address, codelist_base_address + CODE_SIZE + i); - PowerPC::HostWrite_U32(code.data, codelist_base_address + CODE_SIZE + 4 + i); - i += CODE_SIZE; - } - } + NOTICE_LOG(ACTIONREPLAY, "Too many GeckoCodes! Ran out of storage space in Game RAM. Could " + "not write: \"%s\". Need %zu bytes, only %u remain.", + active_code.name.c_str(), active_code.codes.size() * CODE_SIZE, + end_address - next_address); + continue; + } + + for (const GeckoCode::Code& code : active_code.codes) + { + PowerPC::HostWrite_U32(code.address, next_address); + PowerPC::HostWrite_U32(code.data, next_address + 4); + next_address += CODE_SIZE; } } + WARN_LOG(ACTIONREPLAY, "GeckoCodes: Using %u of %u bytes", next_address - start_address, + end_address - start_address); + // Stop code. Tells the handler that this is the end of the list. - PowerPC::HostWrite_U32(0xF0000000, codelist_base_address + CODE_SIZE + i); - PowerPC::HostWrite_U32(0x00000000, codelist_base_address + CODE_SIZE + 4 + i); + PowerPC::HostWrite_U32(0xF0000000, next_address); + PowerPC::HostWrite_U32(0x00000000, next_address + 4); // Turn on codes PowerPC::HostWrite_U8(1, INSTALLER_BASE_ADDRESS + 7); From 541a42a7e39a0ae9962dc05b4816f296774e47f1 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Fri, 23 Sep 2016 12:58:40 +0000 Subject: [PATCH 04/10] GeckoCode: Use named constants in ICache flush hack Turns out one of the magic numbers was very magic. The gameid is an ad-hoc comm protocol with HLE_Misc to control the number of times the ICache is reset. --- Source/Core/Core/Boot/Boot.cpp | 3 ++- Source/Core/Core/GeckoCode.cpp | 7 +++---- Source/Core/Core/GeckoCode.h | 14 ++++++++++++++ Source/Core/Core/HLE/HLE_Misc.cpp | 13 +++++++------ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 44d01a87f2..50cc596bb8 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -17,6 +17,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/Debugger/Debugger_SymbolMap.h" +#include "Core/GeckoCode.h" #include "Core/HLE/HLE.h" #include "Core/HW/DVDInterface.h" #include "Core/HW/EXI_DeviceIPL.h" @@ -481,6 +482,6 @@ bool CBoot::BootUp() // Not part of the binary itself, but either we or Gecko OS might insert // this, and it doesn't clear the icache properly. - HLE::Patch(0x800018a8, "GeckoCodehandler"); + HLE::Patch(Gecko::ENTRY_POINT, "GeckoCodehandler"); return true; } diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 9ad15b94f6..7e033a8d9e 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -18,10 +18,8 @@ namespace Gecko { -static constexpr u32 INSTALLER_BASE_ADDRESS = 0x80001800; static constexpr u32 INSTALLER_END_ADDRESS = 0x80003000; static constexpr u32 CODE_SIZE = 8; -static constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; // return true if a code exists bool GeckoCode::Exist(u32 address, u32 data) const @@ -102,6 +100,7 @@ static bool InstallCodeHandlerLocked() const u32 codelist_end_address = INSTALLER_END_ADDRESS; // Write a magic value to 'gameid' (codehandleronly does not actually read this). + // This value will be read back and modified over time by HLE_Misc::HLEGeckoCodehandler. PowerPC::HostWrite_U32(MAGIC_GAMEID, INSTALLER_BASE_ADDRESS); // Create GCT in memory @@ -161,7 +160,7 @@ void RunCodeHandler() if (s_active_codes.empty()) return; - if (!s_code_handler_installed || PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS) - MAGIC_GAMEID > 5) + if (!s_code_handler_installed) { s_code_handler_installed = InstallCodeHandlerLocked(); @@ -175,7 +174,7 @@ void RunCodeHandler() // the original return address (which will still be in the link register) at the end. if (PC == LR) { - PC = NPC = INSTALLER_BASE_ADDRESS + 0xA8; + PC = NPC = ENTRY_POINT; } } diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index a54fa22149..47f408f164 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -33,6 +33,20 @@ public: bool Exist(u32 address, u32 data) const; }; +// Installation address for codehandler.bin in the Game's RAM +constexpr u32 INSTALLER_BASE_ADDRESS = 0x80001800; +constexpr u32 ENTRY_POINT = 0x800018A8; + +// This forms part of a communication protocol with HLE_Misc::HLEGeckoCodehandler. +// Basically, codehandleronly.s doesn't use ICBI like it's supposed to when patching the +// game's code. This results in the JIT happily ignoring all code patches for blocks that +// are already compiled. The hack for getting around that is that the first 5 frames after +// the handler is installed (0xD01F1BAD -> +5 -> 0xD01F1BB2) cause full ICache resets. +// +// HLEGeckoCodehandler will increment this value 5 times then cease flushing the ICache to +// preserve the emulation performance. +constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; + void SetActiveCodes(const std::vector& gcodes); void RunCodeHandler(); diff --git a/Source/Core/Core/HLE/HLE_Misc.cpp b/Source/Core/Core/HLE/HLE_Misc.cpp index a59b474916..d2a32262d4 100644 --- a/Source/Core/Core/HLE/HLE_Misc.cpp +++ b/Source/Core/Core/HLE/HLE_Misc.cpp @@ -7,6 +7,7 @@ #include "Common/CommonTypes.h" #include "Core/ConfigManager.h" +#include "Core/GeckoCode.h" #include "Core/HLE/HLE_Misc.h" #include "Core/HW/CPU.h" #include "Core/Host.h" @@ -47,17 +48,17 @@ void HLEGeckoCodehandler() // been read into memory, or such, so we do the first 5 frames. More // robust alternative would be to actually detect memory writes, but that // would be even uglier.) - u32 magic = 0xd01f1bad; - u32 existing = PowerPC::HostRead_U32(0x80001800); - if (existing - magic == 5) + u32 gch_gameid = PowerPC::HostRead_U32(Gecko::INSTALLER_BASE_ADDRESS); + if (gch_gameid - Gecko::MAGIC_GAMEID == 5) { return; } - else if (existing - magic > 5) + else if (gch_gameid - Gecko::MAGIC_GAMEID > 5) { - existing = magic; + gch_gameid = Gecko::MAGIC_GAMEID; } - PowerPC::HostWrite_U32(existing + 1, 0x80001800); + PowerPC::HostWrite_U32(gch_gameid + 1, Gecko::INSTALLER_BASE_ADDRESS); + PowerPC::ppcState.iCache.Reset(); } } From 249d8a76e163c7b899f5f636cddc8bc913624224 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Sat, 24 Sep 2016 03:17:39 +0000 Subject: [PATCH 05/10] GeckoCode: Don't spam retry after the install fails If the installation fails because codehandler.bin is missing or unusable then Dolphin will try again every single frame even though it's highly unlikely a disk file will have changed. Better to just fail once then only try again when the active code set is changed. Suppresses generating 60 log messages per second. --- Source/Core/Core/GeckoCode.cpp | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 7e033a8d9e..63c22a7599 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -39,7 +39,14 @@ bool GeckoCode::Compare(const GeckoCode& compare) const }); } -static bool s_code_handler_installed = false; +enum class Installation +{ + Uninstalled, + Installed, + Failed +}; + +static Installation s_code_handler_installed = Installation::Uninstalled; // the currently active codes static std::vector s_active_codes; static std::mutex s_active_codes_lock; @@ -54,24 +61,24 @@ void SetActiveCodes(const std::vector& gcodes) [](const GeckoCode& code) { return code.enabled; }); s_active_codes.shrink_to_fit(); - s_code_handler_installed = false; + s_code_handler_installed = Installation::Uninstalled; } // Requires s_active_codes_lock // NOTE: Refer to "codehandleronly.s" from Gecko OS. -static bool InstallCodeHandlerLocked() +static Installation InstallCodeHandlerLocked() { std::string data; if (!File::ReadFileToString(File::GetSysDirectory() + GECKO_CODE_HANDLER, data)) { ERROR_LOG(ACTIONREPLAY, "Could not enable cheats because " GECKO_CODE_HANDLER " was missing."); - return false; + return Installation::Failed; } if (data.size() > INSTALLER_END_ADDRESS - INSTALLER_BASE_ADDRESS - CODE_SIZE) { ERROR_LOG(ACTIONREPLAY, GECKO_CODE_HANDLER " is too big. The file may be corrupt."); - return false; + return Installation::Failed; } u8 mmio_addr = 0xCC; @@ -148,7 +155,7 @@ static bool InstallCodeHandlerLocked() { PowerPC::ppcState.iCache.Invalidate(INSTALLER_BASE_ADDRESS + j); } - return true; + return Installation::Installed; } void RunCodeHandler() @@ -157,15 +164,17 @@ void RunCodeHandler() return; std::lock_guard codes_lock(s_active_codes_lock); - if (s_active_codes.empty()) + // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be + // fixed within 1 frame of the last error. + if (s_active_codes.empty() || s_code_handler_installed == Installation::Failed) return; - if (!s_code_handler_installed) + if (s_code_handler_installed != Installation::Installed) { s_code_handler_installed = InstallCodeHandlerLocked(); // A warning was already issued for the install failing - if (!s_code_handler_installed) + if (s_code_handler_installed != Installation::Installed) return; } From 83407263e59450b940d3df7d98c022e77c25726e Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Sun, 25 Sep 2016 03:03:25 +0000 Subject: [PATCH 06/10] HLE/GeckoCode: Add new HLE hook exit trampoline Dolphin emulates GeckoCodes by fiddling with the CPU state when a VI Interrupt occurs. The problem with this is that we don't know where the PC is so it's non-deterministic and not necessarily suitable for use with the codehandler. There are two options: Patch the game like Gecko OS either directly or using HLE::Patch, or use a trampoline so we can branch from any PC even if it would otherwise not be valid. The problem with Gecko OS patches is there are 10 of them and they have to be configured manually (i.e. Game INIs to would need to have a [Core]GeckoHookType property). HLE_Misc::GeckoReturnTrampoline enables the Code Handler to be entered from anywhere, the trampoline restores all the registers that had to be secretly saved to the stack. --- Source/Core/Core/Boot/Boot.cpp | 4 +++ Source/Core/Core/GeckoCode.cpp | 50 ++++++++++++++++++++++++++---- Source/Core/Core/GeckoCode.h | 12 ++++--- Source/Core/Core/HLE/HLE.cpp | 4 ++- Source/Core/Core/HLE/HLE_Misc.cpp | 33 ++++++++++++++------ Source/Core/Core/HLE/HLE_Misc.h | 3 +- Source/Core/Core/PatchEngine.cpp | 2 +- Source/Core/Core/PowerPC/MMU.cpp | 5 +++ Source/Core/Core/PowerPC/PowerPC.h | 1 + 9 files changed, 91 insertions(+), 23 deletions(-) diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 50cc596bb8..4d3f25a797 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -483,5 +483,9 @@ bool CBoot::BootUp() // Not part of the binary itself, but either we or Gecko OS might insert // this, and it doesn't clear the icache properly. HLE::Patch(Gecko::ENTRY_POINT, "GeckoCodehandler"); + if (SConfig::GetInstance().bEnableCheats) + { + HLE::Patch(Gecko::HLE_TRAMPOLINE_ADDRESS, "GeckoHandlerReturnTrampoline"); + } return true; } diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 63c22a7599..1d4efa9b9b 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -18,7 +18,6 @@ namespace Gecko { -static constexpr u32 INSTALLER_END_ADDRESS = 0x80003000; static constexpr u32 CODE_SIZE = 8; // return true if a code exists @@ -107,7 +106,7 @@ static Installation InstallCodeHandlerLocked() const u32 codelist_end_address = INSTALLER_END_ADDRESS; // Write a magic value to 'gameid' (codehandleronly does not actually read this). - // This value will be read back and modified over time by HLE_Misc::HLEGeckoCodehandler. + // This value will be read back and modified over time by HLE_Misc::GeckoCodeHandlerICacheFlush. PowerPC::HostWrite_U32(MAGIC_GAMEID, INSTALLER_BASE_ADDRESS); // Create GCT in memory @@ -158,11 +157,25 @@ static Installation InstallCodeHandlerLocked() return Installation::Installed; } -void RunCodeHandler() +void RunCodeHandler(u32 msr_reg) { if (!SConfig::GetInstance().bEnableCheats) return; + // Dolphin's hook mechanism is less 'precise' than Gecko OS' which detects particular + // instruction sequences (uses configuration, not automatic) that only run once per frame + // which includes a BLR as one of the instructions. It then overwrites the BLR with a + // "B 0x800018A8" to establish the hook. Dolphin uses its own internal VI interrupt which + // means the PC is non-deterministic and could be anywhere. + UReg_MSR msr = msr_reg; + if (!msr.DR || !msr.IR) + { + WARN_LOG(ACTIONREPLAY, "GeckoCode: Skipping frame update. MSR.IR/DR is currently disabled. " + "PC = 0x%08X, MSR = 0x%08X", + PC, msr_reg); + return; + } + std::lock_guard codes_lock(s_active_codes_lock); // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be // fixed within 1 frame of the last error. @@ -180,11 +193,36 @@ void RunCodeHandler() // If the last block that just executed ended with a BLR instruction then we can intercept it and // redirect control into the Gecko Code Handler. The Code Handler will automatically BLR back to - // the original return address (which will still be in the link register) at the end. - if (PC == LR) + // the original return address (which will still be in the link register). + if (PC != LR) { - PC = NPC = ENTRY_POINT; + // We're at a random address in the middle of something so we have to do this the hard way. + // The codehandler will STMW all of the GPR registers, but we need to fix the Stack's Red + // Zone, the LR, PC (return address) and the volatile floating point registers. + // Build a function call stack frame. + u32 SFP = GPR(1); // Stack Frame Pointer + GPR(1) -= 224; // Stack's Red Zone + GPR(1) -= 16 + 2 * 14 * sizeof(u64); // Our stack frame (HLE_Misc::GeckoReturnTrampoline) + GPR(1) -= 8; // Fake stack frame for codehandler + GPR(1) &= 0xFFFFFFF0; // Align stack to 16bytes + u32 SP = GPR(1); // Stack Pointer + PowerPC::HostWrite_U32(SP + 8, SP); + // SP + 4 is reserved for the codehandler to save LR to the stack. + PowerPC::HostWrite_U32(SFP, SP + 8); // Real stack frame + PowerPC::HostWrite_U32(PC, SP + 12); + PowerPC::HostWrite_U32(LR, SP + 16); + // Registers FPR0->13 are volatile + for (int i = 0; i < 14; ++i) + { + PowerPC::HostWrite_U64(riPS0(i), SP + 24 + 2 * i * sizeof(u64)); + PowerPC::HostWrite_U64(riPS1(i), SP + 24 + (2 * i + 1) * sizeof(u64)); + } + LR = HLE_TRAMPOLINE_ADDRESS; + DEBUG_LOG(ACTIONREPLAY, "GeckoCodes: Initiating phantom branch-and-link. " + "PC = 0x%08X, SP = 0x%08X, SFP = 0x%08X", + PC, SP, SFP); } + PC = NPC = ENTRY_POINT; } } // namespace Gecko diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 47f408f164..3e1cd2710a 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -35,19 +35,23 @@ public: // Installation address for codehandler.bin in the Game's RAM constexpr u32 INSTALLER_BASE_ADDRESS = 0x80001800; -constexpr u32 ENTRY_POINT = 0x800018A8; +constexpr u32 INSTALLER_END_ADDRESS = 0x80003000; +constexpr u32 ENTRY_POINT = INSTALLER_BASE_ADDRESS + 0xA8; +// If the GCT is max-length then this is the second word of the End code (0xF0000000 0x00000000) +// If the table is shorter than the max-length then this address is unused / contains trash. +constexpr u32 HLE_TRAMPOLINE_ADDRESS = INSTALLER_END_ADDRESS - 4; -// This forms part of a communication protocol with HLE_Misc::HLEGeckoCodehandler. +// This forms part of a communication protocol with HLE_Misc::GeckoCodeHandlerICacheFlush. // Basically, codehandleronly.s doesn't use ICBI like it's supposed to when patching the // game's code. This results in the JIT happily ignoring all code patches for blocks that // are already compiled. The hack for getting around that is that the first 5 frames after // the handler is installed (0xD01F1BAD -> +5 -> 0xD01F1BB2) cause full ICache resets. // -// HLEGeckoCodehandler will increment this value 5 times then cease flushing the ICache to +// GeckoCodeHandlerICacheFlush will increment this value 5 times then cease flushing the ICache to // preserve the emulation performance. constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; void SetActiveCodes(const std::vector& gcodes); -void RunCodeHandler(); +void RunCodeHandler(u32 msr_reg); } // namespace Gecko diff --git a/Source/Core/Core/HLE/HLE.cpp b/Source/Core/Core/HLE/HLE.cpp index 2c60f7d2e6..ac60657143 100644 --- a/Source/Core/Core/HLE/HLE.cpp +++ b/Source/Core/Core/HLE/HLE.cpp @@ -60,7 +60,9 @@ static const SPatch OSPatches[] = { {"___blank", HLE_OS::HLE_GeneralDebugPrint, HLE_HOOK_REPLACE, HLE_TYPE_DEBUG}, {"__write_console", HLE_OS::HLE_write_console, HLE_HOOK_REPLACE, HLE_TYPE_DEBUG}, // used by sysmenu (+more?) - {"GeckoCodehandler", HLE_Misc::HLEGeckoCodehandler, HLE_HOOK_START, HLE_TYPE_GENERIC}, + {"GeckoCodehandler", HLE_Misc::GeckoCodeHandlerICacheFlush, HLE_HOOK_START, HLE_TYPE_GENERIC}, + {"GeckoHandlerReturnTrampoline", HLE_Misc::GeckoReturnTrampoline, HLE_HOOK_REPLACE, + HLE_TYPE_GENERIC}, }; static const SPatch OSBreakPoints[] = { diff --git a/Source/Core/Core/HLE/HLE_Misc.cpp b/Source/Core/Core/HLE/HLE_Misc.cpp index d2a32262d4..e6a5525bf8 100644 --- a/Source/Core/Core/HLE/HLE_Misc.cpp +++ b/Source/Core/Core/HLE/HLE_Misc.cpp @@ -2,22 +2,17 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include -#include - -#include "Common/CommonTypes.h" -#include "Core/ConfigManager.h" -#include "Core/GeckoCode.h" #include "Core/HLE/HLE_Misc.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "Core/GeckoCode.h" #include "Core/HW/CPU.h" #include "Core/Host.h" -#include "Core/PowerPC/PPCCache.h" #include "Core/PowerPC/PowerPC.h" namespace HLE_Misc { -static std::string args; - // If you just want to kill a function, one of the three following are usually appropriate. // According to the PPC ABI, the return value is always in r3. void UnimplementedFunction() @@ -40,7 +35,7 @@ void HBReload() Host_Message(WM_USER_STOP); } -void HLEGeckoCodehandler() +void GeckoCodeHandlerICacheFlush() { // Work around the codehandler not properly invalidating the icache, but // only the first few frames. @@ -61,4 +56,22 @@ void HLEGeckoCodehandler() PowerPC::ppcState.iCache.Reset(); } + +// Because Dolphin messes around with the CPU state instead of patching the game binary, we +// need a way to branch into the GCH from an arbitrary PC address. Branching is easy, returning +// back is the hard part. This HLE function acts as a trampoline that restores the original LR, SP, +// and PC before the magic, invisible BL instruction happened. +void GeckoReturnTrampoline() +{ + // Stack frame is built in GeckoCode.cpp, Gecko::RunCodeHandler. + u32 SP = GPR(1); + GPR(1) = PowerPC::HostRead_U32(SP + 8); + NPC = PowerPC::HostRead_U32(SP + 12); + LR = PowerPC::HostRead_U32(SP + 16); + for (int i = 0; i < 14; ++i) + { + riPS0(i) = PowerPC::HostRead_U64(SP + 24 + 2 * i * sizeof(u64)); + riPS1(i) = PowerPC::HostRead_U64(SP + 24 + (2 * i + 1) * sizeof(u64)); + } +} } diff --git a/Source/Core/Core/HLE/HLE_Misc.h b/Source/Core/Core/HLE/HLE_Misc.h index 2ba9bb367c..7bf2d8496c 100644 --- a/Source/Core/Core/HLE/HLE_Misc.h +++ b/Source/Core/Core/HLE/HLE_Misc.h @@ -9,5 +9,6 @@ namespace HLE_Misc void HLEPanicAlert(); void UnimplementedFunction(); void HBReload(); -void HLEGeckoCodehandler(); +void GeckoCodeHandlerICacheFlush(); +void GeckoReturnTrampoline(); } diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 9a4302c15b..6ee403b9b7 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -216,7 +216,7 @@ void ApplyFramePatches() ApplyPatches(onFrame); // Run the Gecko code handler - Gecko::RunCodeHandler(); + Gecko::RunCodeHandler(oldMSR); ActionReplay::RunAllActive(); MSR = oldMSR; } diff --git a/Source/Core/Core/PowerPC/MMU.cpp b/Source/Core/Core/PowerPC/MMU.cpp index 13d879a004..a637399ce0 100644 --- a/Source/Core/Core/PowerPC/MMU.cpp +++ b/Source/Core/Core/PowerPC/MMU.cpp @@ -565,6 +565,11 @@ u32 HostRead_U32(const u32 address) return var; } +u64 HostRead_U64(const u32 address) +{ + return ReadFromHardware(address); +} + void HostWrite_U8(const u8 var, const u32 address) { WriteToHardware(address, var); diff --git a/Source/Core/Core/PowerPC/PowerPC.h b/Source/Core/Core/PowerPC/PowerPC.h index 51dc8eb658..54c26c7096 100644 --- a/Source/Core/Core/PowerPC/PowerPC.h +++ b/Source/Core/Core/PowerPC/PowerPC.h @@ -209,6 +209,7 @@ void UpdatePerformanceMonitor(u32 cycles, u32 num_load_stores, u32 num_fp_inst); u8 HostRead_U8(const u32 address); u16 HostRead_U16(const u32 address); u32 HostRead_U32(const u32 address); +u64 HostRead_U64(const u32 address); u32 HostRead_Instruction(const u32 address); void HostWrite_U8(const u8 var, const u32 address); From c3cef5491014540386f3656a98cb4a27fb43b482 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Sat, 24 Sep 2016 15:14:51 +0000 Subject: [PATCH 07/10] PatchEngine: Handle MSR more cleanly Instead of fiddling with the MSR value, just reschedule and try again after the game fixes it itself. --- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 4 +++- Source/Core/Core/GeckoCode.cpp | 16 +-------------- Source/Core/Core/GeckoCode.h | 2 +- Source/Core/Core/HW/SystemTimers.cpp | 26 ++++++++++++++++++++---- Source/Core/Core/PatchEngine.cpp | 29 +++++++++++++++++---------- Source/Core/Core/PatchEngine.h | 2 +- 6 files changed, 46 insertions(+), 33 deletions(-) diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index 68a975476b..2f73cd7c68 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include "Common/Assert.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" #include "Common/FileUtil.h" @@ -178,7 +179,8 @@ bool CBoot::EmulatedBS2_GC(bool skipAppLoader) PatchEngine::LoadPatches(); // If we have any patches that need to be applied very early, here's a good place - PatchEngine::ApplyFramePatches(); + bool patched = PatchEngine::ApplyFramePatches(); + _assert_(patched); return true; } diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 1d4efa9b9b..c0185ff3af 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -157,25 +157,11 @@ static Installation InstallCodeHandlerLocked() return Installation::Installed; } -void RunCodeHandler(u32 msr_reg) +void RunCodeHandler() { if (!SConfig::GetInstance().bEnableCheats) return; - // Dolphin's hook mechanism is less 'precise' than Gecko OS' which detects particular - // instruction sequences (uses configuration, not automatic) that only run once per frame - // which includes a BLR as one of the instructions. It then overwrites the BLR with a - // "B 0x800018A8" to establish the hook. Dolphin uses its own internal VI interrupt which - // means the PC is non-deterministic and could be anywhere. - UReg_MSR msr = msr_reg; - if (!msr.DR || !msr.IR) - { - WARN_LOG(ACTIONREPLAY, "GeckoCode: Skipping frame update. MSR.IR/DR is currently disabled. " - "PC = 0x%08X, MSR = 0x%08X", - PC, msr_reg); - return; - } - std::lock_guard codes_lock(s_active_codes_lock); // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be // fixed within 1 frame of the last error. diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 3e1cd2710a..4fa17c8c5e 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -52,6 +52,6 @@ constexpr u32 HLE_TRAMPOLINE_ADDRESS = INSTALLER_END_ADDRESS - 4; constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; void SetActiveCodes(const std::vector& gcodes); -void RunCodeHandler(u32 msr_reg); +void RunCodeHandler(); } // namespace Gecko diff --git a/Source/Core/Core/HW/SystemTimers.cpp b/Source/Core/Core/HW/SystemTimers.cpp index 3e8b96381f..e2f8e3e9f4 100644 --- a/Source/Core/Core/HW/SystemTimers.cpp +++ b/Source/Core/Core/HW/SystemTimers.cpp @@ -166,11 +166,29 @@ s64 GetLocalTimeRTCOffset() return s_localtime_rtc_offset; } -static void PatchEngineCallback(u64 userdata, s64 cyclesLate) +static void PatchEngineCallback(u64 userdata, s64 cycles_late) { - // Patch mem and run the Action Replay - PatchEngine::ApplyFramePatches(); - CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerField() - cyclesLate, et_PatchEngine); + // We have 2 periods, a 1000 cycle error period and the VI period. + // We have to carefully combine these together so that we stay on the VI period without drifting. + u32 vi_interval = VideoInterface::GetTicksPerField(); + s64 cycles_pruned = (userdata + cycles_late) % vi_interval; + s64 next_schedule = 0; + + // Try to patch mem and run the Action Replay + if (PatchEngine::ApplyFramePatches()) + { + next_schedule = vi_interval - cycles_pruned; + cycles_pruned = 0; + } + else + { + // The patch failed, usually because the CPU is in an inappropriate state (interrupt handler). + // We'll try again after 1000 cycles. + next_schedule = 1000; + cycles_pruned += next_schedule; + } + + CoreTiming::ScheduleEvent(next_schedule, et_PatchEngine, cycles_pruned); } static void ThrottleCallback(u64 last_time, s64 cyclesLate) diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index 6ee403b9b7..e17a1d7720 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -203,22 +203,29 @@ static void ApplyPatches(const std::vector& patches) } } -void ApplyFramePatches() +bool ApplyFramePatches() { - // TODO: Messing with MSR this way is really, really, evil; we should - // probably be using some sort of Gecko OS-style hooking mechanism - // so the emulated CPU is in a predictable state when we process cheats. - u32 oldMSR = MSR; - UReg_MSR newMSR = oldMSR; - newMSR.IR = 1; - newMSR.DR = 1; - MSR = newMSR.Hex; + // Because we're using the VI Interrupt to time this instead of patching the game with a + // callback hook we can end up catching the game in an exception vector. + // We deal with this by returning false so that SystemTimers will reschedule us in a few cycles + // where we can try again after the CPU hopefully returns back to the normal instruction flow. + UReg_MSR msr = MSR; + if (!msr.DR || !msr.IR) + { + INFO_LOG( + ACTIONREPLAY, + "Need to retry later. CPU configuration is currently incorrect. PC = 0x%08X, MSR = 0x%08X", + PC, MSR); + return false; + } + ApplyPatches(onFrame); // Run the Gecko code handler - Gecko::RunCodeHandler(oldMSR); + Gecko::RunCodeHandler(); ActionReplay::RunAllActive(); - MSR = oldMSR; + + return true; } void Shutdown() diff --git a/Source/Core/Core/PatchEngine.h b/Source/Core/Core/PatchEngine.h index 61975a8d82..c7d3c9b838 100644 --- a/Source/Core/Core/PatchEngine.h +++ b/Source/Core/Core/PatchEngine.h @@ -43,7 +43,7 @@ int GetSpeedhackCycles(const u32 addr); void LoadPatchSection(const std::string& section, std::vector& patches, IniFile& globalIni, IniFile& localIni); void LoadPatches(); -void ApplyFramePatches(); +bool ApplyFramePatches(); void Shutdown(); inline int GetPatchTypeCharLength(PatchType type) From 31cf8432bf92e06db5c9e97c38e591492639cd98 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Sat, 1 Oct 2016 06:17:24 +0000 Subject: [PATCH 08/10] HLE: Differentiate Address hooks from Symbol hooks GeckoCodes require address hooks which don't correspond to any symbol in the symbol table. The hooks get deleted when repatching the game because they did not persist across calls to HLE::PatchFunctions. --- Source/Core/Core/Core.cpp | 2 + Source/Core/Core/HLE/HLE.cpp | 94 +++++++++++++++++++++++++++++++----- Source/Core/Core/HLE/HLE.h | 7 ++- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 89c242b937..73b4079a80 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -38,6 +38,7 @@ #endif #include "Core/Boot/Boot.h" #include "Core/FifoPlayer/FifoPlayer.h" +#include "Core/HLE/HLE.h" #include "Core/HW/AudioInterface.h" #include "Core/HW/CPU.h" #include "Core/HW/DSP.h" @@ -674,6 +675,7 @@ void EmuThread() INFO_LOG(CONSOLE, "Stop [Video Thread]\t\t---- Shutdown complete ----"); Movie::Shutdown(); PatchEngine::Shutdown(); + HLE::Clear(); s_is_stopping = false; diff --git a/Source/Core/Core/HLE/HLE.cpp b/Source/Core/Core/HLE/HLE.cpp index ac60657143..6064e554e8 100644 --- a/Source/Core/Core/HLE/HLE.cpp +++ b/Source/Core/Core/HLE/HLE.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include +#include + #include "Common/CommonTypes.h" #include "Core/ConfigManager.h" @@ -33,11 +36,12 @@ struct SPatch { char m_szPatchName[128]; TPatchFunction PatchFunction; - int type; - int flags; + HookType type; + HookFlag flags; }; static const SPatch OSPatches[] = { + // Placeholder, OSPatches[0] is the "non-existent function" index {"FAKE_TO_SKIP_0", HLE_Misc::UnimplementedFunction, HLE_HOOK_REPLACE, HLE_TYPE_GENERIC}, {"PanicAlert", HLE_Misc::HLEPanicAlert, HLE_HOOK_REPLACE, HLE_TYPE_DEBUG}, @@ -60,9 +64,10 @@ static const SPatch OSPatches[] = { {"___blank", HLE_OS::HLE_GeneralDebugPrint, HLE_HOOK_REPLACE, HLE_TYPE_DEBUG}, {"__write_console", HLE_OS::HLE_write_console, HLE_HOOK_REPLACE, HLE_TYPE_DEBUG}, // used by sysmenu (+more?) - {"GeckoCodehandler", HLE_Misc::GeckoCodeHandlerICacheFlush, HLE_HOOK_START, HLE_TYPE_GENERIC}, + + {"GeckoCodehandler", HLE_Misc::GeckoCodeHandlerICacheFlush, HLE_HOOK_START, HLE_TYPE_FIXED}, {"GeckoHandlerReturnTrampoline", HLE_Misc::GeckoReturnTrampoline, HLE_HOOK_REPLACE, - HLE_TYPE_GENERIC}, + HLE_TYPE_FIXED}, }; static const SPatch OSBreakPoints[] = { @@ -71,11 +76,12 @@ static const SPatch OSBreakPoints[] = { void Patch(u32 addr, const char* hle_func_name) { - for (u32 i = 0; i < sizeof(OSPatches) / sizeof(SPatch); i++) + for (u32 i = 1; i < ArraySize(OSPatches); ++i) { if (!strcmp(OSPatches[i].m_szPatchName, hle_func_name)) { s_original_instructions[addr] = i; + PowerPC::ppcState.iCache.Invalidate(addr); return; } } @@ -83,15 +89,33 @@ void Patch(u32 addr, const char* hle_func_name) void PatchFunctions() { - s_original_instructions.clear(); - for (u32 i = 0; i < sizeof(OSPatches) / sizeof(SPatch); i++) + // Remove all hooks that aren't fixed address hooks + for (auto i = s_original_instructions.begin(); i != s_original_instructions.end();) { + if (OSPatches[i->second].flags != HLE_TYPE_FIXED) + { + PowerPC::ppcState.iCache.Invalidate(i->first); + i = s_original_instructions.erase(i); + } + else + { + ++i; + } + } + + for (u32 i = 1; i < ArraySize(OSPatches); ++i) + { + // Fixed hooks don't map to symbols + if (OSPatches[i].flags == HLE_TYPE_FIXED) + continue; + Symbol* symbol = g_symbolDB.GetSymbolFromName(OSPatches[i].m_szPatchName); if (symbol) { for (u32 addr = symbol->address; addr < symbol->address + symbol->size; addr += 4) { s_original_instructions[addr] = i; + PowerPC::ppcState.iCache.Invalidate(addr); } INFO_LOG(OSHLE, "Patching %s %08x", OSPatches[i].m_szPatchName, symbol->address); } @@ -99,7 +123,7 @@ void PatchFunctions() if (SConfig::GetInstance().bEnableDebugging) { - for (size_t i = 1; i < sizeof(OSBreakPoints) / sizeof(SPatch); i++) + for (size_t i = 1; i < ArraySize(OSBreakPoints); ++i) { Symbol* symbol = g_symbolDB.GetSymbolFromName(OSPatches[i].m_szPatchName); if (symbol) @@ -113,10 +137,15 @@ void PatchFunctions() // CBreakPoints::AddBreakPoint(0x8000D3D0, false); } +void Clear() +{ + s_original_instructions.clear(); +} + void Execute(u32 _CurrentPC, u32 _Instruction) { unsigned int FunctionIndex = _Instruction & 0xFFFFF; - if ((FunctionIndex > 0) && (FunctionIndex < (sizeof(OSPatches) / sizeof(SPatch)))) + if (FunctionIndex > 0 && FunctionIndex < ArraySize(OSPatches)) { OSPatches[FunctionIndex].PatchFunction(); } @@ -154,15 +183,40 @@ bool IsEnabled(int flags) return true; } -u32 UnPatch(const std::string& patchName) +u32 UnPatch(const std::string& patch_name) { - Symbol* symbol = g_symbolDB.GetSymbolFromName(patchName); + auto* patch = std::find_if(std::begin(OSPatches), std::end(OSPatches), [&](const SPatch& patch) { + return patch_name == patch.m_szPatchName; + }); + if (patch == std::end(OSPatches)) + return 0; - if (symbol) + if (patch->type == HLE_TYPE_FIXED) + { + u32 patch_idx = static_cast(patch - OSPatches); + u32 addr = 0; + // Reverse search by OSPatch key instead of address + for (auto i = s_original_instructions.begin(); i != s_original_instructions.end();) + { + if (i->second == patch_idx) + { + addr = i->first; + PowerPC::ppcState.iCache.Invalidate(i->first); + i = s_original_instructions.erase(i); + } + else + { + ++i; + } + } + return addr; + } + + if (Symbol* symbol = g_symbolDB.GetSymbolFromName(patch_name)) { for (u32 addr = symbol->address; addr < symbol->address + symbol->size; addr += 4) { - s_original_instructions[addr] = 0; + s_original_instructions.erase(addr); PowerPC::ppcState.iCache.Invalidate(addr); } return symbol->address; @@ -171,4 +225,18 @@ u32 UnPatch(const std::string& patchName) return 0; } +bool UnPatch(u32 addr, const std::string& name) +{ + auto itr = s_original_instructions.find(addr); + if (itr == s_original_instructions.end()) + return false; + + if (!name.empty() && name != OSPatches[itr->second].m_szPatchName) + return false; + + s_original_instructions.erase(itr); + PowerPC::ppcState.iCache.Invalidate(addr); + return true; +} + } // end of namespace HLE diff --git a/Source/Core/Core/HLE/HLE.h b/Source/Core/Core/HLE/HLE.h index 42f3be0bcb..61b50a8e72 100644 --- a/Source/Core/Core/HLE/HLE.h +++ b/Source/Core/Core/HLE/HLE.h @@ -10,23 +10,26 @@ namespace HLE { -enum +enum HookType { HLE_HOOK_START = 0, // Hook the beginning of the function and execute the function afterwards HLE_HOOK_REPLACE = 1, // Replace the function with the HLE version HLE_HOOK_NONE = 2, // Do not hook the function }; -enum +enum HookFlag { HLE_TYPE_GENERIC = 0, // Miscellaneous function HLE_TYPE_DEBUG = 1, // Debug output function + HLE_TYPE_FIXED = 2, // An arbitrary hook mapped to a fixed address instead of a symbol }; void PatchFunctions(); +void Clear(); void Patch(u32 pc, const char* func_name); u32 UnPatch(const std::string& patchName); +bool UnPatch(u32 addr, const std::string& name = {}); void Execute(u32 _CurrentPC, u32 _Instruction); u32 GetFunctionIndex(u32 em_address); From b3547870ee4ed879e7514b2229c701a3d53e36b9 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Fri, 30 Sep 2016 15:29:35 +0000 Subject: [PATCH 09/10] PatchEngine/GeckoCode: Heuristic stack checks Try to make sure the stack is sane before calling into the codehandler. This is intended to reduce the possibility of random memory corruption. --- Source/Core/Core/Boot/Boot.cpp | 7 +-- Source/Core/Core/Boot/Boot_BS2Emu.cpp | 4 -- Source/Core/Core/GeckoCode.cpp | 86 ++++++++++++++------------- Source/Core/Core/HLE/HLE_Misc.cpp | 1 + Source/Core/Core/PatchEngine.cpp | 29 ++++++++- 5 files changed, 78 insertions(+), 49 deletions(-) diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 4d3f25a797..37f51eaaff 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -483,9 +483,8 @@ bool CBoot::BootUp() // Not part of the binary itself, but either we or Gecko OS might insert // this, and it doesn't clear the icache properly. HLE::Patch(Gecko::ENTRY_POINT, "GeckoCodehandler"); - if (SConfig::GetInstance().bEnableCheats) - { - HLE::Patch(Gecko::HLE_TRAMPOLINE_ADDRESS, "GeckoHandlerReturnTrampoline"); - } + // This has to always be installed even if cheats are not enabled because of the possiblity of + // loading a savestate where PC is inside the code handler while cheats are disabled. + HLE::Patch(Gecko::HLE_TRAMPOLINE_ADDRESS, "GeckoHandlerReturnTrampoline"); return true; } diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp index 2f73cd7c68..7825f0132a 100644 --- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp +++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp @@ -178,10 +178,6 @@ bool CBoot::EmulatedBS2_GC(bool skipAppLoader) // Load patches PatchEngine::LoadPatches(); - // If we have any patches that need to be applied very early, here's a good place - bool patched = PatchEngine::ApplyFramePatches(); - _assert_(patched); - return true; } diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index c0185ff3af..2eaead2f8d 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -145,6 +145,7 @@ static Installation InstallCodeHandlerLocked() // Stop code. Tells the handler that this is the end of the list. PowerPC::HostWrite_U32(0xF0000000, next_address); PowerPC::HostWrite_U32(0x00000000, next_address + 4); + PowerPC::HostWrite_U32(0, HLE_TRAMPOLINE_ADDRESS); // Turn on codes PowerPC::HostWrite_U8(1, INSTALLER_BASE_ADDRESS + 7); @@ -157,57 +158,62 @@ static Installation InstallCodeHandlerLocked() return Installation::Installed; } +// FIXME: Gecko needs to participate in the savestate system (remember installation state). +// Current bug: Loading a savestate causes the handler to be replaced, if the PC is inside it +// and the on disk codehandler.bin is different then the PC will effectively be pointing to +// a random instruction different from the one when the state was created and break or crash. +// [Also, self-modifying handler will break it since the modifications will be reset] void RunCodeHandler() { if (!SConfig::GetInstance().bEnableCheats) return; - std::lock_guard codes_lock(s_active_codes_lock); - // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be - // fixed within 1 frame of the last error. - if (s_active_codes.empty() || s_code_handler_installed == Installation::Failed) - return; - - if (s_code_handler_installed != Installation::Installed) + // NOTE: Need to release the lock because of GUI deadlocks with PanicAlert in HostWrite_* { - s_code_handler_installed = InstallCodeHandlerLocked(); - - // A warning was already issued for the install failing - if (s_code_handler_installed != Installation::Installed) + std::lock_guard codes_lock(s_active_codes_lock); + // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be + // fixed within 1 frame of the last error. + if (s_active_codes.empty() || s_code_handler_installed == Installation::Failed) return; + + if (s_code_handler_installed != Installation::Installed) + { + s_code_handler_installed = InstallCodeHandlerLocked(); + + // A warning was already issued for the install failing + if (s_code_handler_installed != Installation::Installed) + return; + } } - // If the last block that just executed ended with a BLR instruction then we can intercept it and - // redirect control into the Gecko Code Handler. The Code Handler will automatically BLR back to - // the original return address (which will still be in the link register). - if (PC != LR) + // We always do this to avoid problems with the stack since we're branching in random locations. + // Even with function call return hooks (PC == LR), hand coded assembler won't necessarily + // follow the ABI. [Volatile FPR, GPR, CR may not be volatile] + // The codehandler will STMW all of the GPR registers, but we need to fix the Stack's Red + // Zone, the LR, PC (return address) and the volatile floating point registers. + // Build a function call stack frame. + u32 SFP = GPR(1); // Stack Frame Pointer + GPR(1) -= 256; // Stack's Red Zone + GPR(1) -= 16 + 2 * 14 * sizeof(u64); // Our stack frame (HLE_Misc::GeckoReturnTrampoline) + GPR(1) -= 8; // Fake stack frame for codehandler + GPR(1) &= 0xFFFFFFF0; // Align stack to 16bytes + u32 SP = GPR(1); // Stack Pointer + PowerPC::HostWrite_U32(SP + 8, SP); + // SP + 4 is reserved for the codehandler to save LR to the stack. + PowerPC::HostWrite_U32(SFP, SP + 8); // Real stack frame + PowerPC::HostWrite_U32(PC, SP + 12); + PowerPC::HostWrite_U32(LR, SP + 16); + PowerPC::HostWrite_U32(PowerPC::CompactCR(), SP + 20); + // Registers FPR0->13 are volatile + for (int i = 0; i < 14; ++i) { - // We're at a random address in the middle of something so we have to do this the hard way. - // The codehandler will STMW all of the GPR registers, but we need to fix the Stack's Red - // Zone, the LR, PC (return address) and the volatile floating point registers. - // Build a function call stack frame. - u32 SFP = GPR(1); // Stack Frame Pointer - GPR(1) -= 224; // Stack's Red Zone - GPR(1) -= 16 + 2 * 14 * sizeof(u64); // Our stack frame (HLE_Misc::GeckoReturnTrampoline) - GPR(1) -= 8; // Fake stack frame for codehandler - GPR(1) &= 0xFFFFFFF0; // Align stack to 16bytes - u32 SP = GPR(1); // Stack Pointer - PowerPC::HostWrite_U32(SP + 8, SP); - // SP + 4 is reserved for the codehandler to save LR to the stack. - PowerPC::HostWrite_U32(SFP, SP + 8); // Real stack frame - PowerPC::HostWrite_U32(PC, SP + 12); - PowerPC::HostWrite_U32(LR, SP + 16); - // Registers FPR0->13 are volatile - for (int i = 0; i < 14; ++i) - { - PowerPC::HostWrite_U64(riPS0(i), SP + 24 + 2 * i * sizeof(u64)); - PowerPC::HostWrite_U64(riPS1(i), SP + 24 + (2 * i + 1) * sizeof(u64)); - } - LR = HLE_TRAMPOLINE_ADDRESS; - DEBUG_LOG(ACTIONREPLAY, "GeckoCodes: Initiating phantom branch-and-link. " - "PC = 0x%08X, SP = 0x%08X, SFP = 0x%08X", - PC, SP, SFP); + PowerPC::HostWrite_U64(riPS0(i), SP + 24 + 2 * i * sizeof(u64)); + PowerPC::HostWrite_U64(riPS1(i), SP + 24 + (2 * i + 1) * sizeof(u64)); } + DEBUG_LOG(ACTIONREPLAY, "GeckoCodes: Initiating phantom branch-and-link. " + "PC = 0x%08X, SP = 0x%08X, SFP = 0x%08X", + PC, SP, SFP); + LR = HLE_TRAMPOLINE_ADDRESS; PC = NPC = ENTRY_POINT; } diff --git a/Source/Core/Core/HLE/HLE_Misc.cpp b/Source/Core/Core/HLE/HLE_Misc.cpp index e6a5525bf8..ce1295e06f 100644 --- a/Source/Core/Core/HLE/HLE_Misc.cpp +++ b/Source/Core/Core/HLE/HLE_Misc.cpp @@ -68,6 +68,7 @@ void GeckoReturnTrampoline() GPR(1) = PowerPC::HostRead_U32(SP + 8); NPC = PowerPC::HostRead_U32(SP + 12); LR = PowerPC::HostRead_U32(SP + 16); + PowerPC::ExpandCR(PowerPC::HostRead_U32(SP + 20)); for (int i = 0; i < 14; ++i) { riPS0(i) = PowerPC::HostRead_U64(SP + 24 + 2 * i * sizeof(u64)); diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index e17a1d7720..f9e9890f0a 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -20,6 +20,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonPaths.h" #include "Common/FileUtil.h" #include "Common/IniFile.h" @@ -203,6 +204,32 @@ static void ApplyPatches(const std::vector& patches) } } +// Requires MSR.DR, MSR.IR +// There's no perfect way to do this, it's just a heuristic. +// We require at least 2 stack frames, if the stack is shallower than that then it won't work. +static bool IsStackSane() +{ + _dbg_assert_(ACTIONREPLAY, UReg_MSR(MSR).DR && UReg_MSR(MSR).IR); + + // Check the stack pointer + u32 SP = GPR(1); + if (!PowerPC::HostIsRAMAddress(SP)) + return false; + + // Read the frame pointer from the stack (find 2nd frame from top), assert that it makes sense + u32 next_SP = PowerPC::HostRead_U32(SP); + if (next_SP <= SP || !PowerPC::HostIsRAMAddress(next_SP) || + !PowerPC::HostIsRAMAddress(next_SP + 4)) + return false; + + // Check the link register makes sense (that it points to a valid IBAT address) + auto insn = PowerPC::TryReadInstruction(PowerPC::HostRead_U32(next_SP + 4)); + if (!insn.valid || !insn.hex) + return false; + + return true; +} + bool ApplyFramePatches() { // Because we're using the VI Interrupt to time this instead of patching the game with a @@ -210,7 +237,7 @@ bool ApplyFramePatches() // We deal with this by returning false so that SystemTimers will reschedule us in a few cycles // where we can try again after the CPU hopefully returns back to the normal instruction flow. UReg_MSR msr = MSR; - if (!msr.DR || !msr.IR) + if (!msr.DR || !msr.IR || !IsStackSane()) { INFO_LOG( ACTIONREPLAY, From 09372a55da3ed91bff343c6bfda1405149fddbd6 Mon Sep 17 00:00:00 2001 From: EmptyChaos Date: Fri, 30 Sep 2016 16:19:47 +0000 Subject: [PATCH 10/10] GeckoCode: Save installation state to savestates Because of the way this works, randomly overwriting the handler when loading a savestate will break things because of the self-modifying nature of the handler. --- Source/Core/Core/GeckoCode.cpp | 44 ++++++++++++++++++++++---------- Source/Core/Core/GeckoCode.h | 4 +++ Source/Core/Core/PatchEngine.cpp | 2 +- Source/Core/Core/State.cpp | 5 +++- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 2eaead2f8d..b3f6b096f6 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -7,9 +7,9 @@ #include #include +#include "Common/ChunkFile.h" #include "Common/CommonPaths.h" #include "Common/FileUtil.h" -#include "Common/Thread.h" #include "Core/ConfigManager.h" #include "Core/GeckoCode.h" @@ -55,9 +55,12 @@ void SetActiveCodes(const std::vector& gcodes) std::lock_guard lk(s_active_codes_lock); s_active_codes.clear(); - s_active_codes.reserve(gcodes.size()); - std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), - [](const GeckoCode& code) { return code.enabled; }); + if (SConfig::GetInstance().bEnableCheats) + { + s_active_codes.reserve(gcodes.size()); + std::copy_if(gcodes.begin(), gcodes.end(), std::back_inserter(s_active_codes), + [](const GeckoCode& code) { return code.enabled; }); + } s_active_codes.shrink_to_fit(); s_code_handler_installed = Installation::Uninstalled; @@ -158,11 +161,25 @@ static Installation InstallCodeHandlerLocked() return Installation::Installed; } -// FIXME: Gecko needs to participate in the savestate system (remember installation state). -// Current bug: Loading a savestate causes the handler to be replaced, if the PC is inside it -// and the on disk codehandler.bin is different then the PC will effectively be pointing to -// a random instruction different from the one when the state was created and break or crash. -// [Also, self-modifying handler will break it since the modifications will be reset] +// Gecko needs to participate in the savestate system because the handler is embedded within the +// game directly. The PC may be inside the code handler in the save state and the codehandler.bin +// on the disk may be different resulting in the PC pointing at a different instruction and then +// the game malfunctions or crashes. [Also, self-modifying codes will break since the +// modifications will be reset] +void DoState(PointerWrap& p) +{ + std::lock_guard codes_lock(s_active_codes_lock); + p.Do(s_code_handler_installed); + // FIXME: The active codes list will disagree with the embedded GCT +} + +void Shutdown() +{ + std::lock_guard codes_lock(s_active_codes_lock); + s_active_codes.clear(); + s_code_handler_installed = Installation::Uninstalled; +} + void RunCodeHandler() { if (!SConfig::GetInstance().bEnableCheats) @@ -171,13 +188,12 @@ void RunCodeHandler() // NOTE: Need to release the lock because of GUI deadlocks with PanicAlert in HostWrite_* { std::lock_guard codes_lock(s_active_codes_lock); - // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be - // fixed within 1 frame of the last error. - if (s_active_codes.empty() || s_code_handler_installed == Installation::Failed) - return; - if (s_code_handler_installed != Installation::Installed) { + // Don't spam retry if the install failed. The corrupt / missing disk file is not likely to be + // fixed within 1 frame of the last error. + if (s_active_codes.empty() || s_code_handler_installed == Installation::Failed) + return; s_code_handler_installed = InstallCodeHandlerLocked(); // A warning was already issued for the install failing diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 4fa17c8c5e..8324f85b02 100644 --- a/Source/Core/Core/GeckoCode.h +++ b/Source/Core/Core/GeckoCode.h @@ -9,6 +9,8 @@ #include "Common/CommonTypes.h" +class PointerWrap; + namespace Gecko { class GeckoCode @@ -53,5 +55,7 @@ constexpr u32 MAGIC_GAMEID = 0xD01F1BAD; void SetActiveCodes(const std::vector& gcodes); void RunCodeHandler(); +void Shutdown(); +void DoState(PointerWrap&); } // namespace Gecko diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp index f9e9890f0a..656bb44c72 100644 --- a/Source/Core/Core/PatchEngine.cpp +++ b/Source/Core/Core/PatchEngine.cpp @@ -260,7 +260,7 @@ void Shutdown() onFrame.clear(); speedHacks.clear(); ActionReplay::ApplyCodes({}); - Gecko::SetActiveCodes({}); + Gecko::Shutdown(); } } // namespace diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index c8801a9373..89bd1d7211 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -23,6 +23,7 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/GeckoCode.h" #include "Core/HW/HW.h" #include "Core/HW/Wiimote.h" #include "Core/Host.h" @@ -70,7 +71,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -static const u32 STATE_VERSION = 60; // Last changed in PR 4242 +static const u32 STATE_VERSION = 61; // Last changed in PR 4216 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, @@ -174,6 +175,8 @@ static std::string DoState(PointerWrap& p) p.DoMarker("HW"); Movie::DoState(p); p.DoMarker("Movie"); + Gecko::DoState(p); + p.DoMarker("Gecko"); #if defined(HAVE_LIBAV) || defined(_WIN32) AVIDump::DoState();