Browse Source

Merge pull request #157 from BepInEx/feature-memory-allocator

Page allocator for FastNativeDetour
Geoffrey Horsington 3 years ago
parent
commit
00fd0b5fc9

+ 28 - 0
BepInEx.IL2CPP/Hook/Allocator/LinuxPageAllocator.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+
+namespace BepInEx.IL2CPP.Allocator
+{
+	internal class LinuxPageAllocator : UnixPageAllocator
+	{
+		protected override IEnumerable<(IntPtr, IntPtr)> MapMemoryAreas()
+		{
+			// Each row of /proc/self/maps is as follows:
+			// <start_address>-<end_address> <perms> <offset> <dev> <inode>		<owner_name>
+			// More info: https://stackoverflow.com/a/1401595
+			using var procMap = new StreamReader(File.OpenRead("/proc/self/maps"));
+
+			string line;
+			while ((line = procMap.ReadLine()) != null)
+			{
+				int startIndex = line.IndexOf('-');
+				int endIndex = line.IndexOf(' ');
+				long startAddr = long.Parse(line.Substring(0, startIndex), NumberStyles.HexNumber);
+				long endAddr = long.Parse(line.Substring(startIndex + 1, endIndex - startIndex - 1), NumberStyles.HexNumber);
+				yield return (new IntPtr(startAddr), new IntPtr(endAddr));
+			}
+		}
+	}
+}

+ 75 - 0
BepInEx.IL2CPP/Hook/Allocator/MacOsPageAllocator.cs

@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using MonoMod.Utils;
+
+namespace BepInEx.IL2CPP.Allocator
+{
+	internal class MacOsPageAllocator : UnixPageAllocator
+	{
+		protected override IEnumerable<(IntPtr, IntPtr)> MapMemoryAreas()
+		{
+			var size = IntPtr.Zero;
+			var info = new LibSystem.vm_region_basic_info_64();
+			var infoCount = (uint)(Marshal.SizeOf<LibSystem.vm_region_basic_info_64>() / sizeof(int));
+			var objectName = 0u;
+			var address = IntPtr.Zero;
+
+			while (LibSystem.vm_region_64(LibSystem.TaskSelf, ref address, ref size, LibSystem.VM_REGION_BASIC_INFO_64, ref info, ref infoCount, ref objectName) == LibSystem.KERN_SUCCESS)
+			{
+				var start = new IntPtr(address.ToInt64());
+				var end = new IntPtr(address.ToInt64() + size.ToInt64());
+				address = end;
+				yield return (start, end);
+			}
+		}
+
+		private static class LibSystem
+		{
+			public const int VM_REGION_BASIC_INFO_64 = 9;
+			public const int KERN_SUCCESS = 0;
+			public static readonly IntPtr TaskSelf;
+
+			static LibSystem()
+			{
+				typeof(LibSystem).ResolveDynDllImports(new Dictionary<string, List<DynDllMapping>>
+				{
+					["libSystem"] = new List<DynDllMapping>
+					{
+						"/usr/lib/libSystem.dylib" // OSX POSIX
+					}
+				});
+
+				var libsystem = DynDll.OpenLibrary("/usr/lib/libSystem.dylib");
+				TaskSelf = libsystem.GetFunction("mach_task_self_"); // This isn't a function but rather an exported symbol
+			}
+
+			// ReSharper disable InconsistentNaming
+			[StructLayout(LayoutKind.Sequential)]
+			public readonly struct vm_region_basic_info_64
+			{
+				public readonly int protection;
+				public readonly int max_protection;
+				public readonly uint inheritance;
+
+				[MarshalAs(UnmanagedType.I4)]
+				public readonly bool shared;
+
+				[MarshalAs(UnmanagedType.I4)]
+				public readonly bool reserved;
+
+				public readonly ulong offset;
+				public readonly int behavior;
+				public readonly ushort user_wired_count;
+			}
+			// ReSharper restore InconsistentNaming
+
+			// ReSharper disable InconsistentNaming
+			[DynDllImport("libSystem")]
+			public static vm_region_64Delegate vm_region_64;
+
+			public delegate int vm_region_64Delegate(IntPtr target_task, ref IntPtr address, ref IntPtr size, int flavor, ref vm_region_basic_info_64 info, ref uint infoCnt, ref uint object_name);
+			// ReSharper restore InconsistentNaming
+		}
+	}
+}

+ 152 - 0
BepInEx.IL2CPP/Hook/Allocator/PageAllocator.cs

@@ -0,0 +1,152 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using MonoMod.Utils;
+
+namespace BepInEx.IL2CPP.Allocator
+{
+	public class PageAllocatorException : Exception
+	{
+		public PageAllocatorException(string message) : base(message) { }
+	}
+
+	/// <summary>
+	///     A general purpose page allocator for patching purposes.
+	///     Allows to allocate pages (4k memory chunks) within the 1GB radius of a given address.
+	/// </summary>
+	/// <remarks>Based on https://github.com/kubo/funchook</remarks>
+	internal abstract class PageAllocator
+	{
+		/// <summary>
+		///     Common page size on Unix and Windows (4k).
+		///     Call to <see cref="Allocate" /> will allocate a single page of this size.
+		/// </summary>
+		public const int PAGE_SIZE = 0x1000;
+
+		/// <summary>
+		///     Allocation granularity on Windows (but can be reused in other implementations).
+		/// </summary>
+		protected const int ALLOCATION_UNIT = 0x100000;
+
+		protected const int PAGES_PER_UNIT = ALLOCATION_UNIT / PAGE_SIZE;
+
+		private static PageAllocator instance;
+
+		private readonly List<PageChunk> allocatedChunks = new List<PageChunk>();
+
+		/// <summary>
+		///     Platform-specific instance of page allocator.
+		/// </summary>
+		public static PageAllocator Instance => instance ??= Init();
+
+		/// <summary>
+		///     Allocates a single 64k chunk of memory near the given address
+		/// </summary>
+		/// <param name="hint">Address near which to attempt allocate the chunk</param>
+		/// <returns>Allocated chunk</returns>
+		/// <exception cref="PageAllocatorException">Allocation failed</exception>
+		protected abstract IntPtr AllocateChunk(IntPtr hint);
+
+		/// <summary>
+		///     Allocates a single page of size <see cref="PAGE_SIZE" /> near the provided address.
+		///     Attempts to allocate the page within the +-1GB region of the hinted address.
+		/// </summary>
+		/// <param name="hint">Address near which to attempt to allocate the page.</param>
+		/// <returns>Address to the allocated page.</returns>
+		public virtual IntPtr Allocate(IntPtr hint)
+		{
+			foreach (var allocatedChunk in allocatedChunks)
+			{
+				// Small shortcut to speed up page lookup
+				if (allocatedChunk.UsedPages == PAGES_PER_UNIT)
+					continue;
+				for (var i = 0; i < allocatedChunk.Pages.Length; i++)
+				{
+					if (allocatedChunk.Pages[i])
+						continue;
+					var pageAddr = allocatedChunk.GetPage(i);
+					if (!IsInRelJmpRange(hint, pageAddr))
+						continue;
+					allocatedChunk.Pages[i] = true;
+					allocatedChunk.UsedPages++;
+					return pageAddr;
+				}
+			}
+
+			var chunk = new PageChunk
+			{
+				BaseAddress = AllocateChunk(hint)
+			};
+			allocatedChunks.Add(chunk);
+			chunk.Pages[0] = true;
+			chunk.UsedPages++;
+			return chunk.BaseAddress;
+		}
+
+		/// <summary>
+		///     Frees the page allocated with <see cref="Allocate" />
+		/// </summary>
+		/// <param name="page"></param>
+		public void Free(IntPtr page)
+		{
+			foreach (var allocatedChunk in allocatedChunks)
+			{
+				long index = (page.ToInt64() - allocatedChunk.BaseAddress.ToInt64()) / PAGE_SIZE;
+				if (index < 0 || index > PAGES_PER_UNIT)
+					continue;
+				allocatedChunk.Pages[index] = false;
+				return;
+			}
+		}
+
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		protected static long RoundUp(long num, long unit)
+		{
+			return (num + unit - 1) & ~ (unit - 1);
+		}
+
+		/// <summary>
+		///     Checks if the given address is within the relative jump range.
+		/// </summary>
+		/// <param name="src">Source address to jump from.</param>
+		/// <param name="dst">Destination address to jump to.</param>
+		/// <returns>True, if the distance between the addresses is within the relative jump range (usually 1GB), otherwise false.</returns>
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		public static bool IsInRelJmpRange(IntPtr src, IntPtr dst)
+		{
+			long diff = dst.ToInt64() - src.ToInt64();
+			return int.MinValue <= diff && diff <= int.MaxValue;
+		}
+
+		private static PageAllocator Init()
+		{
+			return PlatformHelper.Current switch
+			{
+				var v when v.Is(Platform.Windows) => new WindowsPageAllocator(),
+				var v when v.Is(Platform.Linux)   => new LinuxPageAllocator(),
+				var v when v.Is(Platform.MacOS)   => new MacOsPageAllocator(),
+				_                                 => throw new NotImplementedException()
+			};
+		}
+
+		private class PageChunk
+		{
+			public readonly bool[] Pages = new bool[PAGES_PER_UNIT];
+			public IntPtr BaseAddress;
+			public int UsedPages;
+
+			public IntPtr GetPage(int index)
+			{
+				return BaseAddress + index * PAGE_SIZE;
+			}
+		}
+	}
+
+	internal static class PlatformExt
+	{
+		public static bool Is(this Platform pl, Platform val)
+		{
+			return (pl & val) == val;
+		}
+	}
+}

+ 133 - 0
BepInEx.IL2CPP/Hook/Allocator/UnixPageAllocator.cs

@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using MonoMod.Utils;
+
+namespace BepInEx.IL2CPP.Allocator
+{
+	/// <summary>
+	///     Based on https://github.com/kubo/funchook
+	/// </summary>
+	internal abstract class UnixPageAllocator : PageAllocator
+	{
+		protected abstract IEnumerable<(IntPtr, IntPtr)> MapMemoryAreas();
+
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		private static bool CheckFreeRegionBefore(IntPtr start, IntPtr hint, ref (IntPtr Start, IntPtr End) region)
+		{
+			if (start.ToInt64() < hint.ToInt64())
+			{
+				var addr = start - PAGE_SIZE;
+				if (hint.ToInt64() - addr.ToInt64() < int.MaxValue)
+					region.Start = addr;
+			}
+
+			return false;
+		}
+
+		[MethodImpl(MethodImplOptions.AggressiveInlining)]
+		private static bool CheckFreeRegionAfter(IntPtr end, IntPtr hint, ref (IntPtr Start, IntPtr End) region)
+		{
+			if (hint.ToInt64() < end.ToInt64())
+			{
+				if (end.ToInt64() - hint.ToInt64() < int.MaxValue)
+					region.End = end;
+				return true;
+			}
+
+			return false;
+		}
+
+		private (IntPtr, IntPtr) GetFreeArea(IntPtr hint)
+		{
+			var result = (IntPtr.Zero, IntPtr.Zero);
+			var prevEnd = IntPtr.Zero;
+
+			foreach (var (start, end) in MapMemoryAreas())
+			{
+				if ((prevEnd + PAGE_SIZE).ToInt64() <= start.ToInt64())
+					if (CheckFreeRegionBefore(start, hint, ref result) || CheckFreeRegionAfter(prevEnd, hint, ref result))
+						return result;
+				prevEnd = end;
+			}
+
+			if (CheckFreeRegionAfter(prevEnd, hint, ref result))
+				return result;
+			throw new PageAllocatorException($"Could not find free region near {hint.ToInt64():X8}");
+		}
+
+		protected override IntPtr AllocateChunk(IntPtr hint)
+		{
+			/* From https://github.com/kubo/funchook/blob/master/src/funchook_unix.c#L251-L254:
+			 * Loop three times just to avoid rare cases such as
+			 * unused memory region is used between 'get_free_address()'
+			 * and 'mmap()'.
+			*/
+			const int retryCount = 3;
+
+			for (var attempt = 0; attempt < retryCount; attempt++)
+			{
+				var (start, end) = GetFreeArea(hint);
+				// Try to allocate to end (after original method) first, then try before
+				var addrs = new [] { end, start };
+				foreach (var addr in addrs)
+				{
+					if (addr == IntPtr.Zero)
+						continue;
+					var result = Unix.mmap(addr, (UIntPtr)PAGE_SIZE, Unix.Protection.PROT_READ | Unix.Protection.PROT_WRITE, Unix.MapFlags.MAP_PRIVATE | Unix.MapFlags.MAP_ANONYMOUS, -1, 0);
+					if (result == addr)
+						return result;
+					if (result == Unix.MAP_FAILED)
+						throw new Win32Exception(Marshal.GetLastWin32Error()); // Yes, this should work on unix too
+					Unix.munmap(result, (UIntPtr)PAGE_SIZE);
+				}
+			}
+
+			throw new PageAllocatorException("Failed to allocate memory in unused regions");
+		}
+
+		private static class Unix
+		{
+			public static readonly IntPtr MAP_FAILED = new IntPtr(-1);
+
+			[DynDllImport("mmap")]
+			public static mmapDelegate mmap;
+
+			[DynDllImport("munmap")]
+			public static munmapDelegate munmap;
+
+			public delegate IntPtr mmapDelegate(IntPtr addr, UIntPtr length, Protection prot, MapFlags flags, int fd, int offset);
+
+			public delegate int munmapDelegate(IntPtr addr, UIntPtr length);
+
+			[Flags]
+			public enum MapFlags
+			{
+				MAP_PRIVATE = 0x02,
+				MAP_ANONYMOUS = 0x20
+			}
+
+			[Flags]
+			public enum Protection
+			{
+				PROT_READ = 0x1,
+				PROT_WRITE = 0x2
+			}
+
+			static Unix()
+			{
+				typeof(Unix).ResolveDynDllImports(new Dictionary<string, List<DynDllMapping>>
+				{
+					["libc"] = new List<DynDllMapping>
+					{
+						"libc.so.6",               // Ubuntu glibc
+						"libc",                    // Linux glibc,
+						"/usr/lib/libSystem.dylib" // OSX POSIX
+					}
+				});
+			}
+		}
+	}
+}

+ 117 - 0
BepInEx.IL2CPP/Hook/Allocator/WindowsPageAllocator.cs

@@ -0,0 +1,117 @@
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace BepInEx.IL2CPP.Allocator
+{
+	/// <summary>
+	///     Based on https://github.com/kubo/funchook
+	/// </summary>
+	internal class WindowsPageAllocator : PageAllocator
+	{
+		protected override IntPtr AllocateChunk(IntPtr hint)
+		{
+			while (true)
+			{
+				var mbi = new WinApi.MEMORY_BASIC_INFORMATION();
+				if (WinApi.VirtualQuery(hint, ref mbi, Marshal.SizeOf<WinApi.MEMORY_BASIC_INFORMATION>()) == 0)
+					throw new Win32Exception(Marshal.GetLastWin32Error());
+
+				if (mbi.State == WinApi.PageState.MEM_FREE)
+				{
+					long nextAddress = RoundUp(mbi.BaseAddress.ToInt64(), ALLOCATION_UNIT);
+					long d = nextAddress - mbi.BaseAddress.ToInt64();
+					if (d >= 0 && mbi.RegionSize.ToInt64() - d >= ALLOCATION_UNIT)
+					{
+						hint = (IntPtr)nextAddress;
+						break;
+					}
+				}
+
+				hint = (IntPtr)(mbi.BaseAddress.ToInt64() + mbi.RegionSize.ToInt64());
+			}
+
+			var chunk = WinApi.VirtualAlloc(hint, (UIntPtr)ALLOCATION_UNIT, WinApi.AllocationType.MEM_RESERVE, WinApi.ProtectConstant.PAGE_NOACCESS);
+			if (chunk == null)
+				throw new Win32Exception(Marshal.GetLastWin32Error());
+			var addr = WinApi.VirtualAlloc(chunk, (UIntPtr)PAGE_SIZE, WinApi.AllocationType.MEM_COMMIT, WinApi.ProtectConstant.PAGE_READWRITE);
+			if (addr == IntPtr.Zero)
+			{
+				int error = Marshal.GetLastWin32Error();
+				WinApi.VirtualFree(chunk, UIntPtr.Zero, WinApi.FreeType.MEM_RELEASE);
+				throw new Win32Exception(error);
+			}
+
+			return chunk;
+		}
+
+		public override IntPtr Allocate(IntPtr hint)
+		{
+			var pageAddress = base.Allocate(hint);
+			if (WinApi.VirtualAlloc(pageAddress, (UIntPtr)PAGE_SIZE, WinApi.AllocationType.MEM_COMMIT, WinApi.ProtectConstant.PAGE_READWRITE) == IntPtr.Zero)
+				throw new Win32Exception(Marshal.GetLastWin32Error());
+			return pageAddress;
+		}
+
+		private static class WinApi
+		{
+			[Flags]
+			public enum AllocationType : uint
+			{
+				// ReSharper disable InconsistentNaming
+				MEM_COMMIT = 0x00001000,
+
+				MEM_RESERVE = 0x00002000
+				// ReSharper restore InconsistentNaming
+			}
+
+
+			[Flags]
+			public enum FreeType : uint
+			{
+				// ReSharper disable InconsistentNaming
+				MEM_RELEASE = 0x00008000
+				// ReSharper restore InconsistentNaming
+			}
+
+			public enum PageState : uint
+			{
+				// ReSharper disable InconsistentNaming
+				MEM_FREE = 0x10000
+				// ReSharper restore InconsistentNaming
+			}
+
+			[Flags]
+			public enum ProtectConstant : uint
+			{
+				// ReSharper disable InconsistentNaming
+				PAGE_NOACCESS = 0x01,
+
+				PAGE_READWRITE = 0x04
+				// ReSharper restore InconsistentNaming
+			}
+
+			[DllImport("kernel32", SetLastError = true)]
+			public static extern int VirtualQuery(IntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
+
+			[DllImport("kernel32", SetLastError = true)]
+			public static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, ProtectConstant flProtect);
+
+			[DllImport("kernel32", SetLastError = true)]
+			public static extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, FreeType dwFreeType);
+
+			[StructLayout(LayoutKind.Sequential)]
+			// ReSharper disable once InconsistentNaming
+			public struct MEMORY_BASIC_INFORMATION
+			{
+				public readonly IntPtr BaseAddress;
+				public readonly IntPtr AllocationBase;
+				public readonly uint AllocationProtect;
+				public readonly IntPtr RegionSize;
+				public readonly PageState State;
+				public readonly uint Protect;
+				public readonly uint Type;
+			}
+		}
+	}
+}

+ 7 - 32
BepInEx.IL2CPP/Hook/DetourGenerator.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Runtime.InteropServices;
+using BepInEx.IL2CPP.Allocator;
 using BepInEx.Logging;
 using Iced.Intel;
 using MonoMod.RuntimeDetour;
@@ -66,9 +67,9 @@ namespace BepInEx.IL2CPP
 			byte[] instructionBuffer = new byte[32];
 			Marshal.Copy(originalFuncPointer, instructionBuffer, 0, 32);
 
-			var trampolinePtr = DetourHelper.Native.MemAlloc(80);
+			var trampolinePtr = PageAllocator.Instance.Allocate(originalFuncPointer);
 
-			DetourHelper.Native.MakeWritable(trampolinePtr, 80);
+			DetourHelper.Native.MakeWritable(trampolinePtr, PageAllocator.PAGE_SIZE);
 
 			var arch = IntPtr.Size == 8 ? Architecture.X64 : Architecture.X86;
 
@@ -77,7 +78,7 @@ namespace BepInEx.IL2CPP
 			CreateTrampolineFromFunction(instructionBuffer, originalFuncPointer, trampolinePtr, minimumTrampolineLength, arch, out trampolineLength, out jmpLength);
 
 			DetourHelper.Native.MakeExecutable(originalFuncPointer, 32);
-			DetourHelper.Native.MakeExecutable(trampolinePtr, (uint)trampolineLength);
+			DetourHelper.Native.MakeExecutable(trampolinePtr, PageAllocator.PAGE_SIZE);
 
 			return trampolinePtr;
 		}
@@ -106,27 +107,6 @@ namespace BepInEx.IL2CPP
 			{
 				decoder.Decode(out var instr);
 
-				if (instr.IsIPRelativeMemoryOperand)
-				{
-					// TODO: AssemlberRegisters not needed, figure out what props to actually change
-					// TODO: Check if it's better to use InternalOp0Kind (and other similar props) instead of normal ones
-					// TODO: Probably need to check if the target is within the trampoline boundaries and thus shouldn't be fixed
-					logger.LogDebug($"Got ptr with relative memory operand: {instr}");
-					var addr = instr.IPRelativeMemoryAddress;
-					logger.LogDebug($"Address: {addr:X}");
-					instr.MemoryBase = Register.None;
-					var op = AssemblerRegisters.__byte_ptr[addr].ToMemoryOperand(64);
-					instr.Op0Kind = OpKind.Memory;
-					instr.MemoryBase = op.Base;
-					instr.MemoryIndex = op.Index;
-					instr.MemoryIndexScale = op.Scale;
-					instr.MemoryDisplSize = op.DisplSize;
-					instr.MemoryDisplacement = (uint)op.Displacement;
-					instr.IsBroadcast = op.IsBroadcast;
-					instr.SegmentPrefix = op.SegmentPrefix;
-					logger.LogDebug($"After edit: {instr}");
-				}
-				
 				origInstructions.Add(instr);
 				
 				totalBytes += (uint)instr.Length;
@@ -140,19 +120,14 @@ namespace BepInEx.IL2CPP
 					case FlowControl.Next:
 						break;
 
-					case FlowControl.UnconditionalBranch:
-						if (instr.Op0Kind == OpKind.NearBranch64)
-						{
-							var target = instr.NearBranchTarget;
-						}
-
-						break;
-					//goto default;
 					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
 					case FlowControl.Call:// eg. call method
 					case FlowControl.IndirectCall:// eg. call reg/mem

+ 9 - 15
BepInEx.IL2CPP/Hook/FastNativeDetour.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Reflection;
 using System.Runtime.InteropServices;
+using BepInEx.IL2CPP.Allocator;
 using BepInEx.Logging;
 using MonoMod.RuntimeDetour;
 using MonoMod.Utils;
@@ -36,13 +37,13 @@ namespace BepInEx.IL2CPP.Hook
 			Marshal.Copy(originalFunctionPtr, BackupBytes, 0, 20);
 		}
 
+		private static ManualLogSource logger = Logger.CreateLogSource("FastNativeDetour");
 
 		public void Apply()
 		{
 			Apply(null);
 		}
 
-
 		public void Apply(ManualLogSource debuggerLogSource)
 		{
 			if (IsApplied)
@@ -51,14 +52,10 @@ namespace BepInEx.IL2CPP.Hook
 
 			DetourHelper.Native.MakeWritable(OriginalFunctionPtr, 32);
 
-
 			if (debuggerLogSource != null)
 			{
 				debuggerLogSource.LogDebug($"Detouring 0x{OriginalFunctionPtr.ToString("X")} -> 0x{DetourFunctionPtr.ToString("X")}");
-
-
 				debuggerLogSource.LogDebug("Original (32) asm");
-
 				DetourGenerator.Disassemble(debuggerLogSource, OriginalFunctionPtr, 32);
 			}
 
@@ -68,17 +65,12 @@ namespace BepInEx.IL2CPP.Hook
 
 			DetourGenerator.ApplyDetour(OriginalFunctionPtr, DetourFunctionPtr, arch, trampolineLength - jmpLength);
 
-
 			if (debuggerLogSource != null)
 			{
 				debuggerLogSource.LogDebug($"Trampoline allocation: 0x{TrampolinePtr.ToString("X")}");
-
 				debuggerLogSource.LogDebug("Modified (32) asm");
-
 				DetourGenerator.Disassemble(debuggerLogSource, OriginalFunctionPtr, 32);
-
 				debuggerLogSource.LogDebug($"Trampoline ({trampolineLength}) asm");
-
 				DetourGenerator.Disassemble(debuggerLogSource, TrampolinePtr, trampolineLength);
 			}
 
@@ -100,16 +92,18 @@ namespace BepInEx.IL2CPP.Hook
 			byte[] instructionBuffer = new byte[32];
 			Marshal.Copy(OriginalFunctionPtr, instructionBuffer, 0, 32);
 
-			var trampolineAlloc = DetourHelper.Native.MemAlloc(80);
-
-			DetourHelper.Native.MakeWritable(trampolineAlloc, 80);
+			var trampolineAlloc = PageAllocator.Instance.Allocate(OriginalFunctionPtr);
+			
+			logger.LogDebug($"Original: {OriginalFunctionPtr.ToInt64():X}, Trampoline: {trampolineAlloc.ToInt64():X}, diff: {Math.Abs(OriginalFunctionPtr.ToInt64() - trampolineAlloc.ToInt64()):X}; is within +-1GB range: {PageAllocator.IsInRelJmpRange(OriginalFunctionPtr, trampolineAlloc)}");
+			
+			DetourHelper.Native.MakeWritable(trampolineAlloc, PageAllocator.PAGE_SIZE);
 
 			var arch = IntPtr.Size == 8 ? Architecture.X64 : Architecture.X86;
 
 			DetourGenerator.CreateTrampolineFromFunction(instructionBuffer, OriginalFunctionPtr, trampolineAlloc,
 				DetourGenerator.GetDetourLength(arch), arch, out trampolineLength, out jmpLength);
 
-			DetourHelper.Native.MakeExecutable(trampolineAlloc, 80);
+			DetourHelper.Native.MakeExecutable(trampolineAlloc, PageAllocator.PAGE_SIZE);
 
 			TrampolinePtr = trampolineAlloc;
 			TrampolineSize = trampolineLength;
@@ -124,7 +118,7 @@ namespace BepInEx.IL2CPP.Hook
 
 			Marshal.Copy(BackupBytes, 0, OriginalFunctionPtr, BackupBytes.Length);
 
-			DetourHelper.Native.MemFree(TrampolinePtr);
+			PageAllocator.Instance.Free(TrampolinePtr);
 
 			TrampolinePtr = IntPtr.Zero;
 			TrampolineSize = 0;