|
1 | 1 | using System; |
2 | 2 | using System.Collections.Generic; |
3 | 3 | using System.Diagnostics; |
| 4 | +using System.IO; |
4 | 5 | using System.Linq; |
5 | 6 | using System.Runtime.InteropServices; |
| 7 | +using System.Text; |
6 | 8 | using Il2CppInterop.Common; |
7 | 9 | using Il2CppInterop.Common.XrefScans; |
8 | 10 | using Microsoft.Extensions.Logging; |
9 | 11 |
|
10 | 12 | namespace Il2CppInterop.Runtime; |
11 | 13 |
|
12 | | -internal class MemoryUtils |
| 14 | +public class MemoryUtils |
13 | 15 | { |
14 | 16 | [DllImport("kernel32.dll", SetLastError = true)] |
15 | 17 | internal static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead); |
@@ -94,6 +96,120 @@ public static void SetModuleRegions(List<MEMORY_BASIC_INFORMATION> protectedRegi |
94 | 96 | } |
95 | 97 | } |
96 | 98 |
|
| 99 | + public static void RuntimeModuleDump(ILogger logger, out byte[] il2cppBytes, out byte[] metadataBytes, byte[] metadataSignatureToScan, byte[] magicToFix, int metadataSignatureOffset = 252) |
| 100 | + { |
| 101 | + Process process = Process.GetCurrentProcess(); |
| 102 | + var module = process |
| 103 | + .Modules.OfType<ProcessModule>() |
| 104 | + .Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.so" or "UserAssembly.dll"); ; |
| 105 | + if (module.ModuleName == null) |
| 106 | + { |
| 107 | + logger.LogError("GameAssembly.dll or GameAssembly.so or UserAssembly.dll not found"); |
| 108 | + il2cppBytes = []; |
| 109 | + metadataBytes = []; |
| 110 | + return; |
| 111 | + } |
| 112 | + var moduleBytes = new byte[module.ModuleMemorySize]; |
| 113 | + GetModuleRegions(module, out var protectedRegions); |
| 114 | + SetModuleRegions(protectedRegions, PAGE_EXECUTE_READWRITE); |
| 115 | + if (!ReadProcessMemory(process.Handle, module.BaseAddress, moduleBytes, module.ModuleMemorySize, out _)) |
| 116 | + { |
| 117 | + logger.LogError("Failed to read process memory"); |
| 118 | + il2cppBytes = []; |
| 119 | + metadataBytes = []; |
| 120 | + return; |
| 121 | + } |
| 122 | + SetModuleRegions(protectedRegions); |
| 123 | + using (var stream = new MemoryStream(moduleBytes)) |
| 124 | + using (var reader = new BinaryReader(stream)) |
| 125 | + using (var writer = new BinaryWriter(stream)) |
| 126 | + { |
| 127 | + // Parse the PE header to get the section headers |
| 128 | + stream.Position = 0x3C; |
| 129 | + var peHeaderOffset = reader.ReadInt32(); |
| 130 | + logger.LogDebug("peHeaderOffset: {peHeaderOffset}", peHeaderOffset); |
| 131 | + stream.Position = peHeaderOffset + 6; |
| 132 | + var numberOfSections = reader.ReadUInt16(); |
| 133 | + var timeDateStame = reader.ReadUInt32(); |
| 134 | + var pointerToSymbolTable = reader.ReadUInt32(); |
| 135 | + var numberOfSymbols = reader.ReadUInt32(); |
| 136 | + var sizeOfOptionalHeader = reader.ReadUInt16(); |
| 137 | + var characteristics = reader.ReadUInt16(); |
| 138 | + var section0StartPosition = (int)stream.Position + sizeOfOptionalHeader; |
| 139 | + |
| 140 | + // Update each section header's PointerToRawData and SizeOfRawData fields |
| 141 | + for (var i = 0; i < numberOfSections; i++) |
| 142 | + { |
| 143 | + logger.LogDebug("numberOfSections: {numberOfSections}", numberOfSections); |
| 144 | + stream.Position = section0StartPosition + (i * 40); |
| 145 | + logger.LogDebug("stream.Position: {stream.Position}", stream.Position); |
| 146 | + var sectionNameBytes = reader.ReadBytes(8); |
| 147 | + var sectionName = Encoding.ASCII.GetString(sectionNameBytes).TrimEnd('\0'); |
| 148 | + logger.LogDebug("sectionName: {sectionName}", sectionName); |
| 149 | + var virtualSize = reader.ReadUInt32(); |
| 150 | + logger.LogDebug("VirtualSize: {virtualSize:X} stream.Position: {stream.Position}", virtualSize, stream.Position); |
| 151 | + var virtualAddress = reader.ReadUInt32(); |
| 152 | + logger.LogDebug("VirtualAddress: {virtualAddress:X} stream.Position: {stream.Position}", virtualAddress, stream.Position); |
| 153 | + writer.Write(virtualSize); |
| 154 | + logger.LogDebug("Replacing SizeOfRawData with VirtualSize value of {virtualSize:X} stream.Position: {stream.Position}", virtualSize, stream.Position); |
| 155 | + writer.Write(virtualAddress); |
| 156 | + logger.LogDebug("Replacing SizeOfRawData with VirtualSize value of {virtualAddress:X} stream.Position: {stream.Position}", virtualAddress, stream.Position); |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + logger.LogDebug("Processed {module.ModuleName}", module.ModuleName); |
| 161 | + il2cppBytes = moduleBytes; |
| 162 | + |
| 163 | + var byteArray = moduleBytes; |
| 164 | + |
| 165 | + // search for pattern in the byte array |
| 166 | + var index = Array.IndexOf(byteArray, (byte)metadataSignatureToScan[0]); |
| 167 | + while (index >= 0 && index <= byteArray.Length - metadataSignatureToScan.Length) |
| 168 | + { |
| 169 | + if (byteArray.Skip(index).Take(metadataSignatureToScan.Length).SequenceEqual(metadataSignatureToScan)) |
| 170 | + { |
| 171 | + // pattern found, trim everything before it |
| 172 | + var trimmedArray = new byte[byteArray.Length - index + metadataSignatureOffset]; |
| 173 | + // copy the metadata bytes |
| 174 | + Array.Copy(byteArray, index - metadataSignatureOffset, trimmedArray, 0, trimmedArray.Length); |
| 175 | + // this is required for il2cppdumper to work |
| 176 | + if (magicToFix.Length >= 0) |
| 177 | + Array.Copy(magicToFix, 0, trimmedArray, 0, magicToFix.Length); |
| 178 | + |
| 179 | + byteArray = trimmedArray; |
| 180 | + break; |
| 181 | + } |
| 182 | + index = Array.IndexOf(byteArray, (byte)metadataSignatureToScan[0], index + 1); |
| 183 | + } |
| 184 | + |
| 185 | + logger.LogDebug("Processed global-metadata.dat"); |
| 186 | + metadataBytes = byteArray; |
| 187 | + return; |
| 188 | + } |
| 189 | + |
| 190 | + public static void ValidateMetadata(ILogger logger, string metadataPath, byte[] il2cppBytes, ref byte[] metadataBytes) |
| 191 | + { |
| 192 | + //metadataBytes will equal il2cppBytes if the search pattern did not match. |
| 193 | + //In this case, global-metadata.dat is not embedded in GameAssembly.dll and most likely at the default path. |
| 194 | + if (il2cppBytes == metadataBytes) |
| 195 | + { |
| 196 | + logger.LogWarning("global-metadata.dat is not embedded in GameAssembly.dll."); |
| 197 | + if (File.Exists(metadataPath)) |
| 198 | + { |
| 199 | + logger.LogWarning("Found global-metadata.dat at the default path, using it instead."); |
| 200 | + metadataBytes = File.ReadAllBytes(metadataPath); |
| 201 | + } |
| 202 | + else |
| 203 | + { |
| 204 | + logger.LogWarning("global-meatadata.dat is not found at the default location. " + |
| 205 | + "It may be hidden somewhere else. " + |
| 206 | + "\n Input the file path: (Example: C:\\Users\\_\\{YourGame}\\fake-global-metadata-name.fakeExtension", null); |
| 207 | + metadataPath = Path.Combine(Console.ReadLine() ?? string.Empty); |
| 208 | + metadataBytes = File.ReadAllBytes(metadataPath); |
| 209 | + } |
| 210 | + } |
| 211 | + } |
| 212 | + |
97 | 213 | public const uint PAGE_EXECUTE_READWRITE = 0x40; |
98 | 214 |
|
99 | 215 | public struct SignatureDefinition |
|
0 commit comments