From d8aaba375ebc7aa642ddd284a3e807cfa98ada14 Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Thu, 25 Jun 2020 11:02:21 +0200 Subject: [PATCH] C++: Parse typedef struct definitions and nested types as fields --- Il2CppInspector.Common/Cpp/CppType.cs | 141 ++++++++++++++++++-------- 1 file changed, 98 insertions(+), 43 deletions(-) diff --git a/Il2CppInspector.Common/Cpp/CppType.cs b/Il2CppInspector.Common/Cpp/CppType.cs index a69c2ab..91e31dd 100644 --- a/Il2CppInspector.Common/Cpp/CppType.cs +++ b/Il2CppInspector.Common/Cpp/CppType.cs @@ -5,6 +5,7 @@ */ using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -18,52 +19,56 @@ using Il2CppInspector.Reflection; namespace Il2CppInspector.Cpp { - public abstract class CppType + // A type with no fields + public class CppType { // The name of the type - public string Name { get; } + public string Name { get; internal set; } - // Calculate the size of the C++ type in bytes - public abstract int GetSize(); + // The size of the C++ type in bytes + public int Size { get; protected set; } - protected CppType(string name) => Name = name; + public CppType(string name, int size) { + Name = name; + Size = size; + } } - // A non-struct non-class type - public class CppPrimitiveType : CppType - { - private readonly int size; - - public override int GetSize() => size; - - public CppPrimitiveType(string name, int size) : base(name) => this.size = size; - } - - // A struct or class type + // A struct, union or class type (type with fields) public class CppComplexType : CppType { // Dictionary of byte offset in the type to each field - public Dictionary Fields { get; } = new Dictionary(); + // Unions and bitfields can have more than one field at the same offset + public Dictionary> Fields { get; } = new Dictionary>(); - // Calculate the size by summing the size of each field's type - public override int GetSize() => Fields.Values.Select(f => f.Type.GetSize()).Sum(); + public CppComplexType() : base("", 0) {} - public CppComplexType(string name) : base(name) {} + // Add a field to the type. Returns the offset of the field in the type + public int AddField(CppField field) { + // TODO: Use InheritanceStyleEnum to determine whether the field is embedded or a pointer + field.Offset = Size; - // TODO: Add fields + if (Fields.ContainsKey(Size)) + Fields[Size].Add(field); + else + Fields.Add(Size, new List { field }); + + Size += field.Type.Size; + return Size; + } } // A field in a C++ type public struct CppField { // The name of the field - public string Name { get; } + public string Name { get; set; } // The offset of the field into the type - public int Offset { get; } + public int Offset { get; set; } // The type of the field - public CppType Type { get; } + public CppType Type { get; set; } } // A collection of C++ types @@ -77,16 +82,16 @@ namespace Il2CppInspector.Cpp // Architecture width in bytes (4 bytes for 32-bit or 8 bytes for 64-bit, to determine pointer sizes) public int WordSize { get; } - private static readonly List primitiveTypes = new List { - new CppPrimitiveType("uint8_t", 1), - new CppPrimitiveType("uint16_t", 2), - new CppPrimitiveType("uint32_t", 4), - new CppPrimitiveType("uint64_t", 8), - new CppPrimitiveType("int8_t", 1), - new CppPrimitiveType("int16_t", 2), - new CppPrimitiveType("int32_t", 4), - new CppPrimitiveType("int64_t", 8), - new CppPrimitiveType("char", 1) + private static readonly List primitiveTypes = new List { + new CppType("uint8_t", 1), + new CppType("uint16_t", 2), + new CppType("uint32_t", 4), + new CppType("uint64_t", 8), + new CppType("int8_t", 1), + new CppType("int16_t", 2), + new CppType("int32_t", 4), + new CppType("int64_t", 8), + new CppType("char", 1) }; public CppTypes(int wordSize) { @@ -94,8 +99,8 @@ namespace Il2CppInspector.Cpp types = primitiveTypes.ToDictionary(t => t.Name, t => (CppType) t); // This is all compiler-dependent, let's hope for the best! - types.Add("uintptr_t", new CppPrimitiveType("uintptr_t", WordSize)); - types.Add("size_t", new CppPrimitiveType("size_t", WordSize)); + types.Add("uintptr_t", new CppType("uintptr_t", WordSize)); + types.Add("size_t", new CppType("size_t", WordSize)); } // Parse a block of C++ source code, adding any types found @@ -105,24 +110,29 @@ namespace Il2CppInspector.Cpp var rgxTypedef = new Regex(@"typedef (\S+) (\S+);"); var rgxTypedefFnPtr = new Regex(@"typedef\s+\S+\s*\(\s*\*(\S+)\)\s*\(.*\);"); + var currentType = new Stack(); string line; + while ((line = lines.ReadLine()) != null) { // Sanitize line = line.Trim(); line = line.Replace(" const ", " "); + if (line.StartsWith("const ")) + line = line.Substring(6); + // Function pointer // typedef (*)(); var typedef = rgxTypedefFnPtr.Match(line); if (typedef.Success) { var alias = typedef.Groups[1].Captures[0].ToString(); - - Debug.WriteLine($"[TYPEDEF] {line} -- Adding method pointer typedef to {alias}"); - types.Add(alias, types["uintptr_t"]); + + Debug.WriteLine($"[TYPEDEF PTR ] {line} -- Adding method pointer typedef to {alias}"); continue; } + // Alias // typedef ; typedef = rgxTypedef.Match(line); if (typedef.Success) { @@ -135,18 +145,63 @@ namespace Il2CppInspector.Cpp } // Regular aliases else { - Debug.WriteLine($"[TYPEDEF] {line} -- Adding typedef from {existingType} to {alias}"); - types.Add(alias, types[existingType]); + + Debug.WriteLine($"[TYPEDEF ALIAS] {line} -- Adding typedef from {existingType} to {alias}"); continue; } } - Debug.WriteLine("[IGNORE ] " + line); + // Start of struct + // typedef struct + if (line.StartsWith("typedef struct") && line.IndexOf(";", StringComparison.Ordinal) == -1) { + currentType.Push(new CppComplexType()); + + Debug.WriteLine($"[STRUCT START ] {line}"); + continue; + } + + // Nested complex field + // struct + // union + var words = line.Split(' '); + if ((words[0] == "union" || words[0] == "struct") && words.Length <= 2) { + currentType.Push(new CppComplexType()); + + Debug.WriteLine($"[FIELD START ] {line}"); + continue; + } + + // End of complex field or complex type + // end of [typedef] struct/union + if (line.StartsWith("}") && line.EndsWith(";") && currentType.Count > 0) { + var ct = currentType.Pop(); + var name = line[1..^1].Trim(); + + // End of top-level typedef, so it's a type name + if (currentType.Count == 0) { + ct.Name = name; + types.Add(ct.Name, ct); + + Debug.WriteLine($"[STRUCT END ] {line} -- {name}"); + } + + // Otherwise it's a field name in the current type + else { + var parent = currentType.Peek(); + parent.AddField(new CppField { Name = name, Type = ct }); + + Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {name}"); + } + + continue; + } + + Debug.WriteLine("[IGNORE ] " + line); } } - // Generate a populated CppTypes object from an + // Generate a populated CppTypes object from a set of Unity headers public static CppTypes FromUnityHeaders(UnityVersion version) { var cppTypes = new CppTypes(8);