diff --git a/Il2CppInspector.CLI/Program.cs b/Il2CppInspector.CLI/Program.cs index 10f29b7..e454c73 100644 --- a/Il2CppInspector.CLI/Program.cs +++ b/Il2CppInspector.CLI/Program.cs @@ -8,9 +8,10 @@ using System.IO; using System.Linq; using CommandLine; using Il2CppInspector.Cpp; -using Il2CppInspector.Reflection; -using Il2CppInspector.Outputs; using Il2CppInspector.Cpp.UnityHeaders; +using Il2CppInspector.Model; +using Il2CppInspector.Outputs; +using Il2CppInspector.Reflection; namespace Il2CppInspector.CLI { @@ -70,8 +71,8 @@ namespace Il2CppInspector.CLI [Option('j', "project", Required = false, HelpText = "Create a Visual Studio solution and projects. Implies --layout tree, --must-compile and --separate-attributes")] public bool CreateSolution { get; set; } - [Option("cpp-compiler", Required = false, HelpText = "Compiler to make C++ output compatible with (MSVC or GCC); selects based on binary executable type by default", Default = Cpp.CppCompiler.Type.BinaryFormat)] - public CppCompiler.Type CppCompiler { get; set; } + [Option("cpp-compiler", Required = false, HelpText = "Compiler to target for C++ output (MSVC or GCC); selects based on binary executable type by default", Default = CppCompilerType.BinaryFormat)] + public CppCompilerType CppCompiler { get; set; } [Option("unity-path", Required = false, HelpText = "Path to Unity editor (when using --project). Wildcards select last matching folder in alphanumeric order", Default = @"C:\Program Files\Unity\Hub\Editor\*")] public string UnityPath { get; set; } @@ -182,9 +183,14 @@ namespace Il2CppInspector.CLI int i = 0; foreach (var il2cpp in il2cppInspectors) { // Create model - Il2CppModel model; - using (new Benchmark("Create type model")) - model = new Il2CppModel(il2cpp); + TypeModel model; + using (new Benchmark("Create .NET type model")) + model = new TypeModel(il2cpp); + + AppModel appModel; + using (new Benchmark("Create C++ application model")) { + appModel = new AppModel(model).Build(options.UnityVersion, options.CppCompiler); + } // C# signatures output using (new Benchmark("Generate C# code")) { @@ -243,19 +249,12 @@ namespace Il2CppInspector.CLI // IDA Python script output using (new Benchmark("Generate IDAPython script")) { - var idaWriter = new IDAPythonScript(model) { - UnityVersion = options.UnityVersion - }; - idaWriter.WriteScriptToFile(options.PythonOutFile); + new IDAPythonScript(appModel).WriteScriptToFile(options.PythonOutFile); } // C++ output using (new Benchmark("Generate C++ code")) { - var cppWriter = new CppScaffolding(model) { - UnityVersion = options.UnityVersion, - Compiler = options.CppCompiler - }; - cppWriter.WriteCppToFile(options.CppOutFile); + new CppScaffolding(appModel).WriteCppToFile(options.CppOutFile); } } diff --git a/Il2CppInspector.Common/Cpp/CppApplicationModel.cs b/Il2CppInspector.Common/Cpp/CppApplicationModel.cs deleted file mode 100644 index 9d458c1..0000000 --- a/Il2CppInspector.Common/Cpp/CppApplicationModel.cs +++ /dev/null @@ -1,69 +0,0 @@ -/* - Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using Il2CppInspector.Cpp.UnityHeaders; -using Il2CppInspector.Reflection; - -namespace Il2CppInspector.Cpp -{ - // Class that represents a composite IL/C++ type - public class CppModelType - { - // The corresponding C++ type definition which represents an instance of the object - // If a .NET type, this is derived from Il2CppObject, otherwise it can be any type - // If the underlying .NET type is a struct (value type), this will return the boxed version - public CppType CppType { get; internal set; } - - // For an underlying .NET type which is a struct (value type), the unboxed type, otherwise null - public CppType CppValueType { get; internal set; } - - // The type in the model this object represents (for .NET types, otherwise null) - public TypeInfo ILType { get; internal set; } - - // The VA of the Il2CppClass object which defines this type (for .NET types, otherwise zero) - public uint VirtualAddress { get; internal set; } - } - - // 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 CppApplicationModel - { - public CppCompiler.Type Compiler { get; } - public UnityVersion UnityVersion { get; } - public CppTypes Types { get; } - public Il2CppModel ILModel { get; } - public List Exports { get; } - public int WordSize => ILModel.Package.BinaryImage.Bits; - - public CppApplicationModel(Il2CppModel model, UnityVersion unityVersion = null, CppCompiler.Type compiler = CppCompiler.Type.BinaryFormat) { - // Set key properties - Compiler = compiler == CppCompiler.Type.BinaryFormat ? CppCompiler.GuessFromImage(model.Package.BinaryImage) : compiler; - - var unityHeader = unityVersion != null ? UnityHeader.GetHeaderForVersion(unityVersion) : UnityHeader.GuessHeadersForModel(model)[0]; - - UnityVersion = unityVersion ?? unityHeader.MinVersion; - ILModel = model; - - // Check for matching metadata and binary versions - if (unityHeader.MetadataVersion != model.Package.BinaryImage.Version) { - Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {unityHeader.MetadataVersion})" + - $" does not match metadata version {model.Package.BinaryImage.Version}."); - } - - // Get addresses of IL2CPP API function exports - Exports = model.Package.Binary.Image.GetExports().ToList(); - - // Start creation of type model by parsing all of the Unity IL2CPP headers - Types = CppTypes.FromUnityHeaders(unityHeader, WordSize); - - // TODO: Process every type in the binary - //var decl = new CppDeclarationGenerator(this); - } - } -} diff --git a/Il2CppInspector.Common/Cpp/CppCompiler.cs b/Il2CppInspector.Common/Cpp/CppCompiler.cs deleted file mode 100644 index cd79797..0000000 --- a/Il2CppInspector.Common/Cpp/CppCompiler.cs +++ /dev/null @@ -1,20 +0,0 @@ -/* - Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -namespace Il2CppInspector.Cpp -{ - public static class CppCompiler - { - public enum Type - { - BinaryFormat, // Inheritance structs use C syntax, and will automatically choose MSVC or GCC based on inferred compiler. - MSVC, // Inheritance structs are laid out assuming the MSVC compiler, which recursively includes base classes - GCC, // Inheritance structs are laid out assuming the GCC compiler, which packs members from all bases + current class together - } - - public static Type GuessFromImage(IFileFormatReader image) => (image is PEReader? Type.MSVC : Type.GCC); - } -} diff --git a/Il2CppInspector.Common/Cpp/CppCompilerType.cs b/Il2CppInspector.Common/Cpp/CppCompilerType.cs new file mode 100644 index 0000000..bbab1ed --- /dev/null +++ b/Il2CppInspector.Common/Cpp/CppCompilerType.cs @@ -0,0 +1,21 @@ +/* + Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +namespace Il2CppInspector.Cpp +{ + public enum CppCompilerType + { + BinaryFormat, // Inheritance structs use C syntax, and will automatically choose MSVC or GCC based on inferred compiler. + MSVC, // Inheritance structs are laid out assuming the MSVC compiler, which recursively includes base classes + GCC, // Inheritance structs are laid out assuming the GCC compiler, which packs members from all bases + current class together + } + + public static class CppCompiler + { + // Attempt to guess the compiler used to build the binary via its file type + public static CppCompilerType GuessFromImage(IFileFormatReader image) => (image is PEReader? CppCompilerType.MSVC : CppCompilerType.GCC); + } +} diff --git a/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs b/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs index 5e699d1..5625854 100644 --- a/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs +++ b/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs @@ -8,43 +8,33 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.RegularExpressions; using Il2CppInspector.Cpp.UnityHeaders; +using Il2CppInspector.Model; using Il2CppInspector.Reflection; namespace Il2CppInspector.Cpp { // Class for generating C header declarations from Reflection objects (TypeInfo, etc.) - public class CppDeclarationGenerator + internal class CppDeclarationGenerator { - private readonly Il2CppModel model; + private readonly AppModel appModel; + + private TypeModel model => appModel.ILModel; + private CppTypeCollection types => appModel.TypeCollection; // Version number and header file to generate structures for - public UnityVersion UnityVersion { get; } - public UnityHeader UnityHeader { get; } + public UnityVersion UnityVersion => appModel.UnityVersion; // How inheritance of type structs should be represented. // Different C++ compilers lay out C++ class structures differently, // meaning that the compiler must be known in order to generate class type structures // with the correct layout. - public CppCompiler.Type InheritanceStyle; - - public CppDeclarationGenerator(Il2CppModel model, UnityVersion version) { - this.model = model; - if (version == null) { - UnityHeader = UnityHeader.GuessHeadersForModel(model)[0]; - UnityVersion = UnityHeader.MinVersion; - } else { - UnityVersion = version; - UnityHeader = UnityHeader.GetHeaderForVersion(version); - if (UnityHeader.MetadataVersion != model.Package.BinaryImage.Version) { - /* this can only happen in the CLI frontend with a manually-supplied version number */ - Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeader.MetadataVersion})" + - $" does not match metadata version {model.Package.BinaryImage.Version}."); - } - } + public CppCompilerType InheritanceStyle; + public CppDeclarationGenerator(AppModel appModel) { + this.appModel = appModel; + InitializeNaming(); InitializeConcreteImplementations(); @@ -53,34 +43,38 @@ namespace Il2CppInspector.Cpp } // C type declaration used to name variables of the given C# type - public string AsCType(TypeInfo ti) { + private static Dictionary primitiveTypeMap = new Dictionary { + ["Boolean"] = "bool", + ["Byte"] = "uint8_t", + ["SByte"] = "int8_t", + ["Int16"] = "int16_t", + ["UInt16"] = "uint16_t", + ["Int32"] = "int32_t", + ["UInt32"] = "uint32_t", + ["Int64"] = "int64_t", + ["UInt64"] = "uint64_t", + ["IntPtr"] = "void *", + ["UIntPtr"] = "void *", + ["Char"] = "uint16_t", + ["Double"] = "double", + ["Single"] = "float" + }; + + public CppType AsCType(TypeInfo ti) { // IsArray case handled by TypeNamer.GetName if (ti.IsByRef || ti.IsPointer) { - return $"{AsCType(ti.ElementType)} *"; - } else if (ti.IsValueType) { - if (ti.IsPrimitive) { - switch (ti.Name) { - case "Boolean": return "bool"; - case "Byte": return "uint8_t"; - case "SByte": return "int8_t"; - case "Int16": return "int16_t"; - case "UInt16": return "uint16_t"; - case "Int32": return "int32_t"; - case "UInt32": return "uint32_t"; - case "Int64": return "int64_t"; - case "UInt64": return "uint64_t"; - case "IntPtr": return "void *"; - case "UIntPtr": return "void *"; - case "Char": return "uint16_t"; - case "Double": return "double"; - case "Single": return "float"; - } - } - return $"struct {TypeNamer.GetName(ti)}"; - } else if (ti.IsEnum) { - return $"enum {TypeNamer.GetName(ti)}"; + return AsCType(ti.ElementType).AsPointer(types.WordSize); } - return $"struct {TypeNamer.GetName(ti)} *"; + if (ti.IsValueType) { + if (ti.IsPrimitive && primitiveTypeMap.ContainsKey(ti.Name)) { + return types.GetType(primitiveTypeMap[ti.Name]); + } + return types.GetType(TypeNamer.GetName(ti)); + } + if (ti.IsEnum) { + return types.GetType(TypeNamer.GetName(ti)); + } + return types.GetType(TypeNamer.GetName(ti) + " *"); } // Resets the cache of visited types and pending types to output, but preserve any names we have already generated @@ -130,68 +124,65 @@ namespace Il2CppInspector.Cpp // Generate the fields for the base class of all objects (Il2CppObject) // The two fields are inlined so that we can specialize the klass member for each type object - private void GenerateObjectFields(StringBuilder csrc, TypeInfo ti) { - csrc.Append( - $" struct {TypeNamer.GetName(ti)}__Class *klass;\n" + - $" struct MonitorData *monitor;\n"); + private CppComplexType GenerateObjectStruct(string name, TypeInfo ti) { + var type = types.Struct(name); + types.AddField(type, "klass", TypeNamer.GetName(ti) + "__Class *"); + types.AddField(type, "monitor", "MonitorData *"); + return type; } // Generate structure fields for each field of a given type - private void GenerateFieldList(StringBuilder csrc, CppNamespace ns, TypeInfo ti) { - var namer = ns.MakeNamer((field) => field.Name.ToCIdentifier()); + private void GenerateFieldList(CppComplexType type, CppNamespace ns, TypeInfo ti) { + var namer = ns.MakeNamer(field => field.Name.ToCIdentifier()); foreach (var field in ti.DeclaredFields) { if (field.IsLiteral || field.IsStatic) continue; - csrc.Append($" {AsCType(field.FieldType)} {namer.GetName(field)};\n"); + type.AddField(namer.GetName(field), AsCType(field.FieldType)); } } // Generate the C structure for a value type, such as an enum or struct - private void GenerateValueFieldStruct(StringBuilder csrc, TypeInfo ti) { + private (CppComplexType valueType, CppComplexType boxedType) GenerateValueFieldStruct(TypeInfo ti) { + CppComplexType valueType, boxedType; string name = TypeNamer.GetName(ti); + if (ti.IsEnum) { // Enums should be represented using enum syntax // They otherwise behave like value types - csrc.Append($"enum {name} : {AsCType(ti.GetEnumUnderlyingType())} {{\n"); + var underlyingType = AsCType(ti.GetEnumUnderlyingType()); + valueType = types.Enum(underlyingType, name); foreach (var field in ti.DeclaredFields) { if (field.Name != "value__") - csrc.Append($" {EnumNamer.GetName(field)} = {field.DefaultValue},\n"); + ((CppEnumType)valueType).AddField(EnumNamer.GetName(field), field.DefaultValue); } - csrc.Append($"}};\n"); // Use System.Enum base type as klass - csrc.Append($"struct {name}__Boxed {{\n"); - GenerateObjectFields(csrc, ti.BaseType); - csrc.Append($" {AsCType(ti)} value;\n"); - csrc.Append($"}};\n"); + boxedType = GenerateObjectStruct(name + "__Boxed", ti.BaseType); + boxedType.AddField("value", AsCType(ti)); } else { // This structure is passed by value, so it doesn't include Il2CppObject fields. - csrc.Append($"struct {name} {{\n"); - GenerateFieldList(csrc, CreateNamespace(), ti); - csrc.Append($"}};\n"); + valueType = types.Struct(name); + GenerateFieldList(valueType, CreateNamespace(), ti); // Also generate the boxed form of the structure which includes the Il2CppObject header. - csrc.Append($"struct {name}__Boxed {{\n"); - GenerateObjectFields(csrc, ti); - csrc.Append($" {AsCType(ti)} fields;\n"); - csrc.Append($"}};\n"); + boxedType = GenerateObjectStruct(name + "__Boxed", ti); + boxedType.AddField("fields", AsCType(ti)); } + return (valueType, boxedType); } // Generate the C structure for a reference type, such as a class or array - private void GenerateRefFieldStruct(StringBuilder csrc, TypeInfo ti) { + private (CppComplexType objectOrArrayType, CppComplexType fieldsType) GenerateRefFieldStruct(TypeInfo ti) { var name = TypeNamer.GetName(ti); + if (ti.IsArray || ti.FullName == "System.Array") { var klassType = ti.IsArray ? ti : ti.BaseType; - var elementType = ti.IsArray ? AsCType(ti.ElementType) : "void *"; - csrc.Append($"struct {name} {{\n"); - GenerateObjectFields(csrc, klassType); - csrc.Append( - $" struct Il2CppArrayBounds *bounds;\n" + - $" il2cpp_array_size_t max_length;\n" + - $" {elementType} vector[32];\n"); - csrc.Append($"}};\n"); - return; + var elementType = ti.IsArray ? AsCType(ti.ElementType) : types.GetType("void *"); + var type = GenerateObjectStruct(name, klassType); + types.AddField(type, "bounds", "Il2CppArrayBounds *"); + types.AddField(type, "max_length", "il2cpp_array_size_t"); + type.AddField("vector", elementType.AsArray(32)); + return (type, null); } /* Generate a list of all base classes starting from the root */ @@ -202,7 +193,7 @@ namespace Il2CppInspector.Cpp var ns = CreateNamespace(); - if (InheritanceStyle == CppCompiler.Type.MSVC) { + if (InheritanceStyle == CppCompilerType.MSVC) { /* MSVC style: classes directly contain their base class as the first member. * This causes all classes to be aligned to the alignment of their base class. */ TypeInfo firstNonEmpty = null; @@ -214,56 +205,63 @@ namespace Il2CppInspector.Cpp } if (firstNonEmpty == null) { /* This struct is completely empty. Omit __Fields entirely. */ - csrc.Append($"struct {name} {{\n"); - GenerateObjectFields(csrc, ti); - csrc.Append($"}};\n"); + return (GenerateObjectStruct(name, ti), null); } else { + CppComplexType fieldType; if (firstNonEmpty == ti) { /* All base classes are empty, so this class forms the root of a new hierarchy. - * We have to be a little careful: the rootmost class needs to have its alignment + * We have to be a little careful: the root-most class needs to have its alignment * set to that of Il2CppObject, but we can't explicitly include Il2CppObject * in the hierarchy because we want to customize the type of the klass parameter. */ var align = model.Package.BinaryImage.Bits == 32 ? 4 : 8; - csrc.Append($"struct __declspec(align({align})) {name}__Fields {{\n"); - GenerateFieldList(csrc, ns, ti); - csrc.Append($"}};\n"); + fieldType = types.Struct(name + "__Fields", align); + GenerateFieldList(fieldType, ns, ti); } else { /* Include the base class fields. Alignment will be dictated by the hierarchy. */ ns.ReserveName("_"); - csrc.Append($"struct {name}__Fields {{\n"); - csrc.Append($" struct {TypeNamer.GetName(ti.BaseType)}__Fields _;\n"); - GenerateFieldList(csrc, ns, ti); - csrc.Append($"}};\n"); + fieldType = types.Struct(name + "__Fields"); + var baseFieldType = types[TypeNamer.GetName(ti.BaseType) + "__Fields"]; + fieldType.AddField("_", baseFieldType); + GenerateFieldList(fieldType, ns, ti); } - csrc.Append($"struct {name} {{\n"); - GenerateObjectFields(csrc, ti); - csrc.Append($" struct {name}__Fields fields;\n"); - csrc.Append($"}};\n"); + + var type = GenerateObjectStruct(name, ti); + types.AddField(type, "fields", name + "__Fields"); + return (type, fieldType); } - } else if (InheritanceStyle == CppCompiler.Type.GCC) { + } else if (InheritanceStyle == CppCompilerType.GCC) { /* GCC style: after the base class, all fields in the hierarchy are concatenated. * This saves space (fields are "packed") but requires us to repeat fields from * base classes. */ ns.ReserveName("klass"); ns.ReserveName("monitor"); - csrc.Append($"struct {name} {{\n"); - GenerateObjectFields(csrc, ti); + var type = GenerateObjectStruct(name, ti); foreach (var bti in baseClasses) - GenerateFieldList(csrc, ns, bti); - csrc.Append($"}};\n"); + GenerateFieldList(type, ns, bti); + return (type, null); } + throw new InvalidOperationException("Could not generate ref field struct"); } // "Flush" the list of visited types, generating C structures for each one - private void GenerateVisitedFieldStructs(StringBuilder csrc) { + private List GenerateVisitedFieldStructs() { + var structs = new List(TodoTypeStructs.Count); foreach (var ti in TodoFieldStructs) { - if (ti.IsEnum || ti.IsValueType) - GenerateValueFieldStruct(csrc, ti); - else - GenerateRefFieldStruct(csrc, ti); + if (ti.IsEnum || ti.IsValueType) { + var (valueType, boxedType) = GenerateValueFieldStruct(ti); + structs.Add(valueType); + structs.Add(boxedType); + } + else { + var (objectOrArrayType, fieldsType) = GenerateRefFieldStruct(ti); + if (fieldsType != null) + structs.Add(fieldsType); + structs.Add(objectOrArrayType); + } } TodoFieldStructs.Clear(); + return structs; } #endregion @@ -365,7 +363,7 @@ namespace Il2CppInspector.Cpp } // Generate the C structure for virtual function calls in a given type (the VTable) - private void GenerateVTableStruct(StringBuilder csrc, TypeInfo ti) { + private CppComplexType GenerateVTableStruct(TypeInfo ti) { MethodBase[] vtable; if (ti.IsInterface) { /* Interface vtables are just all of the interface methods. @@ -385,55 +383,51 @@ namespace Il2CppInspector.Cpp // Previous versions used `MethodInfo **vtable`. // TODO: Consider adding function types. This considerably increases the script size // but can significantly help with reverse-engineering certain binaries. - csrc.Append($"struct {name}__VTable {{\n"); + var vtableStruct = types.Struct(name + "__VTable"); + if (UnityVersion.CompareTo("5.3.6") < 0) { for (int i = 0; i < vtable.Length; i++) { - csrc.Append($" MethodInfo *{namer.GetName(i)};\n"); + types.AddField(vtableStruct, namer.GetName(i), "MethodInfo *"); } } else { for (int i = 0; i < vtable.Length; i++) { - csrc.Append($" VirtualInvokeData {namer.GetName(i)};\n"); + types.AddField(vtableStruct, namer.GetName(i), "VirtualInvokeData"); } } - csrc.Append($"}};\n"); + return vtableStruct; } // Generate the overall Il2CppClass-shaped structure for the given type - private void GenerateTypeStruct(StringBuilder csrc, TypeInfo ti) { + private (CppComplexType type, CppComplexType staticFields, CppComplexType vtable) GenerateTypeStruct(TypeInfo ti) { var name = TypeNamer.GetName(ti); - GenerateVTableStruct(csrc, ti); + var vtable = GenerateVTableStruct(ti); - csrc.Append($"struct {name}__StaticFields {{\n"); + var statics = types.Struct(name + "__StaticFields"); var namer = CreateNamespace().MakeNamer((field) => field.Name.ToCIdentifier()); foreach (var field in ti.DeclaredFields) { if (field.IsLiteral || !field.IsStatic) continue; - csrc.Append($" {AsCType(field.FieldType)} {namer.GetName(field)};\n"); + statics.AddField(namer.GetName(field), AsCType(field.FieldType)); } - csrc.Append($"}};\n"); /* TODO: type the rgctx_data */ + var cls = types.Struct(name + "__Class"); + types.AddField(cls, "_0", "Il2CppClass_0"); + if (UnityVersion.CompareTo("5.5.0") < 0) { - csrc.Append( - $"struct {name}__Class {{\n" + - $" struct Il2CppClass_0 _0;\n" + - $" struct {name}__VTable *vtable;\n" + - $" Il2CppRuntimeInterfaceOffsetPair *interfaceOffsets;\n" + - $" struct {name}__StaticFields *static_fields;\n" + - $" const Il2CppRGCTXData *rgctx_data;\n" + - $" struct Il2CppClass_1 _1;\n" + - $"}};\n"); + cls.AddField("vtable", vtable.AsPointer(types.WordSize)); + types.AddField(cls, "interfaceOffsets", "Il2CppRuntimeInterfaceOffsetPair *"); + cls.AddField("static_fields", statics.AsPointer(types.WordSize)); + types.AddField(cls, "rgctx_data", "Il2CppRGCTXData *", true); + types.AddField(cls, "_1", "Il2CppClass_1"); } else { - csrc.Append( - $"struct {name}__Class {{\n" + - $" struct Il2CppClass_0 _0;\n" + - $" Il2CppRuntimeInterfaceOffsetPair *interfaceOffsets;\n" + - $" struct {name}__StaticFields *static_fields;\n" + - $" const Il2CppRGCTXData *rgctx_data;\n" + - $" struct Il2CppClass_1 _1;\n" + - $" struct {name}__VTable vtable;\n" + - $"}};\n"); + types.AddField(cls, "interfaceOffsets", "Il2CppRuntimeInterfaceOffsetPair *"); + cls.AddField("static_fields", statics.AsPointer(types.WordSize)); + types.AddField(cls, "rgctx_data", "Il2CppRGCTXData *", true); + types.AddField(cls, "_1", "Il2CppClass_1"); + cls.AddField("vtable", vtable); } + return (cls, statics, vtable); } /// @@ -441,15 +435,18 @@ namespace Il2CppInspector.Cpp /// Type declarations that have previously been generated by this instance of CppDeclarationGenerator will not be generated again. /// /// A string containing C type declarations - public string GenerateRemainingTypeDeclarations() { - var csrc = new StringBuilder(); - GenerateVisitedFieldStructs(csrc); + public List GenerateRemainingTypeDeclarations() { + var decl = GenerateVisitedFieldStructs(); - foreach (var ti in TodoTypeStructs) - GenerateTypeStruct(csrc, ti); + foreach (var ti in TodoTypeStructs) { + var (cls, statics, vtable) = GenerateTypeStruct(ti); + decl.Add(vtable); + decl.Add(statics); + decl.Add(cls); + } TodoTypeStructs.Clear(); - return csrc.ToString(); + return decl; } #endregion @@ -473,40 +470,40 @@ namespace Il2CppInspector.Cpp } // Generate a C declaration for a method - private string GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) { - string retType; + private CppFnPtrType GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) { + CppType retType; if (method is MethodInfo mi) { - retType = mi.ReturnType.FullName == "System.Void" ? "void" : AsCType(mi.ReturnType); + retType = mi.ReturnType.FullName == "System.Void" ? types["void"] : AsCType(mi.ReturnType); } else { - retType = "void"; + retType = types["void"]; } var paramNs = CreateNamespace(); paramNs.ReserveName("method"); var paramNamer = paramNs.MakeNamer((pi) => pi.Name == "" ? "arg" : pi.Name.ToCIdentifier()); - var paramList = new List(); + var paramList = new List<(string, CppType)>(); // Figure out the "this" param if (method.IsStatic) { // In older versions, static methods took a dummy this parameter if (UnityVersion.CompareTo("2018.3.0") < 0) - paramList.Add("void *this"); + paramList.Add(("this", types.GetType("void *"))); } else { if (declaringType.IsValueType) { // Methods for structs take the boxed object as the this param - paramList.Add($"struct {TypeNamer.GetName(declaringType)}__Boxed * this"); + paramList.Add(("this", types.GetType(TypeNamer.GetName(declaringType) + "__Boxed *"))); } else { - paramList.Add($"{AsCType(declaringType)} this"); + paramList.Add(("this", AsCType(declaringType))); } } foreach (var pi in method.DeclaredParameters) { - paramList.Add($"{AsCType(pi.ParameterType)} {paramNamer.GetName(pi)}"); + paramList.Add((paramNamer.GetName(pi), AsCType(pi.ParameterType))); } - paramList.Add($"struct MethodInfo *method"); + paramList.Add(("method", types.GetType("MethodInfo *"))); - return $"{retType} {name}({string.Join(", ", paramList)})"; + return new CppFnPtrType(types.WordSize, retType, paramList) {Name = name}; } /// @@ -516,21 +513,9 @@ namespace Il2CppInspector.Cpp /// /// /// - public string GenerateMethodDeclaration(MethodBase method) { + public CppFnPtrType GenerateMethodDeclaration(MethodBase method) { return GenerateMethodDeclaration(method, GlobalNamer.GetName(method), method.DeclaringType); } - - /// - /// Generate a declaration of the form "retType (*name)(argTypes...)" - /// You must first visit the method using VisitMethod and then call - /// GenerateVisitedTypes in order to generate any dependent types. - /// - /// Method to generate (only the signature will be used) - /// Name of the function pointer - /// - public string GenerateFunctionPointer(MethodBase method, string name, TypeInfo declaringType = null) { - return GenerateMethodDeclaration(method, $"(*{name})", declaringType ?? method.DeclaringType); - } #endregion #region Naming @@ -539,7 +524,7 @@ namespace Il2CppInspector.Cpp private void InitializeNaming() { TypeNamespace = CreateNamespace(); // Type names that may appear in the header - foreach (var typeName in new string[] { "CustomAttributesCache", "CustomAttributeTypeCache", "EventInfo", "FieldInfo", "Hash16", "MemberInfo", "MethodInfo", "MethodVariableKind", "MonitorData", "ParameterInfo", "PInvokeArguments", "PropertyInfo", "SequencePointKind", "StackFrameType", "VirtualInvokeData" }) { + foreach (var typeName in new [] { "CustomAttributesCache", "CustomAttributeTypeCache", "EventInfo", "FieldInfo", "Hash16", "MemberInfo", "MethodInfo", "MethodVariableKind", "MonitorData", "ParameterInfo", "PInvokeArguments", "PropertyInfo", "SequencePointKind", "StackFrameType", "VirtualInvokeData" }) { TypeNamespace.ReserveName(typeName); } TypeNamer = TypeNamespace.MakeNamer((ti) => { diff --git a/Il2CppInspector.Common/Cpp/CppField.cs b/Il2CppInspector.Common/Cpp/CppField.cs index adf9a96..d4dcd85 100644 --- a/Il2CppInspector.Common/Cpp/CppField.cs +++ b/Il2CppInspector.Common/Cpp/CppField.cs @@ -15,6 +15,9 @@ namespace Il2CppInspector.Cpp // The type of the field public CppType Type { get; } + // Whether the field is const + public bool IsConst { get; } + // The offset of the field into the type public int Offset { get; internal set; } @@ -36,36 +39,36 @@ namespace Il2CppInspector.Cpp public int BitfieldMSB => BitfieldLSB + Size - 1; // Initialize field - public CppField(string name, CppType type, int bitfieldSize = 0) { + public CppField(string name, CppType type, int bitfieldSize = 0, bool isConst = false) { Name = name; Type = type; BitfieldSize = bitfieldSize; + IsConst = isConst; } // C++ representation of field public virtual string ToString(string format = "") { - var offset = format == "o" ? $"/* 0x{OffsetBytes:x2} - 0x{OffsetBytes + SizeBytes - 1:x2} (0x{SizeBytes:x2}) */" : ""; + var offset = format == "o" ? $"/* 0x{OffsetBytes:x2} - 0x{OffsetBytes + SizeBytes - 1:x2} (0x{SizeBytes:x2}) */ " : ""; + + var prefix = (IsConst ? "const " : ""); var field = Type switch { - // nested anonymous types - CppComplexType t when string.IsNullOrEmpty(t.Name) => (format == "o"? "\n" : "") + t.ToString(format)[..^1] + (Name.Length > 0? " " + Name : ""), + // nested anonymous types (trim semi-colon and newline from end) + CppComplexType t when string.IsNullOrEmpty(t.Name) => (format == "o"? "\n" : "") + + t.ToString(format)[..^2] + (Name.Length > 0? " " + Name : ""), // function pointers - CppFnPtrType t when string.IsNullOrEmpty(t.Name) => (format == "o"? " " : "") + t.FieldToString(Name), + CppFnPtrType t when string.IsNullOrEmpty(t.Name) => t.ToFieldString(Name), // regular fields - _ => $"{(format == "o"? " ":"")}{Type.Name} {Name}" + (BitfieldSize > 0? $" : {BitfieldSize}" : "") + _ => $"{Type.ToFieldString(Name)}" + (BitfieldSize > 0? $" : {BitfieldSize}" : "") }; var suffix = ""; - // arrays - if (Type is CppArrayType a) - suffix += "[" + a.Length + "]"; - // bitfields if (BitfieldSize > 0 && format == "o") suffix += $" /* bits {BitfieldLSB} - {BitfieldMSB} */"; - return offset + field + suffix; + return offset + prefix + field + suffix; } public override string ToString() => ToString(); } @@ -74,9 +77,9 @@ namespace Il2CppInspector.Cpp public class CppEnumField : CppField { // The value of this key name - public ulong Value { get; } + public object Value { get; } - public CppEnumField(string name, CppType type, ulong value) : base(name, type) => Value = value; + public CppEnumField(string name, CppType type, object value) : base(name, type) => Value = value; public override string ToString(string format = "") => Name + " = " + Value; } diff --git a/Il2CppInspector.Common/Cpp/CppType.cs b/Il2CppInspector.Common/Cpp/CppType.cs index 34dbe25..478c9c6 100644 --- a/Il2CppInspector.Common/Cpp/CppType.cs +++ b/Il2CppInspector.Common/Cpp/CppType.cs @@ -4,15 +4,12 @@ All rights reserved. */ -using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Linq; using System.Text; -using System.Text.RegularExpressions; -using Il2CppInspector.Cpp.UnityHeaders; + +// NOTE: The types in this file should not be created directly. Always create types using the CppTypeCollection API! namespace Il2CppInspector.Cpp { @@ -30,15 +27,23 @@ namespace Il2CppInspector.Cpp // The name of the type public virtual string Name { get; set; } + // 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 size of the C++ type in bits public virtual int Size { get; set; } + // The alignment of the type + public int AlignmentBytes { get; set; } + // The size of the C++ type in bytes public virtual int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0); - public CppType(string name = null, int size = 0) { + public CppType(string name = null, int size = 0, int alignmentBytes = 0) { Name = name; Size = size; + AlignmentBytes = alignmentBytes; } // Generate pointer to this type @@ -50,12 +55,10 @@ namespace Il2CppInspector.Cpp // Generate typedef to this type public CppAlias AsAlias(string Name) => new CppAlias(Name, this); - // Helper factories - public static CppComplexType NewStruct(string name = "") => new CppComplexType(ComplexValueType.Struct) {Name = name}; - public static CppComplexType NewUnion(string name = "") => new CppComplexType(ComplexValueType.Union) {Name = name}; - public static CppEnumType NewEnum(CppType underlyingType, string name = "") => new CppEnumType(underlyingType) {Name = name}; + // Return the type as a field + public virtual string ToFieldString(string fieldName) => Name + " " + fieldName; - public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : $"/* {Name} */"; + public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : ""; public override string ToString() => ToString(); } @@ -63,11 +66,14 @@ namespace Il2CppInspector.Cpp // A pointer type public class CppPointerType : CppType { - public override string Name => ElementType.Name + "*"; + public override string Name => ElementType.Name + " *"; public CppType ElementType { get; } public CppPointerType(int WordSize, CppType elementType) : base(null, WordSize) => ElementType = elementType; + + // Return the type as a field + public override string ToFieldString(string fieldName) => ElementType.ToFieldString("*" + fieldName); } // An array type @@ -89,6 +95,9 @@ namespace Il2CppInspector.Cpp Length = length; } + // Return the type as a field + public override string ToFieldString(string fieldName) => ElementType.ToFieldString(fieldName) + "[" + Length + "]"; + public override string ToString(string format = "") => ElementType + "[" + Length + "]"; } @@ -110,7 +119,7 @@ namespace Il2CppInspector.Cpp } // Generate a CppFnPtrType from a text signature (typedef or field) - public static CppFnPtrType FromSignature(CppTypes types, string text) { + public static CppFnPtrType FromSignature(CppTypeCollection types, string text) { if (text.StartsWith("typedef ")) text = text.Substring(8); @@ -131,13 +140,17 @@ namespace Il2CppInspector.Cpp } // Output as a named field in a type - public string FieldToString(string name) => $"{ReturnType.Name} (*{name})({string.Join(", ", Arguments.Select(a => a.Type.Name + (a.Name.Length > 0? " " + a.Name : "")))})"; + public override string ToFieldString(string name) => $"{ReturnType.Name} (*{name})({string.Join(", ", Arguments.Select(a => a.Type.Name + (a.Name.Length > 0? " " + a.Name : "")))})"; // Output as a typedef declaration - public override string ToString(string format = "") => "typedef " + FieldToString(Name) + ";"; + public override string ToString(string format = "") => "typedef " + ToFieldString(Name) + ";\n"; + + // Output as a function signature + public string ToSignatureString() => $"{ReturnType.Name} {Name}({string.Join(", ", Arguments.Select(a => a.Type.Name + (a.Name.Length > 0? " " + a.Name : "")))})"; } - // A typedef alias + // A named alias for another type + // These are not stored in the type collection but generated on-the-fly for fields by GetType() public class CppAlias : CppType { public CppType ElementType { get; } @@ -148,7 +161,7 @@ namespace Il2CppInspector.Cpp public CppAlias(string name, CppType elementType) : base(name) => ElementType = elementType; - public override string ToString(string format = "") => $"typedef {ElementType.Name} {Name};"; + public override string ToString(string format = "") => $"typedef {ElementType.ToFieldString(Name)};"; } // A struct, union, enum or class type (type with fields) @@ -174,7 +187,11 @@ namespace Il2CppInspector.Cpp var flattened = new SortedDictionary>(); foreach (var field in t.Fields.Values.SelectMany(f => f)) { - if (field.Type is CppComplexType ct) { + var type = field.Type; + while (type is CppAlias aliasType) + type = aliasType.ElementType; + + if (type is CppComplexType ct) { var baseOffset = field.Offset; var fields = ct.Flattened.Fields.Select(kl => new { Key = kl.Key + baseOffset, @@ -219,15 +236,6 @@ namespace Il2CppInspector.Cpp public CppComplexType(ComplexValueType complexValueType) : base("", 0) => ComplexValueType = complexValueType; - // Size can't be calculated lazily (as we go along adding fields) because of forward declarations - public override int Size => - ComplexValueType == ComplexValueType.Union - // Union size is the size of the largest element in the union - ? Fields.Values.SelectMany(f => f).Select(f => f.Size).Max() - // For structs we look for the last item and add the size; - // adding all the sizes might fail because of alignment padding - : Fields.Values.Any() ? Fields.Values.SelectMany(f => f).Select(f => f.Offset + f.Size).Max() : 0; - // Add a field to the type. Returns the offset of the field in the type public int AddField(CppField field, int alignmentBytes = 0) { // Unions and enums always have an offset of zero @@ -241,39 +249,52 @@ namespace Il2CppInspector.Cpp if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0) field.Offset += (alignmentBytes - field.OffsetBytes % alignmentBytes) * 8; + if (field.Type.AlignmentBytes > 0 && field.OffsetBytes % field.Type.AlignmentBytes != 0) + field.Offset += (field.Type.AlignmentBytes - field.OffsetBytes % field.Type.AlignmentBytes) * 8; + if (Fields.ContainsKey(field.Offset)) Fields[field.Offset].Add(field); else Fields.Add(field.Offset, new List { field }); + // Update type size. This lazy evaluation only works if there are no value type forward declarations in the type + // Union size is the size of the largest element in the union + if (ComplexValueType == ComplexValueType.Union) + if (field.Size > Size) + Size = field.Size; + + // For structs we look for the last item and add the size; adding the sizes without offsets might fail because of alignment padding + if (ComplexValueType == ComplexValueType.Struct) + Size = field.Offset + field.Size; + return Size; } // Add a field to the type - public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0) - => AddField(new CppField(name, type, bitfield), alignmentBytes); + public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0, bool isConst = false) + => AddField(new CppField(name, type, bitfield, isConst), alignmentBytes); + + // Return the type as a field + public override string ToFieldString(string fieldName) => (ComplexValueType == ComplexValueType.Struct ? "struct " : "union ") + Name + " " + fieldName; // Summarize all field names and offsets public override string ToString(string format = "") { var sb = new StringBuilder(); - if (Name.Length > 0) - sb.Append("typedef "); sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union "); + + if (AlignmentBytes != 0) + sb.Append($"__declspec(align({AlignmentBytes})) "); + sb.Append(Name + (Name.Length > 0 ? " " : "")); - if (Fields.Any()) { - sb.Append("{"); - foreach (var field in Fields.Values.SelectMany(f => f)) - sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ";"); + sb.Append("{"); + foreach (var field in Fields.Values.SelectMany(f => f)) + sb.Append("\n " + string.Join("\n ", field.ToString(format).Split('\n')) + ";"); - sb.Append($"\n}}{(Name.Length > 0? " " + Name : "")}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); - } - // Forward declaration - else { - sb.Append($"{Name};"); - } + sb.Append($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); + sb.Append("\n"); return sb.ToString(); } } @@ -288,27 +309,20 @@ namespace Il2CppInspector.Cpp public CppEnumType(CppType underlyingType) : base(ComplexValueType.Enum) => UnderlyingType = underlyingType; - public void AddField(string name, ulong value) => AddField(new CppEnumField(name, UnderlyingType, value)); + public void AddField(string name, object value) => AddField(new CppEnumField(name, UnderlyingType, value)); + + // Return the type as a field + public override string ToFieldString(string fieldName) => "enum " + Name + " " + fieldName; public override string ToString(string format = "") { var sb = new StringBuilder(); - sb.Append($"typedef enum {Name} : {UnderlyingType.Name}"); + sb.Append($"enum {Name} : {UnderlyingType.Name} {{"); - if (Fields.Any()) { - sb.Append(" {"); - foreach (var field in Fields.Values.SelectMany(f => f)) - sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ","); - - // Chop off final comma - sb = sb.Remove(sb.Length - 1, 1); - sb.Append($"\n}} {Name}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); - } - // Forward declaration - else { - sb.Append($"{Name};"); - } + foreach (var field in Fields.Values.SelectMany(f => f)) + sb.Append("\n " + string.Join("\n ", field.ToString(format).Split('\n')) + ","); + sb.AppendLine($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); return sb.ToString(); } } diff --git a/Il2CppInspector.Common/Cpp/CppTypes.cs b/Il2CppInspector.Common/Cpp/CppTypeCollection.cs similarity index 60% rename from Il2CppInspector.Common/Cpp/CppTypes.cs rename to Il2CppInspector.Common/Cpp/CppTypeCollection.cs index 589fbca..3c58b1d 100644 --- a/Il2CppInspector.Common/Cpp/CppTypes.cs +++ b/Il2CppInspector.Common/Cpp/CppTypeCollection.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; @@ -16,11 +17,14 @@ using Il2CppInspector.Cpp.UnityHeaders; namespace Il2CppInspector.Cpp { // A collection of C++ types - public class CppTypes : IEnumerable + public class CppTypeCollection : IEnumerable { // All of the types public Dictionary Types { get; } + // All of the literal typedef aliases + public Dictionary TypedefAliases { get; } = new Dictionary(); + public CppType this[string s] => Types[s]; public IEnumerator GetEnumerator() => Types.Values.GetEnumerator(); @@ -29,6 +33,16 @@ namespace Il2CppInspector.Cpp // Architecture width in bits (32/64) - to determine pointer sizes public int WordSize { get; } + private Dictionary complexTypeMap = new Dictionary { + ["struct"] = ComplexValueType.Struct, + ["union"] = ComplexValueType.Union, + ["enum"] = ComplexValueType.Enum + }; + + // The group that the next added type(s) will be placed in + private string currentGroup = string.Empty; + public void SetGroup(string group) => currentGroup = group; + private static readonly List primitiveTypes = new List { new CppType("uint8_t", 8), new CppType("uint16_t", 16), @@ -46,7 +60,7 @@ namespace Il2CppInspector.Cpp new CppType("void", 0) }; - public CppTypes(int wordSize) { + public CppTypeCollection(int wordSize) { if (wordSize != 32 && wordSize != 64) throw new ArgumentOutOfRangeException("Architecture word size must be 32 or 64-bit to generate C++ data"); @@ -58,6 +72,9 @@ namespace Il2CppInspector.Cpp Add(new CppType("intptr_t", WordSize)); Add(new CppType("uintptr_t", WordSize)); Add(new CppType("size_t", WordSize)); + + foreach (var type in Types.Values) + type.Group = "primitive"; } #region Code parser @@ -65,20 +82,22 @@ namespace Il2CppInspector.Cpp public void AddFromDeclarationText(string text) { using StringReader lines = new StringReader(text); - var rgxExternDecl = new Regex(@"struct (\S+);"); - var rgxTypedefForwardDecl = new Regex(@"typedef struct (\S+) (\S+);"); + var rgxForwardDecl = new Regex(@"(struct|union) (\S+);"); + var rgxTypedefAlias = new Regex(@"typedef (struct|union) (\S+) (\S+);"); var rgxTypedefFnPtr = new Regex(@"typedef\s+(?:struct )?" + CppFnPtrType.Regex + ";"); - var rgxTypedef = new Regex(@"typedef (\S+?)\s*\**\s*(\S+);"); + var rgxTypedefPtr = new Regex(@"typedef (\S+?\s*\**)\s*(\S+);"); + var rgxDefinition = new Regex(@"^(typedef )?(struct|union|enum)"); var rgxFieldFnPtr = new Regex(CppFnPtrType.Regex + @";"); - var rgxField = new Regex(@"^(?:struct |enum )?(\S+?)\s*\**\s*((?:\S|\s*,\s*)+)(?:\s*:\s*([0-9]+))?;"); + var rgxField = new Regex(@"^(?:struct |enum )?(\S+?\s*\**)\s*((?:\S|\s*,\s*)+)(?:\s*:\s*([0-9]+))?;"); var rgxEnumValue = new Regex(@"^\s*([A-Za-z0-9_]+)(?:\s*=\s*(.+?))?,?\s*$"); + var rgxIsConst = new Regex(@"\bconst\b"); var rgxStripKeywords = new Regex(@"\b(?:const|unsigned|volatile)\b"); var rgxCompressPtrs = new Regex(@"\*\s+\*"); var rgxArrayField = new Regex(@"(\S+?)\[([0-9]+)\]"); - var rgxAlignment = new Regex(@"__attribute__\(\(aligned\(([0-9]+)\)\)\)"); + var rgxAlignment = new Regex(@"__attribute__\(\(aligned\(([0-9]+)\)\)\)\s+"); var rgxIsBitDirective = new Regex(@"#ifdef\s+IS_(32|64)BIT"); var rgxSingleLineComment = new Regex(@"/\*.*?\*/"); @@ -86,10 +105,11 @@ namespace Il2CppInspector.Cpp bool falseIfBlock = false; bool inComment = false; bool inMethod = false; + bool inTypedef = false; var nextEnumValue = 0ul; - string line; + string rawLine, line; - while ((line = lines.ReadLine()) != null) { + while ((rawLine = line = lines.ReadLine()) != null) { // Remove comments if (line.Contains("//")) @@ -191,101 +211,103 @@ namespace Il2CppInspector.Cpp continue; } - // External declaration - // struct ; - // NOTE: Unfortunately we're not going to ever know the size of this type - var externDecl = rgxExternDecl.Match(line); - if (externDecl.Success) { - var declType = externDecl.Groups[1].Captures[0].ToString(); - - Types.Add(declType, CppType.NewStruct(declType)); - - Debug.WriteLine($"[EXTERN DECL ] {line}"); - continue; - } - // Forward declaration - // typedef struct - var typedef = rgxTypedefForwardDecl.Match(line); - if (typedef.Success) { - var alias = typedef.Groups[2].Captures[0].ToString(); - var declType = typedef.Groups[1].Captures[0].ToString(); + // ; + var externDecl = rgxForwardDecl.Match(line); + if (externDecl.Success) { + var complexType = complexTypeMap[externDecl.Groups[1].Captures[0].ToString()]; + var declType = externDecl.Groups[2].Captures[0].ToString(); - // Sometimes we might get multiple forward declarations for the same type - if (!Types.ContainsKey(declType)) - Types.Add(declType, CppType.NewStruct(declType)); - - // Sometimes the alias might be the same name as the type (this is usually the case) - if (!Types.ContainsKey(alias)) - Types.Add(alias, Types[declType].AsAlias(alias)); + switch (complexType) { + case ComplexValueType.Struct: Struct(declType); break; + case ComplexValueType.Union: Union(declType); break; + } Debug.WriteLine($"[FORWARD DECL ] {line}"); continue; } - // Function pointer + // Struct or union alias + // typedef + var typedef = rgxTypedefAlias.Match(line); + if (typedef.Success) { + var complexType = complexTypeMap[typedef.Groups[1].Captures[0].ToString()]; + var declType = typedef.Groups[2].Captures[0].ToString(); + var alias = typedef.Groups[3].Captures[0].ToString(); + + // Sometimes we might get multiple forward declarations for the same type + if (!Types.ContainsKey(declType)) { + switch (complexType) { + case ComplexValueType.Struct: Struct(declType); break; + case ComplexValueType.Union: Union(declType); break; + } + } + + // C++ allows the same typedef to be defined more than once + TypedefAliases.TryAdd(alias, Types[declType]); + + Debug.WriteLine($"[TYPEDEF STRUC] {line}"); + continue; + } + + // Function pointer alias // typedef (*)(); typedef = rgxTypedefFnPtr.Match(line); if (typedef.Success) { var alias = typedef.Groups[2].Captures[0].ToString(); var fnPtrType = CppFnPtrType.FromSignature(this, line); - fnPtrType.Name = alias; + fnPtrType.Group = currentGroup; - Types.Add(alias, fnPtrType); + TypedefAliases.Add(alias, fnPtrType); Debug.WriteLine($"[TYPEDEF FNPTR] {line} -- Adding method pointer typedef to {alias}"); continue; } - // Alias - // typedef [*..] ; - typedef = rgxTypedef.Match(line); + // Type (pointer) alias + // typedef ; + typedef = rgxTypedefPtr.Match(line); if (typedef.Success) { var alias = typedef.Groups[2].Captures[0].ToString(); var existingType = typedef.Groups[1].Captures[0].ToString(); // Potential multiple indirection - var type = Types[existingType]; + var type = GetType(existingType); + + TypedefAliases.TryAdd(alias, type); + var pointers = line.Count(c => c == '*'); - for (int i = 0; i < pointers; i++) - type = type.AsPointer(WordSize); - - Types.Add(alias, type.AsAlias(alias)); - Debug.WriteLine($"[TYPEDEF {(pointers > 0? "PTR":"VAL")} ] {line} -- Adding typedef from {type.Name} to {alias}"); continue; } - // Start of struct - // typedef struct - if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1 - && currentType.Count == 0) { - currentType.Push(CppType.NewStruct()); + // Start of struct/union/enum + // [typedef] [optional-tag-name] + var definition = rgxDefinition.Match(line); + if (definition.Success && line.IndexOf(";", StringComparison.Ordinal) == -1 && currentType.Count == 0) { + // Must have a name if not a typedef, might have a name if it is + var split = line.Split(' '); - if (line.StartsWith("struct ")) - currentType.Peek().Name = line.Split(' ')[1]; + if (split[0] == "typedef") + split = split.Skip(1).ToArray(); - Debug.WriteLine($"\n[STRUCT START ] {line}"); - continue; - } + var name = split.Length > 1 && split[1] != "{" ? split[1] : ""; - // Start of union - // typedef union - if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) { - currentType.Push(CppType.NewUnion()); + currentType.Push(complexTypeMap[split[0]] switch { + ComplexValueType.Struct => Struct(name), + ComplexValueType.Union => Union(name), + ComplexValueType.Enum => NewDefaultEnum(name), + _ => throw new InvalidOperationException("Unknown complex type") + }); - Debug.WriteLine($"\n[UNION START ] {line}"); - continue; - } + // Remember we have to set an alias later + inTypedef = line.StartsWith("typedef "); - // Start of enum - // typedef enum - if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) { - currentType.Push(NewDefaultEnum()); + // Reset next enum value if needed nextEnumValue = 0; - Debug.WriteLine($"\n[ENUM START ] {line}"); + Debug.WriteLine($"\n[COMPLEX START] {line}"); continue; } @@ -294,17 +316,17 @@ namespace Il2CppInspector.Cpp // union var words = line.Split(' '); if ((words[0] == "union" || words[0] == "struct") && words.Length <= 2) { - currentType.Push(words[0] == "struct" ? CppType.NewStruct() : CppType.NewUnion()); + currentType.Push(words[0] == "struct" ? Struct() : Union()); Debug.WriteLine($"[FIELD START ] {line}"); continue; } - // End of already named struct + // End of already named (non-typedef) struct if (line == "};" && currentType.Count == 1) { var ct = currentType.Pop(); if (!Types.ContainsKey(ct.Name)) - Types.Add(ct.Name, ct); + Add(ct); else ((CppComplexType) Types[ct.Name]).Fields = ct.Fields; @@ -313,35 +335,42 @@ namespace Il2CppInspector.Cpp } // End of complex field, complex type or enum - // end of [typedef] struct/union/enum + // end of [typedef] struct/union/enum (in which case inTypedef == true) if (line.StartsWith("}") && line.EndsWith(";")) { - var name = line[1..^1].Trim(); + var fieldNameOrTypedefAlias = line[1..^1].Trim(); var ct = currentType.Pop(); - // End of top-level typedef, so it's a type name + // End of top-level definition, so it's a complete type, not a field if (currentType.Count == 0) { - ct.Name = name; - if (!Types.ContainsKey(name)) - Types.Add(name, ct); + if (inTypedef) + TypedefAliases.TryAdd(fieldNameOrTypedefAlias, ct); + + // If the type doesn't have a name because it's a tagless typedef, give it the same name as the alias + if (inTypedef && string.IsNullOrEmpty(ct.Name)) + ct.Name = fieldNameOrTypedefAlias; + + // Add the type to the collection if we haven't already when it was created + if (!Types.ContainsKey(ct.Name)) + Add(ct); // We will have to copy the type data if the type was forward declared, // because other types are already referencing it; replacing it in the // collection will not replace the references to the empty version in // other types else { - ((CppComplexType) Types[name]).Fields = ct.Fields; + ((CppComplexType) Types[ct.Name]).Fields = ct.Fields; } - Debug.WriteLine($"[STRUCT END ] {line} -- {name}\n"); + Debug.WriteLine($"[COMPLEX END ] {line} -- {ct.Name}\n"); } // Otherwise it's a field name in the current type else { var parent = currentType.Peek(); - parent.AddField(name, ct, alignment); + parent.AddField(fieldNameOrTypedefAlias, ct, alignment); - Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {name}"); + Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {fieldNameOrTypedefAlias}"); } continue; } @@ -350,6 +379,7 @@ namespace Il2CppInspector.Cpp var fieldFnPtr = rgxFieldFnPtr.Match(line); if (fieldFnPtr.Success) { var fnPtrType = CppFnPtrType.FromSignature(this, line); + fnPtrType.Group = currentGroup; var name = fieldFnPtr.Groups[2].Captures[0].ToString(); @@ -365,7 +395,8 @@ namespace Il2CppInspector.Cpp if (field.Success) { var names = field.Groups[2].Captures[0].ToString(); - var typeName = field.Groups[1].Captures[0].ToString(); + var typeName = field.Groups[1].Captures[0].ToString().Trim(); + var isConst = rgxIsConst.Match(rawLine).Success; // Multiple fields can be separated by commas foreach (var fieldName in names.Split(',')) { @@ -384,21 +415,20 @@ namespace Il2CppInspector.Cpp if (field.Groups[3].Captures.Count > 0) bitfield = int.Parse(field.Groups[3].Captures[0].ToString()); - // Potential multiple indirection - var type = Types[typeName]; - var pointers = line.Count(c => c == '*'); - for (int i = 0; i < pointers; i++) - type = type.AsPointer(WordSize); + // Potential multiple indirection or use of alias + var type = GetType(typeName); var ct = currentType.Peek(); if (arraySize > 0) type = type.AsArray(arraySize); - ct.AddField(name, type, alignment, bitfield); + ct.AddField(name, type, alignment, bitfield, isConst); - if (bitfield == 0) + if (bitfield == 0) { + var pointers = line.Count(c => c == '*'); Debug.WriteLine($"[FIELD {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- {name}"); + } else Debug.WriteLine($"[BITFIELD ] {line} -- {name} : {bitfield}"); } @@ -414,7 +444,7 @@ namespace Il2CppInspector.Cpp if (enumValue.Groups[2].Captures.Count > 0) { // Convert the text to a ulong even if it's hexadecimal with a 0x prefix var valueText = enumValue.Groups[2].Captures[0].ToString(); - var conv = new System.ComponentModel.UInt64Converter(); + var conv = new UInt64Converter(); // Handle bit shift operator var values = valueText.Split("<<").Select(t => (ulong) conv.ConvertFromInvariantString(t.Trim())).ToArray(); @@ -453,39 +483,95 @@ namespace Il2CppInspector.Cpp } #endregion - // Get a type from its name, handling pointer types + // Get a type from its name, handling typedef aliases and pointer types public CppType GetType(string typeName) { + + // Separate type name from pointers var baseName = typeName.Replace("*", ""); var indirectionCount = typeName.Length - baseName.Length; + baseName = baseName.Trim(); - var type = Types[baseName.Trim()]; + CppType type; + + // Typedef alias + if (TypedefAliases.TryGetValue(baseName, out CppType aliasType)) + type = aliasType.AsAlias(baseName); + + // Non-aliased type + else { + // Allow auto-generation of forward declarations + // This will break type generation unless the ultimate wanted type is a pointer + // Note this can still be the case with indirectionCount == 0 if .AsPointer() is called afterwards + if (!Types.ContainsKey(baseName)) + Struct(baseName); + + type = Types[baseName]; + } + + // Resolve pointer indirections for (int i = 0; i < indirectionCount; i++) type = type.AsPointer(WordSize); return type; } + // Get all of the types in a logical group + public IEnumerable GetTypeGroup(string groupName) => Types.Values.Where(t => t.Group == groupName); + // Add a type externally - public void Add(CppType type) => Types.Add(type.Name, type); + public void Add(CppType type) { + type.Group = currentGroup; + Types.Add(type.Name, type); + } // Add a field to a type specifying the field type and/or declaring type name as a string // Convenient when the field type is a pointer, or to avoid referencing this.Types or this.WordSize externally - public int AddField(CppComplexType declaringType, string fieldName, string typeName) - => declaringType.AddField(fieldName, GetType(typeName)); + public int AddField(CppComplexType declaringType, string fieldName, string typeName, bool isConst = false) + => declaringType.AddField(fieldName, GetType(typeName), isConst: isConst); + + // Helper factories + // If the type is named, it gets added to the dictionary; otherwise it must be added manually + // If the type already exists, it is fetched, otherwise it is created + public CppComplexType Struct(string name = "", int alignmentBytes = 0) { + if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType)) + return (CppComplexType) cppType; + var type = new CppComplexType(ComplexValueType.Struct) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes}; + if (!string.IsNullOrEmpty(name)) + Add(type); + return type; + } + + public CppComplexType Union(string name = "", int alignmentBytes = 0) { + if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType)) + return (CppComplexType) cppType; + var type = new CppComplexType(ComplexValueType.Union) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes}; + if (!string.IsNullOrEmpty(name)) + Add(type); + return type; + } + + public CppEnumType Enum(CppType underlyingType, string name = "") { + var type = new CppEnumType(underlyingType) {Name = name, Group = currentGroup}; + if (!string.IsNullOrEmpty(name)) + Add(type); + return type; + } // Create an empty enum with the default underlying type for the architecture (32 or 64-bit) - public CppEnumType NewDefaultEnum() => CppType.NewEnum(Types["int"]); + public CppEnumType NewDefaultEnum(string name = "") => Enum(Types["int"], name); - // Generate a populated CppTypes object from a set of Unity headers - public static CppTypes FromUnityVersion(UnityVersion version, int wordSize = 32) + // Generate a populated CppTypeCollection object from a set of Unity headers + public static CppTypeCollection FromUnityVersion(UnityVersion version, int wordSize = 32) => FromUnityHeaders(UnityHeader.GetHeaderForVersion(version), wordSize); - public static CppTypes FromUnityHeaders(UnityHeader header, int wordSize = 32) { - var cppTypes = new CppTypes(wordSize); + public static CppTypeCollection FromUnityHeaders(UnityHeader header, int wordSize = 32) { + var cppTypes = new CppTypeCollection(wordSize); + cppTypes.SetGroup("il2cpp"); + // Add junk from config files we haven't included - cppTypes.Add(new CppType("Il2CppIManagedObjectHolder")); - cppTypes.Add(new CppType("Il2CppIUnknown")); + cppTypes.TypedefAliases.Add("Il2CppIManagedObjectHolder", cppTypes["void"].AsPointer(wordSize)); + cppTypes.TypedefAliases.Add("Il2CppIUnknown", cppTypes["void"].AsPointer(wordSize)); // Process Unity headers var headers = header.GetHeaderText(); diff --git a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs index 11656d8..04763c7 100644 --- a/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs +++ b/Il2CppInspector.Common/Cpp/UnityHeaders/UnityHeader.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2020 Robert Xiao - https://robertxiao.ca All rights reserved. @@ -77,7 +77,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders // Guess which header file(s) correspond to the given metadata+binary. // Note that this may match multiple headers due to structural changes between versions // that are not reflected in the metadata version. - public static List GuessHeadersForModel(Reflection.Il2CppModel model) { + public static List GuessHeadersForModel(Reflection.TypeModel model) { List result = new List(); foreach (var v in GetAllHeaders()) { if (v.MetadataVersion != model.Package.BinaryImage.Version) diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs new file mode 100644 index 0000000..3b5757b --- /dev/null +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -0,0 +1,188 @@ +/* + Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Il2CppInspector.Cpp; +using Il2CppInspector.Cpp.UnityHeaders; +using Il2CppInspector.Reflection; + +namespace Il2CppInspector.Model +{ + // Class that represents a composite IL/C++ type + public class AppType + { + // The corresponding C++ type definition which represents an instance of the object + // If a .NET type, this is derived from Il2CppObject, otherwise it can be any type + // If the underlying .NET type is a struct (value type), this will return the boxed version + public CppType CppType { get; internal set; } + + // For an underlying .NET type which is a struct (value type), the unboxed type, otherwise null + public CppType CppValueType { get; internal set; } + + // The type in the model this object represents (for .NET types, otherwise null) + public TypeInfo ILType { get; internal set; } + + // The VA of the Il2CppClass object which defines this type (for .NET types, otherwise zero) + public ulong VirtualAddress { get; internal set; } + } + + // Class that represents a composite IL/C++ method + public class AppMethod + { + // The corresponding C++ function pointer type + public CppFnPtrType CppFnPtrType { get; internal set; } + + // The corresponding .NET method + public MethodBase ILMethod { get; internal set; } + + // The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method + public ulong MethodInfoPtrAddress { get; internal set; } + + // The VA of the method code itself, or 0 if unknown/not compiled + public ulong MethodCodeAddress => ILMethod.VirtualAddress?.Start ?? 0; + } + + // 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; set; } // TODO: Change to private set after integrating IDA output + + // 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 + + // 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 DeclarationOrderedTypes + public CppTypeCollection TypeCollection { get; set; } // TODO: Change to private set after integrating IDA output + + // 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 DependencyOrderedTypes { get; private set; } + + // The .NET type model for the application + public TypeModel ILModel { get; } + + // All of the function exports for the binary + public List Exports { get; } + + // Delegated C++ types iterator + public IEnumerator GetEnumerator() => TypeCollection.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) TypeCollection).GetEnumerator(); + + // The C++ declaration generator for this binary + // TODO: Make this private once IDA output integration is completed + internal CppDeclarationGenerator declarationGenerator; + + // Convenience properties + + // The word size of the binary in bits + public int WordSize => ILModel.Package.BinaryImage.Bits; + + // The IL2CPP package for this application + public Il2CppInspector Package => ILModel.Package; + + // The compiler used to build the binary + public CppCompilerType SourceCompiler => declarationGenerator.InheritanceStyle; + + // The Unity header text including word size define + public string UnityHeaderText => (WordSize == 32 ? "#define IS_32BIT\n" : "") + UnityHeader.GetHeaderText(); + + // Initialize + public AppModel(TypeModel model) { + // Save .NET type model + ILModel = model; + + // Get addresses of IL2CPP API function exports + Exports = model.Package.Binary.Image.GetExports()?.ToList() ?? new List(); + } + + // 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) { + // Set target compiler + TargetCompiler = compiler == CppCompilerType.BinaryFormat ? CppCompiler.GuessFromImage(ILModel.Package.BinaryImage) : compiler; + + // Determine Unity version and get headers + UnityHeader = unityVersion != null ? UnityHeader.GetHeaderForVersion(unityVersion) : UnityHeader.GuessHeadersForModel(ILModel)[0]; + UnityVersion = unityVersion ?? UnityHeader.MinVersion; + + // Check for matching metadata and binary versions + if (UnityHeader.MetadataVersion != ILModel.Package.BinaryImage.Version) { + Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeader.MetadataVersion})" + + $" does not match metadata version {ILModel.Package.BinaryImage.Version}."); + } + + // Start creation of type model by parsing all of the Unity IL2CPP headers + // Calling declarationGenerator.GenerateRemainingTypeDeclarations() below will automatically add to this collection + TypeCollection = CppTypeCollection.FromUnityHeaders(UnityHeader, WordSize); + + // Initialize declaration generator to process every type in the binary + declarationGenerator = new CppDeclarationGenerator(this); + + // Initialize ordered type list for code output + DependencyOrderedTypes = new List(); + + // Add method definitions to C++ type model + TypeCollection.SetGroup("type_definitions"); + + foreach (var method in ILModel.MethodsByDefinitionIndex.Where(m => m.VirtualAddress.HasValue)) { + declarationGenerator.IncludeMethod(method); + DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + } + + // Add generic methods to C++ type model + TypeCollection.SetGroup("types_from_generics"); + + foreach (var method in ILModel.GenericMethods.Values.Where(m => m.VirtualAddress.HasValue)) { + declarationGenerator.IncludeMethod(method); + DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + } + + // Add metadata usage types to C++ type model + // Not supported in il2cpp <19 + TypeCollection.SetGroup("types_from_usages"); + + if (Package.MetadataUsages != null) + foreach (var usage in Package.MetadataUsages) { + switch (usage.Type) { + case MetadataUsageType.Type: + case MetadataUsageType.TypeInfo: + var type = ILModel.GetMetadataUsageType(usage); + declarationGenerator.IncludeType(type); + DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + break; + case MetadataUsageType.MethodDef: + case MetadataUsageType.MethodRef: + var method = ILModel.GetMetadataUsageMethod(usage); + declarationGenerator.IncludeMethod(method); + DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations()); + break; + } + } + + // TODO: Build composite types + + // This is to allow this method to be chained after a new expression + return this; + } + + // Get all the types for a group + public IEnumerable GetTypeGroup(string groupName) => TypeCollection.GetTypeGroup(groupName); + public IEnumerable GetDependencyOrderedTypeGroup(string groupName) => DependencyOrderedTypes.Where(t => t.Group == groupName); + } +} diff --git a/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs b/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs index b8fec64..6274de8 100644 --- a/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs +++ b/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs @@ -20,7 +20,7 @@ namespace Il2CppInspector.Outputs { public class CSharpCodeStubs { - private readonly Il2CppModel model; + private readonly TypeModel model; private Exception lastException; // Namespace prefixes whose contents should be skipped @@ -41,7 +41,7 @@ namespace Il2CppInspector.Outputs private HashSet usedAssemblyAttributes = new HashSet(); private readonly object usedAssemblyAttributesLock = new object(); - public CSharpCodeStubs(Il2CppModel model) => this.model = model; + public CSharpCodeStubs(TypeModel model) => this.model = model; // Get the last error that occurred and clear the error state public Exception GetAndClearLastException() { diff --git a/Il2CppInspector.Common/Outputs/CppScaffolding.cs b/Il2CppInspector.Common/Outputs/CppScaffolding.cs index d490b23..11e6c6f 100644 --- a/Il2CppInspector.Common/Outputs/CppScaffolding.cs +++ b/Il2CppInspector.Common/Outputs/CppScaffolding.cs @@ -2,106 +2,65 @@ // Copyright (c) 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty // All rights reserved -using System.Collections.Generic; using System.Linq; using System.IO; using System.Text; using System.Text.RegularExpressions; using Il2CppInspector.Reflection; using Il2CppInspector.Cpp; -using Il2CppInspector.Cpp.UnityHeaders; +using Il2CppInspector.Model; namespace Il2CppInspector.Outputs { public class CppScaffolding { - private readonly Il2CppModel model; - public CppCompiler.Type Compiler = CppCompiler.Type.BinaryFormat; + private readonly AppModel model; private StreamWriter writer; - public UnityVersion UnityVersion; - private CppDeclarationGenerator declGenerator; private readonly Regex rgxGCCalign = new Regex(@"__attribute__\s*?\(\s*?\(\s*?aligned\s*?\(\s*?([0-9]+)\s*?\)\s*?\)\s*?\)"); private readonly Regex rgxMSVCalign = new Regex(@"__declspec\s*?\(\s*?align\s*?\(\s*?([0-9]+)\s*?\)\s*?\)"); - public CppScaffolding(Il2CppModel model) => this.model = model; + public CppScaffolding(AppModel model) => this.model = model; public void WriteCppToFile(string outputFile) { - declGenerator = new CppDeclarationGenerator(model, UnityVersion); - UnityVersion = declGenerator.UnityVersion; - - // Can be overridden in the object initializer - if (Compiler == CppCompiler.Type.BinaryFormat) - Compiler = CppCompiler.GuessFromImage(model.Package.BinaryImage); - using var fs = new FileStream(outputFile, FileMode.Create); writer = new StreamWriter(fs, Encoding.UTF8); writeLine("// Generated C++ file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty"); - writeLine("// Target Unity version: " + declGenerator.UnityHeader); + writeLine("// Target Unity version: " + model.UnityHeader); writeLine(""); - // TODO: The implementation of C++ header output is temporary and will be replaced by a C++ type model in a later version writeSectionHeader("IL2CPP internal types"); - writeUnityHeaders(); + writeCode(model.UnityHeaderText); // Prevent conflicts with symbols that are in scope for compilers by default writeCode("namespace app {"); writeLine(""); - writeSectionHeader("Application type definitions"); - writeTypesForMethods(model.MethodsByDefinitionIndex); - - writeSectionHeader("Application generic method type usages"); - writeTypesForMethods(model.GenericMethods.Values); - - writeSectionHeader("Application type usages"); - writeUsages(); + writeTypesForGroup("Application type definitions", "type_definitions"); + writeTypesForGroup("Application generic method type usages", "types_from_generics"); + writeTypesForGroup("Application type usages", "types_from_usages"); writeCode("}"); writer.Close(); } - + private void writeUnityHeaders() { var prefix = (model.Package.BinaryImage.Bits == 32) ? "#define IS_32BIT\n" : ""; - writeCode(prefix + declGenerator.UnityHeader.GetHeaderText()); + writeCode(prefix + model.UnityHeader.GetHeaderText()); } - private void writeTypesForMethods(IEnumerable methods) { - foreach (var method in methods.Where(m => m.VirtualAddress.HasValue)) { - declGenerator.IncludeMethod(method); - writeCode(declGenerator.GenerateRemainingTypeDeclarations()); - } + private void writeTypesForGroup(string header, string group) { + writeSectionHeader(header); + foreach (var cppType in model.GetDependencyOrderedTypeGroup(group)) + writeCode(cppType.ToString()); } - - private void writeUsages() { - // Not supported in il2cpp <19 - if (model.Package.MetadataUsages == null) - return; - - foreach (var usage in model.Package.MetadataUsages) { - switch (usage.Type) { - case MetadataUsageType.Type: - case MetadataUsageType.TypeInfo: - var type = model.GetMetadataUsageType(usage); - declGenerator.IncludeType(type); - writeCode(declGenerator.GenerateRemainingTypeDeclarations()); - break; - case MetadataUsageType.MethodDef: - case MetadataUsageType.MethodRef: - var method = model.GetMetadataUsageMethod(usage); - declGenerator.IncludeMethod(method); - writeCode(declGenerator.GenerateRemainingTypeDeclarations()); - break; - } - } - } - + private void writeCode(string text) { - if (Compiler == CppCompiler.Type.MSVC) + if (model.TargetCompiler == CppCompilerType.MSVC) text = rgxGCCalign.Replace(text, @"__declspec(align($1))"); - if (Compiler == CppCompiler.Type.GCC) + if (model.TargetCompiler == CppCompilerType.GCC) text = rgxMSVCalign.Replace(text, @"__attribute__((aligned($1)))"); var lines = text.Replace("\r", "").Split('\n'); diff --git a/Il2CppInspector.Common/Outputs/IDAPythonScript.cs b/Il2CppInspector.Common/Outputs/IDAPythonScript.cs index bd8e011..4b51b97 100755 --- a/Il2CppInspector.Common/Outputs/IDAPythonScript.cs +++ b/Il2CppInspector.Common/Outputs/IDAPythonScript.cs @@ -10,28 +10,34 @@ using System.IO; using System.Text; using Il2CppInspector.Reflection; using Il2CppInspector.Cpp; -using Il2CppInspector.Cpp.UnityHeaders; +using Il2CppInspector.Model; namespace Il2CppInspector.Outputs { public class IDAPythonScript { - private readonly Il2CppModel model; + // TODO: Make this readonly when we've integrated with ApplicationModel + private AppModel model; private StreamWriter writer; - public UnityVersion UnityVersion; + // TODO: Remove when integrated with ApplicationModel private CppDeclarationGenerator declGenerator; - public IDAPythonScript(Il2CppModel model) => this.model = model; + public IDAPythonScript(AppModel model) => this.model = model; public void WriteScriptToFile(string outputFile) { - declGenerator = new CppDeclarationGenerator(model, UnityVersion); - UnityVersion = declGenerator.UnityVersion; + // 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.TypeCollection = 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); writeLine("# Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty"); - writeLine("# Target Unity version: " + declGenerator.UnityHeader.ToString()); + writeLine("# Target Unity version: " + model.UnityHeader); writeLine("print('Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty')"); writeSectionHeader("Preamble"); writePreamble(); @@ -50,7 +56,7 @@ namespace Il2CppInspector.Outputs writeLine("print('Script execution complete.')"); writer.Close(); } - + private void writePreamble() { writeLine( @"import idaapi @@ -81,27 +87,26 @@ typedef __int16 int16_t; typedef __int32 int32_t; typedef __int64 int64_t; "); - - var prefix = (model.Package.BinaryImage.Bits == 32) ? "#define IS_32BIT\n" : ""; - writeDecls(prefix + declGenerator.UnityHeader.GetHeaderText()); + // IL2CPP internal types + writeDecls(model.UnityHeaderText); } private void writeMethods() { writeSectionHeader("Method definitions"); - writeMethods(model.MethodsByDefinitionIndex); + writeMethods(model.ILModel.MethodsByDefinitionIndex); writeSectionHeader("Constructed generic methods"); - writeMethods(model.GenericMethods.Values); + writeMethods(model.ILModel.GenericMethods.Values); writeSectionHeader("Custom attributes generators"); - foreach (var method in model.AttributesByIndices.Values.Where(m => m.VirtualAddress.HasValue)) { + foreach (var method in model.ILModel.AttributesByIndices.Values.Where(m => m.VirtualAddress.HasValue)) { var address = method.VirtualAddress.Value.Start; writeName(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator"); writeComment(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator(CustomAttributesCache *)"); } writeSectionHeader("Method.Invoke thunks"); - foreach (var method in model.MethodInvokers.Where(m => m != null)) { + foreach (var method in model.ILModel.MethodInvokers.Where(m => m != null)) { var address = method.VirtualAddress.Start; writeName(address, method.Name); writeComment(address, method); @@ -113,11 +118,11 @@ typedef __int64 int64_t; declGenerator.IncludeMethod(method); writeDecls(declGenerator.GenerateRemainingTypeDeclarations()); var address = method.VirtualAddress.Value.Start; - writeTypedName(address, declGenerator.GenerateMethodDeclaration(method), declGenerator.GlobalNamer.GetName(method)); + writeTypedName(address, declGenerator.GenerateMethodDeclaration(method).ToSignatureString(), declGenerator.GlobalNamer.GetName(method)); writeComment(address, method); } } - + private static string stringToIdentifier(string str) { str = str.Substring(0, Math.Min(32, str.Length)); return str.ToCIdentifier(); @@ -140,21 +145,21 @@ typedef __int64 int64_t; return; } - - var stringType = declGenerator.AsCType(model.TypesByFullName["System.String"]); + + 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.GetMetadataUsageName(usage); - writeTypedName(address, stringType, $"StringLiteral_{stringToIdentifier(str)}"); + 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.GetMetadataUsageType(usage); + var type = model.ILModel.GetMetadataUsageType(usage); declGenerator.IncludeType(type); writeDecls(declGenerator.GenerateRemainingTypeDeclarations()); @@ -167,7 +172,7 @@ typedef __int64 int64_t; break; case MetadataUsageType.MethodDef: case MetadataUsageType.MethodRef: - var method = model.GetMetadataUsageMethod(usage); + var method = model.ILModel.GetMetadataUsageMethod(usage); declGenerator.IncludeMethod(method); writeDecls(declGenerator.GenerateRemainingTypeDeclarations()); @@ -220,6 +225,9 @@ typedef __int64 int64_t; writeLine("idc.parse_decls('''" + declString + "''')"); } + // TODO: Temporary compatibility function, remove when integrated with ApplicationModel + private void writeDecls(List types) => writeDecls(string.Join("\n", types.Select(t => t.ToString()))); + private void writeName(ulong address, string name) { writeLine($"SetName({address.ToAddressString()}, r'{name.ToEscapedString()}')"); } diff --git a/Il2CppInspector.Common/Reflection/Assembly.cs b/Il2CppInspector.Common/Reflection/Assembly.cs index 2afb698..20f31cb 100644 --- a/Il2CppInspector.Common/Reflection/Assembly.cs +++ b/Il2CppInspector.Common/Reflection/Assembly.cs @@ -1,6 +1,5 @@ /* - Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com - + Copyright 2017-2019 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ @@ -12,7 +11,7 @@ namespace Il2CppInspector.Reflection { public class Assembly { // IL2CPP-specific data - public Il2CppModel Model { get; } + public TypeModel Model { get; } public Il2CppImageDefinition ImageDefinition { get; } public Il2CppAssemblyDefinition AssemblyDefinition { get; } public Il2CppCodeGenModule ModuleDefinition { get; } @@ -37,7 +36,7 @@ namespace Il2CppInspector.Reflection { public TypeInfo GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName); // Initialize from specified assembly index in package - public Assembly(Il2CppModel model, int imageIndex) { + public Assembly(TypeModel model, int imageIndex) { Model = model; ImageDefinition = Model.Package.Images[imageIndex]; AssemblyDefinition = Model.Package.Assemblies[ImageDefinition.assemblyIndex]; diff --git a/Il2CppInspector.Common/Reflection/CustomAttributeData.cs b/Il2CppInspector.Common/Reflection/CustomAttributeData.cs index 6ed663b..25e8a12 100644 --- a/Il2CppInspector.Common/Reflection/CustomAttributeData.cs +++ b/Il2CppInspector.Common/Reflection/CustomAttributeData.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ @@ -15,7 +15,7 @@ namespace Il2CppInspector.Reflection public class CustomAttributeData { // IL2CPP-specific data - public Il2CppModel Model => AttributeType.Assembly.Model; + public TypeModel Model => AttributeType.Assembly.Model; public int Index { get; set; } // The type of the attribute diff --git a/Il2CppInspector.Common/Reflection/MethodInvoker.cs b/Il2CppInspector.Common/Reflection/MethodInvoker.cs index 45c4967..4f73a25 100644 --- a/Il2CppInspector.Common/Reflection/MethodInvoker.cs +++ b/Il2CppInspector.Common/Reflection/MethodInvoker.cs @@ -1,6 +1,5 @@ /* - Copyright 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com - + Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ @@ -48,7 +47,7 @@ namespace Il2CppInspector.Reflection } // The invokers use Object for all reference types, and SByte for booleans - private TypeInfo mapParameterType(Il2CppModel model, TypeInfo type) => type switch { + private TypeInfo mapParameterType(TypeModel model, TypeInfo type) => type switch { { IsValueType: false } => model.TypesByFullName["System.Object"], { FullName: "System.Boolean" } => model.TypesByFullName["System.SByte"], _ => type diff --git a/Il2CppInspector.Common/Reflection/Il2CppModel.cs b/Il2CppInspector.Common/Reflection/TypeModel.cs similarity index 99% rename from Il2CppInspector.Common/Reflection/Il2CppModel.cs rename to Il2CppInspector.Common/Reflection/TypeModel.cs index ddc61e1..d5db0c4 100644 --- a/Il2CppInspector.Common/Reflection/Il2CppModel.cs +++ b/Il2CppInspector.Common/Reflection/TypeModel.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2020 Robert Xiao - https://robertxiao.ca All rights reserved. @@ -12,7 +12,7 @@ using System.Linq; namespace Il2CppInspector.Reflection { - public class Il2CppModel + public class TypeModel { public Il2CppInspector Package { get; } public List Assemblies { get; } = new List(); @@ -62,7 +62,7 @@ namespace Il2CppInspector.Reflection && m.GetGenericArguments().SequenceEqual(typeArguments)); // Create type model - public Il2CppModel(Il2CppInspector package) { + public TypeModel(Il2CppInspector package) { Package = package; TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length]; TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count]; diff --git a/Il2CppInspector.Common/Reflection/TypeRef.cs b/Il2CppInspector.Common/Reflection/TypeRef.cs index 4d18d9f..3aeae24 100644 --- a/Il2CppInspector.Common/Reflection/TypeRef.cs +++ b/Il2CppInspector.Common/Reflection/TypeRef.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2020 Robert Xiao - https://robertxiao.ca All rights reserved. @@ -11,7 +11,7 @@ namespace Il2CppInspector.Reflection /// A class which lazily refers to a TypeInfo instance /// internal class TypeRef { - private Il2CppModel model; + private TypeModel model; private int referenceIndex = -1; private int definitionIndex = -1; private TypeInfo typeInfo = null; @@ -28,10 +28,10 @@ namespace Il2CppInspector.Reflection } } - public static TypeRef FromReferenceIndex(Il2CppModel model, int index) + public static TypeRef FromReferenceIndex(TypeModel model, int index) => new TypeRef { model = model, referenceIndex = index }; - public static TypeRef FromDefinitionIndex(Il2CppModel model, int index) + public static TypeRef FromDefinitionIndex(TypeModel model, int index) => new TypeRef { model = model, definitionIndex = index }; public static TypeRef FromTypeInfo(TypeInfo type) diff --git a/Il2CppInspector.GUI/App.xaml.cs b/Il2CppInspector.GUI/App.xaml.cs index 0db77eb..ab20540 100644 --- a/Il2CppInspector.GUI/App.xaml.cs +++ b/Il2CppInspector.GUI/App.xaml.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using System.Windows; using Il2CppInspector; +using Il2CppInspector.Model; using Il2CppInspector.Reflection; using Inspector = Il2CppInspector.Il2CppInspector; @@ -20,7 +21,7 @@ namespace Il2CppInspectorGUI { private Metadata metadata; - public List Il2CppModels { get; } = new List(); + public List AppModels { get; } = new List(); public Exception LastException { get; private set; } @@ -82,7 +83,7 @@ namespace Il2CppInspectorGUI } // Multi-image binaries may contain more than one Il2Cpp image - Il2CppModels.Clear(); + AppModels.Clear(); foreach (var image in stream.Images) { OnStatusUpdate?.Invoke(this, "Analyzing IL2CPP data"); @@ -93,14 +94,18 @@ namespace Il2CppInspectorGUI var inspector = new Inspector(binary, metadata); // Build type model - OnStatusUpdate?.Invoke(this, "Building type model"); - Il2CppModels.Add(new Il2CppModel(inspector)); + OnStatusUpdate?.Invoke(this, "Building .NET type model"); + var typeModel = new TypeModel(inspector); + + // Initialize (but don't build) application model + // We will build the model after the user confirms the Unity version and target compiler + AppModels.Add(new AppModel(typeModel)); } } // Unsupported architecture; ignore it catch (NotImplementedException) { } } - if (!Il2CppModels.Any()) { + if (!AppModels.Any()) { throw new InvalidOperationException("Could not auto-detect any IL2CPP binary images in the file. This may mean the binary file is packed, encrypted or obfuscated, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data. Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!"); } return true; diff --git a/Il2CppInspector.GUI/MainWindow.xaml.cs b/Il2CppInspector.GUI/MainWindow.xaml.cs index 8e03635..d7224f9 100644 --- a/Il2CppInspector.GUI/MainWindow.xaml.cs +++ b/Il2CppInspector.GUI/MainWindow.xaml.cs @@ -25,6 +25,7 @@ using Microsoft.Win32; using Il2CppInspector; using Il2CppInspector.Cpp; using Il2CppInspector.GUI; +using Il2CppInspector.Model; using Il2CppInspector.Outputs; using Il2CppInspector.Reflection; using Ookii.Dialogs.Wpf; @@ -112,7 +113,7 @@ namespace Il2CppInspectorGUI // Binary loaded successfully areaBusyIndicator.Visibility = Visibility.Hidden; - lstImages.ItemsSource = app.Il2CppModels; + lstImages.ItemsSource = app.AppModels; lstImages.SelectedIndex = 0; } else { @@ -144,7 +145,7 @@ namespace Il2CppInspectorGUI // Package loaded successfully areaBusyIndicator.Visibility = Visibility.Hidden; - lstImages.ItemsSource = app.Il2CppModels; + lstImages.ItemsSource = app.AppModels; lstImages.SelectedIndex = 0; } else { @@ -175,10 +176,10 @@ namespace Il2CppInspectorGUI } // Get selected image - var model = (Il2CppModel)((ListBox)sender).SelectedItem; + var model = (AppModel)((ListBox)sender).SelectedItem; // Get namespaces - var namespaces = model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace).Select(n => n.Key); + var namespaces = model.ILModel.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace).Select(n => n.Key); // Break namespaces down into a tree var namespaceTree = deconstructNamespaces(namespaces); @@ -204,7 +205,7 @@ namespace Il2CppInspectorGUI var prevCppSelection = cboCppUnityVersion.SelectedItem; cboUnityVersion.Items.Clear(); cboCppUnityVersion.Items.Clear(); - foreach (var version in UnityHeader.GuessHeadersForModel(model)) { + foreach (var version in UnityHeader.GuessHeadersForModel(model.ILModel)) { cboUnityVersion.Items.Add(version); cboCppUnityVersion.Items.Add(version); } @@ -287,7 +288,7 @@ namespace Il2CppInspectorGUI /// Perform export /// private async void BtnExport_OnClick(object sender, RoutedEventArgs e) { - var model = (Il2CppModel) lstImages.SelectedItem; + var model = (AppModel) lstImages.SelectedItem; var unityPath = txtUnityPath.Text; var unityAssembliesPath = txtUnityScriptPath.Text; @@ -318,7 +319,7 @@ namespace Il2CppInspectorGUI // Get options var excludedNamespaces = constructExcludedNamespaces((IEnumerable) trvNamespaces.ItemsSource); - var writer = new CSharpCodeStubs(model) { + var writer = new CSharpCodeStubs(model.ILModel) { ExcludedNamespaces = excludedNamespaces.ToList(), SuppressMetadata = cbSuppressMetadata.IsChecked == true, MustCompile = cbMustCompile.IsChecked == true @@ -404,14 +405,14 @@ namespace Il2CppInspectorGUI var outFile = scriptSaveFileDialog.FileName; - txtBusyStatus.Text = "Generating IDAPython script..."; areaBusyIndicator.Visibility = Visibility.Visible; var selectedVersion = ((UnityHeader)cboUnityVersion.SelectedItem)?.MinVersion; await Task.Run(() => { - var idaWriter = new IDAPythonScript(model) { - UnityVersion = selectedVersion, - }; - idaWriter.WriteScriptToFile(outFile); + OnStatusUpdate(this, "Building C++ application model"); + model.Build(selectedVersion, CppCompilerType.GCC); + + OnStatusUpdate(this, "Generating IDAPython script"); + new IDAPythonScript(model).WriteScriptToFile(outFile); }); break; @@ -430,16 +431,15 @@ namespace Il2CppInspectorGUI var cppOutFile = cppSaveFileDialog.FileName; - txtBusyStatus.Text = "Generating C++ scaffolding..."; areaBusyIndicator.Visibility = Visibility.Visible; var selectedCppUnityVersion = ((UnityHeader)cboCppUnityVersion.SelectedItem)?.MinVersion; - var cppCompiler = (CppCompiler.Type) Enum.Parse(typeof(CppCompiler.Type), cboCppCompiler.SelectionBoxItem.ToString()); + var cppCompiler = (CppCompilerType) Enum.Parse(typeof(CppCompilerType), cboCppCompiler.SelectionBoxItem.ToString()); await Task.Run(() => { - var cppWriter = new CppScaffolding(model) { - UnityVersion = selectedCppUnityVersion, - Compiler = cppCompiler - }; - cppWriter.WriteCppToFile(cppOutFile); + OnStatusUpdate(this, "Building C++ application model"); + model.Build(selectedCppUnityVersion, cppCompiler); + + OnStatusUpdate(this, "Generating C++ scaffolding"); + new CppScaffolding(model).WriteCppToFile(cppOutFile); }); break; } diff --git a/Il2CppTests/TestCppTypes.cs b/Il2CppTests/TestCppTypeDeclarations.cs similarity index 62% rename from Il2CppTests/TestCppTypes.cs rename to Il2CppTests/TestCppTypeDeclarations.cs index 3ffa1ce..ca8678e 100644 --- a/Il2CppTests/TestCppTypes.cs +++ b/Il2CppTests/TestCppTypeDeclarations.cs @@ -19,7 +19,7 @@ namespace Il2CppInspector public partial class FixedTests { [Test] - public void TestCppTypes() { + public void TestCppTypeDeclarations() { // NOTE: This test doesn't check for correct results, only that parsing doesn't fail! var unityAllHeaders = UnityHeader.GetAllHeaders(); @@ -30,16 +30,15 @@ namespace Il2CppInspector // Ensure we can interpret every header from every version of Unity without errors // This will throw InvalidOperationException if there is a problem foreach (var unityHeader in unityAllHeaders) { - var cppTypes = CppTypes.FromUnityHeaders(unityHeader); - + var cppTypes = CppTypeCollection.FromUnityHeaders(unityHeader); foreach (var cppType in cppTypes.Types) - Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value.ToString("o") + "\n"); + Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value.ToString("o")); } // Do a few sanity checks taken from real applications // NOTE: Does not provide full code coverage! - var cppTypes2 = CppTypes.FromUnityVersion(new UnityVersion("2019.3.1f1"), 64); + var cppTypes2 = CppTypeCollection.FromUnityVersion(new UnityVersion("2019.3.1f1"), 64); CppComplexType ct; CppField field; @@ -82,6 +81,39 @@ namespace Il2CppInspector field = fields["vtable"]; Assert.AreEqual(field.OffsetBytes, 0x128); + + // Bitfield + ct = (CppComplexType) cppTypes2["Il2CppType"]; + + field = ct.Fields[0xB * 8 + 7].First(); + + Assert.AreEqual(field.Name, "pinned"); + + // Nested fields + ct = (CppComplexType) cppTypes2["Il2CppWin32Decimal"]; + fields = ct.Flattened; + + field = fields[0x08].First(); + + Assert.AreEqual(field.Name, "lo32"); + + field = fields[0x08].Last(); + + Assert.AreEqual(field.Name, "lo64"); + + field = fields[0x0C].First(); + + Assert.AreEqual(field.Name, "mid32"); + + // Pointer alias + var alias = (CppAlias) cppTypes2.GetType("Il2CppHString"); + + Assert.AreEqual(alias.ElementType.GetType(), typeof(CppPointerType)); + Assert.AreEqual(alias.ElementType.Name, "Il2CppHString__ *"); + + // Typedef struct with no tag + Assert.True(cppTypes2.Types.ContainsKey("Il2CppGenericMethodIndices")); + Assert.True(((CppComplexType)cppTypes2["Il2CppGenericMethodIndices"]).ComplexValueType == ComplexValueType.Struct); } } } diff --git a/Il2CppTests/TestGenerics.cs b/Il2CppTests/TestGenerics.cs index 0ea9be3..6708c82 100644 --- a/Il2CppTests/TestGenerics.cs +++ b/Il2CppTests/TestGenerics.cs @@ -1,5 +1,5 @@ /* - Copyright 2019-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2019-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ @@ -25,7 +25,7 @@ namespace Il2CppInspector // Build model var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\GenericTypes-ARMv7.so", testPath + @"\global-metadata.dat"); - var model = new Il2CppModel(inspectors[0]); + var model = new TypeModel(inspectors[0]); var asm = model.GetAssembly("GenericTypes.dll"); diff --git a/Il2CppTests/TestNames.cs b/Il2CppTests/TestNames.cs index 28ed02a..478c865 100644 --- a/Il2CppTests/TestNames.cs +++ b/Il2CppTests/TestNames.cs @@ -1,5 +1,5 @@ /* - Copyright 2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2019 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ @@ -23,7 +23,7 @@ namespace Il2CppInspector // Build model var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\References-ARMv7.so", testPath + @"\global-metadata.dat"); - var model = new Il2CppModel(inspectors[0]); + var model = new TypeModel(inspectors[0]); var asm = model.GetAssembly("References.dll"); diff --git a/Il2CppTests/TestRunner.cs b/Il2CppTests/TestRunner.cs index d239b6b..18a7fca 100644 --- a/Il2CppTests/TestRunner.cs +++ b/Il2CppTests/TestRunner.cs @@ -5,11 +5,11 @@ */ using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using Il2CppInspector.Reflection; +using Il2CppInspector.Model; using Il2CppInspector.Outputs; +using Il2CppInspector.Reflection; using NUnit.Framework; namespace Il2CppInspector @@ -20,6 +20,8 @@ namespace Il2CppInspector private void runTest(string testPath) { // Android var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so"; + if (!File.Exists(testFile)) + testFile = testPath + @"\libil2cpp.so"; // Windows if (!File.Exists(testFile)) testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll"; @@ -28,9 +30,6 @@ namespace Il2CppInspector // iOS if (!File.Exists(testFile)) testFile = testPath + @"\" + Path.GetFileName(testPath); - // Android - if (!File.Exists(testFile)) - testFile = testPath + @"\libil2cpp.so"; var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat"); @@ -44,7 +43,8 @@ namespace Il2CppInspector // Dump each image in the binary separately int i = 0; foreach (var il2cpp in inspectors) { - var model = new Il2CppModel(il2cpp); + var model = new TypeModel(il2cpp); + var appModel = new AppModel(model).Build(); var nameSuffix = i++ > 0 ? "-" + (i - 1) : ""; new CSharpCodeStubs(model) { @@ -53,10 +53,10 @@ namespace Il2CppInspector MustCompile = true }.WriteSingleFile(testPath + $@"\test-result{nameSuffix}.cs"); - new IDAPythonScript(model) + new IDAPythonScript(appModel) .WriteScriptToFile(testPath + $@"\test-ida-result{nameSuffix}.py"); - new CppScaffolding(model) + new CppScaffolding(appModel) .WriteCppToFile(testPath + $@"\test-result{nameSuffix}.h"); } @@ -65,8 +65,8 @@ namespace Il2CppInspector var suffix = (i > 0 ? "-" + i : ""); compareFiles(testPath, suffix + ".cs", $"test-result{suffix}.cs"); - compareFiles(testPath, suffix + ".py", $"test-ida-result{suffix}.py"); compareFiles(testPath, suffix + ".h", $"test-result{suffix}.h"); + compareFiles(testPath, suffix + ".py", $"test-ida-result{suffix}.py"); } } diff --git a/README.md b/README.md index 77358c5..65859f3 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Main features: * Create IL2CPP binaries from arbitrary C# source code without a Unity project -* .NET Reflection-style API to allow you to query the IL2CPP type model, easily create new output modules and integrate Il2CppInspector with your own applications +* Three major APIs for use in custom static analysis projects * Supports all major file formats and processor architectures @@ -48,10 +48,14 @@ Nice to have: * Static symbol table scanning for Mach-O binaries * Automatically defeats certain basic obfuscation methods -Reusable class libraries: +Reusable class library APIs: + +* **Il2CppInspector** - low-level access to the binary image and metadata +* **TypeModel** - high-level .NET Reflection-like query API for all of the .NET types in the source project as a tree model +* **ApplicationModel** - access to all of the C++ types and methods, plus the IL2CPP API exports, with detailed address and offset data and mappings to their .NET equivalents + + Use these APIs to easily query IL2CPP types, create new output modules and integrate Il2CppInspector with your own static analysis applications. -* **Il2CppInspector** for low-level access to IL2CPP binaries and metadata -* **Il2CppModel** for high-level .NET Reflection-style access to IL2CPP types and data as a tree model * Test chassis for automated integration testing of IL2CPP binaries Class library targets .NET Standard 2.1. Application targets .NET Core 3.0. Built with Visual Studio 2019. @@ -207,9 +211,17 @@ The auto-generated tests generate a file in the test IL2CPP binary's folder call To learn more about this feature, see the section entitled **Using Il2CppInspector to generate IL2CPP code** in [IL2CPP Reverse Engineering Part 1](https://katyscode.wordpress.com/2020/06/24/il2cpp-part-1/). -### Using the class library +### Using the APIs for programmatic analysis -To utilize Il2CppInspector in your own programs, add a reference to `Il2CppInspector.Common.dll` and add a using statement for the namespace `Il2CppInspector.Reflection`. See the source code for further details. +To utilize Il2CppInspector in your own projects, add a reference to `Il2CppInspector.Common.dll`. + +Include the following `using` directives: + +* `using Il2CppInspector` to use `Il2CppInspector`. +* `using Il2CppInspector.Reflection` to use `TypeModel`. +* `using Il2CppInspector.Model` to use `ApplicationModel`. + +See the source code for further details. ### Version support