From f48969451530685dd5bb1af3e456bdca7bac8a31 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 30 May 2014 22:45:06 -0700 Subject: [PATCH] Add the option to hook, rather than replace, funcs. This can be useful for debugging or developing translations/game hacks, and also gives us options when dealing with GLES incompatibilities. --- Core/HLE/ReplaceTables.cpp | 36 ++++++++++++++++++++++--------- Core/HLE/ReplaceTables.h | 6 +++++- Core/MIPS/ARM/ArmJit.cpp | 43 ++++++++++++++++++++++++-------------- Core/MIPS/MIPSAnalyst.cpp | 2 +- Core/MIPS/MIPSInt.cpp | 11 +++++++--- Core/MIPS/x86/Jit.cpp | 39 ++++++++++++++++++++++------------ 6 files changed, 93 insertions(+), 44 deletions(-) diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 0c0ab7ce5b..0b00e90e36 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -23,6 +23,7 @@ #include "Core/Debugger/Breakpoints.h" #include "Core/MemMap.h" #include "Core/MIPS/JitCommon/JitCommon.h" +#include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/HLE/ReplaceTables.h" #include "Core/HLE/FunctionWrappers.h" @@ -515,20 +516,35 @@ const ReplacementTableEntry *GetReplacementFunc(int i) { return &entries[i]; } -void WriteReplaceInstruction(u32 address, u64 hash, int size) { +static void WriteReplaceInstruction(u32 address, int index) { + const u32 prevInstr = Memory::Read_U32(address); + if (MIPS_IS_REPLACEMENT(prevInstr)) { + return; + } + if (MIPS_IS_RUNBLOCK(prevInstr)) { + // Likely already both replaced and jitted. Ignore. + return; + } + replacedInstructions[address] = prevInstr; + Memory::Write_U32(MIPS_EMUHACK_CALL_REPLACEMENT | index, address); +} + +void WriteReplaceInstructions(u32 address, u64 hash, int size) { int index = GetReplacementFuncIndex(hash, size); if (index >= 0) { - u32 prevInstr = Memory::Read_U32(address); - if (MIPS_IS_REPLACEMENT(prevInstr)) { - return; + auto entry = GetReplacementFunc(index); + if (entry->flags & REPFLAG_HOOKEXIT) { + // When hooking func exit, we search for jr ra, and replace those. + for (u32 offset = 0; offset < (u32)size; offset += 4) { + const u32 op = Memory::Read_U32(address + offset); + if (op == MIPS_MAKE_JR_RA()) { + WriteReplaceInstruction(address, index); + } + } + } else { + WriteReplaceInstruction(address, index); } - if (MIPS_IS_RUNBLOCK(prevInstr)) { - // Likely already both replaced and jitted. Ignore. - return; - } - replacedInstructions[address] = prevInstr; INFO_LOG(HLE, "Replaced %s at %08x with hash %016llx", entries[index].name, address, hash); - Memory::Write_U32(MIPS_EMUHACK_CALL_REPLACEMENT | (int)index, address); } } diff --git a/Core/HLE/ReplaceTables.h b/Core/HLE/ReplaceTables.h index 8c8f1d5ee7..f438533ddc 100644 --- a/Core/HLE/ReplaceTables.h +++ b/Core/HLE/ReplaceTables.h @@ -40,6 +40,10 @@ typedef int (* ReplaceFunc)(); enum { REPFLAG_ALLOWINLINE = 1, + // Note that this will re-execute in a funciton that loops at start. + REPFLAG_HOOKENTER = 2, + // Only hooks jr ra, so only use on funcs that have that. + REPFLAG_HOOKEXIT = 4, }; // Kind of similar to HLE functions but with different data. @@ -57,7 +61,7 @@ int GetNumReplacementFuncs(); int GetReplacementFuncIndex(u64 hash, int funcSize); const ReplacementTableEntry *GetReplacementFunc(int index); -void WriteReplaceInstruction(u32 address, u64 hash, int size); +void WriteReplaceInstructions(u32 address, u64 hash, int size); void RestoreReplacedInstruction(u32 address); void RestoreReplacedInstructions(u32 startAddr, u32 endAddr); bool GetReplacedOpAt(u32 address, u32 *op); diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index 5f477e7043..6d6fbf7b08 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -357,6 +357,11 @@ bool Jit::ReplaceJalTo(u32 dest) { return false; } + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // If it's a hook, we can't replace the jal, we have to go inside the func. + return false; + } + // Warning - this might be bad if the code at the destination changes... if (entry->flags & REPFLAG_ALLOWINLINE) { // Jackpot! Just do it, no flushing. The code will be entirely inlined. @@ -397,11 +402,18 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op) if (entry->jitReplaceFunc) { MIPSReplaceFunc repl = entry->jitReplaceFunc; int cycles = (this->*repl)(); - FlushAll(); - // Flushed, so R1 is safe. - LDR(R1, CTXREG, MIPS_REG_RA * 4); - js.downcountAmount = cycles; - WriteExitDestInR(R1); + + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // Compile the original instruction at this address. We ignore cycles for hooks. + MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true)); + } else { + FlushAll(); + // Flushed, so R1 is safe. + LDR(R1, CTXREG, MIPS_REG_RA * 4); + js.downcountAmount = cycles; + WriteExitDestInR(R1); + js.compiling = false; + } } else if (entry->replaceFunc) { FlushAll(); // Standard function call, nothing fancy. @@ -412,21 +424,20 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op) MOVI2R(R0, (u32)entry->replaceFunc); BL(R0); } - // Alternatively, we could inline it here, instead of calling out, if it's a function - // we can emit. - LDR(R1, CTXREG, MIPS_REG_RA * 4); - WriteDownCountR(R0); - js.downcountAmount = 0; // we just subtracted most of it - WriteExitDestInR(R1); + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // Compile the original instruction at this address. We ignore cycles for hooks. + MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true)); + } else { + LDR(R1, CTXREG, MIPS_REG_RA * 4); + WriteDownCountR(R0); + js.downcountAmount = 0; // we just subtracted most of it + WriteExitDestInR(R1); + js.compiling = false; + } } else { ERROR_LOG(HLE, "Replacement function %s has neither jit nor regular impl", entry->name); } - - js.compiling = false; - - // We could even do this in the jal that is branching to the function - // but having the op is necessary for the interpreter anyway. } void Jit::Comp_Generic(MIPSOpcode op) diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index 5e13526247..eec715f835 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -953,7 +953,7 @@ skip: lock_guard guard(functions_lock); for (size_t i = 0; i < functions.size(); i++) { - WriteReplaceInstruction(functions[i].start, functions[i].hash, functions[i].size); + WriteReplaceInstructions(functions[i].start, functions[i].hash, functions[i].size); } } diff --git a/Core/MIPS/MIPSInt.cpp b/Core/MIPS/MIPSInt.cpp index d480bcd9dd..602ee61f67 100644 --- a/Core/MIPS/MIPSInt.cpp +++ b/Core/MIPS/MIPSInt.cpp @@ -1022,11 +1022,16 @@ namespace MIPSInt const ReplacementTableEntry *entry = GetReplacementFunc(index); if (entry && entry->replaceFunc) { entry->replaceFunc(); + + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // Interpret the original instruction under the hook. + MIPSInterpret(Memory::Read_Instruction(PC, true)); + } else { + PC = currentMIPS->r[MIPS_REG_RA]; + } } else { ERROR_LOG(CPU, "Bad replacement function index %i", index); - } - - PC = currentMIPS->r[MIPS_REG_RA]; + } } diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index e6c9b3d0b3..00c78c6069 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -423,6 +423,11 @@ bool Jit::ReplaceJalTo(u32 dest) { return false; } + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // If it's a hook, we can't replace the jal, we have to go inside the func. + return false; + } + // Warning - this might be bad if the code at the destination changes... if (entry->flags & REPFLAG_ALLOWINLINE) { // Jackpot! Just do it, no flushing. The code will be entirely inlined. @@ -470,26 +475,34 @@ void Jit::Comp_ReplacementFunc(MIPSOpcode op) if (entry->jitReplaceFunc) { MIPSReplaceFunc repl = entry->jitReplaceFunc; int cycles = (this->*repl)(); - FlushAll(); - MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA])); - js.downcountAmount = cycles; - WriteExitDestInReg(ECX); - js.compiling = false; + + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // Compile the original instruction at this address. We ignore cycles for hooks. + MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true)); + } else { + FlushAll(); + MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA])); + js.downcountAmount = cycles; + WriteExitDestInReg(ECX); + js.compiling = false; + } } else if (entry->replaceFunc) { FlushAll(); // Standard function call, nothing fancy. // The function returns the number of cycles it took in EAX. ABI_CallFunction(entry->replaceFunc); - // Alternatively, we could inline it here, instead of calling out, if it's a function - // we can emit. - MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA])); - SUB(32, M(¤tMIPS->downcount), R(EAX)); - js.downcountAmount = 0; // we just subtracted most of it - WriteExitDestInReg(ECX); - - js.compiling = false; + if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) { + // Compile the original instruction at this address. We ignore cycles for hooks. + MIPSCompileOp(Memory::Read_Instruction(js.compilerPC, true)); + } else { + MOV(32, R(ECX), M(¤tMIPS->r[MIPS_REG_RA])); + SUB(32, M(¤tMIPS->downcount), R(EAX)); + js.downcountAmount = 0; // we just subtracted most of it + WriteExitDestInReg(ECX); + js.compiling = false; + } } else { ERROR_LOG(HLE, "Replacement function %s has neither jit nor regular impl", entry->name); }