/// <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> /// Prepends, appends or leaves a set of original instruction bytes before or after our own custom assembled bytes. /// </summary> /// <param name="assembledBytes">Our own custom assembled bytes to which to append a jump back address.</param> /// <param name="originalInstructionBytes">The original instructions to which append or prepend.</param> /// <param name="originalInstructionOptions">Lets us know what to do with the said original instruction bytes.</param> /// <returns></returns> private byte[] ProcessCustomInstructions(byte[] assembledBytes, byte[] originalInstructionBytes, OriginalInstructionOptions originalInstructionOptions) { // Create a list of bytes and depending on the options, populate it. List <byte> newBytes = new List <byte>(assembledBytes.Length + originalInstructionBytes.Length); // Append bytes in the required order to the list. switch (originalInstructionOptions) { case OriginalInstructionOptions.DoNotInclude: newBytes.AddRange(assembledBytes); break; case OriginalInstructionOptions.ExecuteFirst: newBytes.AddRange(originalInstructionBytes); newBytes.AddRange(assembledBytes); break; case OriginalInstructionOptions.ExecuteLast: newBytes.AddRange(assembledBytes); newBytes.AddRange(originalInstructionBytes); break; } return(newBytes.ToArray()); }