diff --git a/XexUtils.vcxproj b/XexUtils.vcxproj
index a789b1f..5a1ad75 100644
--- a/XexUtils.vcxproj
+++ b/XexUtils.vcxproj
@@ -61,6 +61,7 @@
+
@@ -74,6 +75,7 @@
+
diff --git a/XexUtils.vcxproj.filters b/XexUtils.vcxproj.filters
index a3bd3b9..8efc388 100644
--- a/XexUtils.vcxproj.filters
+++ b/XexUtils.vcxproj.filters
@@ -38,6 +38,9 @@
Source Files
+
+ Source Files
+
@@ -73,5 +76,8 @@
Header Files
+
+ Header Files
+
\ No newline at end of file
diff --git a/include/Detour.h b/include/Detour.h
new file mode 100644
index 0000000..bbae2d7
--- /dev/null
+++ b/include/Detour.h
@@ -0,0 +1,168 @@
+// Original version made my iMoD1998 (https://gist.github.com/iMoD1998/4aa48d5c990535767a3fc3251efc0348)
+
+#pragma once
+
+
+#define MASK_N_BITS(N) ((1 << (N)) - 1)
+
+#define POWERPC_HI(X) ((X >> 16) & 0xFFFF)
+#define POWERPC_LO(X) (X & 0xFFFF)
+
+// PowerPC most significant bit is addressed as bit 0 in documentation.
+#define POWERPC_BIT32(N) (31 - N)
+
+// Opcode is bits 0-5.
+// Allowing for op codes ranging from 0-63.
+#define POWERPC_OPCODE(OP) (OP << 26)
+#define POWERPC_OPCODE_ADDI POWERPC_OPCODE(14)
+#define POWERPC_OPCODE_ADDIS POWERPC_OPCODE(15)
+#define POWERPC_OPCODE_BC POWERPC_OPCODE(16)
+#define POWERPC_OPCODE_B POWERPC_OPCODE(18)
+#define POWERPC_OPCODE_BCCTR POWERPC_OPCODE(19)
+#define POWERPC_OPCODE_ORI POWERPC_OPCODE(24)
+#define POWERPC_OPCODE_EXTENDED POWERPC_OPCODE(31) // Use extended opcodes.
+#define POWERPC_OPCODE_STW POWERPC_OPCODE(36)
+#define POWERPC_OPCODE_LWZ POWERPC_OPCODE(32)
+#define POWERPC_OPCODE_LD POWERPC_OPCODE(58)
+#define POWERPC_OPCODE_STD POWERPC_OPCODE(62)
+#define POWERPC_OPCODE_MASK POWERPC_OPCODE(63)
+
+#define POWERPC_EXOPCODE(OP) (OP << 1)
+#define POWERPC_EXOPCODE_BCCTR POWERPC_EXOPCODE(528)
+#define POWERPC_EXOPCODE_MTSPR POWERPC_EXOPCODE(467)
+
+// SPR field is encoded as two 5 bit bitfields.
+#define POWERPC_SPR(SPR) static_cast(((SPR & 0x1F) << 5) | ((SPR >> 5) & 0x1F))
+
+// Instruction helpers.
+//
+// rD - Destination register.
+// rS - Source register.
+// rA/rB - Register inputs.
+// SPR - Special purpose register.
+// UIMM/SIMM - Unsigned/signed immediate.
+#define POWERPC_ADDI(rD, rA, SIMM) static_cast(POWERPC_OPCODE_ADDI | (rD << POWERPC_BIT32(10)) | (rA << POWERPC_BIT32(15)) | SIMM)
+#define POWERPC_ADDIS(rD, rA, SIMM) static_cast(POWERPC_OPCODE_ADDIS | (rD << POWERPC_BIT32(10)) | (rA << POWERPC_BIT32(15)) | SIMM)
+#define POWERPC_LIS(rD, SIMM) POWERPC_ADDIS(rD, 0, SIMM) // Mnemonic for addis %rD, 0, SIMM
+#define POWERPC_LI(rD, SIMM) POWERPC_ADDIrD, 0, SIMM) // Mnemonic for addi %rD, 0, SIMM
+#define POWERPC_MTSPR(SPR, rS) static_cast(POWERPC_OPCODE_EXTENDED | (rS << POWERPC_BIT32(10)) | (POWERPC_SPR(SPR) << POWERPC_BIT32(20)) | POWERPC_EXOPCODE_MTSPR)
+#define POWERPC_MTCTR(rS) POWERPC_MTSPR(9, rS) // Mnemonic for mtspr 9, rS
+#define POWERPC_ORI(rS, rA, UIMM) static_cast(POWERPC_OPCODE_ORI | (rS << POWERPC_BIT32(10)) | (rA << POWERPC_BIT32(15)) | UIMM)
+#define POWERPC_BCCTR(BO, BI, LK) static_cast(POWERPC_OPCODE_BCCTR | (BO << POWERPC_BIT32(10)) | (BI << POWERPC_BIT32(15) | LK & 1) | POWERPC_EXOPCODE_BCCTR)
+#define POWERPC_STD(rS, DS, rA) static_cast(POWERPC_OPCODE_STD | (rS << POWERPC_BIT32(10)) | (rA << POWERPC_BIT32(15)) | (static_cast(DS) & 0xFFFF))
+#define POWERPC_LD(rS, DS, rA) static_cast(POWERPC_OPCODE_LD | (rS << POWERPC_BIT32(10)) | (rA << POWERPC_BIT32(15)) | (static_cast(DS) & 0xFFFF))
+
+// Branch related fields.
+#define POWERPC_BRANCH_LINKED 1
+#define POWERPC_BRANCH_ABSOLUTE 2
+#define POWERPC_BRANCH_TYPE_MASK (POWERPC_BRANCH_LINKED | POWERPC_BRANCH_ABSOLUTE)
+
+#define POWERPC_BRANCH_OPTIONS_ALWAYS (20)
+
+
+namespace XexUtils
+{
+
+class Detour
+{
+public:
+ /**
+ * Constructor
+ *
+ * @param pHookSource - The function that will be hooked.
+ * @param pHookTarget - The function that the hook will be redirected to.
+ */
+ Detour(DWORD dwHookSourceAddress, const void *pHookTarget);
+
+ /**
+ * Destructor
+ */
+ ~Detour();
+
+ /**
+ * Sets up the detour.
+ * @return true if the detour was set up successfully, false otherwise.
+ */
+ bool Install();
+
+ /**
+ * Removes the detour.
+ * @return true if the detour was removed successfully, false otherwise.
+ */
+ bool Remove();
+
+ /**
+ * Get a pointer to the original function.
+ * @return A pointer to the original function.
+ */
+ template
+ T GetOriginal() const
+ {
+ return reinterpret_cast(m_pbTrampolineAddress);
+ }
+
+private:
+ // The funtion we are pointing the hook to.
+ const void *m_pHookTarget;
+
+ // The function we are hooking.
+ DWORD m_dwHookSourceAddress;
+
+ // Pointer to the trampoline for this detour.
+ byte *m_pbTrampolineAddress;
+
+ // Any bytes overwritten by the hook.
+ byte m_pOriginalInstructions[30];
+
+ // The amount of bytes overwritten by the hook.
+ size_t m_uiOriginalLength;
+
+ // Shared
+ static byte s_pTrampolineBuffer[200 * 20];
+ static size_t s_uiTrampolineSize;
+
+ /**
+ * Writes an unconditional branch to the destination address that will branch to the target address.
+ *
+ * @param Destination - Where the branch will be written to.
+ * @param BranchTarget - The address the branch will jump to.
+ * @param Linked - Branch is a call or a jump? aka bl or b
+ * @param PreserveRegister - Preserve the register clobbered after loading the branch address.
+ * @return The size of the branch in bytes.
+ */
+ static size_t WriteFarBranch(void *pDestination, const void *pBranchTarget, bool bLinked = true, bool bPreserveRegister = false);
+
+ /**
+ * Writes both conditional and unconditional branches using the count register to the destination address that will branch to the target address.
+ *
+ * @param Destination - Where the branch will be written to.
+ * @param BranchTarget - The address the branch will jump to.
+ * @param Linked - Branch is a call or a jump? aka bl or b
+ * @param PreserveRegister - Preserve the register clobbered after loading the branch address.
+ * @param BranchOptions - Options for determining when a branch to be followed.
+ * @param ConditionRegisterBit - The bit of the condition register to compare.
+ * @param RegisterIndex - Register to use when loading the destination address into the count register.
+ * @return The size of the branch in bytes.
+ */
+ static size_t WriteFarBranchEx(void *pDestination, const void *pBranchTarget, bool bLinked = false, bool bPreserveRegister = false, DWORD dwBranchOptions = POWERPC_BRANCH_OPTIONS_ALWAYS, byte bConditionRegisterBit = 0, byte bRegisterIndex = 0);
+
+ /**
+ * Copies and fixes relative branch instructions to a new location.
+ *
+ * @param Destination - Where to write the new branch.
+ * @param Source - Address to the instruction that is being relocated.
+ * @return The size of the branch in bytes.
+ */
+ static size_t RelocateBranch(DWORD *pdwDestination, const DWORD *pdwSource);
+
+ /**
+ * Copies an instruction enusuring things such as PC relative offsets are fixed.
+ *
+ * @param Destination - Where to write the new instruction(s).
+ * @param Source - Address to the instruction that is being copied.
+ * @return The size of the instruction in bytes.
+ */
+ static size_t CopyInstruction(DWORD *pdwDestination, const DWORD *pdwSource);
+};
+
+}
diff --git a/include/Memory.h b/include/Memory.h
index 33c0b93..0d811a9 100644
--- a/include/Memory.h
+++ b/include/Memory.h
@@ -17,9 +17,6 @@ class Memory
// Start a thread with special creation flags.
static void ThreadEx(LPTHREAD_START_ROUTINE pStartAddress, void *pParameters, DWORD dwCreationFlags);
- // Hook a function.
- static void HookFunctionStart(DWORD *pdwAddress, DWORD *pdwSaveStub, DWORD dwDestination);
-
// Write data at dwAddress.
template
static void Write(DWORD dwAddress, T data)
@@ -45,13 +42,6 @@ class Memory
return *(T *)dwAddress;
}
-private:
- // Insert a jump instruction into an existing function to jump to another function.
- static void PatchInJump(DWORD *pdwAddress, DWORD dwDestination, bool bLinked);
-
- static void GLPR();
-
- static DWORD RelinkGPLR(int nOffset, DWORD *pdwSaveStubAddr, DWORD *pdwOrgAddr);
};
}
diff --git a/include/XexUtils.h b/include/XexUtils.h
index 73301b7..7e911ca 100644
--- a/include/XexUtils.h
+++ b/include/XexUtils.h
@@ -11,5 +11,6 @@
#include "Kernel.h"
#include "Math_.h"
#include "Memory.h"
+#include "Detour.h"
#include "Xam_.h"
#include "Log.h"
diff --git a/src/Detour.cpp b/src/Detour.cpp
new file mode 100644
index 0000000..00220b1
--- /dev/null
+++ b/src/Detour.cpp
@@ -0,0 +1,189 @@
+#include "pch.h"
+#include "Detour.h"
+
+
+namespace XexUtils
+{
+
+byte Detour::s_pTrampolineBuffer[200 * 20] = {};
+size_t Detour::s_uiTrampolineSize = 0;
+
+Detour::Detour(DWORD dwHookSourceAddress, const void *pHookTarget)
+ : m_dwHookSourceAddress(dwHookSourceAddress), m_pHookTarget(pHookTarget), m_pbTrampolineAddress(nullptr), m_uiOriginalLength(0)
+{
+ Install();
+}
+
+Detour::~Detour()
+{
+ Remove();
+}
+
+bool Detour::Install()
+{
+ // Check if we are already hooked
+ if (m_uiOriginalLength != 0)
+ return false;
+
+ const size_t HookSize = WriteFarBranch(nullptr, m_pHookTarget, false, false);
+
+ // Save the original instructions for unhooking later on
+ memcpy(m_pOriginalInstructions, reinterpret_cast(m_dwHookSourceAddress), HookSize);
+
+ m_uiOriginalLength = HookSize;
+
+ // Create trampoline and copy and fix instructions to the trampoline
+ m_pbTrampolineAddress = &s_pTrampolineBuffer[s_uiTrampolineSize];
+
+ for (size_t i = 0; i < (HookSize / 4); i++)
+ {
+ const auto pdwInstruction = reinterpret_cast(m_dwHookSourceAddress + (i * 4));
+
+ s_uiTrampolineSize += CopyInstruction(reinterpret_cast(&s_pTrampolineBuffer[s_uiTrampolineSize]), pdwInstruction);
+ }
+
+ // Trampoline branches back to the original function after the branch we used to hook
+ const void *pAfterBranchAddress = reinterpret_cast(m_dwHookSourceAddress + HookSize);
+
+ s_uiTrampolineSize += WriteFarBranch(&s_pTrampolineBuffer[s_uiTrampolineSize], pAfterBranchAddress, false, true);
+
+ // Finally write the branch to the function that we are hooking
+ WriteFarBranch(reinterpret_cast(m_dwHookSourceAddress), m_pHookTarget, false, false);
+
+ return true;
+}
+
+bool Detour::Remove()
+{
+ if (m_dwHookSourceAddress && m_uiOriginalLength)
+ {
+ // Trying to remove a hook from a game function after the game has been closed could cause a segfault so we
+ // make sure the hook function is still loaded in memory
+ bool bIsHookSourceAddressValid = Kernel::MmIsAddressValid(reinterpret_cast(m_dwHookSourceAddress));
+ if (bIsHookSourceAddressValid)
+ memcpy(reinterpret_cast(m_dwHookSourceAddress), m_pOriginalInstructions, m_uiOriginalLength);
+
+ m_uiOriginalLength = 0;
+ m_dwHookSourceAddress = 0;
+
+ return true;
+ }
+
+ return false;
+}
+
+size_t Detour::WriteFarBranch(void *pDestination, const void *pBranchTarget, bool bLinked, bool bPreserveRegister)
+{
+ return WriteFarBranchEx(pDestination, pBranchTarget, bLinked, bPreserveRegister);
+}
+
+size_t Detour::WriteFarBranchEx(void *pDestination, const void *pBranchTarget, bool bLinked, bool bPreserveRegister, DWORD dwBranchOptions, byte bConditionRegisterBit, byte bRegisterIndex)
+{
+ const DWORD pBranchFarAsm[] =
+ {
+ POWERPC_LIS(bRegisterIndex, POWERPC_HI(reinterpret_cast(pBranchTarget))), // lis %rX, BranchTarget@hi
+ POWERPC_ORI(bRegisterIndex, bRegisterIndex, POWERPC_LO(reinterpret_cast(pBranchTarget))), // ori %rX, %rX, BranchTarget@lo
+ POWERPC_MTCTR(bRegisterIndex), // mtctr %rX
+ POWERPC_BCCTR(dwBranchOptions, bConditionRegisterBit, bLinked) // bcctr (bcctr 20, 0 == bctr)
+ };
+
+ const DWORD pBranchFarAsmPreserve[] =
+ {
+ POWERPC_STD(bRegisterIndex, -0x30, 1), // std %rX, -0x30(%r1)
+ POWERPC_LIS(bRegisterIndex, POWERPC_HI(reinterpret_cast(pBranchTarget))), // lis %rX, BranchTarget@hi
+ POWERPC_ORI(bRegisterIndex, bRegisterIndex, POWERPC_LO(reinterpret_cast(pBranchTarget))), // ori %rX, %rX, BranchTarget@lo
+ POWERPC_MTCTR(bRegisterIndex), // mtctr %rX
+ POWERPC_LD(bRegisterIndex, -0x30, 1), // lwz %rX, -0x30(%r1)
+ POWERPC_BCCTR(dwBranchOptions, bConditionRegisterBit, bLinked) // bcctr (bcctr 20, 0 == bctr)
+ };
+
+ const DWORD *pBranchAsm = bPreserveRegister ? pBranchFarAsmPreserve : pBranchFarAsm;
+ const DWORD dwBranchAsmSize = bPreserveRegister ? sizeof(pBranchFarAsmPreserve) : sizeof(pBranchFarAsm);
+
+ if (pDestination)
+ memcpy(pDestination, pBranchAsm, dwBranchAsmSize);
+
+ return dwBranchAsmSize;
+}
+
+size_t Detour::RelocateBranch(DWORD *pdwDestination, const DWORD *pdwSource)
+{
+ const DWORD dwInstruction = *pdwSource;
+ const DWORD dwInstructionAddress = reinterpret_cast(pdwSource);
+
+ // Absolute branches don't need to be handled
+ if (dwInstruction & POWERPC_BRANCH_ABSOLUTE)
+ {
+ *pdwDestination = dwInstruction;
+
+ return 4;
+ }
+
+ size_t uiBranchOffsetBitSize = 0;
+ int iBranchOffsetBitBase = 0;
+ DWORD dwBranchOptions = 0;
+ byte bConditionRegisterBit = 0;
+
+ switch (dwInstruction & POWERPC_OPCODE_MASK)
+ {
+ // B - Branch
+ // [Opcode] [Address] [Absolute] [Linked]
+ // 0-5 6-29 30 31
+ //
+ // Example
+ // 010010 0000 0000 0000 0000 0000 0001 0 0
+ case POWERPC_OPCODE_B:
+ uiBranchOffsetBitSize = 24;
+ iBranchOffsetBitBase = 2;
+ dwBranchOptions = POWERPC_BRANCH_OPTIONS_ALWAYS;
+ bConditionRegisterBit = 0;
+ break;
+
+ // BC - Branch Conditional
+ // [Opcode] [Branch Options] [Condition Register] [Address] [Absolute] [Linked]
+ // 0-5 6-10 11-15 16-29 30 31
+ //
+ // Example
+ // 010000 00100 00001 00 0000 0000 0001 0 0
+ case POWERPC_OPCODE_BC:
+ uiBranchOffsetBitSize = 14;
+ iBranchOffsetBitBase = 2;
+ dwBranchOptions = (dwInstruction >> POWERPC_BIT32(10)) & MASK_N_BITS(5);
+ bConditionRegisterBit = (dwInstruction >> POWERPC_BIT32(15)) & MASK_N_BITS(5);
+ break;
+ }
+
+ // Even though the address part of the instruction begins from bit 29 in the case of bc and b.
+ // The value of the first bit is 4 as all addresses are aligned to for 4 for code therefore,
+ // the branch offset can be caluclated by anding in place and removing any suffix bits such as the
+ // link register or absolute flags.
+ DWORD dwBranchOffset = dwInstruction & (MASK_N_BITS(uiBranchOffsetBitSize) << iBranchOffsetBitBase);
+
+ // Check if the MSB of the offset is set
+ if (dwBranchOffset >> ((uiBranchOffsetBitSize + iBranchOffsetBitBase) - 1))
+ {
+ // Add the nessasary bits to our integer to make it negative
+ dwBranchOffset |= ~MASK_N_BITS(uiBranchOffsetBitSize + iBranchOffsetBitBase);
+ }
+
+ const void *pBranchAddress = reinterpret_cast(dwInstructionAddress + dwBranchOffset);
+
+ return WriteFarBranchEx(pdwDestination, pBranchAddress, dwInstruction & POWERPC_BRANCH_LINKED, true, dwBranchOptions, bConditionRegisterBit);
+}
+
+size_t Detour::CopyInstruction(DWORD *pdwDestination, const DWORD *pdwSource)
+{
+ const DWORD dwInstruction = *pdwSource;
+
+ switch (dwInstruction & POWERPC_OPCODE_MASK)
+ {
+ case POWERPC_OPCODE_B: // B BL BA BLA
+ case POWERPC_OPCODE_BC: // BEQ BNE BLT BGE
+ return RelocateBranch(pdwDestination, pdwSource);
+ default:
+ *pdwDestination = dwInstruction;
+ return 4;
+ }
+}
+
+}
diff --git a/src/Memory.cpp b/src/Memory.cpp
index 92dec73..64d6ee9 100644
--- a/src/Memory.cpp
+++ b/src/Memory.cpp
@@ -24,123 +24,4 @@ void Memory::ThreadEx(LPTHREAD_START_ROUTINE pStartAddress, void *pParameters, D
Kernel::ExCreateThread(nullptr, 0, nullptr, nullptr, pStartAddress, pParameters, dwCreationFlags);
}
-void Memory::HookFunctionStart(DWORD *pdwAddress, DWORD *pdwSaveStub, DWORD dwDestination)
-{
- if (pdwSaveStub != NULL && pdwAddress != NULL)
- {
- DWORD dwAddrReloc = reinterpret_cast(&pdwAddress[4]);
- DWORD dwWriteBuffer;
-
- if (dwAddrReloc & 0x8000)
- dwWriteBuffer = 0x3D600000 + (((dwAddrReloc >> 16) & 0xFFFF) + 1);
- else
- dwWriteBuffer = 0x3D600000 + ((dwAddrReloc >> 16) & 0xFFFF);
-
- pdwSaveStub[0] = dwWriteBuffer;
- dwWriteBuffer = 0x396B0000 + (dwAddrReloc & 0xFFFF);
- pdwSaveStub[1] = dwWriteBuffer;
- dwWriteBuffer = 0x7D6903A6;
- pdwSaveStub[2] = dwWriteBuffer;
-
- for (int i = 0; i < 4; i++)
- {
- if ((pdwAddress[i] & 0x48000003) == 0x48000001)
- {
- dwWriteBuffer = RelinkGPLR((pdwAddress[i] &~ 0x48000003), &pdwSaveStub[i + 3], &pdwAddress[i]);
- pdwSaveStub[i + 3] = dwWriteBuffer;
- }
- else
- {
- dwWriteBuffer = pdwAddress[i];
- pdwSaveStub[i + 3] = dwWriteBuffer;
- }
- }
-
- dwWriteBuffer = 0x4E800420; // bctr
- pdwSaveStub[7] = dwWriteBuffer;
-
- __dcbst(0, pdwSaveStub);
- __sync();
- __isync();
-
- PatchInJump(pdwAddress, dwDestination, false);
- }
-}
-
-void Memory::PatchInJump(DWORD *pdwAddress, DWORD dwDestination, bool bLinked)
-{
- DWORD dwWriteBuffer;
-
- if (dwDestination & 0x8000)
- dwWriteBuffer = 0x3D600000 + (((dwDestination >> 16) & 0xFFFF) + 1);
- else
- dwWriteBuffer = 0x3D600000 + ((dwDestination >> 16) & 0xFFFF);
-
- pdwAddress[0] = dwWriteBuffer;
- dwWriteBuffer = 0x396B0000 + (dwDestination & 0xFFFF);
- pdwAddress[1] = dwWriteBuffer;
- dwWriteBuffer = 0x7D6903A6;
- pdwAddress[2] = dwWriteBuffer;
-
- if (bLinked)
- dwWriteBuffer = 0x4E800421;
- else
- dwWriteBuffer = 0x4E800420;
-
- pdwAddress[3] = dwWriteBuffer;
-
- __dcbst(0, pdwAddress);
- __sync();
- __isync();
-}
-
-void __declspec(naked) Memory::GLPR()
-{
- __asm
- {
- std r14, -0x98(sp)
- std r15, -0x90(sp)
- std r16, -0x88(sp)
- std r17, -0x80(sp)
- std r18, -0x78(sp)
- std r19, -0x70(sp)
- std r20, -0x68(sp)
- std r21, -0x60(sp)
- std r22, -0x58(sp)
- std r23, -0x50(sp)
- std r24, -0x48(sp)
- std r25, -0x40(sp)
- std r26, -0x38(sp)
- std r27, -0x30(sp)
- std r28, -0x28(sp)
- std r29, -0x20(sp)
- std r30, -0x18(sp)
- std r31, -0x10(sp)
- stw r12, -0x8(sp)
- blr
- }
-}
-
-DWORD Memory::RelinkGPLR(int nOffset, DWORD *pdwSaveStubAddr, DWORD *pdwOrgAddr)
-{
- DWORD dwInst = 0, dwRepl;
- DWORD *pdwSaver = reinterpret_cast(GLPR);
-
- if (nOffset & 0x2000000)
- nOffset = nOffset | 0xFC000000;
-
- dwRepl = pdwOrgAddr[nOffset / 4];
-
- for (int i = 0; i < 20; i++)
- {
- if (dwRepl == pdwSaver[i])
- {
- int nNewOffset = reinterpret_cast(&pdwSaver[i]) - reinterpret_cast(pdwSaveStubAddr);
- dwInst = 0x48000001 | (nNewOffset & 0x3FFFFFC);
- }
- }
-
- return dwInst;
-}
-
}