From 0808fe966a9fdca5bb4f294e1e60c161731fbe5a Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Thu, 16 Jul 2020 12:37:39 +0200 Subject: [PATCH] AppModel: IDA output integration; item groups --- Il2CppInspector.Common/Model/AppMethod.cs | 4 + Il2CppInspector.Common/Model/AppModel.cs | 51 ++++--- Il2CppInspector.Common/Model/AppType.cs | 4 + .../Outputs/CppScaffolding.cs | 8 +- .../Outputs/IDAPythonScript.cs | 128 ++++++++---------- 5 files changed, 103 insertions(+), 92 deletions(-) diff --git a/Il2CppInspector.Common/Model/AppMethod.cs b/Il2CppInspector.Common/Model/AppMethod.cs index d5a0a51..7dc5e6a 100644 --- a/Il2CppInspector.Common/Model/AppMethod.cs +++ b/Il2CppInspector.Common/Model/AppMethod.cs @@ -12,6 +12,10 @@ namespace Il2CppInspector.Model // Class that represents a composite IL/C++ method public class AppMethod { + // The logical group this method is part of + // This is purely for querying methods in related groups and has no bearing on the code + public string Group { get; set; } + // The corresponding C++ function pointer type public CppFnPtrType CppFnPtrType { get; internal set; } diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs index 8009ef7..3a45cb6 100644 --- a/Il2CppInspector.Common/Model/AppModel.cs +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -26,16 +26,16 @@ namespace Il2CppInspector.Model public CppCompilerType TargetCompiler { get; private set; } // The Unity version used to build the binary - public UnityVersion UnityVersion { get; set; } // TODO: Change to private set after integrating IDA output + public UnityVersion UnityVersion { get; private set; } // The Unity IL2CPP C++ headers for the binary // Use this for code output - public UnityHeader UnityHeader { get; set; } // TODO: Change to private set after integrating IDA output + public UnityHeader UnityHeader { 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; set; } // TODO: Change to private set after integrating IDA output + 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 @@ -53,6 +53,8 @@ namespace Il2CppInspector.Model // For il2cpp < 19, the key is the string literal ordinal instead of the address public Dictionary Strings = new Dictionary(); + public bool StringIndexesAreOrdinals => Package.MetadataUsages == null; + // The .NET type model for the application public TypeModel ILModel { get; } @@ -64,8 +66,7 @@ namespace Il2CppInspector.Model IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) CppTypeCollection).GetEnumerator(); // The C++ declaration generator for this binary - // TODO: Make this private once IDA output integration is completed - internal CppDeclarationGenerator declarationGenerator; + internal CppDeclarationGenerator declarationGenerator; // TODO: Make private when name integration completed // Convenience properties @@ -81,6 +82,16 @@ namespace Il2CppInspector.Model // The Unity header text including word size define public string UnityHeaderText => (WordSize == 32 ? "#define IS_32BIT\n" : "") + UnityHeader.GetHeaderText(); + // 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) { // Save .NET type model @@ -120,30 +131,30 @@ namespace Il2CppInspector.Model DependencyOrderedCppTypes = new List(); // Add method definitions to C++ type model - CppTypeCollection.SetGroup("type_definitions"); + Group = "types_from_methods"; foreach (var method in ILModel.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)); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); } // Add generic methods to C++ type model - CppTypeCollection.SetGroup("types_from_generics"); + Group = "types_from_generic_methods"; foreach (var method in ILModel.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)); + Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) {Group = Group}); } // Add metadata usage types to C++ type model // Not supported in il2cpp <19 - CppTypeCollection.SetGroup("types_from_usages"); + Group = "types_from_usages"; if (Package.MetadataUsages != null) foreach (var usage in Package.MetadataUsages) { @@ -168,12 +179,12 @@ namespace Il2CppInspector.Model Debug.Assert(type.IsPointer); // TODO: This should really be handled by CppDeclarationGenerator, and doesn't generate the full definition - var cppType = CppTypeCollection.Struct(declarationGenerator.TypeNamer.GetName(type) + "__TypeInfo"); + var cppType = CppTypeCollection.Struct(declarationGenerator.TypeNamer.GetName(type)); var cppObjectType = (CppComplexType) CppTypeCollection["Il2CppObject"]; cppType.Fields = new SortedDictionary>(cppObjectType.Fields); DependencyOrderedCppTypes.Add(cppType); - Types.Add(type, cppType, new AppType(type, cppType, cppClassPtr: address)); + Types.Add(type, cppType, new AppType(type, cppType, cppClassPtr: address) {Group = Group}); } else // Regular type definition @@ -181,7 +192,7 @@ namespace Il2CppInspector.Model 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)); + Types.Add(type, new AppType(type, null, cppTypeRefPtr: address) {Group = Group}); else // Regular type reference Types[type].TypeRefPtrAddress = address; @@ -232,11 +243,17 @@ namespace Il2CppInspector.Model // 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)); + Types.Add(type.ilType, type.referenceType, new AppType(type.ilType, type.referenceType, type.valueType) {Group = Group}); } - // Get all the types for a group - public IEnumerable GetTypeGroup(string groupName) => CppTypeCollection.GetTypeGroup(groupName); - public IEnumerable GetDependencyOrderedTypeGroup(string groupName) => DependencyOrderedCppTypes.Where(t => t.Group == groupName); + // 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); } } diff --git a/Il2CppInspector.Common/Model/AppType.cs b/Il2CppInspector.Common/Model/AppType.cs index da6f520..7f00b55 100644 --- a/Il2CppInspector.Common/Model/AppType.cs +++ b/Il2CppInspector.Common/Model/AppType.cs @@ -11,6 +11,10 @@ namespace Il2CppInspector.Model { public class AppType { + // The logical group this type is part of + // This is purely for querying types in related groups and has no bearing on the code + public string Group { get; set; } + // The corresponding C++ type definition which represents an instance of the object // This is derived from Il2CppObject // If the underlying .NET type is a struct (value type), this will return the boxed version diff --git a/Il2CppInspector.Common/Outputs/CppScaffolding.cs b/Il2CppInspector.Common/Outputs/CppScaffolding.cs index 11e6c6f..59844f4 100644 --- a/Il2CppInspector.Common/Outputs/CppScaffolding.cs +++ b/Il2CppInspector.Common/Outputs/CppScaffolding.cs @@ -37,9 +37,9 @@ namespace Il2CppInspector.Outputs writeCode("namespace app {"); writeLine(""); - writeTypesForGroup("Application type definitions", "type_definitions"); - writeTypesForGroup("Application generic method type usages", "types_from_generics"); - writeTypesForGroup("Application type usages", "types_from_usages"); + writeTypesForGroup("Application types from method calls", "types_from_methods"); + writeTypesForGroup("Application types from generic methods", "types_from_generic_methods"); + writeTypesForGroup("Application types from usages", "types_from_usages"); writeCode("}"); @@ -53,7 +53,7 @@ namespace Il2CppInspector.Outputs private void writeTypesForGroup(string header, string group) { writeSectionHeader(header); - foreach (var cppType in model.GetDependencyOrderedTypeGroup(group)) + foreach (var cppType in model.GetDependencyOrderedCppTypeGroup(group)) writeCode(cppType.ToString()); } diff --git a/Il2CppInspector.Common/Outputs/IDAPythonScript.cs b/Il2CppInspector.Common/Outputs/IDAPythonScript.cs index 232bec7..c3be3a7 100755 --- a/Il2CppInspector.Common/Outputs/IDAPythonScript.cs +++ b/Il2CppInspector.Common/Outputs/IDAPythonScript.cs @@ -16,22 +16,12 @@ namespace Il2CppInspector.Outputs { public class IDAPythonScript { - // TODO: Make this readonly when we've integrated with ApplicationModel - private AppModel model; + private readonly AppModel model; private StreamWriter writer; - // TODO: Remove when integrated with ApplicationModel - private CppDeclarationGenerator declGenerator; public IDAPythonScript(AppModel model) => this.model = model; public void WriteScriptToFile(string outputFile) { - // TODO: Integrate with ApplicationModel - use this hack so we can use CppDeclarationGenerator without disturbing the model passed in - var internalModel = new AppModel(model.ILModel); - internalModel.UnityVersion = model.UnityVersion; - internalModel.UnityHeader = model.UnityHeader; - internalModel.CppTypeCollection = CppTypeCollection.FromUnityHeaders(model.UnityHeader, model.WordSize); - model = internalModel; - declGenerator = new CppDeclarationGenerator(model); using var fs = new FileStream(outputFile, FileMode.Create); writer = new StreamWriter(fs, Encoding.UTF8); @@ -93,10 +83,12 @@ typedef __int64 int64_t; private void writeMethods() { writeSectionHeader("Method definitions"); - writeMethods(model.ILModel.MethodsByDefinitionIndex); + writeTypes(model.GetDependencyOrderedCppTypeGroup("types_from_methods")); + writeMethods(model.GetMethodGroup("types_from_methods")); writeSectionHeader("Constructed generic methods"); - writeMethods(model.ILModel.GenericMethods.Values); + writeTypes(model.GetDependencyOrderedCppTypeGroup("types_from_generic_methods")); + writeMethods(model.GetMethodGroup("types_from_generic_methods")); writeSectionHeader("Custom attributes generators"); foreach (var method in model.ILModel.AttributesByIndices.Values.Where(m => m.VirtualAddress.HasValue)) { @@ -113,13 +105,16 @@ typedef __int64 int64_t; } } - private void writeMethods(IEnumerable methods) { - foreach (var method in methods.Where(m => m.VirtualAddress.HasValue)) { - declGenerator.IncludeMethod(method); - writeDecls(declGenerator.GenerateRemainingTypeDeclarations()); - var address = method.VirtualAddress.Value.Start; - writeTypedName(address, declGenerator.GenerateMethodDeclaration(method).ToSignatureString(), declGenerator.GlobalNamer.GetName(method)); - writeComment(address, method); + private void writeTypes(IEnumerable types) { + foreach (var type in types) + writeDecls(type.ToString()); + } + + private void writeMethods(IEnumerable methods) { + foreach (var method in methods) { + var address = method.MethodCodeAddress; + writeTypedName(address, method.CppFnPtrType.ToSignatureString(), method.CppFnPtrType.Name); + writeComment(address, method.Method); } } @@ -129,59 +124,58 @@ typedef __int64 int64_t; } private void writeUsages() { - if (model.Package.MetadataUsages == null) { - /* 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. */ + + // String literals + + // For version < 19 + if (model.StringIndexesAreOrdinals) { var enumSrc = new StringBuilder(); enumSrc.Append("enum StringLiteralIndex {\n"); - for (int i = 0; i < model.Package.StringLiterals.Length; i++) { - var str = model.Package.StringLiterals[i]; - enumSrc.Append($" STRINGLITERAL_{i}_{stringToIdentifier(str)},\n"); - } + foreach (var str in model.Strings) + enumSrc.Append($" STRINGLITERAL_{str.Key}_{stringToIdentifier(str.Value)},\n"); enumSrc.Append("};\n"); writeDecls(enumSrc.ToString()); - - return; } - - var stringType = declGenerator.AsCType(model.ILModel.TypesByFullName["System.String"]); - foreach (var usage in model.Package.MetadataUsages) { - var address = usage.VirtualAddress; - string name; - switch (usage.Type) { - case MetadataUsageType.StringLiteral: - var str = model.ILModel.GetMetadataUsageName(usage); - writeTypedName(address, stringType.ToString(), $"StringLiteral_{stringToIdentifier(str)}"); - writeComment(address, str); - break; - case MetadataUsageType.Type: - case MetadataUsageType.TypeInfo: - var type = model.ILModel.GetMetadataUsageType(usage); - declGenerator.IncludeType(type); - writeDecls(declGenerator.GenerateRemainingTypeDeclarations()); + // For version >= 19 + else { + var stringType = model.CppTypeCollection.GetType("String *"); - name = declGenerator.TypeNamer.GetName(type); - if (usage.Type == MetadataUsageType.TypeInfo) - writeTypedName(address, $"struct {name}__Class *", $"{name}__TypeInfo"); - else - writeTypedName(address, $"struct Il2CppType *", $"{name}__TypeRef"); - writeComment(address, type.CSharpName); - break; - case MetadataUsageType.MethodDef: - case MetadataUsageType.MethodRef: - var method = model.ILModel.GetMetadataUsageMethod(usage); - declGenerator.IncludeMethod(method); - writeDecls(declGenerator.GenerateRemainingTypeDeclarations()); - - name = declGenerator.GlobalNamer.GetName(method); - writeTypedName(address, "struct MethodInfo *", $"{name}__MethodInfo"); - writeComment(address, method); - break; + foreach (var str in model.Strings) { + writeTypedName(str.Key, stringType.ToString(), $"StringLiteral_{stringToIdentifier(str.Value)}"); + writeComment(str.Key, str.Value); } } + + // Metadata usage C++ type dependencies + var usageCppTypes = model.GetDependencyOrderedCppTypeGroup("types_from_usages"); + writeTypes(usageCppTypes); + + // Definition and reference addresses for all types from metadata usages + foreach (var type in model.Types.Values) { + // A type may have no addresses, for example an unreferenced array type + + // Value types must not used the boxed definition + var name = type.CppValueType?.Name ?? type.CppType?.Name ?? model.declarationGenerator.TypeNamer.GetName(type.ILType); + + if (type.TypeClassAddress != 0xffffffff_ffffffff) { + writeTypedName(type.TypeClassAddress, $"struct {name}__Class *", $"{name}__TypeInfo"); + writeComment(type.TypeClassAddress, type.ILType.CSharpName); + } + + if (type.TypeRefPtrAddress != 0xffffffff_ffffffff) { + // A generic type definition does not have any direct C++ types, but may have a reference + writeTypedName(type.TypeRefPtrAddress, "struct Il2CppType *", $"{name}__TypeRef"); + writeComment(type.TypeRefPtrAddress, type.ILType.CSharpName); + } + } + + // Metedata usage methods + foreach (var method in model.Methods.Values.Where(m => m.MethodInfoPtrAddress != 0xffffffff_ffffffff)) { + writeTypedName(method.MethodInfoPtrAddress, "struct MethodInfo *", $"{method.CppFnPtrType.Name}__MethodInfo"); + writeComment(method.MethodInfoPtrAddress, method.Method); + } } private void writeFunctions() { @@ -225,14 +219,6 @@ typedef __int64 int64_t; writeLine("idc.parse_decls('''" + declString + "''')"); } - // TODO: Temporary compatibility function, remove when integrated with ApplicationModel - private void writeDecls(List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, - CppComplexType fieldsType, CppComplexType vtableType, CppComplexType staticsType)> types) - => writeDecls(string.Join("\n", - types.SelectMany(t => new List {t.vtableType, t.staticsType, t.fieldsType, t.valueType, t.referenceType}) - .Where(t => t != null) - .Select(t => t.ToString()))); - private void writeName(ulong address, string name) { writeLine($"SetName({address.ToAddressString()}, r'{name.ToEscapedString()}')"); }