/// <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> /// Assembles an absolute jump either from the end of the hook back, or to the hook. /// </summary> /// <param name="is64Bit">Set to true to assemble a 64bit jump, else 32bit.</param> /// <param name="jumpAddress">The address to jump to.</param> /// <returns>An bytes for an absolute jump back to the end of the hook.</returns> private byte[] AssembleJump(bool is64Bit, IntPtr jumpAddress) { return(is64Bit ? HookCommon.X64AssembleAbsoluteJump(jumpAddress, new X64ReloadedFunctionAttribute(X64CallingConventions.Microsoft)): HookCommon.X86AssembleAbsoluteJump(jumpAddress)); }
/// <summary> /// Assembles a dummy jump to an absolute address in order to get the length of an absolute jump under /// X86/X64. /// </summary> /// <param name="is64Bit">Set to true to calculate a 64bit absolute jump length.</param> /// <returns></returns> private int GetDefaultHookLength(bool is64Bit) { return(is64Bit ? HookCommon.X64AssembleAbsoluteJump((IntPtr)0x11223344, new X64ReloadedFunctionAttribute(X64CallingConventions.Microsoft)).Length : HookCommon.X86AssembleAbsoluteJump((IntPtr)0x11223344).Length); }