Skip to content

Instantly share code, notes, and snippets.

@iMoD1998
Last active October 26, 2024 17:30
Show Gist options
  • Save iMoD1998/4aa48d5c990535767a3fc3251efc0348 to your computer and use it in GitHub Desktop.
Save iMoD1998/4aa48d5c990535767a3fc3251efc0348 to your computer and use it in GitHub Desktop.
Xbox 360 Detours
#include "Detour.h"
BYTE Detour::TrampolineBuffer[ 200 * 20 ] = {};
SIZE_T Detour::TrampolineSize = 0;
//
// Made by iMoD1998
// V3.1
//
#ifndef DETOUR_H
#define DETOUR_H
#include <xtl.h>
#include <stdint.h>
#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) (UINT32)( ( ( 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) (UINT32)( POWERPC_OPCODE_ADDI | ( rD << POWERPC_BIT32( 10 ) ) | ( rA << POWERPC_BIT32( 15 ) ) | SIMM )
#define POWERPC_ADDIS(rD, rA, SIMM) (UINT32)( 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_ADDI( rD, 0, SIMM ) // Mnemonic for addi %rD, 0, SIMM
#define POWERPC_MTSPR(SPR, rS) (UINT32)( 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) (UINT32)( POWERPC_OPCODE_ORI | ( rS << POWERPC_BIT32( 10 ) ) | ( rA << POWERPC_BIT32( 15 ) ) | UIMM )
#define POWERPC_BCCTR(BO, BI, LK) (UINT32)( POWERPC_OPCODE_BCCTR | ( BO << POWERPC_BIT32( 10 ) ) | ( BI << POWERPC_BIT32( 15 ) ) | ( LK & 1 ) | POWERPC_EXOPCODE_BCCTR )
#define POWERPC_STD(rS, DS, rA) (UINT32)( POWERPC_OPCODE_STD | ( rS << POWERPC_BIT32( 10 ) ) | ( rA << POWERPC_BIT32( 15 ) ) | ( (INT16)DS & 0xFFFF ) )
#define POWERPC_LD(rS, DS, rA) (UINT32)( POWERPC_OPCODE_LD | ( rS << POWERPC_BIT32( 10 ) ) | ( rA << POWERPC_BIT32( 15 ) ) | ( (INT16)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 )
class Detour
{
public:
Detour() {}
//
// HookSource - The function that will be hooked.
// HookTarget - The function that the hook will be redirected to.
//
Detour(
_Inout_ void* HookSource,
_In_ const void* HookTarget
) :
HookSource( HookSource ),
HookTarget( HookTarget ),
TrampolineAddress( NULL ),
OriginalLength( 0 )
{
}
~Detour()
{
this->Remove();
}
//
// Writes an unconditional branch to the destination address that will branch to the target address.
//
// Destination - Where the branch will be written to.
// BranchTarget - The address the branch will jump to.
// Linked - Branch is a call or a jump? aka bl or b
// PreserveRegister - Preserve the register clobbered after loading the branch address.
//
static SIZE_T WriteFarBranch(
_Out_ void* Destination,
_In_ const void* BranchTarget,
_In_ bool Linked = true,
_In_ bool PreserveRegister = false
)
{
return Detour::WriteFarBranchEx( Destination, BranchTarget, Linked, PreserveRegister );
}
//
// Writes both conditional and unconditional branches using the count register to the destination address that will branch to the target address.
//
// Destination - Where the branch will be written to.
// BranchTarget - The address the branch will jump to.
// Linked - Branch is a call or a jump? aka bl or b
// PreserveRegister - Preserve the register clobbered after loading the branch address.
// BranchOptions - Options for determining when a branch to be followed.
// ConditionRegisterBit - The bit of the condition register to compare.
// RegisterIndex - Register to use when loading the destination address into the count register.
//
static SIZE_T WriteFarBranchEx(
_Out_ void* Destination,
_In_ const void* BranchTarget,
_In_ bool Linked = false,
_In_ bool PreserveRegister = false,
_In_ UINT32 BranchOptions = POWERPC_BRANCH_OPTIONS_ALWAYS,
_In_ BYTE ConditionRegisterBit = 0,
_In_ BYTE RegisterIndex = 0
)
{
const UINT32 BranchFarAsm[] = {
POWERPC_LIS( RegisterIndex, POWERPC_HI( (UINT32)BranchTarget ) ), // lis %rX, BranchTarget@hi
POWERPC_ORI( RegisterIndex, RegisterIndex, POWERPC_LO( (UINT32)BranchTarget ) ), // ori %rX, %rX, BranchTarget@lo
POWERPC_MTCTR( RegisterIndex ), // mtctr %rX
POWERPC_BCCTR( BranchOptions, ConditionRegisterBit, Linked ) // bcctr (bcctr 20, 0 == bctr)
};
const UINT32 BranchFarAsmPreserve[] = {
POWERPC_STD( RegisterIndex, -0x30, 1 ), // std %rX, -0x30(%r1)
POWERPC_LIS( RegisterIndex, POWERPC_HI( (UINT32)BranchTarget ) ), // lis %rX, BranchTarget@hi
POWERPC_ORI( RegisterIndex, RegisterIndex, POWERPC_LO( (UINT32)BranchTarget ) ), // ori %rX, %rX, BranchTarget@lo
POWERPC_MTCTR( RegisterIndex ), // mtctr %rX
POWERPC_LD( RegisterIndex, -0x30, 1 ), // lwz %rX, -0x30(%r1)
POWERPC_BCCTR( BranchOptions, ConditionRegisterBit, Linked ) // bcctr (bcctr 20, 0 == bctr)
};
const auto BranchAsm = PreserveRegister ? BranchFarAsmPreserve : BranchFarAsm;
const auto BranchAsmSize = PreserveRegister ? sizeof( BranchFarAsmPreserve ) : sizeof( BranchFarAsm );
if ( Destination )
memcpy( Destination, BranchAsm, BranchAsmSize );
return BranchAsmSize;
}
//
// Copies and fixes relative branch instructions to a new location.
//
// Destination - Where to write the new branch.
// Source - Address to the instruction that is being relocated.
//
static SIZE_T RelocateBranch(
_Out_ UINT32* Destination,
_In_ const UINT32* Source
)
{
const auto Instruction = *Source;
const auto InstructionAddress = (UINT32)Source;
//
// Absolute branches dont need to be handled.
//
if ( Instruction & POWERPC_BRANCH_ABSOLUTE )
{
*Destination = Instruction;
return 4;
}
INT32 BranchOffsetBitSize;
INT32 BranchOffsetBitBase;
UINT32 BranchOptions;
BYTE ConditionRegisterBit;
switch ( Instruction & 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:
BranchOffsetBitSize = 24;
BranchOffsetBitBase = 2;
BranchOptions = POWERPC_BRANCH_OPTIONS_ALWAYS;
ConditionRegisterBit = 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:
BranchOffsetBitSize = 14;
BranchOffsetBitBase = 2;
BranchOptions = ( Instruction >> POWERPC_BIT32( 10 ) ) & MASK_N_BITS( 5 );
ConditionRegisterBit = ( Instruction >> 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.
//
INT32 BranchOffset = Instruction & ( MASK_N_BITS( BranchOffsetBitSize ) << BranchOffsetBitBase );
//
// Check if the MSB of the offset is set.
//
if ( BranchOffset >> ( ( BranchOffsetBitSize + BranchOffsetBitBase ) - 1 ) )
{
//
// Add the nessasary bits to our integer to make it negative.
//
BranchOffset |= ~MASK_N_BITS( BranchOffsetBitSize + BranchOffsetBitBase );
}
const auto BranchAddress = (void*)(INT32)( InstructionAddress + BranchOffset );
return Detour::WriteFarBranchEx( Destination, BranchAddress, Instruction & POWERPC_BRANCH_LINKED, true, BranchOptions, ConditionRegisterBit );
}
//
// Copies an instruction enusuring things such as PC relative offsets are fixed.
//
// Destination - Where to write the new instruction(s).
// Source - Address to the instruction that is being copied.
//
static SIZE_T CopyInstruction(
_Out_ UINT32* Destination,
_In_ const UINT32* Source
)
{
const auto Instruction = *Source;
const auto InstructionAddress = (UINT32)Source;
switch ( Instruction & POWERPC_OPCODE_MASK )
{
case POWERPC_OPCODE_B: // B BL BA BLA
case POWERPC_OPCODE_BC: // BEQ BNE BLT BGE
return Detour::RelocateBranch( Destination, Source );
default:
*Destination = Instruction;
return 4;
}
}
bool Install()
{
//
// Check if we are already hooked.
//
if ( this->OriginalLength != 0 )
return false;
const auto HookSize = Detour::WriteFarBranch( NULL, this->HookTarget, false, false );
//
// Save the original instructions for unhooking later on.
//
memcpy( this->OriginalInstructions, this->HookSource, HookSize );
this->OriginalLength = HookSize;
//
// Create trampoline and copy and fix instructions to the trampoline.
//
this->TrampolineAddress = &Detour::TrampolineBuffer[ Detour::TrampolineSize ];
for ( SIZE_T i = 0; i < ( HookSize / 4 ); i++ )
{
const auto InstructionPtr = (UINT32*)( ( UINT32 )this->HookSource + ( i * 4 ) );
Detour::TrampolineSize += Detour::CopyInstruction( (UINT32*)&Detour::TrampolineBuffer[ Detour::TrampolineSize ], InstructionPtr );
}
//
// Trampoline branches back to the original function after the branch we used to hook.
//
const auto AfterBranchAddress = (void*)( ( UINT32 )this->HookSource + HookSize );
Detour::TrampolineSize += Detour::WriteFarBranch( &Detour::TrampolineBuffer[ Detour::TrampolineSize ], AfterBranchAddress, false, true );
//
// Finally write the branch to the function that we are hooking.
//
Detour::WriteFarBranch( this->HookSource, this->HookTarget, false, false );
return true;
}
bool Remove()
{
if ( this->HookSource && this->OriginalLength )
{
memcpy( this->HookSource, this->OriginalInstructions, this->OriginalLength );
this->OriginalLength = 0;
this->HookSource = NULL;
return true;
}
return false;
}
template<typename T>
T GetOriginal() const
{
return T( this->TrampolineAddress );
}
private:
const void* HookTarget; // The funtion we are pointing the hook to.
void* HookSource; // The function we are hooking.
BYTE* TrampolineAddress; // Pointer to the trampoline for this detour.
BYTE OriginalInstructions[ 30 ]; // Any bytes overwritten by the hook.
SIZE_T OriginalLength; // The amount of bytes overwritten by the hook.
//
// Shared
//
static BYTE TrampolineBuffer[ 200 * 20 ];
static SIZE_T TrampolineSize;
};
#endif // !DETOUR_H
#include "Detour.h"
#include <stdio.h>
Detour OutputDebugStringWDetour;
void WINAPI OutputDebugStringWHook(
LPCSTR String
)
{
return OutputDebugStringWDetour.GetOriginal<decltype( &OutputDebugStringW )>()( L"Its hooked!\n" );
}
DWORD WINAPI ProcessAttach(
_In_ LPVOID Parameter
)
{
if ( Parameter == NULL )
return FALSE;
OutputDebugStringWDetour = Detour( OutputDebugStringW, OutputDebugStringWHook );
OutputDebugStringWDetour.Install();
printf( "OutputDebugStringW %08X\n", OutputDebugStringW );
printf( "OutputDebugStringWHook %08X\n", OutputDebugStringWHook );
printf( "OutputDebugStringWHook:Trampoline %08X\n", OutputDebugStringWDetour.GetOriginal<void*>() );
OutputDebugStringW( L"Is it hooked?\n" );
OutputDebugStringW( L"Is it hooked?\n" );
OutputDebugStringW( L"Is it hooked?\n" );
OutputDebugStringW( L"Is it hooked?\n" );
return TRUE;
}
DWORD WINAPI ProcessDetach(
_In_ LPVOID Parameter
)
{
if ( Parameter == NULL )
return FALSE;
OutputDebugStringWDetour.Remove();
return TRUE;
}
BOOL APIENTRY DllMain(
_In_ HINSTANCE Instance,
_In_ DWORD Reason,
_In_ LPVOID Reserved
)
{
switch ( Reason )
{
case DLL_PROCESS_ATTACH:
return ProcessAttach( Instance );
case DLL_PROCESS_DETACH:
return ProcessDetach( Instance );
}
return TRUE;
}
@iMoD1998
Copy link
Author

@TheRouletteBoi ok, this stuff is quite old now so I may end up refactoring it abit.

@TheRouletteBoi
Copy link

@iMoD1998 fixed some bugs. https://gist.github.com/TheRouletteBoi/e1e9925699ee8d708881e167e397058b
btw add my discord in pfp. kernel stuff...

@iMoD1998
Copy link
Author

@TheRouletteBoi nice, I will refactor my version soon as well. Not going to incorporate any code for handling TOC though as this is meant for 360 which doesn't have one as apart of it's ABI.

@iMoD1998
Copy link
Author

@TheRouletteBoi Refactored some stuff and tried to make it abit clearer. Have tested it a little bit let me know if you have any problems.

@TheRouletteBoi
Copy link

@iMoD1998
BUG- at line 11 TrampolineAddress should be changed to trampolineBuffer. Causes a crash due to trampoline miss aligment.

BUG- at line 207 if the HookTarget has a branch 'b 0x12345' will result in a crash due to illegal instruction. I was unable to solve it but my guess would be BranchOptions and ConditionRegisterBit are not used in the switch case POWERPC_OPCODE_B

NOTE: HookTarget having a 'BL 0x12345' has not been tested yet so I don't know if that will also crash.

@iMoD1998
Copy link
Author

@TheRouletteBoi Thanks for the info.

Fixed bug at line 11.

Also bug at line 207 was due to no default value for the branch options resulting in trying to encode the invalid instruction bcctr 0, 0 instead of bcctr 20, 0 for unconditional branch.

Should be all fixed now, let me know if you have any more problems.

@TheRouletteBoi
Copy link

it works flawless now 👍

@ClementDreptin
Copy link

ClementDreptin commented Apr 24, 2022

It could be risky to remove a detour from a function that's not loaded in memory anymore. If a function from a game is hooked then the user goes back to the dashboard, trying to remove the detour will cause a segfault. This could be fixed by changing the Remove method like so:

// Add that somewhere
extern "C"
{
	bool MmIsAddressValid(
		_In_ void* Address
	);
}

bool Remove()
{
	if ( this->HookSource && this->OriginalLength )
	{
		//
		// Only restore the original instructions if the function is still loaded in memory.
		//
		if ( MmIsAddressValid( this->HookSource ) )
			memcpy( this->HookSource, this->OriginalInstructions, this->OriginalLength );

		this->OriginalLength = 0;
		this->HookSource = NULL;

		return true;
	}

	return false;
}

@michaeloliverx
Copy link

Confirmed working but on build I do get some warnings on build:

src\Detour.h(266): warning C4189: 'InstructionAddress' : local variable is initialized but not referenced
src\detour.h(236): warning C4701: potentially uninitialized local variable 'BranchOffsetBitSize' used
src\detour.h(236): warning C4701: potentially uninitialized local variable 'BranchOffsetBitBase' used
src\detour.h(251): warning C4701: potentially uninitialized local variable 'BranchOptions' used
src\detour.h(251): warning C4701: potentially uninitialized local variable 'ConditionRegisterBit' used

@iMoD1998
Copy link
Author

Confirmed working but on build I do get some warnings on build:

src\Detour.h(266): warning C4189: 'InstructionAddress' : local variable is initialized but not referenced
src\detour.h(236): warning C4701: potentially uninitialized local variable 'BranchOffsetBitSize' used
src\detour.h(236): warning C4701: potentially uninitialized local variable 'BranchOffsetBitBase' used
src\detour.h(251): warning C4701: potentially uninitialized local variable 'BranchOptions' used
src\detour.h(251): warning C4701: potentially uninitialized local variable 'ConditionRegisterBit' used

They are uninitialised if the instruction is not a branch instruction (B/BC).

They are not initialised as there is no "default".

So on the default case of the switch could error/return.

@AleBello7276
Copy link

I'm having trouble calling the Original Function, it seems that calling the original function on the Xenia emulator causes the game to freeze and crash, I'm not the only one who is having the same problem with xenia, I heard that on hardware they work fine as they should, the hooks work fine as they should on xenia too, it's calling the original function the problem, I initially thought it might be because of the xenia JIT compiler from powerpc to x86 and when you call the original function it execute powerpc assembly, I have no way to verify at the moment

@iMoD1998
Copy link
Author

I'm having trouble calling the Original Function, it seems that calling the original function on the Xenia emulator causes the game to freeze and crash, I'm not the only one who is having the same problem with xenia, I heard that on hardware they work fine as they should, the hooks work fine as they should on xenia too, it's calling the original function the problem, I initially thought it might be because of the xenia JIT compiler from powerpc to x86 and when you call the original function it execute powerpc assembly, I have no way to verify at the moment

This is largely out of scope for this code and I dont know the internals of how xenia works but on modded xbox consoles page permissions are bypassed so that any page can be executed which is why the TrampolineBuffer can be put in the .data segment. Maybe a long shot but it may be worth starting there?

Since calling the original function actually jumps to TrampolineBuffer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment