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