Merge pull request #4216 from EmptyChaos/geckocodes-cleanup

GeckoCodes: Don't run PPC code in CoreTiming callbacks
This commit is contained in:
shuffle2 2016-10-02 22:37:51 -07:00 committed by GitHub
commit fba6801851
15 changed files with 371 additions and 150 deletions

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -2,12 +2,14 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <iterator>
#include <mutex>
#include <vector>
#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<GeckoCode> active_codes;
static std::mutex active_codes_lock;
static std::vector<GeckoCode> s_active_codes;
static std::mutex s_active_codes_lock;
void SetActiveCodes(const std::vector<GeckoCode>& gcodes)
{
std::lock_guard<std::mutex> lk(active_codes_lock);
std::lock_guard<std::mutex> 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<u32>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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

View File

@ -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<GeckoCode>& gcodes);
bool RunActiveCodes();
void RunCodeHandler();
void Shutdown();
void DoState(PointerWrap&);
} // namespace Gecko

View File

@ -2,6 +2,9 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <map>
#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<u32>(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

View File

@ -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);

View File

@ -2,21 +2,17 @@
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <cmath>
#include <string>
#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));
}
}
}

View File

@ -9,5 +9,6 @@ namespace HLE_Misc
void HLEPanicAlert();
void UnimplementedFunction();
void HBReload();
void HLEGeckoCodehandler();
void GeckoCodeHandlerICacheFlush();
void GeckoReturnTrampoline();
}

View File

@ -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)

View File

@ -20,6 +20,7 @@
#include <string>
#include <vector>
#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<Patch>& 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

View File

@ -43,7 +43,7 @@ int GetSpeedhackCycles(const u32 addr);
void LoadPatchSection(const std::string& section, std::vector<Patch>& patches, IniFile& globalIni,
IniFile& localIni);
void LoadPatches();
void ApplyFramePatches();
bool ApplyFramePatches();
void Shutdown();
inline int GetPatchTypeCharLength(PatchType type)

View File

@ -565,6 +565,11 @@ u32 HostRead_U32(const u32 address)
return var;
}
u64 HostRead_U64(const u32 address)
{
return ReadFromHardware<FLAG_NO_EXCEPTION, u64>(address);
}
void HostWrite_U8(const u8 var, const u32 address)
{
WriteToHardware<FLAG_NO_EXCEPTION, u8>(address, var);

View File

@ -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);

View File

@ -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();