using System; using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; namespace BepInEx.IL2CPP { /// /// Based on https://github.com/kubo/funchook /// internal class WindowsMemoryAllocator : MemoryAllocator { private readonly LinkedList allocatedChunks = new LinkedList(); /// /// Allocates a single 64k chunk of memory near the given address /// /// Address near which to attempt allocate the chunk /// Allocated chunk /// Allocation failed private IntPtr AllocateChunk(IntPtr hint) { while (true) { var mbi = new WinApi.MEMORY_BASIC_INFORMATION(); if (WinApi.VirtualQuery(hint, ref mbi, Marshal.SizeOf()) == 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); } allocatedChunks.AddFirst(chunk); return chunk; } public override IntPtr Allocate(IntPtr func) { throw new NotImplementedException(); } public override void Free(IntPtr buffer) { throw new NotImplementedException(); } private static class WinApi { [Flags] public enum AllocationType : uint { // ReSharper disable InconsistentNaming MEM_COMMIT = 0x00001000, MEM_RESERVE = 0x00002000, MEM_RESET = 0x00080000, MEM_RESET_UNDO = 0x1000000, MEM_LARGE_PAGES = 0x20000000, MEM_PHYSICAL = 0x00400000, MEM_TOP_DOWN = 0x00100000, MEM_WRITE_WATCH = 0x00200000 // ReSharper restore InconsistentNaming } [Flags] public enum FreeType : uint { // ReSharper disable InconsistentNaming MEM_DECOMMIT = 0x00004000, MEM_RELEASE = 0x00008000, MEM_COALESCE_PLACEHOLDERS = 0x00000001, MEM_PRESERVE_PLACEHOLDER = 0x00000002 // ReSharper restore InconsistentNaming } public enum PageState : uint { // ReSharper disable InconsistentNaming MEM_COMMIT = 0x1000, MEM_FREE = 0x10000, MEM_RESERVE = 0x2000 // ReSharper restore InconsistentNaming } [Flags] public enum ProtectConstant : uint { // ReSharper disable InconsistentNaming PAGE_EXECUTE = 0x10, PAGE_EXECUTE_READ = 0x20, PAGE_EXECUTE_READWRITE = 0x40, PAGE_EXECUTE_WRITECOPY = 0x80, PAGE_NOACCESS = 0x01, PAGE_READONLY = 0x02, PAGE_READWRITE = 0x04, PAGE_WRITECOPY = 0x08, PAGE_TARGETS_INVALID = 0x40000000, PAGE_TARGETS_NO_UPDATE = 0x40000000, PAGE_GUARD = 0x100, PAGE_NOCACHE = 0x200, PAGE_WRITECOMBINE = 0x400 // 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; } } } }