diff --git a/Il2CppInspector.Common/Cpp/CppApplicationModel.cs b/Il2CppInspector.Common/Cpp/CppApplicationModel.cs index 93de803..97227ed 100644 --- a/Il2CppInspector.Common/Cpp/CppApplicationModel.cs +++ b/Il2CppInspector.Common/Cpp/CppApplicationModel.cs @@ -4,6 +4,7 @@ All rights reserved. */ +using System; using System.Collections.Generic; using System.Linq; using Il2CppInspector.Cpp.UnityHeaders; @@ -40,20 +41,30 @@ namespace Il2CppInspector.Cpp public List Exports { get; } public int WordSize => ILModel.Package.BinaryImage.Bits; - public CppApplicationModel(Il2CppModel model, UnityVersion unityVersion, CppCompiler.Type compiler = CppCompiler.Type.BinaryFormat) { + 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; - UnityVersion = unityVersion; + + 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.FromUnityVersion(unityVersion, WordSize); + Types = CppTypes.FromUnityHeaders(unityHeader, WordSize); // TODO: Process every type in the binary - var decl = new CppDeclarationGenerator(ILModel, UnityVersion); + //var decl = new CppDeclarationGenerator(this); - // Add addresses of IL2CPP API function exports - Exports = model.Package.Binary.Image.GetExports().ToList(); } } } diff --git a/Il2CppInspector.Common/Cpp/CppField.cs b/Il2CppInspector.Common/Cpp/CppField.cs index 65ecb43..adf9a96 100644 --- a/Il2CppInspector.Common/Cpp/CppField.cs +++ b/Il2CppInspector.Common/Cpp/CppField.cs @@ -10,10 +10,16 @@ namespace Il2CppInspector.Cpp public class CppField { // The name of the field - public string Name { get; set; } + public string Name { get; } + + // The type of the field + public CppType Type { get; } // The offset of the field into the type - public int Offset { get; set; } + public int Offset { get; internal set; } + + // The size of the field in bits + public int BitfieldSize { get; } // The offset of the field into the type in bytes public int OffsetBytes => Offset / 8; @@ -23,17 +29,18 @@ namespace Il2CppInspector.Cpp public int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0); - // The size of the field in bits - public int BitfieldSize { get; set; } - // The LSB of the bitfield public int BitfieldLSB => Offset % 8; // The MSB of the bitfield public int BitfieldMSB => BitfieldLSB + Size - 1; - // The type of the field - public CppType Type { get; set; } + // Initialize field + public CppField(string name, CppType type, int bitfieldSize = 0) { + Name = name; + Type = type; + BitfieldSize = bitfieldSize; + } // C++ representation of field public virtual string ToString(string format = "") { @@ -67,7 +74,9 @@ namespace Il2CppInspector.Cpp public class CppEnumField : CppField { // The value of this key name - public ulong Value { get; set; } + public ulong Value { get; } + + public CppEnumField(string name, CppType type, ulong 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 f178028..34dbe25 100644 --- a/Il2CppInspector.Common/Cpp/CppType.cs +++ b/Il2CppInspector.Common/Cpp/CppType.cs @@ -16,8 +16,8 @@ using Il2CppInspector.Cpp.UnityHeaders; namespace Il2CppInspector.Cpp { - // Compound type - public enum CompoundType + // Value type with fields + public enum ComplexValueType { Struct, Union, @@ -50,6 +50,11 @@ 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}; + public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : $"/* {Name} */"; public override string ToString() => ToString(); @@ -173,12 +178,7 @@ namespace Il2CppInspector.Cpp var baseOffset = field.Offset; var fields = ct.Flattened.Fields.Select(kl => new { Key = kl.Key + baseOffset, - Value = kl.Value.Select(f => new CppField { - Name = f.Name, - Type = f.Type, - BitfieldSize = f.BitfieldSize, - Offset = f.Offset + baseOffset - }).ToList() + Value = kl.Value.Select(f => new CppField(f.Name, f.Type, f.BitfieldSize) { Offset = f.Offset + baseOffset }).ToList() }).ToDictionary(kv => kv.Key, kv => kv.Value); flattened = new SortedDictionary>(flattened.Union(fields).ToDictionary(kv => kv.Key, kv => kv.Value)); @@ -211,17 +211,17 @@ namespace Il2CppInspector.Cpp } // The compound type - public CompoundType CompoundType; + public ComplexValueType ComplexValueType; // Dictionary of byte offset in the type to each field // Unions and bitfields can have more than one field at the same offset public SortedDictionary> Fields { get; internal set; } = new SortedDictionary>(); - public CppComplexType(CompoundType compoundType) : base("", 0) => CompoundType = compoundType; + 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 => - CompoundType == CompoundType.Union + 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; @@ -231,7 +231,7 @@ namespace Il2CppInspector.Cpp // 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 - field.Offset = CompoundType == CompoundType.Struct ? Size : 0; + field.Offset = ComplexValueType == ComplexValueType.Struct ? Size : 0; // If we just came out of a bitfield, move to the next byte if necessary if (field.BitfieldSize == 0 && field.Offset % 8 != 0) @@ -249,25 +249,23 @@ namespace Il2CppInspector.Cpp 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); + // 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(CompoundType == CompoundType.Struct ? "struct " : CompoundType == CompoundType.Enum? "enum " : "union "); + sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union "); sb.Append(Name + (Name.Length > 0 ? " " : "")); - var delimiter = CompoundType == CompoundType.Enum ? "," : ";"; - 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')) + delimiter); - - // Chop off final comma - if (CompoundType == CompoundType.Enum) - sb = sb.Remove(sb.Length - 1, 1); + sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ";"); sb.Append($"\n}}{(Name.Length > 0? " " + Name : "")}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); } @@ -279,4 +277,39 @@ namespace Il2CppInspector.Cpp return sb.ToString(); } } + + // Enumeration type + public class CppEnumType : CppComplexType + { + // The underlying type of the enum + public CppType UnderlyingType { get; } + + public override int Size => UnderlyingType.Size; + + public CppEnumType(CppType underlyingType) : base(ComplexValueType.Enum) => UnderlyingType = underlyingType; + + public void AddField(string name, ulong value) => AddField(new CppEnumField(name, UnderlyingType, value)); + + public override string ToString(string format = "") { + var sb = new StringBuilder(); + + sb.Append($"typedef 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};"); + } + + return sb.ToString(); + } + } } diff --git a/Il2CppInspector.Common/Cpp/CppTypes.cs b/Il2CppInspector.Common/Cpp/CppTypes.cs index 718369d..589fbca 100644 --- a/Il2CppInspector.Common/Cpp/CppTypes.cs +++ b/Il2CppInspector.Common/Cpp/CppTypes.cs @@ -198,7 +198,7 @@ namespace Il2CppInspector.Cpp if (externDecl.Success) { var declType = externDecl.Groups[1].Captures[0].ToString(); - Types.Add(declType, new CppComplexType(CompoundType.Struct) {Name = declType}); + Types.Add(declType, CppType.NewStruct(declType)); Debug.WriteLine($"[EXTERN DECL ] {line}"); continue; @@ -213,7 +213,7 @@ namespace Il2CppInspector.Cpp // Sometimes we might get multiple forward declarations for the same type if (!Types.ContainsKey(declType)) - Types.Add(declType, new CppComplexType(CompoundType.Struct) {Name = 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)) @@ -261,7 +261,7 @@ namespace Il2CppInspector.Cpp // typedef struct if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1 && currentType.Count == 0) { - currentType.Push(new CppComplexType(CompoundType.Struct)); + currentType.Push(CppType.NewStruct()); if (line.StartsWith("struct ")) currentType.Peek().Name = line.Split(' ')[1]; @@ -273,7 +273,7 @@ namespace Il2CppInspector.Cpp // Start of union // typedef union if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) { - currentType.Push(new CppComplexType(CompoundType.Union)); + currentType.Push(CppType.NewUnion()); Debug.WriteLine($"\n[UNION START ] {line}"); continue; @@ -282,7 +282,7 @@ namespace Il2CppInspector.Cpp // Start of enum // typedef enum if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) { - currentType.Push(new CppComplexType(CompoundType.Enum)); + currentType.Push(NewDefaultEnum()); nextEnumValue = 0; Debug.WriteLine($"\n[ENUM START ] {line}"); @@ -294,7 +294,7 @@ namespace Il2CppInspector.Cpp // union var words = line.Split(' '); if ((words[0] == "union" || words[0] == "struct") && words.Length <= 2) { - currentType.Push(new CppComplexType(words[0] == "struct"? CompoundType.Struct : CompoundType.Union)); + currentType.Push(words[0] == "struct" ? CppType.NewStruct() : CppType.NewUnion()); Debug.WriteLine($"[FIELD START ] {line}"); continue; @@ -339,7 +339,7 @@ namespace Il2CppInspector.Cpp // Otherwise it's a field name in the current type else { var parent = currentType.Peek(); - parent.AddField(new CppField { Name = name, Type = ct }); + parent.AddField(name, ct, alignment); Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {name}"); } @@ -354,7 +354,7 @@ namespace Il2CppInspector.Cpp var name = fieldFnPtr.Groups[2].Captures[0].ToString(); var ct = currentType.Peek(); - ct.AddField(new CppField {Name = name, Type = fnPtrType}, alignment); + ct.AddField(name, fnPtrType, alignment); Debug.WriteLine($"[FIELD FNPTR ] {line} -- {name}"); continue; @@ -395,7 +395,7 @@ namespace Il2CppInspector.Cpp if (arraySize > 0) type = type.AsArray(arraySize); - ct.AddField(new CppField {Name = name, Type = type, BitfieldSize = bitfield}, alignment); + ct.AddField(name, type, alignment, bitfield); if (bitfield == 0) Debug.WriteLine($"[FIELD {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- {name}"); @@ -423,7 +423,7 @@ namespace Il2CppInspector.Cpp } var ct = currentType.Peek(); - ct.AddField(new CppEnumField {Name = name, Type = WordSize == 32 ? Types["uint32_t"] : Types["uint64_t"], Value = value}); + ((CppEnumType) ct).AddField(name, value); Debug.WriteLine($"[ENUM VALUE ] {line} -- {name} = {value}"); continue; @@ -468,6 +468,14 @@ namespace Il2CppInspector.Cpp // Add a type externally public void Add(CppType type) => 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)); + + // Create an empty enum with the default underlying type for the architecture (32 or 64-bit) + public CppEnumType NewDefaultEnum() => CppType.NewEnum(Types["int"]); + // Generate a populated CppTypes object from a set of Unity headers public static CppTypes FromUnityVersion(UnityVersion version, int wordSize = 32) => FromUnityHeaders(UnityHeader.GetHeaderForVersion(version), wordSize); diff --git a/Il2CppTests/TestCppTypes.cs b/Il2CppTests/TestCppTypes.cs index ce84694..3ffa1ce 100644 --- a/Il2CppTests/TestCppTypes.cs +++ b/Il2CppTests/TestCppTypes.cs @@ -33,7 +33,7 @@ namespace Il2CppInspector var cppTypes = CppTypes.FromUnityHeaders(unityHeader); foreach (var cppType in cppTypes.Types) - Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value + "\n"); + Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value.ToString("o") + "\n"); } // Do a few sanity checks taken from real applications