diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 44d01a87f2..37f51eaaff 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,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(0x800018a8, "GeckoCodehandler"); + HLE::Patch(Gecko::ENTRY_POINT, "GeckoCodehandler"); + // 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 68a975476b..7825f0132a 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" @@ -177,9 +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 - PatchEngine::ApplyFramePatches(); - return true; } 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/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp index 6983eb6224..b3f6b096f6 100644 --- a/Source/Core/Core/GeckoCode.cpp +++ b/Source/Core/Core/GeckoCode.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. +#include +#include #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" @@ -16,128 +18,137 @@ namespace Gecko { -static const u32 INSTALLER_BASE_ADDRESS = 0x80001800; -static const u32 INSTALLER_END_ADDRESS = 0x80003000; +static constexpr u32 CODE_SIZE = 8; // 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; +enum class Installation +{ + Uninstalled, + Installed, + Failed +}; + +static Installation s_code_handler_installed = Installation::Uninstalled; // 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(); - - // add enabled codes - for (const GeckoCode& gecko_code : gcodes) + s_active_codes.clear(); + if (SConfig::GetInstance().bEnableCheats) { - 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); - } + 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(); - code_handler_installed = false; + s_code_handler_installed = Installation::Uninstalled; } -static bool InstallCodeHandler() +// Requires s_active_codes_lock +// NOTE: Refer to "codehandleronly.s" from Gecko OS. +static Installation 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."); - return false; + ERROR_LOG(ACTIONREPLAY, "Could not enable cheats because " GECKO_CODE_HANDLER " was missing."); + return Installation::Failed; } - 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 Installation::Failed; + } + 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); + // 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 PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address); PowerPC::HostWrite_U32(0x00d0c0de, codelist_base_address + 4); - std::lock_guard lk(active_codes_lock); + // 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; - int i = 0; - - for (const GeckoCode& active_code : active_codes) + // 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 + 24 + 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; - } - } + 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; } } - PowerPC::HostWrite_U32(0xff000000, codelist_base_address + 8 + i); - PowerPC::HostWrite_U32(0x00000000, codelist_base_address + 12 + i); + 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, 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); @@ -147,44 +158,79 @@ 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; + return Installation::Installed; +} + +// 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 && active_codes.size() > 0) + if (!SConfig::GetInstance().bEnableCheats) + return; + + // NOTE: Need to release the lock because of GUI deadlocks with PanicAlert in HostWrite_* { - if (!code_handler_installed || PowerPC::HostRead_U32(INSTALLER_BASE_ADDRESS) - 0xd01f1bad > 5) - code_handler_installed = InstallCodeHandler(); - - if (!code_handler_installed) + std::lock_guard codes_lock(s_active_codes_lock); + if (s_code_handler_installed != Installation::Installed) { - // A warning was already issued. - return; - } + // 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(); - 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; + // A warning was already issued for the install failing + if (s_code_handler_installed != Installation::Installed) + return; } } + + // 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) + { + 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; } } // namespace Gecko diff --git a/Source/Core/Core/GeckoCode.h b/Source/Core/Core/GeckoCode.h index 37cc05f768..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 @@ -33,8 +35,27 @@ 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 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::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. +// +// 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); -bool RunActiveCodes(); void RunCodeHandler(); +void Shutdown(); +void DoState(PointerWrap&); } // namespace Gecko diff --git a/Source/Core/Core/HLE/HLE.cpp b/Source/Core/Core/HLE/HLE.cpp index 2c60f7d2e6..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,7 +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::HLEGeckoCodehandler, 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_FIXED}, }; static const SPatch OSBreakPoints[] = { @@ -69,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; } } @@ -81,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); } @@ -97,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) @@ -111,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(); } @@ -152,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; @@ -169,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); diff --git a/Source/Core/Core/HLE/HLE_Misc.cpp b/Source/Core/Core/HLE/HLE_Misc.cpp index a59b474916..ce1295e06f 100644 --- a/Source/Core/Core/HLE/HLE_Misc.cpp +++ b/Source/Core/Core/HLE/HLE_Misc.cpp @@ -2,21 +2,17 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include -#include - -#include "Common/CommonTypes.h" -#include "Core/ConfigManager.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() @@ -39,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. @@ -47,17 +43,36 @@ 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(); } + +// 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); + 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)); + 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/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 9a4302c15b..656bb44c72 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,22 +204,55 @@ static void ApplyPatches(const std::vector& patches) } } -void ApplyFramePatches() +// 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() { - // 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; + _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 + // 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 || !IsStackSane()) + { + 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(); ActionReplay::RunAllActive(); - MSR = oldMSR; + + return true; } void Shutdown() @@ -226,7 +260,7 @@ void Shutdown() onFrame.clear(); speedHacks.clear(); ActionReplay::ApplyCodes({}); - Gecko::SetActiveCodes({}); + Gecko::Shutdown(); } } // namespace 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) 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); 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();