Merge pull request #4216 from EmptyChaos/geckocodes-cleanup
GeckoCodes: Don't run PPC code in CoreTiming callbacks
This commit is contained in:
commit
fba6801851
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,6 @@ namespace HLE_Misc
|
|||
void HLEPanicAlert();
|
||||
void UnimplementedFunction();
|
||||
void HBReload();
|
||||
void HLEGeckoCodehandler();
|
||||
void GeckoCodeHandlerICacheFlush();
|
||||
void GeckoReturnTrampoline();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue