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