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 WindowsPageAllocator : PageAllocator { private readonly List allocatedChunks = new List(); /// /// Allocates a single 64k chunk of memory near the given address /// /// Address near which to attempt allocate the chunk /// Allocated chunk /// Allocation failed private PageChunk 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); } var result = new PageChunk { BaseAddress = chunk }; allocatedChunks.Add(result); return result; } private IntPtr AllocPage(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 = AllocateChunk(hint); chunk.Pages[0] = true; chunk.UsedPages++; return chunk.BaseAddress; } public override IntPtr Allocate(IntPtr hint) { var pageAddress = AllocPage(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; } public override 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; } } private class PageChunk { public IntPtr BaseAddress; public readonly bool[] Pages = new bool[PAGES_PER_UNIT]; public int UsedPages; public IntPtr GetPage(int index) { return BaseAddress + index * PAGE_SIZE; } } 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; } } } }