diff --git a/Il2CppInterop.Runtime/MemoryUtils.cs b/Il2CppInterop.Runtime/MemoryUtils.cs index f0d07798..4c801c1f 100644 --- a/Il2CppInterop.Runtime/MemoryUtils.cs +++ b/Il2CppInterop.Runtime/MemoryUtils.cs @@ -1,15 +1,17 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using Il2CppInterop.Common; using Il2CppInterop.Common.XrefScans; using Microsoft.Extensions.Logging; namespace Il2CppInterop.Runtime; -internal class MemoryUtils +public class MemoryUtils { [DllImport("kernel32.dll", SetLastError = true)] 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 protectedRegi } } + public static void RuntimeModuleDump(ILogger logger, out byte[] il2cppBytes, out byte[] metadataBytes, byte[] metadataSignatureToScan, byte[] magicToFix, int metadataSignatureOffset = 252) + { + Process process = Process.GetCurrentProcess(); + var module = process + .Modules.OfType() + .Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.so" or "UserAssembly.dll"); ; + if (module.ModuleName == null) + { + logger.LogError("GameAssembly.dll or GameAssembly.so or UserAssembly.dll not found"); + il2cppBytes = []; + metadataBytes = []; + return; + } + var moduleBytes = new byte[module.ModuleMemorySize]; + GetModuleRegions(module, out var protectedRegions); + SetModuleRegions(protectedRegions, PAGE_EXECUTE_READWRITE); + if (!ReadProcessMemory(process.Handle, module.BaseAddress, moduleBytes, module.ModuleMemorySize, out _)) + { + logger.LogError("Failed to read process memory"); + il2cppBytes = []; + metadataBytes = []; + return; + } + SetModuleRegions(protectedRegions); + using (var stream = new MemoryStream(moduleBytes)) + using (var reader = new BinaryReader(stream)) + using (var writer = new BinaryWriter(stream)) + { + // Parse the PE header to get the section headers + stream.Position = 0x3C; + var peHeaderOffset = reader.ReadInt32(); + logger.LogDebug("peHeaderOffset: {peHeaderOffset}", peHeaderOffset); + stream.Position = peHeaderOffset + 6; + var numberOfSections = reader.ReadUInt16(); + var timeDateStame = reader.ReadUInt32(); + var pointerToSymbolTable = reader.ReadUInt32(); + var numberOfSymbols = reader.ReadUInt32(); + var sizeOfOptionalHeader = reader.ReadUInt16(); + var characteristics = reader.ReadUInt16(); + var section0StartPosition = (int)stream.Position + sizeOfOptionalHeader; + + // Update each section header's PointerToRawData and SizeOfRawData fields + for (var i = 0; i < numberOfSections; i++) + { + logger.LogDebug("numberOfSections: {numberOfSections}", numberOfSections); + stream.Position = section0StartPosition + (i * 40); + logger.LogDebug("stream.Position: {stream.Position}", stream.Position); + var sectionNameBytes = reader.ReadBytes(8); + var sectionName = Encoding.ASCII.GetString(sectionNameBytes).TrimEnd('\0'); + logger.LogDebug("sectionName: {sectionName}", sectionName); + var virtualSize = reader.ReadUInt32(); + logger.LogDebug("VirtualSize: {virtualSize:X} stream.Position: {stream.Position}", virtualSize, stream.Position); + var virtualAddress = reader.ReadUInt32(); + logger.LogDebug("VirtualAddress: {virtualAddress:X} stream.Position: {stream.Position}", virtualAddress, stream.Position); + writer.Write(virtualSize); + logger.LogDebug("Replacing SizeOfRawData with VirtualSize value of {virtualSize:X} stream.Position: {stream.Position}", virtualSize, stream.Position); + writer.Write(virtualAddress); + logger.LogDebug("Replacing SizeOfRawData with VirtualSize value of {virtualAddress:X} stream.Position: {stream.Position}", virtualAddress, stream.Position); + } + } + + logger.LogDebug("Processed {module.ModuleName}", module.ModuleName); + il2cppBytes = moduleBytes; + + var byteArray = moduleBytes; + + // search for pattern in the byte array + var index = Array.IndexOf(byteArray, (byte)metadataSignatureToScan[0]); + while (index >= 0 && index <= byteArray.Length - metadataSignatureToScan.Length) + { + if (byteArray.Skip(index).Take(metadataSignatureToScan.Length).SequenceEqual(metadataSignatureToScan)) + { + // pattern found, trim everything before it + var trimmedArray = new byte[byteArray.Length - index + metadataSignatureOffset]; + // copy the metadata bytes + Array.Copy(byteArray, index - metadataSignatureOffset, trimmedArray, 0, trimmedArray.Length); + // this is required for il2cppdumper to work + if (magicToFix.Length >= 0) + Array.Copy(magicToFix, 0, trimmedArray, 0, magicToFix.Length); + + byteArray = trimmedArray; + break; + } + index = Array.IndexOf(byteArray, (byte)metadataSignatureToScan[0], index + 1); + } + + logger.LogDebug("Processed global-metadata.dat"); + metadataBytes = byteArray; + return; + } + + public static void ValidateMetadata(ILogger logger, string metadataPath, byte[] il2cppBytes, ref byte[] metadataBytes) + { + //metadataBytes will equal il2cppBytes if the search pattern did not match. + //In this case, global-metadata.dat is not embedded in GameAssembly.dll and most likely at the default path. + if (il2cppBytes == metadataBytes) + { + logger.LogWarning("global-metadata.dat is not embedded in GameAssembly.dll."); + if (File.Exists(metadataPath)) + { + logger.LogWarning("Found global-metadata.dat at the default path, using it instead."); + metadataBytes = File.ReadAllBytes(metadataPath); + } + else + { + logger.LogWarning("global-meatadata.dat is not found at the default location. " + + "It may be hidden somewhere else. " + + "\n Input the file path: (Example: C:\\Users\\_\\{YourGame}\\fake-global-metadata-name.fakeExtension", null); + metadataPath = Path.Combine(Console.ReadLine() ?? string.Empty); + metadataBytes = File.ReadAllBytes(metadataPath); + } + } + } + public const uint PAGE_EXECUTE_READWRITE = 0x40; public struct SignatureDefinition