diff --git a/BepInEx.IL2CPP/Hook/DetourGenerator.cs b/BepInEx.IL2CPP/Hook/DetourGenerator.cs deleted file mode 100644 index e7d1565..0000000 --- a/BepInEx.IL2CPP/Hook/DetourGenerator.cs +++ /dev/null @@ -1,247 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using BepInEx.IL2CPP.Hook.Allocator; -using BepInEx.Logging; -using Iced.Intel; -using MonoMod.RuntimeDetour; - -namespace BepInEx.IL2CPP; - -public static class DetourGenerator -{ - private static ManualLogSource logger = Logger.CreateLogSource("DetourGen"); - - public static void Disassemble(ManualLogSource logSource, IntPtr memoryPtr, int size) - { - var data = new byte[size]; - Marshal.Copy(memoryPtr, data, 0, size); - - var formatter = new NasmFormatter(); - var output = new StringOutput(); - var codeReader = new ByteArrayCodeReader(data); - var decoder = Decoder.Create(64, codeReader); - decoder.IP = (ulong)memoryPtr.ToInt64(); - while (codeReader.CanReadByte) - { - decoder.Decode(out var instr); - formatter.Format(instr, output); - logSource.LogDebug($"{instr.IP:X16} {output.ToStringAndReset()}"); - - if (instr.Code == Code.Jmp_rm64 && instr.Immediate32 == 0 - ) // && instr.IsIPRelativeMemoryOperand && instr.IPRelativeMemoryAddress = 6 - { - var address = new byte[8]; - - for (var i = 0; i < 8; i++) - address[i] = (byte)codeReader.ReadByte(); - - logSource.LogDebug($"{instr.IP + (ulong)instr.Length:X16} db 0x{BitConverter.ToUInt64(address, 0):X16}"); - decoder.IP += 8; - } - } - } - - public static int GetDetourLength(Architecture arch) => arch == Architecture.X64 ? 14 : 5; - - /// - /// Writes a detour on to redirect to . - /// - /// The pointer to the function to apply the detour to. - /// The pointer to the function to redirect to. - /// The architecture of the current platform. - /// - /// The minimum amount of length that the detour should consume. If the generated redirect is - /// smaller than this, the remaining space is padded with NOPs. - /// - public static void ApplyDetour(IntPtr functionPtr, - IntPtr detourPtr, - Architecture architecture, - int minimumLength = 0) - { - var jmp = GenerateAbsoluteJump(detourPtr, functionPtr, architecture); - - Marshal.Copy(jmp, 0, functionPtr, jmp.Length); - - // Fill remaining space with NOP instructions - for (var i = jmp.Length; i < minimumLength; i++) - Marshal.WriteByte(functionPtr + i, 0x90); - } - - public static IntPtr CreateTrampolineFromFunction(IntPtr originalFuncPointer, - out int trampolineLength, - out int jmpLength) - { - var instructionBuffer = new byte[32]; - Marshal.Copy(originalFuncPointer, instructionBuffer, 0, 32); - - var trampolinePtr = PageAllocator.Instance.Allocate(originalFuncPointer); - - DetourHelper.Native.MakeWritable(trampolinePtr, PageAllocator.PAGE_SIZE); - - var arch = IntPtr.Size == 8 ? Architecture.X64 : Architecture.X86; - - var minimumTrampolineLength = GetDetourLength(arch); - - CreateTrampolineFromFunction(instructionBuffer, originalFuncPointer, trampolinePtr, minimumTrampolineLength, - arch, out trampolineLength, out jmpLength); - - DetourHelper.Native.MakeExecutable(originalFuncPointer, 32); - DetourHelper.Native.MakeExecutable(trampolinePtr, PageAllocator.PAGE_SIZE); - - return trampolinePtr; - } - - /// - /// Reads assembly from (at least bytes), and writes - /// it to plus a jmp to continue execution. - /// - /// The buffer to copy assembly from. - /// The pointer to the function to copy assembly from. - /// The pointer to write the trampoline assembly to. - /// The architecture of the current platform. - /// Copies at least this many bytes of assembly from . - /// Returns the total length of the trampoline, in bytes. - /// Returns the length of the jmp at the end of the trampoline, in bytes. - public static void CreateTrampolineFromFunction(byte[] instructionBuffer, - IntPtr functionPtr, - IntPtr trampolinePtr, - int minimumTrampolineLength, - Architecture arch, - out int trampolineLength, - out int jmpLength) - { - // Decode original function up until we go past the needed bytes to write the jump to patchedFunctionPtr - - var codeReader = new ByteArrayCodeReader(instructionBuffer); - var decoder = Decoder.Create(arch == Architecture.X64 ? 64 : 32, codeReader); - decoder.IP = (ulong)functionPtr.ToInt64(); - - uint totalBytes = 0; - var origInstructions = new InstructionList(); - var readOverflowArea = false; - while (codeReader.CanReadByte) - { - decoder.Decode(out var instr); - - // FIXME: This hack somehow fixes the issue with il2cpp_unity_install_unitytls_interface hook crash - _ = instr.ToString(); - - if (!readOverflowArea) - origInstructions.Add(instr); - - totalBytes += (uint)instr.Length; - if (instr.Code == Code.INVALID) - throw new Exception("Found garbage"); - if (totalBytes >= minimumTrampolineLength) - break; - - if (readOverflowArea) - { - if (instr.Mnemonic != Mnemonic.Int3 && instr.Mnemonic != Mnemonic.Nop) - throw new Exception("Function is too short to hook"); - continue; - } - - switch (instr.FlowControl) - { - case FlowControl.Next: - break; - - // Interrupts are usually used for aligning - case FlowControl.Interrupt: // eg. int n - break; - - // Handled by BlockEncoder in most cases - case FlowControl.UnconditionalBranch: - case FlowControl.IndirectBranch: // eg. jmp reg/mem - case FlowControl.ConditionalBranch: // eg. je, jno, etc - break; - case FlowControl.Return: // eg. ret - // There is a possibility of an overflow area, attempt to allocate trampoline onto it - readOverflowArea = true; - break; - case FlowControl.Call: // eg. call method - case FlowControl.IndirectCall: // eg. call reg/mem - case FlowControl.XbeginXabortXend: - case FlowControl.Exception: // eg. ud0 - default: - throw new Exception("Not supported yet - " + instr.FlowControl); - } - } - - if (totalBytes < minimumTrampolineLength) - throw new Exception("Not enough bytes!"); - - if (origInstructions.Count == 0) - throw new Exception("Not enough instructions!"); - - - ref readonly var lastInstr = ref origInstructions[^1]; - - if (lastInstr.FlowControl != FlowControl.Return) - { - Instruction detourInstruction; - - if (arch == Architecture.X64) - detourInstruction = Instruction.CreateBranch(Code.Jmp_rel32_64, lastInstr.NextIP); - else - detourInstruction = Instruction.CreateBranch(Code.Jmp_rel32_32, lastInstr.NextIP); - - origInstructions.Add(detourInstruction); - } - - // Generate trampoline from instruction list - var codeWriter = new CodeWriterImpl(); - var relocatedBaseAddress = (ulong)trampolinePtr; - var block = new InstructionBlock(codeWriter, origInstructions, relocatedBaseAddress); - - var success = BlockEncoder.TryEncode(decoder.Bitness, block, out var errorMessage, out var result); - - if (!success) throw new Exception(errorMessage); - - // Write generated trampoline - var newCode = codeWriter.ToArray(); - Marshal.Copy(newCode, 0, trampolinePtr, newCode.Length); - - jmpLength = newCode.Length - (int)totalBytes; - trampolineLength = newCode.Length; - } - - public static byte[] GenerateAbsoluteJump(IntPtr targetAddress, IntPtr currentAddress, Architecture arch) - { - byte[] jmpBytes; - - if (arch == Architecture.X64) - { - jmpBytes = new byte[] - { - 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // FF25 00000000: JMP [RIP+6] - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Absolute destination address - }; - - Array.Copy(BitConverter.GetBytes(targetAddress.ToInt64()), 0, jmpBytes, 6, 8); - } - else - { - jmpBytes = new byte[] - { - 0xE9, // E9: JMP rel destination - 0x00, 0x00, 0x00, 0x00 // Relative destination address - }; - - Array.Copy(BitConverter.GetBytes(targetAddress.ToInt32() - (currentAddress.ToInt32() + 5)), 0, jmpBytes, - 1, 4); - } - - return jmpBytes; - } - - - private sealed class CodeWriterImpl : CodeWriter - { - private readonly List allBytes = new(); - public override void WriteByte(byte value) => allBytes.Add(value); - public byte[] ToArray() => allBytes.ToArray(); - } -} diff --git a/BepInEx.IL2CPP/Hook/FastNativeDetour.cs b/BepInEx.IL2CPP/Hook/FastNativeDetour.cs deleted file mode 100644 index 148c1b6..0000000 --- a/BepInEx.IL2CPP/Hook/FastNativeDetour.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using BepInEx.IL2CPP.Hook.Allocator; -using BepInEx.Logging; -using MonoMod.RuntimeDetour; -using MonoMod.Utils; - -namespace BepInEx.IL2CPP.Hook; - -[Obsolete("deprecated in favor of FunchookDetour", true)] -public class FastNativeDetour : IDetour -{ - private readonly FunchookDetour funchookDetour; - public FastNativeDetour(IntPtr originalFunctionPtr, IntPtr detourFunctionPtr) - { - funchookDetour = new(originalFunctionPtr, detourFunctionPtr); - } - - public bool IsValid => funchookDetour.IsValid; - public bool IsApplied => funchookDetour.IsApplied; - public void Apply() => funchookDetour.Apply(); - public void Dispose() => funchookDetour.Dispose(); - public void Free() => funchookDetour.Free(); - public MethodBase GenerateTrampoline(MethodBase signature = null) => funchookDetour.GenerateTrampoline(signature); - public T GenerateTrampoline() where T : Delegate => funchookDetour.GenerateTrampoline(); - public void Undo() => funchookDetour.Undo(); - - public static FastNativeDetour CreateAndApply(IntPtr from, - T to, - out T original, - CallingConvention? callingConvention = null) where T : Delegate - { - var toPtr = callingConvention != null - ? MonoExtensions.GetFunctionPointerForDelegate(to, callingConvention.Value) - : Marshal.GetFunctionPointerForDelegate(to); - var result = new FastNativeDetour(from, toPtr); - original = result.GenerateTrampoline(); - result.Apply(); - return result; - } -} diff --git a/BepInEx.IL2CPP/Hook/FunchookDetour.cs b/BepInEx.IL2CPP/Hook/Funchook/FunchookDetour.cs similarity index 65% rename from BepInEx.IL2CPP/Hook/FunchookDetour.cs rename to BepInEx.IL2CPP/Hook/Funchook/FunchookDetour.cs index 8d34a19..3f6037c 100644 --- a/BepInEx.IL2CPP/Hook/FunchookDetour.cs +++ b/BepInEx.IL2CPP/Hook/Funchook/FunchookDetour.cs @@ -6,22 +6,20 @@ using System; using System.Reflection; using System.Runtime.InteropServices; -namespace BepInEx.IL2CPP.Hook; +namespace BepInEx.IL2CPP.Hook.Funchook; -public class FunchookDetour : IDetour +internal class FunchookDetour : INativeDetour { - public static bool DEBUG_DETOURS = false; - private static readonly ManualLogSource logger = Logger.CreateLogSource("FunchookDetour"); - public FunchookDetour(IntPtr originalFunctionPtr, IntPtr detourFunctionPtr) + public FunchookDetour(nint originalMethodPtr, nint detourMethodPtr) { - OriginalFunctionPtr = originalFunctionPtr; - DetourFunctionPtr = detourFunctionPtr; + OriginalMethodPtr = originalMethodPtr; + DetourMethodPtr = detourMethodPtr; } - public IntPtr OriginalFunctionPtr { get; protected set; } - public IntPtr DetourFunctionPtr { get; protected set; } - public IntPtr TrampolinePtr { get; protected set; } + public nint OriginalMethodPtr { get; protected set; } + public nint DetourMethodPtr { get; protected set; } + public nint TrampolinePtr { get; protected set; } public bool IsValid { get; protected set; } = true; @@ -36,11 +34,11 @@ public class FunchookDetour : IDetour if (IsApplied) return; PrepareDetour(); - if (DEBUG_DETOURS) logger.LogDebug($"Installing funchook instance"); + if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Installing funchook instance"); FunchookInstance.Install(); logger.Log(LogLevel.Debug, - $"Original: {OriginalFunctionPtr.ToInt64():X}, Trampoline: {TrampolinePtr:X}, diff: {Math.Abs(OriginalFunctionPtr.ToInt64() - TrampolinePtr.ToInt64()):X}; is within +-1GB range: {PageAllocator.IsInRelJmpRange(OriginalFunctionPtr, TrampolinePtr)}"); + $"Original: {OriginalMethodPtr:X}, Trampoline: {TrampolinePtr:X}, diff: {Math.Abs(OriginalMethodPtr - TrampolinePtr):X}; is within +-1GB range: {PageAllocator.IsInRelJmpRange(OriginalMethodPtr, TrampolinePtr)}"); IsApplied = true; } @@ -48,7 +46,7 @@ public class FunchookDetour : IDetour { if (IsApplied && IsPrepared) { - if (DEBUG_DETOURS) logger.LogDebug($"Uninstalling funchook instance"); + if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Uninstalling funchook instance"); FunchookInstance.Uninstall(); } } @@ -57,17 +55,17 @@ public class FunchookDetour : IDetour { if (IsPrepared) return; - if (DEBUG_DETOURS) logger.LogDebug($"Creating funchook instance"); + if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Creating funchook instance"); if (FunchookInstance == null) FunchookInstance = new FunchookWrapper(); - if (DEBUG_DETOURS) logger.LogDebug($"Preparing detour from 0x{OriginalFunctionPtr.ToInt64():X2} to 0x{DetourFunctionPtr.ToInt64():X2}"); + if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Preparing detour from 0x{OriginalMethodPtr:X2} to 0x{DetourMethodPtr:X2}"); - var trampolinePtr = OriginalFunctionPtr; - FunchookInstance.Prepare(&trampolinePtr, DetourFunctionPtr); + var trampolinePtr = OriginalMethodPtr; + FunchookInstance.Prepare(&trampolinePtr, DetourMethodPtr); - if (DEBUG_DETOURS) logger.LogDebug($"Prepared detour; Trampoline: 0x{trampolinePtr.ToInt64():X2}"); + if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Prepared detour; Trampoline: 0x{trampolinePtr:X2}"); TrampolinePtr = trampolinePtr; IsPrepared = true; @@ -82,7 +80,7 @@ public class FunchookDetour : IDetour public void Free() { - if (DEBUG_DETOURS) logger.LogDebug($"Destroying funchook instance"); + if (NativeDetourHelper.DEBUG_DETOURS) logger.LogDebug($"Destroying funchook instance"); FunchookInstance.Destroy(); IsValid = false; } diff --git a/BepInEx.IL2CPP/Hook/Funchook/FunchookImports.cs b/BepInEx.IL2CPP/Hook/Funchook/FunchookImports.cs new file mode 100644 index 0000000..fe279c3 --- /dev/null +++ b/BepInEx.IL2CPP/Hook/Funchook/FunchookImports.cs @@ -0,0 +1,63 @@ +using MonoMod.Utils; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace BepInEx.IL2CPP.Hook.Funchook; + +internal static unsafe class FunchookImports +{ + #region Imports + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate IntPtr d_funchook_create(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate FunchookResult d_funchook_destroy(IntPtr funchook); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate FunchookResult d_funchook_prepare(IntPtr funchook, IntPtr* target_func, IntPtr hook_func); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate FunchookResult d_funchook_install(IntPtr funchook, int flags); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate FunchookResult d_funchook_uninstall(IntPtr funchook, int flags); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate string d_funchook_error_message(IntPtr funchook); + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public delegate FunchookResult d_funchook_set_debug_file([MarshalAs(UnmanagedType.LPStr)] string name); + #endregion + + [DynDllImport("funchook")] + public static d_funchook_create funchook_create; + + [DynDllImport("funchook")] + public static d_funchook_destroy funchook_destroy; + + [DynDllImport("funchook")] + public static d_funchook_prepare funchook_prepare; + + [DynDllImport("funchook")] + public static d_funchook_install funchook_install; + + [DynDllImport("funchook")] + public static d_funchook_uninstall funchook_uninstall; + + [DynDllImport("funchook")] + public static d_funchook_error_message funchook_error_message; + + [DynDllImport("funchook")] + public static d_funchook_set_debug_file funchook_set_debug_file; + + static FunchookImports() + { + var funchookImports = new Dictionary>() + { + ["funchook"] = new() + { + $"{Paths.BepInExAssemblyDirectory}/native/funchook.dll" // Windows + } + }; + + typeof(FunchookImports).ResolveDynDllImports(funchookImports); + } +} diff --git a/BepInEx.IL2CPP/Hook/Funchook/FunchookWrapper.cs b/BepInEx.IL2CPP/Hook/Funchook/FunchookWrapper.cs new file mode 100644 index 0000000..d39fc98 --- /dev/null +++ b/BepInEx.IL2CPP/Hook/Funchook/FunchookWrapper.cs @@ -0,0 +1,56 @@ +using System; +using System.Runtime.InteropServices; + +namespace BepInEx.IL2CPP.Hook.Funchook; + +internal enum FunchookResult : int +{ + InternalError = -1, + Success = 0, + OutOfMemory = 1, + AlreadyInstalled = 2, + Disassembly = 3, + IPRelativeOffset = 4, + CannotFixIPRelative = 5, + FoundBackJump = 6, + TooShortInstructions = 7, + MemoryAllocation = 8, + MemoryFunction = 9, + NotInstalled = 10, + NoAvailableRegisters = 11, +} + +internal unsafe class FunchookWrapper +{ + internal static void SetDebugFile(string path) => FunchookImports.funchook_set_debug_file(path); + + public FunchookWrapper() + { + funchookInstance = FunchookImports.funchook_create(); + } + + public void Prepare(IntPtr* targetFunc, IntPtr detourFunc) + => EnsureSuccess(FunchookImports.funchook_prepare(funchookInstance, targetFunc, detourFunc), nameof(FunchookImports.funchook_prepare)); + + public void Install(int flags = 0) + => EnsureSuccess(FunchookImports.funchook_install(funchookInstance, flags), nameof(FunchookImports.funchook_install)); + + public void Uninstall(int flags = 0) + => EnsureSuccess(FunchookImports.funchook_uninstall(funchookInstance, flags), nameof(FunchookImports.funchook_uninstall)); + + public string GetErrorMessage() + => FunchookImports.funchook_error_message(funchookInstance); + + public void Destroy() + => EnsureSuccess(FunchookImports.funchook_destroy(funchookInstance), nameof(FunchookImports.funchook_destroy)); + + private void EnsureSuccess(FunchookResult result, string methodName) + { + if (result == FunchookResult.Success) return; + var errorMsg = GetErrorMessage(); + if (result == FunchookResult.OutOfMemory) throw new OutOfMemoryException($"{methodName} failed: {errorMsg}"); + throw new Exception($"{methodName} failed with result {result}: {errorMsg}"); + } + + private readonly IntPtr funchookInstance; +} diff --git a/BepInEx.IL2CPP/Hook/FunchookWrapper.cs b/BepInEx.IL2CPP/Hook/FunchookWrapper.cs deleted file mode 100644 index 0e35d37..0000000 --- a/BepInEx.IL2CPP/Hook/FunchookWrapper.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace BepInEx.IL2CPP.Hook; - -internal enum FunchookResult : int -{ - InternalError = -1, - Success = 0, - OutOfMemory = 1, - AlreadyInstalled = 2, - Disassembly = 3, - IPRelativeOffset = 4, - CannotFixIPRelative = 5, - FoundBackJump = 6, - TooShortInstructions = 7, - MemoryAllocation = 8, - MemoryFunction = 9, - NotInstalled = 10, - NoAvailableRegisters = 11, -} - -internal unsafe class FunchookWrapper -{ - internal static void SetDebugFile(string path) => funchook_set_debug_file(path); - - public FunchookWrapper() - { - funchookInstance = funchook_create(); - } - - public void Prepare(IntPtr* targetFunc, IntPtr detourFunc) - => EnsureSuccess(funchook_prepare(funchookInstance, targetFunc, detourFunc), nameof(funchook_prepare)); - - public void Install(int flags = 0) - => EnsureSuccess(funchook_install(funchookInstance, flags), nameof(funchook_install)); - - public void Uninstall(int flags = 0) - => EnsureSuccess(funchook_uninstall(funchookInstance, flags), nameof(funchook_uninstall)); - - public string GetErrorMessage() - => funchook_error_message(funchookInstance); - - public void Destroy() - => EnsureSuccess(funchook_destroy(funchookInstance), nameof(funchook_destroy)); - - private void EnsureSuccess(FunchookResult result, string methodName) - { - if (result == FunchookResult.Success) return; - var errorMsg = GetErrorMessage(); - if (result == FunchookResult.OutOfMemory) throw new OutOfMemoryException($"{methodName} failed: {errorMsg}"); - throw new Exception($"{methodName} failed with result {result}: {errorMsg}"); - } - - private readonly IntPtr funchookInstance; - - #region Imports - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr funchook_create(); - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl)] - private static extern FunchookResult funchook_destroy(IntPtr funchook); - - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl)] - private static extern FunchookResult funchook_prepare(IntPtr funchook, IntPtr* target_func, IntPtr hook_func); - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl)] - private static extern FunchookResult funchook_install(IntPtr funchook, int flags); - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl)] - private static extern FunchookResult funchook_uninstall(IntPtr funchook, int flags); - - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern string funchook_error_message(IntPtr funchook); - [DllImport("funchook", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - private static extern FunchookResult funchook_set_debug_file(string name); - #endregion -} diff --git a/BepInEx.IL2CPP/Hook/IL2CPPDetourMethodPatcher.cs b/BepInEx.IL2CPP/Hook/IL2CPPDetourMethodPatcher.cs index f1703e5..d081c9e 100644 --- a/BepInEx.IL2CPP/Hook/IL2CPPDetourMethodPatcher.cs +++ b/BepInEx.IL2CPP/Hook/IL2CPPDetourMethodPatcher.cs @@ -62,7 +62,7 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher private bool isValid; private INativeMethodInfoStruct modifiedNativeMethodInfo; - private FunchookDetour nativeDetour; + private INativeDetour nativeDetour; private INativeMethodInfoStruct originalNativeMethodInfo; @@ -98,16 +98,11 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher originalNativeMethodInfo = UnityVersionHandler.Wrap((Il2CppMethodInfo*)(IntPtr)methodField.GetValue(null)); - // Create a trampoline from the original target method - var trampolinePtr = - DetourGenerator.CreateTrampolineFromFunction(originalNativeMethodInfo.MethodPointer, out _, out _); - - // Create a modified native MethodInfo struct to point towards the trampoline + // Create a modified native MethodInfo struct, that will point towards the trampoline modifiedNativeMethodInfo = UnityVersionHandler.NewMethod(); Buffer.MemoryCopy(originalNativeMethodInfo.Pointer.ToPointer(), modifiedNativeMethodInfo.Pointer.ToPointer(), UnityVersionHandler.MethodSize(), UnityVersionHandler.MethodSize()); - modifiedNativeMethodInfo.MethodPointer = trampolinePtr; isValid = true; } catch (Exception e) @@ -124,7 +119,12 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher public override MethodBase DetourTo(MethodBase replacement) { // Unpatch an existing detour if it exists - nativeDetour?.Dispose(); + if (nativeDetour != null) + { + // Point back to the original method before we unpatch + modifiedNativeMethodInfo.MethodPointer = originalNativeMethodInfo.MethodPointer; + nativeDetour.Dispose(); + } // Generate a new DMD of the modified unhollowed method, and apply harmony patches to it var copiedDmd = CopyOriginal(); @@ -142,8 +142,9 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher var detourPtr = Marshal.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType)); - nativeDetour = new FunchookDetour(originalNativeMethodInfo.MethodPointer, detourPtr); + nativeDetour = NativeDetourHelper.Create(originalNativeMethodInfo.MethodPointer, detourPtr); nativeDetour.Apply(); + modifiedNativeMethodInfo.MethodPointer = nativeDetour.TrampolinePtr; // TODO: Add an ILHook for the original unhollowed method to go directly to managedHookedMethod // Right now it goes through three times as much interop conversion as it needs to, when being called from managed side diff --git a/BepInEx.IL2CPP/Hook/INativeDetour.cs b/BepInEx.IL2CPP/Hook/INativeDetour.cs new file mode 100644 index 0000000..20014fc --- /dev/null +++ b/BepInEx.IL2CPP/Hook/INativeDetour.cs @@ -0,0 +1,13 @@ +using MonoMod.RuntimeDetour; +using System; +using System.Collections.Generic; +using System.Text; + +namespace BepInEx.IL2CPP.Hook; + +public interface INativeDetour : IDetour +{ + public nint OriginalMethodPtr { get; } + public nint DetourMethodPtr { get; } + public nint TrampolinePtr { get; } +} diff --git a/BepInEx.IL2CPP/Hook/NativeDetourHelper.cs b/BepInEx.IL2CPP/Hook/NativeDetourHelper.cs new file mode 100644 index 0000000..075ed75 --- /dev/null +++ b/BepInEx.IL2CPP/Hook/NativeDetourHelper.cs @@ -0,0 +1,30 @@ +using BepInEx.IL2CPP.Hook.Funchook; +using MonoMod.RuntimeDetour; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace BepInEx.IL2CPP.Hook; + +public class NativeDetourHelper +{ + public static bool DEBUG_DETOURS = false; + public static INativeDetour Create(nint original, nint target) + { + return new FunchookDetour(original, target); + } + + public static INativeDetour CreateAndApply(nint from, T to, out T original, CallingConvention? callingConvention = null) + where T : Delegate + { + var toPtr = callingConvention != null + ? MonoExtensions.GetFunctionPointerForDelegate(to, callingConvention.Value) + : Marshal.GetFunctionPointerForDelegate(to); + + var detour = Create(from, toPtr); + original = detour.GenerateTrampoline(); + detour.Apply(); + return detour; + } +} diff --git a/BepInEx.IL2CPP/Hook/UnhollowerDetourHandler.cs b/BepInEx.IL2CPP/Hook/UnhollowerDetourHandler.cs index f472cef..0ef73c7 100644 --- a/BepInEx.IL2CPP/Hook/UnhollowerDetourHandler.cs +++ b/BepInEx.IL2CPP/Hook/UnhollowerDetourHandler.cs @@ -7,7 +7,7 @@ public class UnhollowerDetourHandler : IManagedDetour { public T Detour(IntPtr from, T to) where T : Delegate { - FunchookDetour.CreateAndApply(from, to, out var original); + NativeDetourHelper.CreateAndApply(from, to, out var original); return original; } } diff --git a/BepInEx.IL2CPP/IL2CPPChainloader.cs b/BepInEx.IL2CPP/IL2CPPChainloader.cs index 554c609..a2c78b9 100644 --- a/BepInEx.IL2CPP/IL2CPPChainloader.cs +++ b/BepInEx.IL2CPP/IL2CPPChainloader.cs @@ -14,6 +14,7 @@ using BepInEx.Preloader.Core; using BepInEx.Preloader.Core.Logging; using HarmonyLib.Public.Patching; using Il2Cpp.TlsAdapter; +using MonoMod.RuntimeDetour; using MonoMod.Utils; using UnhollowerBaseLib; using UnhollowerRuntimeLib; @@ -37,8 +38,8 @@ public class IL2CPPChainloader : BaseChainloader false, "Include unity log messages in log file output."); - private static FunchookDetour RuntimeInvokeDetour { get; set; } - private static FunchookDetour InstallUnityTlsInterfaceDetour { get; set; } + private static INativeDetour RuntimeInvokeDetour { get; set; } + private static INativeDetour InstallUnityTlsInterfaceDetour { get; set; } public static IL2CPPChainloader Instance { get; set; } @@ -80,13 +81,13 @@ public class IL2CPPChainloader : BaseChainloader gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_runtime_invoke", out var runtimeInvokePtr); PreloaderLogger.Log.Log(LogLevel.Debug, $"Runtime invoke pointer: 0x{runtimeInvokePtr.ToInt64():X}"); RuntimeInvokeDetour = - FunchookDetour.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke, + NativeDetourHelper.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke, CallingConvention.Cdecl); if (gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_unity_install_unitytls_interface", out var installTlsPtr)) InstallUnityTlsInterfaceDetour = - FunchookDetour.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface, + NativeDetourHelper.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface, out originalInstallUnityTlsInterface, CallingConvention.Cdecl); Logger.Log(LogLevel.Debug, "Initializing TLS adapters"); diff --git a/BepInExTests/TrampolineTests.cs b/BepInExTests/TrampolineTests.cs deleted file mode 100644 index 9eca0d0..0000000 --- a/BepInExTests/TrampolineTests.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using BepInEx.IL2CPP; -using Iced.Intel; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace BepInEx.Tests; - -[TestClass] -public class TrampolineTests -{ - [DataTestMethod] - [DataRow(64)] - [DataRow(32)] - public void TrampolineTest(int bitness) - { - byte[] exampleCode = - { - 0x48, 0x89, 0x5C, 0x24, 0x10, 0x48, 0x89, 0x74, 0x24, 0x18, 0x55, 0x57, 0x41, 0x56, 0x48, 0x8D, - 0xAC, 0x24, 0x00, 0xFF, 0xFF, 0xFF, 0x48, 0x81, 0xEC, 0x00, 0x02, 0x00, 0x00, 0x48, 0x8B, 0x05, - 0x18, 0x57, 0x0A, 0x00, 0x48, 0x33, 0xC4, 0x48, 0x89, 0x85, 0xF0, 0x00, 0x00, 0x00, 0x4C, 0x8B, - 0x05, 0x2F, 0x24, 0x0A, 0x00, 0x48, 0x8D, 0x05, 0x78, 0x7C, 0x04, 0x00, 0x33, 0xFF - }; - - var exampleCodePointer = Marshal.AllocHGlobal(80); - var trampolineCodePointer = Marshal.AllocHGlobal(80); - Marshal.Copy(exampleCode, 0, exampleCodePointer, exampleCode.Length); - - - void Disassemble(byte[] data, ulong ip) - { - var formatter = new NasmFormatter(); - var output = new StringOutput(); - var codeReader = new ByteArrayCodeReader(data); - var decoder = Decoder.Create(bitness, codeReader); - decoder.IP = ip; - while (codeReader.CanReadByte) - { - decoder.Decode(out var instr); - formatter.Format(instr, output); - Console.WriteLine($"{instr.IP:X16} {output.ToStringAndReset()}"); - } - - Console.WriteLine(); - } - - - Console.WriteLine("Original:"); - Console.WriteLine(); - - - Disassemble(exampleCode, (ulong) exampleCodePointer.ToInt64()); - - DetourGenerator.CreateTrampolineFromFunction(exampleCodePointer, out var trampolineLength, out _); - - Console.WriteLine("Modified:"); - Console.WriteLine(); - - - Marshal.Copy(exampleCodePointer, exampleCode, 0, exampleCode.Length); - Disassemble(exampleCode, (ulong) exampleCodePointer.ToInt64()); - - - Console.WriteLine(); - Console.WriteLine("Trampoline:"); - Console.WriteLine(); - - var trampolineArray = new byte[trampolineLength]; - Marshal.Copy(trampolineCodePointer, trampolineArray, 0, trampolineLength); - - Disassemble(trampolineArray, (ulong) trampolineCodePointer.ToInt64()); - - - Marshal.FreeHGlobal(exampleCodePointer); - Marshal.FreeHGlobal(trampolineCodePointer); - - Assert.IsFalse(false); - } -}