WindowsPageAllocator.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Runtime.InteropServices;
  5. namespace BepInEx.IL2CPP
  6. {
  7. /// <summary>
  8. /// Based on https://github.com/kubo/funchook
  9. /// </summary>
  10. internal class WindowsPageAllocator : PageAllocator
  11. {
  12. private readonly List<PageChunk> allocatedChunks = new List<PageChunk>();
  13. /// <summary>
  14. /// Allocates a single 64k chunk of memory near the given address
  15. /// </summary>
  16. /// <param name="hint">Address near which to attempt allocate the chunk</param>
  17. /// <returns>Allocated chunk</returns>
  18. /// <exception cref="Win32Exception">Allocation failed</exception>
  19. private PageChunk AllocateChunk(IntPtr hint)
  20. {
  21. while (true)
  22. {
  23. var mbi = new WinApi.MEMORY_BASIC_INFORMATION();
  24. if (WinApi.VirtualQuery(hint, ref mbi, Marshal.SizeOf<WinApi.MEMORY_BASIC_INFORMATION>()) == 0)
  25. throw new Win32Exception(Marshal.GetLastWin32Error());
  26. if (mbi.State == WinApi.PageState.MEM_FREE)
  27. {
  28. long nextAddress = RoundUp(mbi.BaseAddress.ToInt64(), ALLOCATION_UNIT);
  29. long d = nextAddress - mbi.BaseAddress.ToInt64();
  30. if (d >= 0 && mbi.RegionSize.ToInt64() - d >= ALLOCATION_UNIT)
  31. {
  32. hint = (IntPtr)nextAddress;
  33. break;
  34. }
  35. }
  36. hint = (IntPtr)(mbi.BaseAddress.ToInt64() + mbi.RegionSize.ToInt64());
  37. }
  38. var chunk = WinApi.VirtualAlloc(hint, (UIntPtr)ALLOCATION_UNIT, WinApi.AllocationType.MEM_RESERVE, WinApi.ProtectConstant.PAGE_NOACCESS);
  39. if (chunk == null)
  40. throw new Win32Exception(Marshal.GetLastWin32Error());
  41. var addr = WinApi.VirtualAlloc(chunk, (UIntPtr)PAGE_SIZE, WinApi.AllocationType.MEM_COMMIT, WinApi.ProtectConstant.PAGE_READWRITE);
  42. if (addr == IntPtr.Zero)
  43. {
  44. int error = Marshal.GetLastWin32Error();
  45. WinApi.VirtualFree(chunk, UIntPtr.Zero, WinApi.FreeType.MEM_RELEASE);
  46. throw new Win32Exception(error);
  47. }
  48. var result = new PageChunk
  49. {
  50. BaseAddress = chunk
  51. };
  52. allocatedChunks.Add(result);
  53. return result;
  54. }
  55. private IntPtr AllocPage(IntPtr hint)
  56. {
  57. foreach (var allocatedChunk in allocatedChunks)
  58. {
  59. // Small shortcut to speed up page lookup
  60. if (allocatedChunk.UsedPages == PAGES_PER_UNIT)
  61. continue;
  62. for (var i = 0; i < allocatedChunk.Pages.Length; i++)
  63. {
  64. if (allocatedChunk.Pages[i])
  65. continue;
  66. var pageAddr = allocatedChunk.GetPage(i);
  67. if (!IsInRelJmpRange(hint, pageAddr))
  68. continue;
  69. allocatedChunk.Pages[i] = true;
  70. allocatedChunk.UsedPages++;
  71. return pageAddr;
  72. }
  73. }
  74. var chunk = AllocateChunk(hint);
  75. chunk.Pages[0] = true;
  76. chunk.UsedPages++;
  77. return chunk.BaseAddress;
  78. }
  79. public override IntPtr Allocate(IntPtr hint)
  80. {
  81. var pageAddress = AllocPage(hint);
  82. if (WinApi.VirtualAlloc(pageAddress, (UIntPtr)PAGE_SIZE, WinApi.AllocationType.MEM_COMMIT, WinApi.ProtectConstant.PAGE_READWRITE) == IntPtr.Zero)
  83. throw new Win32Exception(Marshal.GetLastWin32Error());
  84. return pageAddress;
  85. }
  86. public override void Free(IntPtr page)
  87. {
  88. foreach (var allocatedChunk in allocatedChunks)
  89. {
  90. long index = (page.ToInt64() - allocatedChunk.BaseAddress.ToInt64()) / PAGE_SIZE;
  91. if (index < 0 || index > PAGES_PER_UNIT)
  92. continue;
  93. allocatedChunk.Pages[index] = false;
  94. return;
  95. }
  96. }
  97. private class PageChunk
  98. {
  99. public IntPtr BaseAddress;
  100. public readonly bool[] Pages = new bool[PAGES_PER_UNIT];
  101. public int UsedPages;
  102. public IntPtr GetPage(int index)
  103. {
  104. return BaseAddress + index * PAGE_SIZE;
  105. }
  106. }
  107. private static class WinApi
  108. {
  109. [Flags]
  110. public enum AllocationType : uint
  111. {
  112. // ReSharper disable InconsistentNaming
  113. MEM_COMMIT = 0x00001000,
  114. MEM_RESERVE = 0x00002000
  115. // ReSharper restore InconsistentNaming
  116. }
  117. [Flags]
  118. public enum FreeType : uint
  119. {
  120. // ReSharper disable InconsistentNaming
  121. MEM_RELEASE = 0x00008000
  122. // ReSharper restore InconsistentNaming
  123. }
  124. public enum PageState : uint
  125. {
  126. // ReSharper disable InconsistentNaming
  127. MEM_FREE = 0x10000
  128. // ReSharper restore InconsistentNaming
  129. }
  130. [Flags]
  131. public enum ProtectConstant : uint
  132. {
  133. // ReSharper disable InconsistentNaming
  134. PAGE_NOACCESS = 0x01,
  135. PAGE_READWRITE = 0x04
  136. // ReSharper restore InconsistentNaming
  137. }
  138. [DllImport("kernel32", SetLastError = true)]
  139. public static extern int VirtualQuery(IntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
  140. [DllImport("kernel32", SetLastError = true)]
  141. public static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, ProtectConstant flProtect);
  142. [DllImport("kernel32", SetLastError = true)]
  143. public static extern bool VirtualFree(IntPtr lpAddress, UIntPtr dwSize, FreeType dwFreeType);
  144. [StructLayout(LayoutKind.Sequential)]
  145. // ReSharper disable once InconsistentNaming
  146. public struct MEMORY_BASIC_INFORMATION
  147. {
  148. public readonly IntPtr BaseAddress;
  149. public readonly IntPtr AllocationBase;
  150. public readonly uint AllocationProtect;
  151. public readonly IntPtr RegionSize;
  152. public readonly PageState State;
  153. public readonly uint Protect;
  154. public readonly uint Type;
  155. }
  156. }
  157. }
  158. }