diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs index 550123d..83db2c8 100644 --- a/Il2CppInspector.Common/Model/AppModel.cs +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -1,359 +1,369 @@ -/* - Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Aron.Weiler; -using Il2CppInspector.Cpp; -using Il2CppInspector.Cpp.UnityHeaders; -using Il2CppInspector.Reflection; - -namespace Il2CppInspector.Model -{ - // Class that represents the entire structure of the IL2CPP binary realized as C++ types and code, - // correlated with .NET types where applicable. Primarily designed to enable automated static analysis of disassembly code. - public class AppModel : IEnumerable - { - // The C++ compiler to target - public CppCompilerType TargetCompiler { get; private set; } - - // The Unity version used to build the binary - public UnityVersion UnityVersion { get; private set; } - - // The Unity IL2CPP C++ headers for the binary - // Use this for code output - public UnityHeaders UnityHeaders { get; private set; } - - // All of the C++ types used in the application including Unity internal types - // NOTE: This is for querying individual types for static analysis - // To generate code output, use DependencyOrderedCppTypes - public CppTypeCollection CppTypeCollection { get; private set; } - - // All of the C++ types used in the application (.NET type translations only) - // The types are ordered to enable the production of code output without forward dependencies - public List DependencyOrderedCppTypes { get; private set; } - - // Composite mapping of all the .NET methods in the IL2CPP binary - public MultiKeyDictionary Methods { get; } = new MultiKeyDictionary(); - - // Composite mapping of all the .NET types in the IL2CPP binary - public MultiKeyDictionary Types { get; } = new MultiKeyDictionary(); - - // All of the string literals in the IL2CPP binary - // Note: Does not include string literals from global-metadata.dat - // Note: The virtual addresses are of String* (VAs of the pointer to String*) objects, not the strings themselves - // For il2cpp < 19, the key is the string literal ordinal instead of the address - public Dictionary Strings { get; } = new Dictionary(); - - public Dictionary Fields { get; } = new Dictionary(); - - public bool StringIndexesAreOrdinals => Package.Version < 19; - - // The .NET type model for the application - public TypeModel TypeModel { get; } - - // All of the exports (including function exports) for the binary - public List Exports { get; } - - // All of the symbols representing function names, signatures or type/field names or address labels for the binary - public Dictionary Symbols { get; } - - // All of the API exports defined in the IL2CPP binary - // Note: Multiple export names may have the same virtual address - public MultiKeyDictionary AvailableAPIs { get; } = new MultiKeyDictionary(); - - // Delegated C++ types iterator - public IEnumerator GetEnumerator() => CppTypeCollection.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) CppTypeCollection).GetEnumerator(); - - // The C++ declaration generator for this binary - private CppDeclarationGenerator declarationGenerator; - - // Convenience properties - - // The word size of the binary in bits - public int WordSizeBits => Image.Bits; - - // The word size of the binary in bytes - public int WordSizeBytes => WordSizeBits / 8; - - // The binary image - public IFileFormatStream Image => Package.BinaryImage; - - // The IL2CPP package for this application - public Il2CppInspector Package => TypeModel.Package; - - // The compiler used to build the binary - public CppCompilerType SourceCompiler => declarationGenerator.InheritanceStyle; - - // The group that the next added type(s) will be placed in - private string group = string.Empty; - private string Group { - get => group; - set { - group = value; - CppTypeCollection.SetGroup(group); - } - } - - // Initialize - public AppModel(TypeModel model, bool makeDefaultBuild = true) { - // Save .NET type model - TypeModel = model; - - // Get addresses of all exports - Exports = Image.GetExports()?.ToList() ?? new List(); - - // Get all symbols - Symbols = Image.GetSymbolTable(); - - // Build if requested - if (makeDefaultBuild) - Build(); - } - - // Build the application model targeting a specific version of Unity and C++ compiler - // If no Unity version is specified, it will be guessed from the contents of the IL2CPP binary - // The C++ compiler used to actually build the original IL2CPP binary will always be guessed based on the binary file format - // (via the constructor of CppDeclarationGenerator, in InheritanceStyle) - // If no target C++ compiler is specified, it will be set to match the one assumed to have been used to compile the binary - public AppModel Build(UnityVersion unityVersion = null, CppCompilerType compiler = CppCompilerType.BinaryFormat, bool silent = false) { - // Don't re-build if not necessary - var targetCompiler = compiler == CppCompilerType.BinaryFormat ? CppCompiler.GuessFromImage(Image) : compiler; - if (UnityVersion == unityVersion && TargetCompiler == targetCompiler) - return this; - - // Silent operation if requested - var stdout = Console.Out; - if (silent) - Console.SetOut(new StreamWriter(Stream.Null)); - - // Reset in case this is not the first build - Methods.Clear(); - Types.Clear(); - Strings.Clear(); - - // Set target compiler - TargetCompiler = targetCompiler; - - // Determine Unity version and get headers - UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last(); - UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min; - - Console.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})"); - - // Check for matching metadata and binary versions - if (UnityHeaders.MetadataVersion != Image.Version) { - Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" + - $" does not match metadata version {Image.Version}."); - } - - // Initialize declaration generator to process every type in the binary - declarationGenerator = new CppDeclarationGenerator(this); - - // Start creation of type model by parsing all of the Unity IL2CPP headers - // Calling declarationGenerator.GenerateRemainingTypeDeclarations() below will automatically add to this collection - CppTypeCollection = CppTypeCollection.FromUnityHeaders(UnityHeaders, declarationGenerator); - - // Populate AvailableAPIs with actual API symbols from Binary.GetAPIExports() and their matching header signatures - // NOTE: This will only be filled with exports that actually exist in both the binary and the API header, - // and have a mappable address. This prevents exceptions when cross-querying the header and binary APIs. - var exports = TypeModel.Package.Binary.APIExports - .Where(e => CppTypeCollection.TypedefAliases.ContainsKey(e.Key)) - .Select(e => new { - VirtualAddress = e.Value, - FnPtr = CppTypeCollection.TypedefAliases[e.Key] - }); - - AvailableAPIs.Clear(); - foreach (var export in exports) - AvailableAPIs.Add(export.FnPtr.Name, export.VirtualAddress, (CppFnPtrType) export.FnPtr); - - // Initialize ordered type list for code output - DependencyOrderedCppTypes = new List(); - - // Add method definitions and types used by them to C++ type model - Group = "types_from_methods"; - - foreach (var method in TypeModel.MethodsByDefinitionIndex.Where(m => m.VirtualAddress.HasValue)) { - declarationGenerator.IncludeMethod(method); - AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); - - var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); - Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); - } - - // Add generic methods definitions and types used by them to C++ type model - Group = "types_from_generic_methods"; - - foreach (var method in TypeModel.GenericMethods.Values.Where(m => m.VirtualAddress.HasValue)) { - declarationGenerator.IncludeMethod(method); - AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); - - var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); - Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); - } - - // Add types from metadata usage list to C++ type model - // Not supported in il2cpp <19 - Group = "types_from_usages"; - - if (Package.MetadataUsages != null) - foreach (var usage in Package.MetadataUsages) { - var address = usage.VirtualAddress; - - switch (usage.Type) { - case MetadataUsageType.StringLiteral: - var str = TypeModel.GetMetadataUsageName(usage); - Strings.Add(address, str); - break; - - case MetadataUsageType.Type: - case MetadataUsageType.TypeInfo: - var type = TypeModel.GetMetadataUsageType(usage); - declarationGenerator.IncludeType(type); - AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); - - if (usage.Type == MetadataUsageType.TypeInfo) - // Regular type definition - Types[type].TypeClassAddress = address; - - else if (!Types.ContainsKey(type)) - // Generic type definition has no associated C++ type, therefore no dictionary sub-key - Types.Add(type, new AppType(type, null, cppTypeRefPtr: address) {Group = Group}); - else - // Regular type reference - Types[type].TypeRefPtrAddress = address; - break; - case MetadataUsageType.MethodDef: - case MetadataUsageType.MethodRef: - var method = TypeModel.GetMetadataUsageMethod(usage); - declarationGenerator.IncludeMethod(method); - AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); - - // Any method here SHOULD already be in the Methods list - // but we have seen one example where this is not the case for a MethodDef - if (!Methods.ContainsKey(method)) { - var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); - Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); - } - Methods[method].MethodInfoPtrAddress = address; - break; - - case MetadataUsageType.FieldInfo: - if (usage.SourceIndex > TypeModel.Package.Metadata.FieldRefs.Length) - break; - - var fieldRef = TypeModel.Package.FieldRefs[usage.SourceIndex]; - var fieldType = TypeModel.GetMetadataUsageType(usage); - var field = fieldType.DeclaredFields.First(f => f.Index == fieldType.Definition.fieldStart + fieldRef.fieldIndex); - Fields.Add(usage.VirtualAddress, $"{fieldType.Name}.{field.Name}".ToCIdentifier()); - break; - } - } - - // Add string literals for metadata <19 to the model - if (Package.Version < 19) { - /* Version < 19 calls `il2cpp_codegen_string_literal_from_index` to get string literals. - * Unfortunately, metadata references are just loose globals in Il2CppMetadataUsage.cpp - * so we can't automatically name those. Next best thing is to define an enum for the strings. */ - for (var i = 0; i < Package.StringLiterals.Length; i++) { - var str = Package.StringLiterals[i]; - Strings.Add((ulong) i, str); - } - } - - // Find unused concrete value types - var usedTypes = Types.Values.Select(t => t.Type); - var unusedTypes = TypeModel.Types.Except(usedTypes); - var unusedConcreteTypes = unusedTypes.Where(t => !t.IsGenericType && !t.IsGenericParameter - && !t.IsByRef && !t.IsPointer && !t.IsArray && !t.IsAbstract && t.Name != ""); - - Group = "unused_concrete_types"; - - foreach (var type in unusedConcreteTypes) - declarationGenerator.IncludeType(type); - AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); - - // Restore stdout - Console.SetOut(stdout); - - // Plugin hook to post-process model - PluginHooks.PostProcessAppModel(this); - - // This is to allow this method to be chained after a new expression - return this; - } - - private void AddTypes(List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, - CppComplexType fieldsType, CppComplexType vtableType, CppComplexType staticsType)> types) { - - // Add types to dependency-ordered list - foreach (var type in types) { - if (type.vtableType != null) - DependencyOrderedCppTypes.Add(type.vtableType); - if (type.staticsType != null) - DependencyOrderedCppTypes.Add(type.staticsType); - - if (type.fieldsType != null) - DependencyOrderedCppTypes.Add(type.fieldsType); - if (type.valueType != null) - DependencyOrderedCppTypes.Add(type.valueType); - - DependencyOrderedCppTypes.Add(type.referenceType); - } - - // Create composite types - foreach (var type in types) - if (!Types.ContainsKey(type.ilType)) - Types.Add(type.ilType, type.referenceType, new AppType(type.ilType, type.referenceType, type.valueType) {Group = Group}); - } - - // Get all the C++ types for a group - public IEnumerable GetCppTypeGroup(string groupName) => CppTypeCollection.GetTypeGroup(groupName); - public IEnumerable GetDependencyOrderedCppTypeGroup(string groupName) => DependencyOrderedCppTypes.Where(t => t.Group == groupName); - - // Get all the composite types for a group - public IEnumerable GetTypeGroup(string groupName) => Types.Values.Where(t => t.Group == groupName); - - // Get all the composite methods for a group - public IEnumerable GetMethodGroup(string groupName) => Methods.Values.Where(m => m.Group == groupName); - - // Static analysis tools - - // Get the address map for the model - // This takes a while to construct so we only build it if requested - private AddressMap addressMap; - public AddressMap GetAddressMap() { - if (addressMap == null) - addressMap = new AddressMap(this); - return addressMap; - } - - // Get the byte offset in Il2CppClass for this app's Unity version to the vtable - public int GetVTableOffset() => CppTypeCollection.GetComplexType("Il2CppClass")["vtable"].OffsetBytes; - - // Get the vtable method index from an offset from the start of the Il2CppClass - // Unity 5.3.0-5.3.5 uses MethodInfo** - a pointer to a list of MethodInfo pointers - // Unity 5.3.6-5.4.6 uses VirtualInvokeData* - a pointer to an array of VirtualInvokeData - // Unity 5.5.0 onwards moves the VirtualInvokeData to the end of Il2CppClass and makes it an array - // We only include support for Unity 5.5.0 onwards - public int GetVTableIndexFromClassOffset(int offset) { - if (UnityVersion.CompareTo("5.5.0") < 0) - throw new NotImplementedException("VTable index resolution is only supported for Unity 5.5.0 and later"); - - // VirtualInvokeData has two members. The first is the jump target. - // Il2CppMethodPointer methodPtr; - // const MethodInfo* method; - var offsetIntoVTable = offset - GetVTableOffset(); - var vidSize = WordSizeBits == 32? 8 : 16; - return offsetIntoVTable / vidSize; - } - } -} +/* + Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Aron.Weiler; +using Il2CppInspector.Cpp; +using Il2CppInspector.Cpp.UnityHeaders; +using Il2CppInspector.Reflection; + +namespace Il2CppInspector.Model +{ + // Class that represents the entire structure of the IL2CPP binary realized as C++ types and code, + // correlated with .NET types where applicable. Primarily designed to enable automated static analysis of disassembly code. + public class AppModel : IEnumerable + { + // The C++ compiler to target + public CppCompilerType TargetCompiler { get; private set; } + + // The Unity version used to build the binary + public UnityVersion UnityVersion { get; private set; } + + // The Unity IL2CPP C++ headers for the binary + // Use this for code output + public UnityHeaders UnityHeaders { get; private set; } + + // All of the C++ types used in the application including Unity internal types + // NOTE: This is for querying individual types for static analysis + // To generate code output, use DependencyOrderedCppTypes + public CppTypeCollection CppTypeCollection { get; private set; } + + // All of the C++ types used in the application (.NET type translations only) + // The types are ordered to enable the production of code output without forward dependencies + public List DependencyOrderedCppTypes { get; private set; } + + // Composite mapping of all the .NET methods in the IL2CPP binary + public MultiKeyDictionary Methods { get; } = new MultiKeyDictionary(); + + // Composite mapping of all the .NET types in the IL2CPP binary + public MultiKeyDictionary Types { get; } = new MultiKeyDictionary(); + + // All of the string literals in the IL2CPP binary + // Note: Does not include string literals from global-metadata.dat + // Note: The virtual addresses are of String* (VAs of the pointer to String*) objects, not the strings themselves + // For il2cpp < 19, the key is the string literal ordinal instead of the address + public Dictionary Strings { get; } = new Dictionary(); + + public Dictionary Fields { get; } = new Dictionary(); + + public bool StringIndexesAreOrdinals => Package.Version < 19; + + // The .NET type model for the application + public TypeModel TypeModel { get; } + + // All of the exports (including function exports) for the binary + public List Exports { get; } + + // All of the symbols representing function names, signatures or type/field names or address labels for the binary + public Dictionary Symbols { get; } + + // All of the API exports defined in the IL2CPP binary + // Note: Multiple export names may have the same virtual address + public MultiKeyDictionary AvailableAPIs { get; } = new MultiKeyDictionary(); + + // Delegated C++ types iterator + public IEnumerator GetEnumerator() => CppTypeCollection.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) CppTypeCollection).GetEnumerator(); + + // The C++ declaration generator for this binary + private CppDeclarationGenerator declarationGenerator; + + // Convenience properties + + // The word size of the binary in bits + public int WordSizeBits => Image.Bits; + + // The word size of the binary in bytes + public int WordSizeBytes => WordSizeBits / 8; + + // The binary image + public IFileFormatStream Image => Package.BinaryImage; + + // The IL2CPP package for this application + public Il2CppInspector Package => TypeModel.Package; + + // The compiler used to build the binary + public CppCompilerType SourceCompiler => declarationGenerator.InheritanceStyle; + + // The group that the next added type(s) will be placed in + private string group = string.Empty; + private string Group { + get => group; + set { + group = value; + CppTypeCollection.SetGroup(group); + } + } + + // Initialize + public AppModel(TypeModel model, bool makeDefaultBuild = true) { + // Save .NET type model + TypeModel = model; + + // Get addresses of all exports + Exports = Image.GetExports()?.ToList() ?? new List(); + + // Get all symbols + Symbols = Image.GetSymbolTable(); + + // Build if requested + if (makeDefaultBuild) + Build(); + } + + // Build the application model targeting a specific version of Unity and C++ compiler + // If no Unity version is specified, it will be guessed from the contents of the IL2CPP binary + // The C++ compiler used to actually build the original IL2CPP binary will always be guessed based on the binary file format + // (via the constructor of CppDeclarationGenerator, in InheritanceStyle) + // If no target C++ compiler is specified, it will be set to match the one assumed to have been used to compile the binary + public AppModel Build(UnityVersion unityVersion = null, CppCompilerType compiler = CppCompilerType.BinaryFormat, bool silent = false) { + // Don't re-build if not necessary + var targetCompiler = compiler == CppCompilerType.BinaryFormat ? CppCompiler.GuessFromImage(Image) : compiler; + if (UnityVersion == unityVersion && TargetCompiler == targetCompiler) + return this; + + // Silent operation if requested + var stdout = Console.Out; + if (silent) + Console.SetOut(new StreamWriter(Stream.Null)); + + // Reset in case this is not the first build + Methods.Clear(); + Types.Clear(); + Strings.Clear(); + + // Set target compiler + TargetCompiler = targetCompiler; + + // Determine Unity version and get headers + UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last(); + UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min; + + Console.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})"); + + // Check for matching metadata and binary versions + if (UnityHeaders.MetadataVersion != Image.Version) { + Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" + + $" does not match metadata version {Image.Version}."); + } + + // Initialize declaration generator to process every type in the binary + declarationGenerator = new CppDeclarationGenerator(this); + + // Start creation of type model by parsing all of the Unity IL2CPP headers + // Calling declarationGenerator.GenerateRemainingTypeDeclarations() below will automatically add to this collection + CppTypeCollection = CppTypeCollection.FromUnityHeaders(UnityHeaders, declarationGenerator); + + // Populate AvailableAPIs with actual API symbols from Binary.GetAPIExports() and their matching header signatures + // NOTE: This will only be filled with exports that actually exist in both the binary and the API header, + // and have a mappable address. This prevents exceptions when cross-querying the header and binary APIs. + var exports = TypeModel.Package.Binary.APIExports + .Where(e => CppTypeCollection.TypedefAliases.ContainsKey(e.Key)) + .Select(e => new { + VirtualAddress = e.Value, + FnPtr = CppTypeCollection.TypedefAliases[e.Key] + }); + + AvailableAPIs.Clear(); + foreach (var export in exports) + AvailableAPIs.Add(export.FnPtr.Name, export.VirtualAddress, (CppFnPtrType) export.FnPtr); + + // Initialize ordered type list for code output + DependencyOrderedCppTypes = new List(); + + // Add method definitions and types used by them to C++ type model + Group = "types_from_methods"; + + foreach (var method in TypeModel.MethodsByDefinitionIndex.Where(m => m.VirtualAddress.HasValue)) { + declarationGenerator.IncludeMethod(method); + AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); + + var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); + } + + // Add generic methods definitions and types used by them to C++ type model + Group = "types_from_generic_methods"; + + foreach (var method in TypeModel.GenericMethods.Values.Where(m => m.VirtualAddress.HasValue)) { + declarationGenerator.IncludeMethod(method); + AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); + + var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); + } + + // Add types from metadata usage list to C++ type model + // Not supported in il2cpp <19 + Group = "types_from_usages"; + + if (Package.MetadataUsages != null) + foreach (var usage in Package.MetadataUsages) { + var address = usage.VirtualAddress; + + switch (usage.Type) { + case MetadataUsageType.StringLiteral: + //if (usage.SourceIndex >= TypeModel.Package.Metadata.StringLiterals.Length) + // break; + + var str = TypeModel.GetMetadataUsageName(usage); + Strings.Add(address, str); + break; + + case MetadataUsageType.Type: + case MetadataUsageType.TypeInfo: + //if (usage.SourceIndex >= TypeModel.TypesByReferenceIndex.Length) + // break; + + var type = TypeModel.GetMetadataUsageType(usage); + declarationGenerator.IncludeType(type); + AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); + + if (usage.Type == MetadataUsageType.TypeInfo) + // Regular type definition + Types[type].TypeClassAddress = address; + + else if (!Types.ContainsKey(type)) + // Generic type definition has no associated C++ type, therefore no dictionary sub-key + Types.Add(type, new AppType(type, null, cppTypeRefPtr: address) { Group = Group }); + else + // Regular type reference + Types[type].TypeRefPtrAddress = address; + break; + case MetadataUsageType.MethodDef: + case MetadataUsageType.MethodRef: + //if (usage.SourceIndex > TypeModel.Package.Metadata.Methods.Length) + // break; + + var method = TypeModel.GetMetadataUsageMethod(usage); + declarationGenerator.IncludeMethod(method); + AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); + + // Any method here SHOULD already be in the Methods list + // but we have seen one example where this is not the case for a MethodDef + if (!Methods.ContainsKey(method)) + { + var fnPtr = declarationGenerator.GenerateMethodDeclaration(method); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) { Group = Group }); + } + Methods[method].MethodInfoPtrAddress = address; + break; + + case MetadataUsageType.FieldInfo: + if (usage.SourceIndex > TypeModel.Package.Metadata.FieldRefs.Length) + break; + + var fieldRef = TypeModel.Package.FieldRefs[usage.SourceIndex]; + var fieldType = TypeModel.GetMetadataUsageType(usage); + var field = fieldType.DeclaredFields.First(f => f.Index == fieldType.Definition.fieldStart + fieldRef.fieldIndex); + Fields.Add(usage.VirtualAddress, $"{fieldType.Name}.{field.Name}".ToCIdentifier()); + break; + } + } + + // Add string literals for metadata <19 to the model + if (Package.Version < 19) { + /* Version < 19 calls `il2cpp_codegen_string_literal_from_index` to get string literals. + * Unfortunately, metadata references are just loose globals in Il2CppMetadataUsage.cpp + * so we can't automatically name those. Next best thing is to define an enum for the strings. */ + for (var i = 0; i < Package.StringLiterals.Length; i++) { + var str = Package.StringLiterals[i]; + Strings.Add((ulong) i, str); + } + } + + // Find unused concrete value types + var usedTypes = Types.Values.Select(t => t.Type); + var unusedTypes = TypeModel.Types.Except(usedTypes); + var unusedConcreteTypes = unusedTypes.Where(t => !t.IsGenericType && !t.IsGenericParameter + && !t.IsByRef && !t.IsPointer && !t.IsArray && !t.IsAbstract && t.Name != ""); + + Group = "unused_concrete_types"; + + foreach (var type in unusedConcreteTypes) + declarationGenerator.IncludeType(type); + AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); + + // Restore stdout + Console.SetOut(stdout); + + // Plugin hook to post-process model + PluginHooks.PostProcessAppModel(this); + + // This is to allow this method to be chained after a new expression + return this; + } + + private void AddTypes(List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, + CppComplexType fieldsType, CppComplexType vtableType, CppComplexType staticsType)> types) { + + // Add types to dependency-ordered list + foreach (var type in types) { + if (type.vtableType != null) + DependencyOrderedCppTypes.Add(type.vtableType); + if (type.staticsType != null) + DependencyOrderedCppTypes.Add(type.staticsType); + + if (type.fieldsType != null) + DependencyOrderedCppTypes.Add(type.fieldsType); + if (type.valueType != null) + DependencyOrderedCppTypes.Add(type.valueType); + + DependencyOrderedCppTypes.Add(type.referenceType); + } + + // Create composite types + foreach (var type in types) + if (!Types.ContainsKey(type.ilType)) + Types.Add(type.ilType, type.referenceType, new AppType(type.ilType, type.referenceType, type.valueType) {Group = Group}); + } + + // Get all the C++ types for a group + public IEnumerable GetCppTypeGroup(string groupName) => CppTypeCollection.GetTypeGroup(groupName); + public IEnumerable GetDependencyOrderedCppTypeGroup(string groupName) => DependencyOrderedCppTypes.Where(t => t.Group == groupName); + + // Get all the composite types for a group + public IEnumerable GetTypeGroup(string groupName) => Types.Values.Where(t => t.Group == groupName); + + // Get all the composite methods for a group + public IEnumerable GetMethodGroup(string groupName) => Methods.Values.Where(m => m.Group == groupName); + + // Static analysis tools + + // Get the address map for the model + // This takes a while to construct so we only build it if requested + private AddressMap addressMap; + public AddressMap GetAddressMap() { + if (addressMap == null) + addressMap = new AddressMap(this); + return addressMap; + } + + // Get the byte offset in Il2CppClass for this app's Unity version to the vtable + public int GetVTableOffset() => CppTypeCollection.GetComplexType("Il2CppClass")["vtable"].OffsetBytes; + + // Get the vtable method index from an offset from the start of the Il2CppClass + // Unity 5.3.0-5.3.5 uses MethodInfo** - a pointer to a list of MethodInfo pointers + // Unity 5.3.6-5.4.6 uses VirtualInvokeData* - a pointer to an array of VirtualInvokeData + // Unity 5.5.0 onwards moves the VirtualInvokeData to the end of Il2CppClass and makes it an array + // We only include support for Unity 5.5.0 onwards + public int GetVTableIndexFromClassOffset(int offset) { + if (UnityVersion.CompareTo("5.5.0") < 0) + throw new NotImplementedException("VTable index resolution is only supported for Unity 5.5.0 and later"); + + // VirtualInvokeData has two members. The first is the jump target. + // Il2CppMethodPointer methodPtr; + // const MethodInfo* method; + var offsetIntoVTable = offset - GetVTableOffset(); + var vidSize = WordSizeBits == 32? 8 : 16; + return offsetIntoVTable / vidSize; + } + } +} diff --git a/Il2CppInspector.Common/Reflection/TypeModel.cs b/Il2CppInspector.Common/Reflection/TypeModel.cs index 482a846..01c2cd6 100644 --- a/Il2CppInspector.Common/Reflection/TypeModel.cs +++ b/Il2CppInspector.Common/Reflection/TypeModel.cs @@ -36,8 +36,18 @@ namespace Il2CppInspector.Reflection public Dictionary TypesByFullName { get; } = new Dictionary(); // Every type - public IEnumerable Types => TypesByDefinitionIndex.Concat(TypesByReferenceIndex) - .Concat(GenericMethods.Values.Select(m => m.DeclaringType)).Distinct().Where(t => t != null); + 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; }