/// <summary>
        /// Checks whether an instance of <see cref="ReloadedFunctionAttribute"/> has the same logical meaning
        /// as the current instance of <see cref="ReloadedFunctionAttribute"/>.
        /// </summary>
        /// <param name="obj">The <see cref="ReloadedFunctionAttribute"/> to compare to the current attribute.</param>
        /// <returns>True if both of the ReloadedFunctions are logically equivalent.</returns>
        public override bool Equals(Object obj)
        {
            // Check for type.
            ReloadedFunctionAttribute functionAttribute = obj as ReloadedFunctionAttribute;

            // Return false if null
            if (functionAttribute == null)
            {
                return(false);
            }

            // Check by value.
            return(functionAttribute.Cleanup == Cleanup &&
                   functionAttribute.ReturnRegister == ReturnRegister &&
                   functionAttribute.SourceRegisters.SequenceEqual(SourceRegisters));
        }
        /// <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));
        }
Exemple #3
0
        /// <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));
        }