From 8ffc7e0021ef3aac5c20292b6eb09aa2af90e779 Mon Sep 17 00:00:00 2001 From: LukeFZ <17146677+LukeFZ@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:32:04 +0100 Subject: [PATCH] Add stuff for v29 --- Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs | 881 +++++++++--------- .../IL2CPP/Il2CppBinaryClasses.cs | 583 ++++++------ .../IL2CPP/Il2CppInspector.cs | 15 +- .../Reflection/CustomAttributeData.cs | 6 + .../Reflection/TypeModel.cs | 733 +++++++-------- 5 files changed, 1145 insertions(+), 1073 deletions(-) diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs b/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs index 66a6e72..928a186 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs @@ -1,437 +1,444 @@ -/* - Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; - -namespace Il2CppInspector -{ - public abstract partial class Il2CppBinary - { - // File image - public IFileFormatStream Image { get; } - - // The metadata associed with this binary - this is optional and may be null. Contents should not be modified - public Metadata Metadata { get; private set; } - - // IL2CPP-only API exports with decrypted names - public Dictionary APIExports { get; } = new Dictionary(); - - // Binary metadata structures - public Il2CppCodeRegistration CodeRegistration { get; protected set; } - public Il2CppMetadataRegistration MetadataRegistration { get; protected set; } - - // Information for disassembly reverse engineering - public ulong CodeRegistrationPointer { get; private set; } - public ulong MetadataRegistrationPointer { get; private set; } - public ulong RegistrationFunctionPointer { get; private set; } - public Dictionary CodeGenModulePointers { get; } = new Dictionary(); - - // Only for <=v24.1 - public ulong[] GlobalMethodPointers { get; set; } - - // Only for >=v24.2 - public Dictionary ModuleMethodPointers { get; set; } = new Dictionary(); - - // Only for >=v24.2. In earlier versions, invoker indices are stored in Il2CppMethodDefinition in the metadata file - public Dictionary MethodInvokerIndices { get; set; } = new Dictionary(); - - // NOTE: In versions <21 and earlier releases of v21, use FieldOffsets: - // global field index => field offset - // In versions >=22 and later releases of v21, use FieldOffsetPointers: - // type index => RVA in image where the list of field offsets for the type start (4 bytes per field) - - // Negative field offsets from start of each function - public uint[] FieldOffsets { get; private set; } - - // Pointers to field offsets - public long[] FieldOffsetPointers { get; private set; } - - // Generated functions which call constructors on custom attributes - // Only for < 27 - public ulong[] CustomAttributeGenerators { get; private set; } - - // IL2CPP-generated functions which implement MethodBase.Invoke with a unique signature per invoker, defined in Il2CppInvokerTable.cpp - // One invoker specifies a return type and argument list. Multiple methods with the same signature can be invoked with the same invoker - public ulong[] MethodInvokePointers { get; private set; } - - // Version 16 and below: method references for vtable - public uint[] VTableMethodReferences { get; private set; } - - // Generic method specs for vtables - public Il2CppMethodSpec[] MethodSpecs { get; private set; } - - // List of run-time concrete generic class and method signatures - public List GenericInstances { get; private set; } - - // List of constructed generic method function pointers corresponding to each possible method instantiation - public Dictionary GenericMethodPointers { get; } = new Dictionary(); - - // List of invoker pointers for concrete generic methods from MethodSpecs (as above) - public Dictionary GenericMethodInvokerIndices { get; } = new Dictionary(); - - // Every type reference (TypeRef) sorted by index - public List TypeReferences { get; private set; } - - // Every type reference index sorted by virtual address - public Dictionary TypeReferenceIndicesByAddress { get; private set; } - - // From v24.2 onwards, this structure is stored for each module (image) - // One assembly may contain multiple modules - public Dictionary Modules { get; private set; } - - // Status update callback - private EventHandler OnStatusUpdate { get; set; } - private void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status); - - // Set if something in the binary has been modified / decrypted - private bool isModified = false; - public bool IsModified => Image.IsModified || isModified; - - protected Il2CppBinary(IFileFormatStream stream, EventHandler statusCallback = null) { - Image = stream; - OnStatusUpdate = statusCallback; - - DiscoverAPIExports(); - } - - protected Il2CppBinary(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler statusCallback = null) { - Image = stream; - OnStatusUpdate = statusCallback; - - DiscoverAPIExports(); - TryPrepareMetadata(codeRegistration, metadataRegistration); - } - - // Load and initialize a binary of any supported architecture - private static Il2CppBinary LoadImpl(IFileFormatStream stream, EventHandler statusCallback) { - // Get type from image architecture - var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper()); - if (type == null) - throw new NotImplementedException("Unsupported architecture: " + stream.Arch); - - // Set width of long (convert to sizeof(int) for 32-bit files) - if (stream[0].Bits == 32) { - try { - stream[0].AddPrimitiveMapping(typeof(long), typeof(int)); - } catch (ArgumentException) { } - try { - stream[0].AddPrimitiveMapping(typeof(ulong), typeof(uint)); - } catch (ArgumentException) { } - } - - return (Il2CppBinary) Activator.CreateInstance(type, stream[0], statusCallback); - } - - // Load binary without a global-metadata.dat available - public static Il2CppBinary Load(IFileFormatStream stream, double metadataVersion, EventHandler statusCallback = null) { - foreach (var loadedImage in stream.TryNextLoadStrategy()) { - var inst = LoadImpl(stream, statusCallback); - if (inst.FindRegistrationStructs(metadataVersion)) - return inst; - } - return null; - } - - // Load binary with a global-metadata.dat available - // Supplying the Metadata class when loading a binary is optional - // If it is specified and both symbol table and function scanning fail, - // Metadata will be used to try to find the required structures with data analysis - // If it is not specified, data analysis will not be performed - public static Il2CppBinary Load(IFileFormatStream stream, Metadata metadata, EventHandler statusCallback = null) { - foreach (var loadedImage in stream.TryNextLoadStrategy()) { - var inst = LoadImpl(stream, statusCallback); - if (inst.FindRegistrationStructs(metadata)) - return inst; - } - return null; - } - - // Save binary to file, overwriting if necessary - // Save metadata to file, overwriting if necessary - public void SaveToFile(string pathname) { - Image.Position = 0; - using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write)) - Image.CopyTo(outFile); - } - - // Initialize binary without a global-metadata.dat available - public bool FindRegistrationStructs(double metadataVersion) { - Image.Version = metadataVersion; - - StatusUpdate("Searching for binary metadata"); - if (!((FindMetadataFromSymbols() ?? FindMetadataFromData() ?? FindMetadataFromCode()) is (ulong code, ulong meta))) - return false; - - TryPrepareMetadata(code, meta); - return true; - } - - // Initialize binary with a global-metadata.dat available - public bool FindRegistrationStructs(Metadata metadata) { - Metadata = metadata; - return FindRegistrationStructs(metadata.Version); - } - - // Try to find data structures via symbol table lookup - private (ulong, ulong)? FindMetadataFromSymbols() { - // Try searching the symbol table - var symbols = Image.GetSymbolTable(); - - if (symbols.Any()) { - Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries"); - - symbols.TryGetValue("g_CodeRegistration", out var code); - symbols.TryGetValue("g_MetadataRegistration", out var metadata); - - if (code == null) - symbols.TryGetValue("_g_CodeRegistration", out code); - if (metadata == null) - symbols.TryGetValue("_g_MetadataRegistration", out metadata); - - if (code != null && metadata != null) { - Console.WriteLine("Required structures acquired from symbol lookup"); - return (code.VirtualAddress, metadata.VirtualAddress); - } else { - Console.WriteLine("No matches in symbol table"); - } - } else if (symbols != null) { - Console.WriteLine("No symbol table present in binary file"); - } else { - Console.WriteLine("Symbol table search not implemented for this binary format"); - } - return null; - } - - // Try to find data structures via init function code analysis - private (ulong, ulong)? FindMetadataFromCode() { - // Try searching the function table - var addrs = Image.GetFunctionTable(); - - Debug.WriteLine("Function table:"); - Debug.WriteLine(string.Join(", ", from a in addrs select string.Format($"0x{a:X8}"))); - - foreach (var loc in addrs) { - var (code, metadata) = ConsiderCode(Image, loc); - if (code != 0) { - RegistrationFunctionPointer = loc + Image.GlobalOffset; - Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer); - return (code, metadata); - } - } - - Console.WriteLine("No matches via code heuristics"); - return null; - } - - // Try to find data structures via data heuristics - // Requires succeesful global-metadata.dat analysis first - private (ulong, ulong)? FindMetadataFromData() { - if (Metadata == null) - return null; - - var (codePtr, metadataPtr) = ImageScan(Metadata); - if (codePtr == 0) { - Console.WriteLine("No matches via data heuristics"); - return null; - } - - Console.WriteLine("Required structures acquired from data heuristics"); - return (codePtr, metadataPtr); - } - - // Architecture-specific search function - protected abstract (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc); - - - // Load all of the discovered metadata in the binary - private void TryPrepareMetadata(ulong codeRegistration, ulong metadataRegistration) { - try { - PrepareMetadata(codeRegistration, metadataRegistration); - } - catch (Exception ex) when (!(ex is NotSupportedException)) { - throw new InvalidOperationException($"Could not analyze IL2CPP data. Ensure that the latest core plugins package is installed and all core plugins are enabled before filing a bug report. The error was: {ex.Message}", ex); - } - } - - // Load all of the discovered metadata in the binary - private void PrepareMetadata(ulong codeRegistration, ulong metadataRegistration) { - // Store locations - CodeRegistrationPointer = codeRegistration; - MetadataRegistrationPointer = metadataRegistration; - - var pointerSize = Image.Bits == 32 ? 4u : 8u; - - Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration)); - Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration)); - - // Root structures from which we find everything else - CodeRegistration = Image.ReadMappedObject(codeRegistration); - MetadataRegistration = Image.ReadMappedObject(metadataRegistration); - - // genericAdjustorThunks was inserted before invokerPointersCount in 24.5 and 27.1 - // pointer expected if we need to bump version - if (Image.Version == 24.4 && CodeRegistration.invokerPointersCount > 0x50000) - { - Image.Version = 24.5; - CodeRegistration = Image.ReadMappedObject(codeRegistration); - } - - if (Image.Version == 24.4 && CodeRegistration.reversePInvokeWrapperCount > 0x50000) { - Image.Version = 24.5; - codeRegistration -= 1 * pointerSize; - CodeRegistration = Image.ReadMappedObject(codeRegistration); - } - - // Plugin hook to pre-process binary - isModified |= PluginHooks.PreProcessBinary(this).IsStreamModified; - - StatusUpdate($"Analyzing IL2CPP data for {Image.Format}/{Image.Arch} image"); - - // Do basic validatation that MetadataRegistration and CodeRegistration are sane - /* - * GlobalMethodPointers (<= 24.1) must be a series of pointers in il2cpp or .text, and in sequential order - * FieldOffsetPointers (>= 21.1) must be a series of pointers in __const or zero, and in sequential order - * typeRefPointers must be a series of pointers in __const - * MethodInvokePointers must be a series of pointers in __text or .text, and in sequential order - */ - if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.typeDefinitionsSizesCount) - || CodeRegistration.reversePInvokeWrapperCount > 0x10000 - || CodeRegistration.unresolvedVirtualCallCount > 0x4000 // >= 22 - || CodeRegistration.interopDataCount > 0x1000 // >= 23 - || (Image.Version <= 24.1 && CodeRegistration.invokerPointersCount > CodeRegistration.methodPointersCount)) - throw new NotSupportedException("The detected Il2CppCodeRegistration / Il2CppMetadataRegistration structs do not pass validation. This may mean that their fields have been re-ordered as a form of obfuscation and Il2CppInspector has not been able to restore the original order automatically. Consider re-ordering the fields in Il2CppBinaryClasses.cs and try again."); - - // The global method pointer list was deprecated in v24.2 in favour of Il2CppCodeGenModule - if (Image.Version <= 24.1) - GlobalMethodPointers = Image.ReadMappedArray(CodeRegistration.pmethodPointers, (int) CodeRegistration.methodPointersCount); - - // After v24 method pointers and RGCTX data were stored in Il2CppCodeGenModules - if (Image.Version >= 24.2) { - Modules = new Dictionary(); - - // In v24.3, windowsRuntimeFactoryTable collides with codeGenModules. So far no samples have had windowsRuntimeFactoryCount > 0; - // if this changes we'll have to get smarter about disambiguating these two. - if (CodeRegistration.codeGenModulesCount == 0) { - Image.Version = 24.3; - CodeRegistration = Image.ReadMappedObject(codeRegistration); - } - - // Array of pointers to Il2CppCodeGenModule - var codeGenModulePointers = Image.ReadMappedArray(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount); - var modules = Image.ReadMappedObjectPointerArray(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount); - - foreach (var mp in modules.Zip(codeGenModulePointers, (m, p) => new { Module = m, Pointer = p })) { - var module = mp.Module; - - var name = Image.ReadMappedNullTerminatedString(module.moduleName); - Modules.Add(name, module); - CodeGenModulePointers.Add(name, mp.Pointer); - - // Read method pointers - // If a module contains only interfaces, abstract methods and/or non-concrete generic methods, - // the entire method pointer array will be NULL values, causing the methodPointer to be mapped to .bss - // and therefore out of scope of the binary image - try { - ModuleMethodPointers.Add(module, Image.ReadMappedArray(module.methodPointers, (int) module.methodPointerCount)); - } catch (InvalidOperationException) { - ModuleMethodPointers.Add(module, new ulong[module.methodPointerCount]); - } - - // Read method invoker pointer indices - one per method - MethodInvokerIndices.Add(module, Image.ReadMappedArray(module.invokerIndices, (int) module.methodPointerCount)); - } - } - - // Field offset data. Metadata <=21.x uses a value-type array; >=21.x uses a pointer array - - // Versions from 22 onwards use an array of pointers in Binary.FieldOffsetData - bool fieldOffsetsArePointers = (Image.Version >= 22); - - // Some variants of 21 also use an array of pointers - if (Image.Version == 21) { - var fieldTest = Image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, 6); - - // We detect this by relying on the fact Module, Object, ValueType, Attribute, _Attribute and Int32 - // are always the first six defined types, and that all but Int32 have no fields - fieldOffsetsArePointers = (fieldTest[0] == 0 && fieldTest[1] == 0 && fieldTest[2] == 0 && fieldTest[3] == 0 && fieldTest[4] == 0 && fieldTest[5] > 0); - } - - // All older versions use values directly in the array - if (!fieldOffsetsArePointers) - FieldOffsets = Image.ReadMappedArray(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount); - else - FieldOffsetPointers = Image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount); - - // Type references (pointer array) - var typeRefPointers = Image.ReadMappedArray(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount); - TypeReferenceIndicesByAddress = typeRefPointers.Zip(Enumerable.Range(0, typeRefPointers.Length), (a, i) => new { a, i }).ToDictionary(x => x.a, x => x.i); - TypeReferences = Image.ReadMappedObjectPointerArray(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount); - - // Custom attribute constructors (function pointers) - // This is managed in Il2CppInspector for metadata >= 27 - if (Image.Version < 27) { - CustomAttributeGenerators = Image.ReadMappedArray(CodeRegistration.customAttributeGenerators, (int) CodeRegistration.customAttributeCount); - } - - // Method.Invoke function pointers - MethodInvokePointers = Image.ReadMappedArray(CodeRegistration.invokerPointers, (int) CodeRegistration.invokerPointersCount); - - // TODO: Function pointers as shown below - // reversePInvokeWrappers - // <=22: delegateWrappersFromManagedToNative, marshalingFunctions - // >=21 <=22: ccwMarshalingFunctions - // >=22: unresolvedVirtualCallPointers - // >=23: interopData - - if (Image.Version < 19) { - VTableMethodReferences = Image.ReadMappedArray(MetadataRegistration.methodReferences, (int)MetadataRegistration.methodReferencesCount); - } - - // Generic type and method specs (open and closed constructed types) - MethodSpecs = Image.ReadMappedArray(MetadataRegistration.methodSpecs, (int) MetadataRegistration.methodSpecsCount); - - // Concrete generic class and method signatures - GenericInstances = Image.ReadMappedObjectPointerArray(MetadataRegistration.genericInsts, (int) MetadataRegistration.genericInstsCount); - - // Concrete generic method pointers - var genericMethodPointers = Image.ReadMappedArray(CodeRegistration.genericMethodPointers, (int) CodeRegistration.genericMethodPointersCount); - var genericMethodTable = Image.ReadMappedArray(MetadataRegistration.genericMethodTable, (int) MetadataRegistration.genericMethodTableCount); - foreach (var tableEntry in genericMethodTable) { - GenericMethodPointers.Add(MethodSpecs[tableEntry.genericMethodIndex], genericMethodPointers[tableEntry.indices.methodIndex]); - GenericMethodInvokerIndices.Add(MethodSpecs[tableEntry.genericMethodIndex], tableEntry.indices.invokerIndex); - } - - // Plugin hook to pre-process binary - isModified |= PluginHooks.PostProcessBinary(this).IsStreamModified; - } - - // IL2CPP API exports - // This strips leading underscores and selects only il2cpp_* symbols which can be mapped into the binary - // (therefore ignoring extern imports) - // Some binaries have functions starting "il2cpp_z_" - ignore these too - private void DiscoverAPIExports() { - var exports = Image.GetExports()? - .Where(e => (e.Name.StartsWith("il2cpp_") || e.Name.StartsWith("_il2cpp_") || e.Name.StartsWith("__il2cpp_")) - && !e.Name.Contains("il2cpp_z_")); - - if (exports == null) - return; - - var exportRgx = new Regex(@"^_+"); - - foreach (var export in exports) - if (Image.TryMapVATR(export.VirtualAddress, out _)) - APIExports.Add(exportRgx.Replace(export.Name, ""), export.VirtualAddress); - } - } -} +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace Il2CppInspector +{ + public abstract partial class Il2CppBinary + { + // File image + public IFileFormatStream Image { get; } + + // The metadata associed with this binary - this is optional and may be null. Contents should not be modified + public Metadata Metadata { get; private set; } + + // IL2CPP-only API exports with decrypted names + public Dictionary APIExports { get; } = new Dictionary(); + + // Binary metadata structures + public Il2CppCodeRegistration CodeRegistration { get; protected set; } + public Il2CppMetadataRegistration MetadataRegistration { get; protected set; } + + // Information for disassembly reverse engineering + public ulong CodeRegistrationPointer { get; private set; } + public ulong MetadataRegistrationPointer { get; private set; } + public ulong RegistrationFunctionPointer { get; private set; } + public Dictionary CodeGenModulePointers { get; } = new Dictionary(); + + // Only for <=v24.1 + public ulong[] GlobalMethodPointers { get; set; } + + // Only for >=v24.2 + public Dictionary ModuleMethodPointers { get; set; } = new Dictionary(); + + // Only for >=v24.2. In earlier versions, invoker indices are stored in Il2CppMethodDefinition in the metadata file + public Dictionary MethodInvokerIndices { get; set; } = new Dictionary(); + + // NOTE: In versions <21 and earlier releases of v21, use FieldOffsets: + // global field index => field offset + // In versions >=22 and later releases of v21, use FieldOffsetPointers: + // type index => RVA in image where the list of field offsets for the type start (4 bytes per field) + + // Negative field offsets from start of each function + public uint[] FieldOffsets { get; private set; } + + // Pointers to field offsets + public long[] FieldOffsetPointers { get; private set; } + + // Generated functions which call constructors on custom attributes + // Only for < 27 + public ulong[] CustomAttributeGenerators { get; private set; } + + // IL2CPP-generated functions which implement MethodBase.Invoke with a unique signature per invoker, defined in Il2CppInvokerTable.cpp + // One invoker specifies a return type and argument list. Multiple methods with the same signature can be invoked with the same invoker + public ulong[] MethodInvokePointers { get; private set; } + + // Version 16 and below: method references for vtable + public uint[] VTableMethodReferences { get; private set; } + + // Generic method specs for vtables + public Il2CppMethodSpec[] MethodSpecs { get; private set; } + + // List of run-time concrete generic class and method signatures + public List GenericInstances { get; private set; } + + // List of constructed generic method function pointers corresponding to each possible method instantiation + public Dictionary GenericMethodPointers { get; } = new Dictionary(); + + // List of invoker pointers for concrete generic methods from MethodSpecs (as above) + public Dictionary GenericMethodInvokerIndices { get; } = new Dictionary(); + + // Every type reference (TypeRef) sorted by index + public List TypeReferences { get; private set; } + + // Every type reference index sorted by virtual address + public Dictionary TypeReferenceIndicesByAddress { get; private set; } + + // From v24.2 onwards, this structure is stored for each module (image) + // One assembly may contain multiple modules + public Dictionary Modules { get; private set; } + + // Status update callback + private EventHandler OnStatusUpdate { get; set; } + private void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status); + + // Set if something in the binary has been modified / decrypted + private bool isModified = false; + public bool IsModified => Image.IsModified || isModified; + + protected Il2CppBinary(IFileFormatStream stream, EventHandler statusCallback = null) { + Image = stream; + OnStatusUpdate = statusCallback; + + DiscoverAPIExports(); + } + + protected Il2CppBinary(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler statusCallback = null) { + Image = stream; + OnStatusUpdate = statusCallback; + + DiscoverAPIExports(); + TryPrepareMetadata(codeRegistration, metadataRegistration); + } + + // Load and initialize a binary of any supported architecture + private static Il2CppBinary LoadImpl(IFileFormatStream stream, EventHandler statusCallback) { + // Get type from image architecture + var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper()); + if (type == null) + throw new NotImplementedException("Unsupported architecture: " + stream.Arch); + + // Set width of long (convert to sizeof(int) for 32-bit files) + if (stream[0].Bits == 32) { + try { + stream[0].AddPrimitiveMapping(typeof(long), typeof(int)); + } catch (ArgumentException) { } + try { + stream[0].AddPrimitiveMapping(typeof(ulong), typeof(uint)); + } catch (ArgumentException) { } + } + + return (Il2CppBinary) Activator.CreateInstance(type, stream[0], statusCallback); + } + + // Load binary without a global-metadata.dat available + public static Il2CppBinary Load(IFileFormatStream stream, double metadataVersion, EventHandler statusCallback = null) { + foreach (var loadedImage in stream.TryNextLoadStrategy()) { + var inst = LoadImpl(stream, statusCallback); + if (inst.FindRegistrationStructs(metadataVersion)) + return inst; + } + return null; + } + + // Load binary with a global-metadata.dat available + // Supplying the Metadata class when loading a binary is optional + // If it is specified and both symbol table and function scanning fail, + // Metadata will be used to try to find the required structures with data analysis + // If it is not specified, data analysis will not be performed + public static Il2CppBinary Load(IFileFormatStream stream, Metadata metadata, EventHandler statusCallback = null) { + foreach (var loadedImage in stream.TryNextLoadStrategy()) { + var inst = LoadImpl(stream, statusCallback); + if (inst.FindRegistrationStructs(metadata)) + return inst; + } + return null; + } + + // Save binary to file, overwriting if necessary + // Save metadata to file, overwriting if necessary + public void SaveToFile(string pathname) { + Image.Position = 0; + using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write)) + Image.CopyTo(outFile); + } + + // Initialize binary without a global-metadata.dat available + public bool FindRegistrationStructs(double metadataVersion) { + Image.Version = metadataVersion; + + StatusUpdate("Searching for binary metadata"); + if (!((FindMetadataFromSymbols() ?? FindMetadataFromData() ?? FindMetadataFromCode()) is (ulong code, ulong meta))) + return false; + + TryPrepareMetadata(code, meta); + return true; + } + + // Initialize binary with a global-metadata.dat available + public bool FindRegistrationStructs(Metadata metadata) { + Metadata = metadata; + return FindRegistrationStructs(metadata.Version); + } + + // Try to find data structures via symbol table lookup + private (ulong, ulong)? FindMetadataFromSymbols() { + // Try searching the symbol table + var symbols = Image.GetSymbolTable(); + + if (symbols.Any()) { + Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries"); + + symbols.TryGetValue("g_CodeRegistration", out var code); + symbols.TryGetValue("g_MetadataRegistration", out var metadata); + + if (code == null) + symbols.TryGetValue("_g_CodeRegistration", out code); + if (metadata == null) + symbols.TryGetValue("_g_MetadataRegistration", out metadata); + + if (code != null && metadata != null) { + Console.WriteLine("Required structures acquired from symbol lookup"); + return (code.VirtualAddress, metadata.VirtualAddress); + } else { + Console.WriteLine("No matches in symbol table"); + } + } else if (symbols != null) { + Console.WriteLine("No symbol table present in binary file"); + } else { + Console.WriteLine("Symbol table search not implemented for this binary format"); + } + return null; + } + + // Try to find data structures via init function code analysis + private (ulong, ulong)? FindMetadataFromCode() { + // Try searching the function table + var addrs = Image.GetFunctionTable(); + + Debug.WriteLine("Function table:"); + Debug.WriteLine(string.Join(", ", from a in addrs select string.Format($"0x{a:X8}"))); + + foreach (var loc in addrs) { + var (code, metadata) = ConsiderCode(Image, loc); + if (code != 0) { + RegistrationFunctionPointer = loc + Image.GlobalOffset; + Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer); + return (code, metadata); + } + } + + Console.WriteLine("No matches via code heuristics"); + return null; + } + + // Try to find data structures via data heuristics + // Requires succeesful global-metadata.dat analysis first + private (ulong, ulong)? FindMetadataFromData() { + if (Metadata == null) + return null; + + var (codePtr, metadataPtr) = ImageScan(Metadata); + if (codePtr == 0) { + Console.WriteLine("No matches via data heuristics"); + return null; + } + + Console.WriteLine("Required structures acquired from data heuristics"); + return (codePtr, metadataPtr); + } + + // Architecture-specific search function + protected abstract (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc); + + + // Load all of the discovered metadata in the binary + private void TryPrepareMetadata(ulong codeRegistration, ulong metadataRegistration) { + try { + PrepareMetadata(codeRegistration, metadataRegistration); + } + catch (Exception ex) when (!(ex is NotSupportedException)) { + throw new InvalidOperationException($"Could not analyze IL2CPP data. Ensure that the latest core plugins package is installed and all core plugins are enabled before filing a bug report. The error was: {ex.Message}", ex); + } + } + + // Load all of the discovered metadata in the binary + private void PrepareMetadata(ulong codeRegistration, ulong metadataRegistration) { + // Store locations + CodeRegistrationPointer = codeRegistration; + MetadataRegistrationPointer = metadataRegistration; + + var pointerSize = Image.Bits == 32 ? 4u : 8u; + + Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration)); + Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration)); + + // Root structures from which we find everything else + CodeRegistration = Image.ReadMappedObject(codeRegistration); + MetadataRegistration = Image.ReadMappedObject(metadataRegistration); + + // genericAdjustorThunks was inserted before invokerPointersCount in 24.5 and 27.1 + // pointer expected if we need to bump version + if (Image.Version == 24.4 && CodeRegistration.invokerPointersCount > 0x50000) + { + Image.Version = 24.5; + CodeRegistration = Image.ReadMappedObject(codeRegistration); + } + + if (Image.Version == 24.4 && CodeRegistration.reversePInvokeWrapperCount > 0x50000) { + Image.Version = 24.5; + codeRegistration -= 1 * pointerSize; + CodeRegistration = Image.ReadMappedObject(codeRegistration); + } + + if (Image.Version == 29 && CodeRegistration.genericMethodPointersCount > 0x50000) + { + Image.Version = 29.1; + codeRegistration -= 2 * pointerSize; + CodeRegistration = Image.ReadMappedObject(codeRegistration); + } + + // Plugin hook to pre-process binary + isModified |= PluginHooks.PreProcessBinary(this).IsStreamModified; + + StatusUpdate($"Analyzing IL2CPP data for {Image.Format}/{Image.Arch} image"); + + // Do basic validatation that MetadataRegistration and CodeRegistration are sane + /* + * GlobalMethodPointers (<= 24.1) must be a series of pointers in il2cpp or .text, and in sequential order + * FieldOffsetPointers (>= 21.1) must be a series of pointers in __const or zero, and in sequential order + * typeRefPointers must be a series of pointers in __const + * MethodInvokePointers must be a series of pointers in __text or .text, and in sequential order + */ + if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.typeDefinitionsSizesCount) + || CodeRegistration.reversePInvokeWrapperCount > 0x10000 + || CodeRegistration.unresolvedVirtualCallCount > 0x4000 // >= 22 + || CodeRegistration.interopDataCount > 0x1000 // >= 23 + || (Image.Version <= 24.1 && CodeRegistration.invokerPointersCount > CodeRegistration.methodPointersCount)) + throw new NotSupportedException("The detected Il2CppCodeRegistration / Il2CppMetadataRegistration structs do not pass validation. This may mean that their fields have been re-ordered as a form of obfuscation and Il2CppInspector has not been able to restore the original order automatically. Consider re-ordering the fields in Il2CppBinaryClasses.cs and try again."); + + // The global method pointer list was deprecated in v24.2 in favour of Il2CppCodeGenModule + if (Image.Version <= 24.1) + GlobalMethodPointers = Image.ReadMappedArray(CodeRegistration.pmethodPointers, (int) CodeRegistration.methodPointersCount); + + // After v24 method pointers and RGCTX data were stored in Il2CppCodeGenModules + if (Image.Version >= 24.2) { + Modules = new Dictionary(); + + // In v24.3, windowsRuntimeFactoryTable collides with codeGenModules. So far no samples have had windowsRuntimeFactoryCount > 0; + // if this changes we'll have to get smarter about disambiguating these two. + if (CodeRegistration.codeGenModulesCount == 0) { + Image.Version = 24.3; + CodeRegistration = Image.ReadMappedObject(codeRegistration); + } + + // Array of pointers to Il2CppCodeGenModule + var codeGenModulePointers = Image.ReadMappedArray(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount); + var modules = Image.ReadMappedObjectPointerArray(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount); + + foreach (var mp in modules.Zip(codeGenModulePointers, (m, p) => new { Module = m, Pointer = p })) { + var module = mp.Module; + + var name = Image.ReadMappedNullTerminatedString(module.moduleName); + Modules.Add(name, module); + CodeGenModulePointers.Add(name, mp.Pointer); + + // Read method pointers + // If a module contains only interfaces, abstract methods and/or non-concrete generic methods, + // the entire method pointer array will be NULL values, causing the methodPointer to be mapped to .bss + // and therefore out of scope of the binary image + try { + ModuleMethodPointers.Add(module, Image.ReadMappedArray(module.methodPointers, (int) module.methodPointerCount)); + } catch (InvalidOperationException) { + ModuleMethodPointers.Add(module, new ulong[module.methodPointerCount]); + } + + // Read method invoker pointer indices - one per method + MethodInvokerIndices.Add(module, Image.ReadMappedArray(module.invokerIndices, (int) module.methodPointerCount)); + } + } + + // Field offset data. Metadata <=21.x uses a value-type array; >=21.x uses a pointer array + + // Versions from 22 onwards use an array of pointers in Binary.FieldOffsetData + bool fieldOffsetsArePointers = (Image.Version >= 22); + + // Some variants of 21 also use an array of pointers + if (Image.Version == 21) { + var fieldTest = Image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, 6); + + // We detect this by relying on the fact Module, Object, ValueType, Attribute, _Attribute and Int32 + // are always the first six defined types, and that all but Int32 have no fields + fieldOffsetsArePointers = (fieldTest[0] == 0 && fieldTest[1] == 0 && fieldTest[2] == 0 && fieldTest[3] == 0 && fieldTest[4] == 0 && fieldTest[5] > 0); + } + + // All older versions use values directly in the array + if (!fieldOffsetsArePointers) + FieldOffsets = Image.ReadMappedArray(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount); + else + FieldOffsetPointers = Image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount); + + // Type references (pointer array) + var typeRefPointers = Image.ReadMappedArray(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount); + TypeReferenceIndicesByAddress = typeRefPointers.Zip(Enumerable.Range(0, typeRefPointers.Length), (a, i) => new { a, i }).ToDictionary(x => x.a, x => x.i); + TypeReferences = Image.ReadMappedObjectPointerArray(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount); + + // Custom attribute constructors (function pointers) + // This is managed in Il2CppInspector for metadata >= 27 + if (Image.Version < 27) { + CustomAttributeGenerators = Image.ReadMappedArray(CodeRegistration.customAttributeGenerators, (int) CodeRegistration.customAttributeCount); + } + + // Method.Invoke function pointers + MethodInvokePointers = Image.ReadMappedArray(CodeRegistration.invokerPointers, (int) CodeRegistration.invokerPointersCount); + + // TODO: Function pointers as shown below + // reversePInvokeWrappers + // <=22: delegateWrappersFromManagedToNative, marshalingFunctions + // >=21 <=22: ccwMarshalingFunctions + // >=22: unresolvedVirtualCallPointers + // >=23: interopData + + if (Image.Version < 19) { + VTableMethodReferences = Image.ReadMappedArray(MetadataRegistration.methodReferences, (int)MetadataRegistration.methodReferencesCount); + } + + // Generic type and method specs (open and closed constructed types) + MethodSpecs = Image.ReadMappedArray(MetadataRegistration.methodSpecs, (int) MetadataRegistration.methodSpecsCount); + + // Concrete generic class and method signatures + GenericInstances = Image.ReadMappedObjectPointerArray(MetadataRegistration.genericInsts, (int) MetadataRegistration.genericInstsCount); + + // Concrete generic method pointers + var genericMethodPointers = Image.ReadMappedArray(CodeRegistration.genericMethodPointers, (int) CodeRegistration.genericMethodPointersCount); + var genericMethodTable = Image.ReadMappedArray(MetadataRegistration.genericMethodTable, (int) MetadataRegistration.genericMethodTableCount); + foreach (var tableEntry in genericMethodTable) { + GenericMethodPointers.Add(MethodSpecs[tableEntry.genericMethodIndex], genericMethodPointers[tableEntry.indices.methodIndex]); + GenericMethodInvokerIndices.Add(MethodSpecs[tableEntry.genericMethodIndex], tableEntry.indices.invokerIndex); + } + + // Plugin hook to pre-process binary + isModified |= PluginHooks.PostProcessBinary(this).IsStreamModified; + } + + // IL2CPP API exports + // This strips leading underscores and selects only il2cpp_* symbols which can be mapped into the binary + // (therefore ignoring extern imports) + // Some binaries have functions starting "il2cpp_z_" - ignore these too + private void DiscoverAPIExports() { + var exports = Image.GetExports()? + .Where(e => (e.Name.StartsWith("il2cpp_") || e.Name.StartsWith("_il2cpp_") || e.Name.StartsWith("__il2cpp_")) + && !e.Name.Contains("il2cpp_z_")); + + if (exports == null) + return; + + var exportRgx = new Regex(@"^_+"); + + foreach (var export in exports) + if (Image.TryMapVATR(export.VirtualAddress, out _)) + APIExports.Add(exportRgx.Replace(export.Name, ""), export.VirtualAddress); + } + } +} diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs b/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs index c6aae60..a1c4ce4 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs @@ -1,267 +1,316 @@ -/* - Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using NoisyCowStudios.Bin2Object; - -namespace Il2CppInspector -{ - // From class-internals.h / il2cpp-class-internals.h - public class Il2CppCodeRegistration - { - // Moved to Il2CppCodeGenModule in v24.2 - [Version(Max = 24.1)] - public ulong methodPointersCount; - [Version(Max = 24.1)] - public ulong pmethodPointers; - - public ulong reversePInvokeWrapperCount; // (was renamed from delegateWrappersFromNativeToManagedCount in v22) - public ulong reversePInvokeWrappers; // (was renamed from delegateWrappersFromNativeToManaged in v22) - - // Removed in metadata v23 - [Version(Max = 22)] - public ulong delegateWrappersFromManagedToNativeCount; - [Version(Max = 22)] - public ulong delegateWrappersFromManagedToNative; - [Version(Max = 22)] - public ulong marshalingFunctionsCount; - [Version(Max = 22)] - public ulong marshalingFunctions; - [Version(Min = 21, Max = 22)] - public ulong ccwMarshalingFunctionsCount; - [Version(Min = 21, Max = 22)] - public ulong ccwMarshalingFunctions; - - public ulong genericMethodPointersCount; - public ulong genericMethodPointers; - [Version(Min = 24.5, Max = 24.5)] - [Version(Min = 27.1)] - public ulong genericAdjustorThunks; - - public ulong invokerPointersCount; - public ulong invokerPointers; - - // Removed in metadata v27 - [Version(Max = 24.5)] - public long customAttributeCount; - [Version(Max = 24.5)] - public ulong customAttributeGenerators; - - // Removed in metadata v23 - [Version(Min = 21, Max = 22)] - public long guidCount; - [Version(Min = 21, Max = 22)] - public ulong guids; // Il2CppGuid - - // Added in metadata v22 - [Version(Min = 22)] - public ulong unresolvedVirtualCallCount; - [Version(Min = 22)] - public ulong unresolvedVirtualCallPointers; - - // Added in metadata v23 - [Version(Min = 23)] - public ulong interopDataCount; - [Version(Min = 23)] - public ulong interopData; - - [Version(Min = 24.3)] - public ulong windowsRuntimeFactoryCount; - [Version(Min = 24.3)] - public ulong windowsRuntimeFactoryTable; - - // Added in metadata v24.2 to replace methodPointers and methodPointersCount - [Version(Min = 24.2)] - public ulong codeGenModulesCount; - [Version(Min = 24.2)] - public ulong pcodeGenModules; - } - - // Introduced in metadata v24.2 (replaces method pointers in Il2CppCodeRegistration) - public class Il2CppCodeGenModule - { - public ulong moduleName; - public ulong methodPointerCount; - public ulong methodPointers; - [Version(Min = 24.5, Max = 24.5)] - [Version(Min = 27.1)] - public long adjustorThunkCount; - [Version(Min = 24.5, Max = 24.5)] - [Version(Min = 27.1)] - public ulong adjustorThunks; //Pointer - public ulong invokerIndices; - public ulong reversePInvokeWrapperCount; - public ulong reversePInvokeWrapperIndices; - public ulong rgctxRangesCount; - public ulong rgctxRanges; - public ulong rgctxsCount; - public ulong rgctxs; - public ulong debuggerMetadata; - - // Added in metadata v27 - public ulong customAttributeCacheGenerator; // CustomAttributesCacheGenerator* - public ulong moduleInitializer; // Il2CppMethodPointer - public ulong staticConstructorTypeIndices; // TypeDefinitionIndex* - public ulong metadataRegistration; // Il2CppMetadataRegistration* // Per-assembly mode only - public ulong codeRegistration; // Il2CppCodeRegistration* // Per-assembly mode only - } - -#pragma warning disable CS0649 - public class Il2CppMetadataRegistration - { - public long genericClassesCount; - public ulong genericClasses; - public long genericInstsCount; - public ulong genericInsts; - public long genericMethodTableCount; - public ulong genericMethodTable; // Il2CppGenericMethodFunctionsDefinitions - public long typesCount; - public ulong ptypes; - public long methodSpecsCount; - public ulong methodSpecs; - [Version(Max = 16)] - public long methodReferencesCount; - [Version(Max = 16)] - public ulong methodReferences; - - public long fieldOffsetsCount; - public ulong pfieldOffsets; // Changed from int32_t* to int32_t** after 5.4.0f3, before 5.5.0f3 - - public long typeDefinitionsSizesCount; - public ulong typeDefinitionsSizes; - [Version(Min = 19)] - public ulong metadataUsagesCount; - [Version(Min = 19)] - public ulong metadataUsages; - } -#pragma warning restore CS0649 - - // From blob.h / il2cpp-blob.h - public enum Il2CppTypeEnum - { - IL2CPP_TYPE_END = 0x00, /* End of List */ - IL2CPP_TYPE_VOID = 0x01, - IL2CPP_TYPE_BOOLEAN = 0x02, - IL2CPP_TYPE_CHAR = 0x03, - IL2CPP_TYPE_I1 = 0x04, - IL2CPP_TYPE_U1 = 0x05, - IL2CPP_TYPE_I2 = 0x06, - IL2CPP_TYPE_U2 = 0x07, - IL2CPP_TYPE_I4 = 0x08, - IL2CPP_TYPE_U4 = 0x09, - IL2CPP_TYPE_I8 = 0x0a, - IL2CPP_TYPE_U8 = 0x0b, - IL2CPP_TYPE_R4 = 0x0c, - IL2CPP_TYPE_R8 = 0x0d, - IL2CPP_TYPE_STRING = 0x0e, - IL2CPP_TYPE_PTR = 0x0f, /* arg: token */ - IL2CPP_TYPE_BYREF = 0x10, /* arg: token */ - IL2CPP_TYPE_VALUETYPE = 0x11, /* arg: token */ - IL2CPP_TYPE_CLASS = 0x12, /* arg: token */ - IL2CPP_TYPE_VAR = 0x13, /* Generic parameter in a generic type definition, represented as number (compressed unsigned integer) number */ - IL2CPP_TYPE_ARRAY = 0x14, /* type, rank, boundsCount, bound1, loCount, lo1 */ - IL2CPP_TYPE_GENERICINST = 0x15, /* \x{2026} */ - IL2CPP_TYPE_TYPEDBYREF = 0x16, - IL2CPP_TYPE_I = 0x18, - IL2CPP_TYPE_U = 0x19, - IL2CPP_TYPE_FNPTR = 0x1b, /* arg: full method signature */ - IL2CPP_TYPE_OBJECT = 0x1c, - IL2CPP_TYPE_SZARRAY = 0x1d, /* 0-based one-dim-array */ - IL2CPP_TYPE_MVAR = 0x1e, /* Generic parameter in a generic method definition, represented as number (compressed unsigned integer) */ - IL2CPP_TYPE_CMOD_REQD = 0x1f, /* arg: typedef or typeref token */ - IL2CPP_TYPE_CMOD_OPT = 0x20, /* optional arg: typedef or typref token */ - IL2CPP_TYPE_INTERNAL = 0x21, /* CLR internal type */ - - IL2CPP_TYPE_MODIFIER = 0x40, /* Or with the following types */ - IL2CPP_TYPE_SENTINEL = 0x41, /* Sentinel for varargs method signature */ - IL2CPP_TYPE_PINNED = 0x45, /* Local var that points to pinned object */ - - IL2CPP_TYPE_ENUM = 0x55 /* an enumeration */ - } - - // From metadata.h / il2cpp-runtime-metadata.h - public class Il2CppType - { - /* - union - { - TypeDefinitionIndex klassIndex; // for VALUETYPE and CLASS ( (uint) bits & 0xffff; /* param attributes or field flags */ - public Il2CppTypeEnum type => (Il2CppTypeEnum)((bits >> 16) & 0xff); - // TODO: Unity 2021.1 (v27.2): num_mods becomes 1 bit shorter, shifting byref and pinned right 1 bit, valuetype bit added - public uint num_mods => (uint) (bits >> 24) & 0x3f; /* max 64 modifiers follow at the end */ - public bool byref => ((bits >> 30) & 1) == 1; - public bool pinned => (bits >> 31) == 1; /* valid when included in a local var signature */ - } - - public class Il2CppGenericClass - { - [Version(Max = 24.5)] - public long typeDefinitionIndex; /* the generic type definition */ - [Version(Min = 27)] - public ulong type; // Il2CppType* /* the generic type definition */ - - public Il2CppGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */ - public ulong cached_class; /* if present, the Il2CppClass corresponding to the instantiation. */ - } - - public class Il2CppGenericContext - { - /* The instantiation corresponding to the class generic parameters */ - public ulong class_inst; - /* The instantiation corresponding to the method generic parameters */ - public ulong method_inst; - } - - public class Il2CppGenericInst - { - public ulong type_argc; - public ulong type_argv; - } - - public class Il2CppArrayType - { - public ulong etype; - public byte rank; - public byte numsizes; - public byte numlobounds; - public ulong sizes; - public ulong lobounds; - } - - public class Il2CppMethodSpec - { - public int methodDefinitionIndex; - public int classIndexIndex; - public int methodIndexIndex; - } - - public class Il2CppGenericMethodFunctionsDefinitions - { - public int genericMethodIndex; - public Il2CppGenericMethodIndices indices; - } - - public class Il2CppGenericMethodIndices - { - public int methodIndex; - public int invokerIndex; - [Version(Min = 24.5, Max = 24.5)] - [Version(Min = 27.1)] - public int adjustorThunk; - } -} +/* + Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using NoisyCowStudios.Bin2Object; + +namespace Il2CppInspector +{ + // From class-internals.h / il2cpp-class-internals.h + public class Il2CppCodeRegistration + { + // Moved to Il2CppCodeGenModule in v24.2 + [Version(Max = 24.1)] + public ulong methodPointersCount; + [Version(Max = 24.1)] + public ulong pmethodPointers; + + public ulong reversePInvokeWrapperCount; // (was renamed from delegateWrappersFromNativeToManagedCount in v22) + public ulong reversePInvokeWrappers; // (was renamed from delegateWrappersFromNativeToManaged in v22) + + // Removed in metadata v23 + [Version(Max = 22)] + public ulong delegateWrappersFromManagedToNativeCount; + [Version(Max = 22)] + public ulong delegateWrappersFromManagedToNative; + [Version(Max = 22)] + public ulong marshalingFunctionsCount; + [Version(Max = 22)] + public ulong marshalingFunctions; + [Version(Min = 21, Max = 22)] + public ulong ccwMarshalingFunctionsCount; + [Version(Min = 21, Max = 22)] + public ulong ccwMarshalingFunctions; + + public ulong genericMethodPointersCount; + public ulong genericMethodPointers; + [Version(Min = 24.5, Max = 24.5)] + [Version(Min = 27.1)] + public ulong genericAdjustorThunks; + + public ulong invokerPointersCount; + public ulong invokerPointers; + + // Removed in metadata v27 + [Version(Max = 24.5)] + public long customAttributeCount; + [Version(Max = 24.5)] + public ulong customAttributeGenerators; + + // Removed in metadata v23 + [Version(Min = 21, Max = 22)] + public long guidCount; + [Version(Min = 21, Max = 22)] + public ulong guids; // Il2CppGuid + + // Added in metadata v22 + [Version(Min = 22, Max = 29)] + public ulong unresolvedVirtualCallCount; + + [Version(Min = 29.1)] + public ulong unresolvedIndirectCallCount; + + [Version(Min = 22)] + public ulong unresolvedVirtualCallPointers; + + [Version(Min = 29.1)] + public ulong unresolvedInstanceCallPointers; + [Version(Min = 29.1)] + public ulong unresolvedStaticCallPointers; + + // Added in metadata v23 + [Version(Min = 23)] + public ulong interopDataCount; + [Version(Min = 23)] + public ulong interopData; + + [Version(Min = 24.3)] + public ulong windowsRuntimeFactoryCount; + [Version(Min = 24.3)] + public ulong windowsRuntimeFactoryTable; + + // Added in metadata v24.2 to replace methodPointers and methodPointersCount + [Version(Min = 24.2)] + public ulong codeGenModulesCount; + [Version(Min = 24.2)] + public ulong pcodeGenModules; + } + + // Introduced in metadata v24.2 (replaces method pointers in Il2CppCodeRegistration) + public class Il2CppCodeGenModule + { + public ulong moduleName; + public ulong methodPointerCount; + public ulong methodPointers; + [Version(Min = 24.5, Max = 24.5)] + [Version(Min = 27.1)] + public long adjustorThunkCount; + [Version(Min = 24.5, Max = 24.5)] + [Version(Min = 27.1)] + public ulong adjustorThunks; //Pointer + public ulong invokerIndices; + public ulong reversePInvokeWrapperCount; + public ulong reversePInvokeWrapperIndices; + public ulong rgctxRangesCount; + public ulong rgctxRanges; + public ulong rgctxsCount; + public ulong rgctxs; + public ulong debuggerMetadata; + + // Added in metadata v27 + [Version(Min = 27, Max = 27.2)] + public ulong customAttributeCacheGenerator; // CustomAttributesCacheGenerator* + [Version(Min = 27)] + public ulong moduleInitializer; // Il2CppMethodPointer + [Version(Min = 27)] + public ulong staticConstructorTypeIndices; // TypeDefinitionIndex* + [Version(Min = 27)] + public ulong metadataRegistration; // Il2CppMetadataRegistration* // Per-assembly mode only + [Version(Min = 27)] + public ulong codeRegistration; // Il2CppCodeRegistration* // Per-assembly mode only + } + +#pragma warning disable CS0649 + public class Il2CppMetadataRegistration + { + public long genericClassesCount; + public ulong genericClasses; + public long genericInstsCount; + public ulong genericInsts; + public long genericMethodTableCount; + public ulong genericMethodTable; // Il2CppGenericMethodFunctionsDefinitions + public long typesCount; + public ulong ptypes; + public long methodSpecsCount; + public ulong methodSpecs; + [Version(Max = 16)] + public long methodReferencesCount; + [Version(Max = 16)] + public ulong methodReferences; + + public long fieldOffsetsCount; + public ulong pfieldOffsets; // Changed from int32_t* to int32_t** after 5.4.0f3, before 5.5.0f3 + + public long typeDefinitionsSizesCount; + public ulong typeDefinitionsSizes; + [Version(Min = 19)] + public ulong metadataUsagesCount; + [Version(Min = 19)] + public ulong metadataUsages; + } +#pragma warning restore CS0649 + + // From blob.h / il2cpp-blob.h + public enum Il2CppTypeEnum + { + IL2CPP_TYPE_END = 0x00, /* End of List */ + IL2CPP_TYPE_VOID = 0x01, + IL2CPP_TYPE_BOOLEAN = 0x02, + IL2CPP_TYPE_CHAR = 0x03, + IL2CPP_TYPE_I1 = 0x04, + IL2CPP_TYPE_U1 = 0x05, + IL2CPP_TYPE_I2 = 0x06, + IL2CPP_TYPE_U2 = 0x07, + IL2CPP_TYPE_I4 = 0x08, + IL2CPP_TYPE_U4 = 0x09, + IL2CPP_TYPE_I8 = 0x0a, + IL2CPP_TYPE_U8 = 0x0b, + IL2CPP_TYPE_R4 = 0x0c, + IL2CPP_TYPE_R8 = 0x0d, + IL2CPP_TYPE_STRING = 0x0e, + IL2CPP_TYPE_PTR = 0x0f, /* arg: token */ + IL2CPP_TYPE_BYREF = 0x10, /* arg: token */ + IL2CPP_TYPE_VALUETYPE = 0x11, /* arg: token */ + IL2CPP_TYPE_CLASS = 0x12, /* arg: token */ + IL2CPP_TYPE_VAR = 0x13, /* Generic parameter in a generic type definition, represented as number (compressed unsigned integer) number */ + IL2CPP_TYPE_ARRAY = 0x14, /* type, rank, boundsCount, bound1, loCount, lo1 */ + IL2CPP_TYPE_GENERICINST = 0x15, /* \x{2026} */ + IL2CPP_TYPE_TYPEDBYREF = 0x16, + IL2CPP_TYPE_I = 0x18, + IL2CPP_TYPE_U = 0x19, + IL2CPP_TYPE_FNPTR = 0x1b, /* arg: full method signature */ + IL2CPP_TYPE_OBJECT = 0x1c, + IL2CPP_TYPE_SZARRAY = 0x1d, /* 0-based one-dim-array */ + IL2CPP_TYPE_MVAR = 0x1e, /* Generic parameter in a generic method definition, represented as number (compressed unsigned integer) */ + IL2CPP_TYPE_CMOD_REQD = 0x1f, /* arg: typedef or typeref token */ + IL2CPP_TYPE_CMOD_OPT = 0x20, /* optional arg: typedef or typref token */ + IL2CPP_TYPE_INTERNAL = 0x21, /* CLR internal type */ + + IL2CPP_TYPE_MODIFIER = 0x40, /* Or with the following types */ + IL2CPP_TYPE_SENTINEL = 0x41, /* Sentinel for varargs method signature */ + IL2CPP_TYPE_PINNED = 0x45, /* Local var that points to pinned object */ + + IL2CPP_TYPE_ENUM = 0x55, /* an enumeration */ + IL2CPP_TYPE_IL2CPP_TYPE_INDEX = 0xff /* Type index metadata table */ + } + + // From metadata.h / il2cpp-runtime-metadata.h + public class Il2CppType + { + /* + union + { + TypeDefinitionIndex klassIndex; // for VALUETYPE and CLASS ( (uint) bits & 0xffff; /* param attributes or field flags */ + public Il2CppTypeEnum type => (Il2CppTypeEnum)((bits >> 16) & 0xff); + // TODO: Unity 2021.1 (v27.2): num_mods becomes 1 bit shorter, shifting byref and pinned right 1 bit, valuetype bit added + public uint num_mods => (uint) (bits >> 24) & 0x3f; /* max 64 modifiers follow at the end */ + public bool byref => ((bits >> 30) & 1) == 1; + public bool pinned => (bits >> 31) == 1; /* valid when included in a local var signature */ + + /*public class Union + { + public ulong dummy; + /// + /// for VALUETYPE and CLASS + /// + public long klassIndex => (long)dummy; + /// + /// for VALUETYPE and CLASS at runtime + /// + public ulong typeHandle => dummy; + /// + /// for PTR and SZARRAY + /// + public ulong type => dummy; + /// + /// for ARRAY + /// + public ulong array => dummy; + /// + /// for VAR and MVAR + /// + public long genericParameterIndex => (long)dummy; + /// + /// for VAR and MVAR at runtime + /// + public ulong genericParameterHandle => dummy; + /// + /// for GENERICINST + /// + public ulong generic_class => dummy; + }*/ + } + + public class Il2CppGenericClass + { + [Version(Max = 24.5)] + public long typeDefinitionIndex; /* the generic type definition */ + [Version(Min = 27)] + public ulong type; // Il2CppType* /* the generic type definition */ + + public Il2CppGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */ + public ulong cached_class; /* if present, the Il2CppClass corresponding to the instantiation. */ + } + + public class Il2CppGenericContext + { + /* The instantiation corresponding to the class generic parameters */ + public ulong class_inst; + /* The instantiation corresponding to the method generic parameters */ + public ulong method_inst; + } + + public class Il2CppGenericInst + { + public ulong type_argc; + public ulong type_argv; + } + + public class Il2CppArrayType + { + public ulong etype; + public byte rank; + public byte numsizes; + public byte numlobounds; + public ulong sizes; + public ulong lobounds; + } + + public class Il2CppMethodSpec + { + public int methodDefinitionIndex; + public int classIndexIndex; + public int methodIndexIndex; + } + + public class Il2CppGenericMethodFunctionsDefinitions + { + public int genericMethodIndex; + public Il2CppGenericMethodIndices indices; + } + + public class Il2CppGenericMethodIndices + { + public int methodIndex; + public int invokerIndex; + [Version(Min = 24.5, Max = 24.5)] + [Version(Min = 27.1)] + public int adjustorThunk; + } +} diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs index c0ca182..4c08644 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs @@ -102,10 +102,14 @@ namespace Il2CppInspector value = Metadata.ReadInt16(); break; case Il2CppTypeEnum.IL2CPP_TYPE_U4: - value = Metadata.ReadUInt32(); + value = Metadata.Version >= 29 + ? Metadata.ReadCompressedUInt32() + : Metadata.ReadUInt32(); break; case Il2CppTypeEnum.IL2CPP_TYPE_I4: - value = Metadata.ReadInt32(); + value = Metadata.Version >= 29 + ? Metadata.ReadCompressedInt32() + : Metadata.ReadInt32(); break; case Il2CppTypeEnum.IL2CPP_TYPE_U8: value = Metadata.ReadUInt64(); @@ -120,7 +124,10 @@ namespace Il2CppInspector value = Metadata.ReadDouble(); break; case Il2CppTypeEnum.IL2CPP_TYPE_STRING: - var uiLen = Metadata.ReadInt32(); + var uiLen = Metadata.Version >= 29 + ? Metadata.ReadCompressedInt32() + : Metadata.ReadInt32(); + value = Encoding.UTF8.GetString(Metadata.ReadBytes(uiLen)); break; } @@ -310,7 +317,7 @@ namespace Il2CppInspector FunctionAddresses.Add(sortedFunctionPointers[^1], sortedFunctionPointers[^1]); // Organize custom attribute indices - if (Version >= 24.1) { + if (Version >= 24.1 && Version < 29) { AttributeIndicesByToken = new Dictionary>(); foreach (var image in Images) { var attsByToken = new Dictionary(); diff --git a/Il2CppInspector.Common/Reflection/CustomAttributeData.cs b/Il2CppInspector.Common/Reflection/CustomAttributeData.cs index cfeb275..288ca64 100644 --- a/Il2CppInspector.Common/Reflection/CustomAttributeData.cs +++ b/Il2CppInspector.Common/Reflection/CustomAttributeData.cs @@ -63,6 +63,12 @@ namespace Il2CppInspector.Reflection yield return attribute; } } + else + { + Console.WriteLine("Skipping custom attributes for 29+"); + yield break; + } + } private static IList getCustomAttributes(Assembly asm, int token, int customAttributeIndex) => getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList(); diff --git a/Il2CppInspector.Common/Reflection/TypeModel.cs b/Il2CppInspector.Common/Reflection/TypeModel.cs index 01c2cd6..6f460ff 100644 --- a/Il2CppInspector.Common/Reflection/TypeModel.cs +++ b/Il2CppInspector.Common/Reflection/TypeModel.cs @@ -1,366 +1,369 @@ -/* - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - Copyright 2020 Robert Xiao - https://robertxiao.ca - - All rights reserved. -*/ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; - -namespace Il2CppInspector.Reflection -{ - public class TypeModel - { - public Il2CppInspector Package { get; } - public List Assemblies { get; } = new List(); - - // List of all namespaces defined by the application - public List Namespaces { get; } - - // List of all types from TypeDefs ordered by their TypeDefinitionIndex - public TypeInfo[] TypesByDefinitionIndex { get; } - - // List of all types from TypeRefs ordered by instanceIndex - public TypeInfo[] TypesByReferenceIndex { get; } - - // List of all types from GenericParameters - public TypeInfo[] GenericParameterTypes { get; } - - // List of all methods from MethodSpecs (closed generic methods that can be called; does not need to be in a generic class) - public Dictionary GenericMethods { get; } = new Dictionary(); - - // List of all type definitions by fully qualified name (TypeDefs only) - public Dictionary TypesByFullName { get; } = new Dictionary(); - - // Every type - public IEnumerable Types - { - get - { - types ??= TypesByDefinitionIndex.Concat(TypesByReferenceIndex) - .Concat(GenericMethods.Values.Select(m => m.DeclaringType)).Distinct().Where(t => t != null).ToList(); - return types; - } - } - - private List types; - - - // List of all methods ordered by their MethodDefinitionIndex - public MethodBase[] MethodsByDefinitionIndex { get; } - - // List of all Method.Invoke functions by invoker index - public MethodInvoker[] MethodInvokers { get; } - - // List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices - public ConcurrentDictionary AttributesByIndices { get; } = new ConcurrentDictionary(); - - // List of unique custom attributes generators indexed by type (multiple indices above may refer to a single generator function) - public Dictionary> CustomAttributeGenerators { get; } - - // List of unique custom attributes generators indexed by virtual address - public Dictionary> CustomAttributeGeneratorsByAddress { get; } - - // Get an assembly by its image name - public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name); - - // Get a type by its fully qualified name including generic type arguments, array brackets etc. - // In other words, rather than only being able to fetch a type definition such as in Assembly.GetType(), - // this method can also find reference types, types created from TypeRefs and constructed types from MethodSpecs - public TypeInfo GetType(string fullName) => Types.FirstOrDefault( - t => fullName == t.Namespace + (!string.IsNullOrEmpty(t.Namespace)? "." : "") + t.Name); - - // Get a concrete instantiation of a generic method from its fully qualified name and type arguments - public MethodBase GetGenericMethod(string fullName, params TypeInfo[] typeArguments) => - GenericMethods.Values.First( - m => fullName == m.DeclaringType.Namespace + (!string.IsNullOrEmpty(m.DeclaringType.Namespace)? "." : "") - + m.DeclaringType.Name + "." + m.Name - && m.GetGenericArguments().SequenceEqual(typeArguments)); - - // Create type model - public TypeModel(Il2CppInspector package) { - Package = package; - TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length]; - TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count]; - GenericParameterTypes = new TypeInfo[package.GenericParameters.Length]; - MethodsByDefinitionIndex = new MethodBase[package.Methods.Length]; - MethodInvokers = new MethodInvoker[package.MethodInvokePointers.Length]; - - // Recursively create hierarchy of assemblies and types from TypeDefs - // No code that executes here can access any type through a TypeRef (ie. via TypesByReferenceIndex) - for (var image = 0; image < package.Images.Length; image++) - Assemblies.Add(new Assembly(this, image)); - - // Create and reference types from TypeRefs - // Note that you can't resolve any TypeRefs until all the TypeDefs have been processed - for (int typeRefIndex = 0; typeRefIndex < package.TypeReferences.Count; typeRefIndex++) { - if(TypesByReferenceIndex[typeRefIndex] != null) { - /* type already generated - probably by forward reference through GetTypeFromVirtualAddress */ - continue; - } - - var typeRef = Package.TypeReferences[typeRefIndex]; - var referencedType = resolveTypeReference(typeRef); - - TypesByReferenceIndex[typeRefIndex] = referencedType; - } - - // Create types and methods from MethodSpec (which incorporates TypeSpec in IL2CPP) - foreach (var spec in Package.MethodSpecs) { - var methodDefinition = MethodsByDefinitionIndex[spec.methodDefinitionIndex]; - var declaringType = methodDefinition.DeclaringType; - - // Concrete instance of a generic class - // If the class index is not specified, we will later create a generic method in a non-generic class - if (spec.classIndexIndex != -1) { - var genericInstance = Package.GenericInstances[spec.classIndexIndex]; - var genericArguments = ResolveGenericArguments(genericInstance); - declaringType = declaringType.MakeGenericType(genericArguments); - } - - MethodBase method; - if (methodDefinition is ConstructorInfo) - method = declaringType.GetConstructorByDefinition((ConstructorInfo)methodDefinition); - else - method = declaringType.GetMethodByDefinition((MethodInfo)methodDefinition); - - if (spec.methodIndexIndex != -1) { - var genericInstance = Package.GenericInstances[spec.methodIndexIndex]; - var genericArguments = ResolveGenericArguments(genericInstance); - method = method.MakeGenericMethod(genericArguments); - } - method.VirtualAddress = Package.GetGenericMethodPointer(spec); - GenericMethods[spec] = method; - } - - // Generate a list of all namespaces used - Namespaces = Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace).Select(n => n.Key).Distinct().ToList(); - - // Find all custom attribute generators (populate AttributesByIndices) (use ToList() to force evaluation) - var allAssemblyAttributes = Assemblies.Select(a => a.CustomAttributes).ToList(); - var allTypeAttributes = TypesByDefinitionIndex.Select(t => t.CustomAttributes).ToList(); - var allEventAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredEvents).Select(e => e.CustomAttributes).ToList(); - var allFieldAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredFields).Select(f => f.CustomAttributes).ToList(); - var allPropertyAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredProperties).Select(p => p.CustomAttributes).ToList(); - var allMethodAttributes = MethodsByDefinitionIndex.Select(m => m.CustomAttributes).ToList(); - var allParameterAttributes = MethodsByDefinitionIndex.SelectMany(m => m.DeclaredParameters).Select(p => p.CustomAttributes).ToList(); - - // Populate list of unique custom attribute generators for each type - CustomAttributeGenerators = AttributesByIndices.Values - .GroupBy(a => a.AttributeType) - .ToDictionary(g => g.Key, g => g.GroupBy(a => a.VirtualAddress.Start).Select(g => g.First()).ToList()); - - // Populate list of unique custom attribute generators for each address - CustomAttributeGeneratorsByAddress = AttributesByIndices.Values - .GroupBy(a => a.VirtualAddress.Start) - .ToDictionary(g => g.Key, g => g.GroupBy(a => a.AttributeType).Select(g => g.First()).ToList()); - - // Create method invokers (one per signature, in invoker index order) - // Generic type definitions have an invoker index of -1 - foreach (var method in MethodsByDefinitionIndex) { - var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition); - if (index != -1) { - if (MethodInvokers[index] == null) - MethodInvokers[index] = new MethodInvoker(method, index); - - method.Invoker = MethodInvokers[index]; - } - } - - // Create method invokers sourced from generic method invoker indices - foreach (var spec in GenericMethods.Keys) { - if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) { - if (MethodInvokers[index] == null) - MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index); - - GenericMethods[spec].Invoker = MethodInvokers[index]; - } - } - - // Post-processing hook - PluginHooks.PostProcessTypeModel(this); - } - - // Get generic arguments from either a type or method instanceIndex from a MethodSpec - public TypeInfo[] ResolveGenericArguments(Il2CppGenericInst inst) { - - // Get list of pointers to type parameters (both unresolved and concrete) - var genericTypeArguments = Package.BinaryImage.ReadMappedArray(inst.type_argv, (int)inst.type_argc); - - return genericTypeArguments.Select(a => GetTypeFromVirtualAddress(a)).ToArray(); - } - - // Initialize type from type reference (TypeRef) - // Much of the following is adapted from il2cpp::vm::Class::FromIl2CppType - private TypeInfo resolveTypeReference(Il2CppType typeRef) { - var image = Package.BinaryImage; - TypeInfo underlyingType; - - switch (typeRef.type) { - // Classes defined in the metadata (reference to a TypeDef) - case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: - case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: - underlyingType = TypesByDefinitionIndex[typeRef.datapoint]; // klassIndex - break; - - // Constructed types - case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: - // TODO: Replace with array load from Il2CppMetadataRegistration.genericClasses - var generic = image.ReadMappedObject(typeRef.datapoint); // Il2CppGenericClass * - - // Get generic type definition - TypeInfo genericTypeDef; - if (Package.Version < 27) { - // It appears that TypeRef can be -1 if the generic depth recursion limit - // (--maximum-recursive-generic-depth=) is reached in Il2Cpp. In this case, - // no generic instance type is generated, so we just produce a null TypeInfo here. - if ((generic.typeDefinitionIndex & 0xffff_ffff) == 0x0000_0000_ffff_ffff) - return null; - - genericTypeDef = TypesByDefinitionIndex[generic.typeDefinitionIndex]; - } else { - genericTypeDef = GetTypeFromVirtualAddress(generic.type); - } - - // Get the instantiation - // TODO: Replace with array load from Il2CppMetadataRegistration.genericInsts - var genericInstance = image.ReadMappedObject(generic.context.class_inst); - var genericArguments = ResolveGenericArguments(genericInstance); - - underlyingType = genericTypeDef.MakeGenericType(genericArguments); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: - var descriptor = image.ReadMappedObject(typeRef.datapoint); - var elementType = GetTypeFromVirtualAddress(descriptor.etype); - underlyingType = elementType.MakeArrayType(descriptor.rank); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: - elementType = GetTypeFromVirtualAddress(typeRef.datapoint); - underlyingType = elementType.MakeArrayType(1); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_PTR: - elementType = GetTypeFromVirtualAddress(typeRef.datapoint); - underlyingType = elementType.MakePointerType(); - break; - - // Generic type and generic method parameters - case Il2CppTypeEnum.IL2CPP_TYPE_VAR: - case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: - underlyingType = GetGenericParameterType((int)typeRef.datapoint); - break; - - // Primitive types - default: - underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type); - break; - } - - // Create a reference type if necessary - return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType; - } - - // Basic primitive types are specified via a flag value - private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) { - if ((int)t >= Il2CppConstants.FullNameTypeString.Count) - return null; - - var fqn = Il2CppConstants.FullNameTypeString[(int)t]; - return TypesByFullName[fqn]; - } - - // Get a TypeRef by its virtual address - // These are always nested types from references within another TypeRef - public TypeInfo GetTypeFromVirtualAddress(ulong ptr) { - var typeRefIndex = Package.TypeReferenceIndicesByAddress[ptr]; - - if (TypesByReferenceIndex[typeRefIndex] != null) - return TypesByReferenceIndex[typeRefIndex]; - - var type = Package.TypeReferences[typeRefIndex]; - var referencedType = resolveTypeReference(type); - - TypesByReferenceIndex[typeRefIndex] = referencedType; - return referencedType; - } - - public TypeInfo GetGenericParameterType(int index) { - if (GenericParameterTypes[index] != null) - return GenericParameterTypes[index]; - - var paramType = Package.GenericParameters[index]; // genericParameterIndex - var container = Package.GenericContainers[paramType.ownerIndex]; - TypeInfo result; - - if (container.is_method == 1) { - var owner = MethodsByDefinitionIndex[container.ownerIndex]; - result = new TypeInfo(owner, paramType); - } else { - var owner = TypesByDefinitionIndex[container.ownerIndex]; - result = new TypeInfo(owner, paramType); - } - GenericParameterTypes[index] = result; - return result; - } - - // The attribute index is an index into AttributeTypeRanges, each of which is a start-end range index into AttributeTypeIndices, each of which is a TypeIndex - public int GetCustomAttributeIndex(Assembly asm, int token, int customAttributeIndex) { - // Prior to v24.1, Type, Field, Parameter, Method, Event, Property, Assembly definitions had their own customAttributeIndex field - if (Package.Version <= 24.0) - return customAttributeIndex; - - // From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image - if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint) token, out var index)) - return -1; - return index; - } - - // Get the name of a metadata typeRef - public string GetMetadataUsageName(MetadataUsage usage) { - switch (usage.Type) { - case MetadataUsageType.TypeInfo: - case MetadataUsageType.Type: - return GetMetadataUsageType(usage).Name; - - case MetadataUsageType.MethodDef: - var method = GetMetadataUsageMethod(usage); - return $"{method.DeclaringType.Name}.{method.Name}"; - - case MetadataUsageType.FieldInfo: - var fieldRef = Package.FieldRefs[usage.SourceIndex]; - var type = GetMetadataUsageType(usage); - var field = type.DeclaredFields.First(f => f.Index == type.Definition.fieldStart + fieldRef.fieldIndex); - return $"{type.Name}.{field.Name}"; - - case MetadataUsageType.StringLiteral: - return Package.StringLiterals[usage.SourceIndex]; - - case MetadataUsageType.MethodRef: - type = GetMetadataUsageType(usage); - method = GetMetadataUsageMethod(usage); - return $"{type.Name}.{method.Name}"; - } - throw new NotImplementedException("Unknown metadata usage type: " + usage.Type); - } - - // Get the type used in a metadata usage - public TypeInfo GetMetadataUsageType(MetadataUsage usage) => usage.Type switch { - MetadataUsageType.Type => TypesByReferenceIndex[usage.SourceIndex], - MetadataUsageType.TypeInfo => TypesByReferenceIndex[usage.SourceIndex], - MetadataUsageType.MethodDef => GetMetadataUsageMethod(usage).DeclaringType, - MetadataUsageType.FieldInfo => TypesByReferenceIndex[Package.FieldRefs[usage.SourceIndex].typeIndex], - MetadataUsageType.MethodRef => GetMetadataUsageMethod(usage).DeclaringType, - - _ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type") - }; - - // Get the method used in a metadata usage - public MethodBase GetMetadataUsageMethod(MetadataUsage usage) => usage.Type switch { - MetadataUsageType.MethodDef => MethodsByDefinitionIndex[usage.SourceIndex], - MetadataUsageType.MethodRef => GenericMethods[Package.MethodSpecs[usage.SourceIndex]], - _ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type") - }; - } +/* + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + Copyright 2020 Robert Xiao - https://robertxiao.ca + + All rights reserved. +*/ + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; + +namespace Il2CppInspector.Reflection +{ + public class TypeModel + { + public Il2CppInspector Package { get; } + public List Assemblies { get; } = new List(); + + // List of all namespaces defined by the application + public List Namespaces { get; } + + // List of all types from TypeDefs ordered by their TypeDefinitionIndex + public TypeInfo[] TypesByDefinitionIndex { get; } + + // List of all types from TypeRefs ordered by instanceIndex + public TypeInfo[] TypesByReferenceIndex { get; } + + // List of all types from GenericParameters + public TypeInfo[] GenericParameterTypes { get; } + + // List of all methods from MethodSpecs (closed generic methods that can be called; does not need to be in a generic class) + public Dictionary GenericMethods { get; } = new Dictionary(); + + // List of all type definitions by fully qualified name (TypeDefs only) + public Dictionary TypesByFullName { get; } = new Dictionary(); + + // Every type + public IEnumerable Types + { + get + { + types ??= TypesByDefinitionIndex.Concat(TypesByReferenceIndex) + .Concat(GenericMethods.Values.Select(m => m.DeclaringType)).Distinct().Where(t => t != null).ToList(); + return types; + } + } + + private List types; + + + // List of all methods ordered by their MethodDefinitionIndex + public MethodBase[] MethodsByDefinitionIndex { get; } + + // List of all Method.Invoke functions by invoker index + public MethodInvoker[] MethodInvokers { get; } + + // List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices + public ConcurrentDictionary AttributesByIndices { get; } = new ConcurrentDictionary(); + + // List of unique custom attributes generators indexed by type (multiple indices above may refer to a single generator function) + public Dictionary> CustomAttributeGenerators { get; } + + // List of unique custom attributes generators indexed by virtual address + public Dictionary> CustomAttributeGeneratorsByAddress { get; } + + // Get an assembly by its image name + public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name); + + // Get a type by its fully qualified name including generic type arguments, array brackets etc. + // In other words, rather than only being able to fetch a type definition such as in Assembly.GetType(), + // this method can also find reference types, types created from TypeRefs and constructed types from MethodSpecs + public TypeInfo GetType(string fullName) => Types.FirstOrDefault( + t => fullName == t.Namespace + (!string.IsNullOrEmpty(t.Namespace)? "." : "") + t.Name); + + // Get a concrete instantiation of a generic method from its fully qualified name and type arguments + public MethodBase GetGenericMethod(string fullName, params TypeInfo[] typeArguments) => + GenericMethods.Values.First( + m => fullName == m.DeclaringType.Namespace + (!string.IsNullOrEmpty(m.DeclaringType.Namespace)? "." : "") + + m.DeclaringType.Name + "." + m.Name + && m.GetGenericArguments().SequenceEqual(typeArguments)); + + // Create type model + public TypeModel(Il2CppInspector package) { + Package = package; + TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length]; + TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count]; + GenericParameterTypes = new TypeInfo[package.GenericParameters.Length]; + MethodsByDefinitionIndex = new MethodBase[package.Methods.Length]; + MethodInvokers = new MethodInvoker[package.MethodInvokePointers.Length]; + + // Recursively create hierarchy of assemblies and types from TypeDefs + // No code that executes here can access any type through a TypeRef (ie. via TypesByReferenceIndex) + for (var image = 0; image < package.Images.Length; image++) + Assemblies.Add(new Assembly(this, image)); + + // Create and reference types from TypeRefs + // Note that you can't resolve any TypeRefs until all the TypeDefs have been processed + for (int typeRefIndex = 0; typeRefIndex < package.TypeReferences.Count; typeRefIndex++) { + if(TypesByReferenceIndex[typeRefIndex] != null) { + /* type already generated - probably by forward reference through GetTypeFromVirtualAddress */ + continue; + } + + var typeRef = Package.TypeReferences[typeRefIndex]; + var referencedType = resolveTypeReference(typeRef); + + TypesByReferenceIndex[typeRefIndex] = referencedType; + } + + // Create types and methods from MethodSpec (which incorporates TypeSpec in IL2CPP) + foreach (var spec in Package.MethodSpecs) { + var methodDefinition = MethodsByDefinitionIndex[spec.methodDefinitionIndex]; + var declaringType = methodDefinition.DeclaringType; + + // Concrete instance of a generic class + // If the class index is not specified, we will later create a generic method in a non-generic class + if (spec.classIndexIndex != -1) { + var genericInstance = Package.GenericInstances[spec.classIndexIndex]; + var genericArguments = ResolveGenericArguments(genericInstance); + declaringType = declaringType.MakeGenericType(genericArguments); + } + + MethodBase method; + if (methodDefinition is ConstructorInfo) + method = declaringType.GetConstructorByDefinition((ConstructorInfo)methodDefinition); + else + method = declaringType.GetMethodByDefinition((MethodInfo)methodDefinition); + + if (spec.methodIndexIndex != -1) { + var genericInstance = Package.GenericInstances[spec.methodIndexIndex]; + var genericArguments = ResolveGenericArguments(genericInstance); + method = method.MakeGenericMethod(genericArguments); + } + method.VirtualAddress = Package.GetGenericMethodPointer(spec); + GenericMethods[spec] = method; + } + + // Generate a list of all namespaces used + Namespaces = Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace).Select(n => n.Key).Distinct().ToList(); + + // Find all custom attribute generators (populate AttributesByIndices) (use ToList() to force evaluation) + var allAssemblyAttributes = Assemblies.Select(a => a.CustomAttributes).ToList(); + var allTypeAttributes = TypesByDefinitionIndex.Select(t => t.CustomAttributes).ToList(); + var allEventAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredEvents).Select(e => e.CustomAttributes).ToList(); + var allFieldAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredFields).Select(f => f.CustomAttributes).ToList(); + var allPropertyAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredProperties).Select(p => p.CustomAttributes).ToList(); + var allMethodAttributes = MethodsByDefinitionIndex.Select(m => m.CustomAttributes).ToList(); + var allParameterAttributes = MethodsByDefinitionIndex.SelectMany(m => m.DeclaredParameters).Select(p => p.CustomAttributes).ToList(); + + // Populate list of unique custom attribute generators for each type + CustomAttributeGenerators = AttributesByIndices.Values + .GroupBy(a => a.AttributeType) + .ToDictionary(g => g.Key, g => g.GroupBy(a => a.VirtualAddress.Start).Select(g => g.First()).ToList()); + + // Populate list of unique custom attribute generators for each address + CustomAttributeGeneratorsByAddress = AttributesByIndices.Values + .GroupBy(a => a.VirtualAddress.Start) + .ToDictionary(g => g.Key, g => g.GroupBy(a => a.AttributeType).Select(g => g.First()).ToList()); + + // Create method invokers (one per signature, in invoker index order) + // Generic type definitions have an invoker index of -1 + foreach (var method in MethodsByDefinitionIndex) { + var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition); + if (index != -1) { + if (MethodInvokers[index] == null) + MethodInvokers[index] = new MethodInvoker(method, index); + + method.Invoker = MethodInvokers[index]; + } + } + + // Create method invokers sourced from generic method invoker indices + foreach (var spec in GenericMethods.Keys) { + if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) { + if (MethodInvokers[index] == null) + MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index); + + GenericMethods[spec].Invoker = MethodInvokers[index]; + } + } + + // Post-processing hook + PluginHooks.PostProcessTypeModel(this); + } + + // Get generic arguments from either a type or method instanceIndex from a MethodSpec + public TypeInfo[] ResolveGenericArguments(Il2CppGenericInst inst) { + + // Get list of pointers to type parameters (both unresolved and concrete) + var genericTypeArguments = Package.BinaryImage.ReadMappedArray(inst.type_argv, (int)inst.type_argc); + + return genericTypeArguments.Select(a => GetTypeFromVirtualAddress(a)).ToArray(); + } + + // Initialize type from type reference (TypeRef) + // Much of the following is adapted from il2cpp::vm::Class::FromIl2CppType + private TypeInfo resolveTypeReference(Il2CppType typeRef) { + var image = Package.BinaryImage; + TypeInfo underlyingType; + + switch (typeRef.type) { + // Classes defined in the metadata (reference to a TypeDef) + case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: + case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: + underlyingType = TypesByDefinitionIndex[typeRef.datapoint]; // klassIndex + break; + + // Constructed types + case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: + // TODO: Replace with array load from Il2CppMetadataRegistration.genericClasses + var generic = image.ReadMappedObject(typeRef.datapoint); // Il2CppGenericClass * + + // Get generic type definition + TypeInfo genericTypeDef; + if (Package.Version < 27) { + // It appears that TypeRef can be -1 if the generic depth recursion limit + // (--maximum-recursive-generic-depth=) is reached in Il2Cpp. In this case, + // no generic instance type is generated, so we just produce a null TypeInfo here. + if ((generic.typeDefinitionIndex & 0xffff_ffff) == 0x0000_0000_ffff_ffff) + return null; + + genericTypeDef = TypesByDefinitionIndex[generic.typeDefinitionIndex]; + } else { + genericTypeDef = GetTypeFromVirtualAddress(generic.type); + } + + // Get the instantiation + // TODO: Replace with array load from Il2CppMetadataRegistration.genericInsts + var genericInstance = image.ReadMappedObject(generic.context.class_inst); + var genericArguments = ResolveGenericArguments(genericInstance); + + underlyingType = genericTypeDef.MakeGenericType(genericArguments); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: + var descriptor = image.ReadMappedObject(typeRef.datapoint); + var elementType = GetTypeFromVirtualAddress(descriptor.etype); + underlyingType = elementType.MakeArrayType(descriptor.rank); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: + elementType = GetTypeFromVirtualAddress(typeRef.datapoint); + underlyingType = elementType.MakeArrayType(1); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_PTR: + elementType = GetTypeFromVirtualAddress(typeRef.datapoint); + underlyingType = elementType.MakePointerType(); + break; + + // Generic type and generic method parameters + case Il2CppTypeEnum.IL2CPP_TYPE_VAR: + case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: + underlyingType = GetGenericParameterType((int)typeRef.datapoint); + break; + + // Primitive types + default: + underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type); + break; + } + + // Create a reference type if necessary + return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType; + } + + // Basic primitive types are specified via a flag value + private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) { + if ((int)t >= Il2CppConstants.FullNameTypeString.Count) + return null; + + var fqn = Il2CppConstants.FullNameTypeString[(int)t]; + return TypesByFullName[fqn]; + } + + // Get a TypeRef by its virtual address + // These are always nested types from references within another TypeRef + public TypeInfo GetTypeFromVirtualAddress(ulong ptr) { + var typeRefIndex = Package.TypeReferenceIndicesByAddress[ptr]; + + if (TypesByReferenceIndex[typeRefIndex] != null) + return TypesByReferenceIndex[typeRefIndex]; + + var type = Package.TypeReferences[typeRefIndex]; + var referencedType = resolveTypeReference(type); + + TypesByReferenceIndex[typeRefIndex] = referencedType; + return referencedType; + } + + public TypeInfo GetGenericParameterType(int index) { + if (GenericParameterTypes[index] != null) + return GenericParameterTypes[index]; + + var paramType = Package.GenericParameters[index]; // genericParameterIndex + var container = Package.GenericContainers[paramType.ownerIndex]; + TypeInfo result; + + if (container.is_method == 1) { + var owner = MethodsByDefinitionIndex[container.ownerIndex]; + result = new TypeInfo(owner, paramType); + } else { + var owner = TypesByDefinitionIndex[container.ownerIndex]; + result = new TypeInfo(owner, paramType); + } + GenericParameterTypes[index] = result; + return result; + } + + // The attribute index is an index into AttributeTypeRanges, each of which is a start-end range index into AttributeTypeIndices, each of which is a TypeIndex + public int GetCustomAttributeIndex(Assembly asm, int token, int customAttributeIndex) { + // Prior to v24.1, Type, Field, Parameter, Method, Event, Property, Assembly definitions had their own customAttributeIndex field + if (Package.Version <= 24.0) + return customAttributeIndex; + + if (Package.Version >= 29) + return -1; + + // From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image + if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint) token, out var index)) + return -1; + return index; + } + + // Get the name of a metadata typeRef + public string GetMetadataUsageName(MetadataUsage usage) { + switch (usage.Type) { + case MetadataUsageType.TypeInfo: + case MetadataUsageType.Type: + return GetMetadataUsageType(usage).Name; + + case MetadataUsageType.MethodDef: + var method = GetMetadataUsageMethod(usage); + return $"{method.DeclaringType.Name}.{method.Name}"; + + case MetadataUsageType.FieldInfo: + var fieldRef = Package.FieldRefs[usage.SourceIndex]; + var type = GetMetadataUsageType(usage); + var field = type.DeclaredFields.First(f => f.Index == type.Definition.fieldStart + fieldRef.fieldIndex); + return $"{type.Name}.{field.Name}"; + + case MetadataUsageType.StringLiteral: + return Package.StringLiterals[usage.SourceIndex]; + + case MetadataUsageType.MethodRef: + type = GetMetadataUsageType(usage); + method = GetMetadataUsageMethod(usage); + return $"{type.Name}.{method.Name}"; + } + throw new NotImplementedException("Unknown metadata usage type: " + usage.Type); + } + + // Get the type used in a metadata usage + public TypeInfo GetMetadataUsageType(MetadataUsage usage) => usage.Type switch { + MetadataUsageType.Type => TypesByReferenceIndex[usage.SourceIndex], + MetadataUsageType.TypeInfo => TypesByReferenceIndex[usage.SourceIndex], + MetadataUsageType.MethodDef => GetMetadataUsageMethod(usage).DeclaringType, + MetadataUsageType.FieldInfo => TypesByReferenceIndex[Package.FieldRefs[usage.SourceIndex].typeIndex], + MetadataUsageType.MethodRef => GetMetadataUsageMethod(usage).DeclaringType, + + _ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type") + }; + + // Get the method used in a metadata usage + public MethodBase GetMetadataUsageMethod(MetadataUsage usage) => usage.Type switch { + MetadataUsageType.MethodDef => MethodsByDefinitionIndex[usage.SourceIndex], + MetadataUsageType.MethodRef => GenericMethods[Package.MethodSpecs[usage.SourceIndex]], + _ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type") + }; + } } \ No newline at end of file