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