/// <summary> /// Assembles an absolute jump to a user specified address and returns /// the resultant bytes of the assembly process. /// </summary> /// <param name="functionAddress">The address to assemble the absolute call to.</param> /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param> /// <returns>A set of X64 assembler bytes to absolute jump to a specified address.</returns> public static List <string> X64AssembleAbsoluteCallMnemonics(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction) { // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string>(); // Assemble the call to the game function in question. // With the rewrite of MemoryBuffer, this piece of code has been greatly simplified. IntPtr gameFunctionPointer; if ((gameFunctionPointer = MemoryBufferManager.Add(functionAddress, IntPtr.Zero)) != IntPtr.Zero) { // Jump to Game Function Pointer (gameFunctionPointer is address at which our function address is written) assemblyCode.Add("call qword [qword 0x" + gameFunctionPointer.ToString("X") + "]"); } // Cannot get buffer in 2GB range. else { // Get register to delegate function calling to. X64ReloadedFunctionAttribute.Register jmpRegister = FunctionCommon.GetCallRegister(reloadedFunction); // Call Game Function Pointer (gameFunctionPointer is address at which our function address is written) assemblyCode.Add($"mov {jmpRegister}, 0x{functionAddress.ToString("X")}"); assemblyCode.Add($"call {jmpRegister}"); } // Assemble the individual bytes. return(assemblyCode); }
/// <summary> /// Allows you to insert a set of your own assembly instructions in the middle of a game function and have the game conditionally redirect /// to said function. /// /// The hook requires a minimum of 6 (X86) or 7 (X64) bytes and will therefore overwrite as many instructions as it needs until it can get enough space /// to place the hook. If you have ever used Cheat ENgine's assembly injection, you're already familliar on how to work with this class. /// </summary> /// <param name="hookAddress"> /// The memory address for which the hook is to be generated. /// </param> /// <param name="mnemonics"> /// X86-32 or X64 FASM compatible assembly code that starts with the line use16, use32 or use64 for identifying the architecture. /// </param> /// <param name="originalInstructionOptions"> /// Defines a switch for the assembly hook builder telling it what should be done with the original set of bytes that are going to be /// replaced with the jump opcode. /// </param> /// <param name="hookLength">[Optional] The explicit amount of bytes to overwrite when inserting the actual hook.</param> public AssemblyHook(IntPtr hookAddress, string[] mnemonics, OriginalInstructionOptions originalInstructionOptions, int hookLength = -1) { // Set hook address. HookAddress = hookAddress; // Our 32bit and 64bit code paths will differ once we get the disassembler rolling. bool is64bit = mnemonics[0] == "use64"; // Get the default for architecture, then true hook length. if (hookLength == -1) { hookLength = GetDefaultHookLength(is64bit); ArchitectureMode disassemblerMode = is64bit ? ArchitectureMode.x86_64 : ArchitectureMode.x86_32; hookLength = HookCommon.GetHookLength(HookAddress, hookLength, disassemblerMode); } // Assemble our custom ASM hook function to be jumped to and executed by the game, then write it to memory. IntPtr returnAddress = HookAddress + hookLength; byte[] assembledBytes = Assembler.Assembler.Assemble(mnemonics); assembledBytes = ProcessCustomInstructions(assembledBytes, OriginalBytes, originalInstructionOptions); assembledBytes = AppendJumpBack(assembledBytes, is64bit, returnAddress); AsmHookAddress = MemoryBufferManager.Add(assembledBytes); // Backup our bytes to hook. OriginalBytes = Bindings.TargetProcess.ReadMemory(HookAddress, hookLength); // Setup our new bytes to overwrite the old ones with (absolute jump). NewBytes = new byte[hookLength]; Populate(NewBytes, (byte)0x90); // NOP byte[] hookJump = AssembleJump(is64bit, AsmHookAddress); // Assemble absolute jump and copy to array of NOPs. Array.Copy(hookJump, NewBytes, hookJump.Length); }
/// <summary> /// Creates the wrapper function that wraps a native function with our own defined or custom calling convention, /// into a regular CDECL function that is natively supported by the C# programming language. /// /// The return value is a pointer to a C# compatible CDECL fcuntion that calls our game function. /// /// This allows us to call non-standard "usercall" game functions, such as for example functions that take values /// in registers as parameters instead of using the stack, functions which take paremeters in a mixture of both stack /// and registers as well as functions which varying return parameters, either caller or callee cleaned up. /// </summary> /// <param name="functionAddress">The address of the function to create a wrapper for.</param> /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param> /// <returns>Pointer to the new CDECL function address to call from C# code to invoke our game function.</returns> private static IntPtr CreateWrapperFunctionInternal <TFunction>(IntPtr functionAddress, ReloadedFunctionAttribute reloadedFunction) { // Retrieve number of parameters. int numberOfParameters = FunctionCommon.GetNumberofParameters(typeof(TFunction)); int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length; // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string> { "use32" }; // Backup Stack Frame assemblyCode.Add("push ebp"); // Backup old call frame assemblyCode.Add("mov ebp, esp"); // Setup new call frame // Reserve Extra Stack Space if (reloadedFunction.ReservedStackSpace > 0) { assemblyCode.Add($"sub esp, {reloadedFunction.ReservedStackSpace}"); } // Setup Function Parameters if (numberOfParameters > 0) { assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction.SourceRegisters)); } // Call Game Function Pointer (gameFunctionPointer is address at which our function address is written) IntPtr gameFunctionPointer = MemoryBufferManager.Add(functionAddress, IntPtr.Zero); assemblyCode.Add("call dword [0x" + gameFunctionPointer.ToString("X") + "]"); // Stack cleanup if necessary // Move back the stack pointer to before our pushed parameters if (nonRegisterParameters > 0 && reloadedFunction.Cleanup == ReloadedFunctionAttribute.StackCleanup.Caller) { int stackCleanupBytes = 4 * nonRegisterParameters; assemblyCode.Add($"add esp, {stackCleanupBytes}"); } if (reloadedFunction.ReturnRegister != ReloadedFunctionAttribute.Register.eax) { // MOV Game's custom calling convention return register into our return register, EAX. assemblyCode.Add("mov eax, " + reloadedFunction.ReturnRegister); } // Unreserve Extra Stack Space if (reloadedFunction.ReservedStackSpace > 0) { assemblyCode.Add($"add esp, {reloadedFunction.ReservedStackSpace}"); } // Restore Stack Frame and Return assemblyCode.Add("pop ebp"); assemblyCode.Add("ret"); // Write function to buffer and return pointer. byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray()); return(MemoryBufferManager.Add(assembledMnemonics)); }
/// <summary> /// Creates the wrapper function that wraps a native function with our own defined or custom calling convention, /// into a regular X64 Microsoft Calling Convention function that is natively supported by the C# programming language. /// /// This allows us to call non-standard "usercall" game functions, such as for example functions that take values /// in registers as parameters instead of using the stack, functions which take paremeters in a mixture of both stack /// and registers as well as functions which varying return parameters, either caller or callee cleaned up. /// </summary> /// <param name="functionAddress">The address of the function to create a wrapper for.</param> /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param> /// <returns>Pointer to the new X64 Microsoft Calling Convention function address to call from C# code to invoke our game function.</returns> private static IntPtr CreateWrapperFunctionInternal <TFunction>(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction) { // Retrieve number of parameters. int numberOfParameters = FunctionCommon.GetNumberofParametersWithoutFloats(typeof(TFunction)); int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length; // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string> { "use64" }; // Backup Stack Frame assemblyCode.Add("push rbp"); // Backup old call frame assemblyCode.Add("mov rbp, rsp"); // Setup new call frame // Setup Function Parameters if (numberOfParameters > 0) { assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction.SourceRegisters)); } // Make Shadow Space if necessary. if (reloadedFunction.ShadowSpace) { assemblyCode.Add("sub rsp, 32"); // Setup new call frame } // Assemble the call to the game function in question. assemblyCode.AddRange(HookCommon.X64AssembleAbsoluteCallMnemonics(functionAddress, reloadedFunction)); // Move return register back if necessary. if (reloadedFunction.ReturnRegister != X64ReloadedFunctionAttribute.Register.rax) { assemblyCode.Add("mov rax, " + reloadedFunction.ReturnRegister); } // Restore the stack pointer from the Shadow Space // 8 = IntPtr.Size // 32 = Shadow Space if (reloadedFunction.ShadowSpace) { assemblyCode.Add("add rsp, " + ((nonRegisterParameters * 8) + 32)); } else { assemblyCode.Add("add rsp, " + (nonRegisterParameters * 8)); } // Restore Stack Frame and Return assemblyCode.Add("pop rbp"); assemblyCode.Add("ret"); // Write function to buffer and return pointer. byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray()); return(MemoryBufferManager.Add(assembledMnemonics)); }
/// <summary> /// Assembles a wrapper for other function hooks' stray bytes that will be overwritten by /// Reloaded's own hooking mechanism. Gives back the old functions their stray bytes and jumps back /// immediately to the end of Reloaded's hook. /// </summary> /// <param name="strayBytes">Bytes from another hook that will be replaced by Reloaded's hooking mechanism.</param> /// <param name="reloadedHookEndAddress">The target address for the mini wrapper to jump back to.</param> /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param> /// <param name="wrapperAddress">[Optional] Target address within of which the wrapper should be placed in 2GB range.</param> /// <returns>The address of a new "mini-wrapper" class for </returns> public static IntPtr X64AssembleMiniWrapper(byte[] strayBytes, long reloadedHookEndAddress, X64ReloadedFunctionAttribute reloadedFunction, long wrapperAddress = 0) { // Get our stray bytes. List <byte> newBytes = strayBytes.ToList(); // Append our absolute jump to the mix. newBytes.AddRange(X64AssembleAbsoluteJump((IntPtr)reloadedHookEndAddress, reloadedFunction)); // Write to memory buffer and return return(MemoryBufferManager.Add(newBytes.ToArray(), (IntPtr)wrapperAddress)); }
/// <summary> /// Assembles a wrapper for other function hooks' stray bytes that will be overwritten by /// Reloaded's own hooking mechanism. Gives back the old functions their stray bytes and jumps back /// immediately to the end of Reloaded's hook. /// </summary> /// <param name="strayBytes">Bytes from another hook that will be replaced by Reloaded's hooking mechanism.</param> /// <param name="reloadedHookEndAddress">The target address for the mini wrapper to jump back to.</param> /// <returns>The address of a new "mini-wrapper" class for </returns> public static IntPtr X86AssembleMiniWrapper(byte[] strayBytes, long reloadedHookEndAddress) { // Get our stray bytes. List <byte> newBytes = strayBytes.ToList(); // Append our absolute jump to the mix. newBytes.AddRange(X86AssembleAbsoluteJump((IntPtr)reloadedHookEndAddress)); // Write to memory buffer and return return(MemoryBufferManager.Add(newBytes.ToArray())); }
/// <summary> /// Assembles an absolute jump to a user specified address and returns /// the resultant bytes of the assembly process. /// </summary> /// <param name="functionAddress">The address to assemble the absolute jump to.</param> /// <returns>A set of X86 assembler bytes to absolute jump to a specified address.</returns> public static byte[] X86AssembleAbsoluteJump(IntPtr functionAddress) { // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string> { "use32" }; // Jump to Game Function Pointer (gameFunctionPointer is address at which our function address is written) IntPtr gameFunctionPointer = MemoryBufferManager.Add(functionAddress, IntPtr.Zero); assemblyCode.Add("jmp dword [0x" + gameFunctionPointer.ToString("X") + "]"); // Assemble the individual bytes. return(Assemble(assemblyCode.ToArray())); }
/// <summary> /// Creates the wrapper function for redirecting program flow to our C# function. /// </summary> /// <param name="functionAddress">The address of the function to create a wrapper for.</param> /// <returns></returns> internal static IntPtr CreateReverseWrapperInternal <TFunction>(IntPtr functionAddress, ReloadedFunctionAttribute reloadedFunction) { // Retrieve number of parameters. int numberOfParameters = FunctionCommon.GetNumberofParameters(typeof(TFunction)); int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length; // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string> { "use32" }; // Backup Stack Frame assemblyCode.Add("push ebp"); // Backup old call frame assemblyCode.Add("mov ebp, esp"); // Setup new call frame // Push registers for our C# method as necessary. assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction.SourceRegisters)); // Call C# Function Pointer (cSharpFunctionPointer is address at which our C# function address is written) IntPtr cSharpFunctionPointer = MemoryBufferManager.Add(functionAddress, IntPtr.Zero); assemblyCode.Add("call dword [0x" + cSharpFunctionPointer.ToString("X") + "]"); // Restore stack pointer + stack frame assemblyCode.Add($"add esp, {numberOfParameters * 4}"); // MOV our own return register, EAX into the register expected by the calling convention assemblyCode.Add($"mov {reloadedFunction.ReturnRegister}, eax"); // Restore Stack Frame and Return assemblyCode.Add("pop ebp"); // Caller/Callee Cleanup if (reloadedFunction.Cleanup == ReloadedFunctionAttribute.StackCleanup.Callee) { assemblyCode.Add($"ret {nonRegisterParameters * 4}"); } else { assemblyCode.Add("ret"); } // Assemble and return pointer to code byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray()); return(MemoryBufferManager.Add(assembledMnemonics)); }
/* Entry Point */ public static unsafe void Init() { #if DEBUG Debugger.Launch(); #endif // Setup controllers. var controllerManager = new ControllerManager(); playerOneController = new ReloadedController(Player.PlayerOne, controllerManager); playerTwoController = new ReloadedController(Player.PlayerTwo, controllerManager); playerThreeController = new ReloadedController(Player.PlayerThree, controllerManager); playerFourController = new ReloadedController(Player.PlayerFour, controllerManager); // Hook get controls function. psPADServerHook = FunctionHook <psPADServerPC> .Create(0x444F30, PSPADServerImpl).Activate(); // Copy the old function to a new place and create a function from it. byte[] periMakeRepeatBytes = GameProcess.ReadMemory((IntPtr)0x00434FF0, 0xDD); IntPtr functionPtr = MemoryBufferManager.Add(periMakeRepeatBytes); periMakeRepeatFunction = FunctionWrapper.CreateWrapperFunction <sGamePeri__MakeRepeatCount>((long)functionPtr); periMakeRepeatCountHook = FunctionHook <sGamePeri__MakeRepeatCount> .Create(0x00434FF0, MakeRepeatCountImpl).Activate(); }
/// <summary> /// Your own user code starts here. /// If this is your first time, do consider reading the notice above. /// It contains some very useful information. /// </summary> public static void Init() { /* * Reloaded Mod Loader Sample: Memory Manipulation * Architectures supported: X86, X64 * * An example of memory manipulation in Reloaded, showing how easily it is * possible to write or read structures to and from memory. */ // Want to see this in with a debugger? Uncomment this line. // Debugger.Launch(); /// ////////////////////////////////// /// Example #1: Reading/Writing Memory /// ////////////////////////////////// // First let's allocate some memory to write our data to. // See footnote at 1*, you should really instead use the MemoryBufferManager class for this. IntPtr addressOfAllocation = GameProcess.AllocateMemory(2048); Bindings.PrintInfo($"Memory allocated at {addressOfAllocation.ToString("X")}"); // Now let's write some memory to the given address. int oneThreeThreeSeven = 1337; GameProcess.WriteMemory(addressOfAllocation, ref oneThreeThreeSeven); Bindings.PrintInfo($"Written {1337} to address {addressOfAllocation.ToString("X")}"); // Wait what!? That simple?? // Well, of course, under the hood we use some generics and fancy C# to automatically convert a type // into an array of bytes to write into memory. Feel free to verify the result with Cheat Engine. // Let's read this address back. int valueAtAddress = GameProcess.ReadMemory <int>(addressOfAllocation); Bindings.PrintInfo($"Read {valueAtAddress} from address {addressOfAllocation.ToString("X")}"); /// ////////////////////////////////////// /// Example #2: Reading/Writing Structures /// ////////////////////////////////////// // Let's try something a bit more complex now. addressOfAllocation += sizeof(int); // Keep our previously written integer for later so you can verify it's there. Bindings.PrintInfo($"Demo #2, Read/Write address now at {addressOfAllocation.ToString("X")}"); // Let's create and write our own custom structure. PlayerCoordinates playerCoordinates = new PlayerCoordinates() { xPosition = 10, yPosition = 1368.62F, zPosition = -5324.677F }; // Now let's write it to memory. GameProcess.WriteMemory(addressOfAllocation, ref playerCoordinates); Bindings.PrintInfo($"Written arbitrary player coordinates {playerCoordinates.xPosition} {playerCoordinates.yPosition} {playerCoordinates.zPosition} to address {addressOfAllocation.ToString("X")}"); // Wait... Nothing changed? // Of course I did say something about generics and fancy C# didn't I? // Let's read it back. PlayerCoordinates newPlayerCoordinates = GameProcess.ReadMemory <PlayerCoordinates>(addressOfAllocation); Bindings.PrintInfo($"Read player coordinates back from {addressOfAllocation.ToString("X")}"); // Check if we are equal. if (newPlayerCoordinates.xPosition == playerCoordinates.xPosition && newPlayerCoordinates.yPosition == playerCoordinates.yPosition && newPlayerCoordinates.zPosition == playerCoordinates.zPosition) { Bindings.PrintInfo($"Success: See? It's incredibly easy!"); } else { Bindings.PrintInfo($"Failure: Read back player coordinates are not equal."); } /// ///////////////////////////////////////// /// Example #3: Reading/Writing Struct Arrays /// ///////////////////////////////////////// // But what about arrays? // Well, libReloaded has an utility for even that. // First let's read some arbitrary array from a file and write it to memory. Bindings.PrintInfo($"Demo #3, Array Read/Write Test Begin! (This one you should see in a debugger)"); // First write our arbitrary physics data into memory. byte[] physicsData = File.ReadAllBytes($"{ModDirectory}\\phys.bin"); // ModDirectory is from Reloaded Template IntPtr arrayLocation = MemoryBufferManager.Add(physicsData); // A good example of using MemoryBufferManager (see note below), // normally no guarantee our data will fit into our allocated memory, // MemoryBufferManager also handles that case without extra code. Bindings.PrintInfo($"Character physics data written to {arrayLocation.ToString("X")}"); // Now let's read back the memory. // Length of array for this sample is known as 40. FixedArrayPtr <AdventurePhysics> adventurePhysicsArray = new FixedArrayPtr <AdventurePhysics>(arrayLocation, 40); // Let's try to read one entry back from memory. AdventurePhysics firstEntry = adventurePhysicsArray[0]; // Wow! It's almost like a native array... isn't it? // But we want to know everything! Give me it all! AdventurePhysics[] adventurePhysicses = adventurePhysicsArray.ToArray(); // Huh? That's it. Yup. // How about writing to one of the entries? firstEntry.HangTime = 1337; adventurePhysicsArray[0] = firstEntry; // You've gotta be kidding me... Bindings.PrintInfo($"Physics at {arrayLocation.ToString("X")} changed."); Bindings.PrintInfo($"Demo end."); }
/// <summary> /// Creates the wrapper function for redirecting program flow to our C# function. /// </summary> /// <param name="reloadedFunction">Structure containing the details of the actual function in question.</param> /// <param name="functionAddress">The address of the function to create a wrapper for.</param> /// <returns></returns> internal static IntPtr CreateX64WrapperFunctionInternal <TFunction>(IntPtr functionAddress, X64ReloadedFunctionAttribute reloadedFunction) { // Retrieve number of parameters. int numberOfParameters = FunctionCommon.GetNumberofParametersWithoutFloats(typeof(TFunction)); int nonRegisterParameters = numberOfParameters - reloadedFunction.SourceRegisters.Length; // List of ASM Instructions to be Compiled List <string> assemblyCode = new List <string> { "use64" }; // If the attribute is Microsoft Call Convention, take fast path! if (reloadedFunction.Equals(new X64ReloadedFunctionAttribute(X64CallingConventions.Microsoft))) { // Backup old call frame assemblyCode.AddRange(HookCommon.X64AssembleAbsoluteJumpMnemonics(functionAddress, reloadedFunction)); } else { // Backup Stack Frame assemblyCode.Add("push rbp"); // Backup old call frame assemblyCode.Add("mov rbp, rsp"); // Setup new call frame // We assume that we are stack aligned in usercall/custom calling conventions, if we are not, game over for now. // Our stack frame alignment is off by 8 here, we must also consider non-register parameters. // Note to self: In the case you forget, dummy. Your stack needs to be 16 byte aligned. // Calculate the bytes our wrapper parameters take on the stack in total. // The stack frame backup, push rbp and CALL negate themselves so it's down to this. // Then our misalignment to the stack. int stackBytesTotal = (nonRegisterParameters * 8); int stackMisalignment = (stackBytesTotal % 16); // Prealign stack assemblyCode.Add($"sub rbp, {stackMisalignment}"); // Setup new call frame // Setup the registers for our C# method. assemblyCode.AddRange(AssembleFunctionParameters(numberOfParameters, reloadedFunction, stackMisalignment)); // And now we add the shadow space for C# method's Microsoft Call Convention. assemblyCode.Add("sub rsp, 32"); // Setup new call frame // Assemble the Call to C# Function Pointer. assemblyCode.AddRange(HookCommon.X64AssembleAbsoluteCallMnemonics(functionAddress, reloadedFunction)); // MOV our own return register, EAX into the register expected by the calling convention assemblyCode.Add($"mov {reloadedFunction.ReturnRegister}, rax"); // Restore stack pointer (this is always the same, our function is Microsoft Call Convention Compliant) assemblyCode.Add("add rsp, " + ((nonRegisterParameters * 8) + 32)); // Restore stack alignment assemblyCode.Add($"add rbp, {stackMisalignment}"); // Restore Stack Frame and Return assemblyCode.Add("pop rbp"); assemblyCode.Add("ret"); } // Assemble and return pointer to code byte[] assembledMnemonics = Assembler.Assembler.Assemble(assemblyCode.ToArray()); return(MemoryBufferManager.Add(assembledMnemonics)); }