diff --git a/Il2CppInspector.Common/Outputs/CppDeclarations.cs b/Il2CppInspector.Common/Outputs/CppDeclarations.cs index 6deed2c..c7a55a5 100644 --- a/Il2CppInspector.Common/Outputs/CppDeclarations.cs +++ b/Il2CppInspector.Common/Outputs/CppDeclarations.cs @@ -15,14 +15,19 @@ using System.Text.RegularExpressions; namespace Il2CppInspector.Outputs { + // Class for generating C header declarations from Reflection objects (TypeInfo, etc.) public class CppDeclarations { private readonly Il2CppModel model; + // Version number and header file to generate structures for public UnityVersion UnityVersion { get; } public UnityHeader UnityHeader { get; } // 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 enum InheritanceStyleEnum { C, // Inheritance structs use C syntax, and will automatically choose MSVC or GCC based on inferred compiler. @@ -59,6 +64,7 @@ namespace Il2CppInspector.Outputs } } + // C type declaration used to name variables of the given C# type public string AsCType(TypeInfo ti) { // IsArray case handled by TypeNamer.GetName if (ti.IsByRef || ti.IsPointer) { @@ -90,9 +96,19 @@ namespace Il2CppInspector.Outputs } #region Field Struct Generation + /* Generating field structures (structures for the fields of a given type) occurs in two passes. + * In the first pass (VisitFieldStructs), we walk over a type and all of the types that the resulting structure would depend on. + * In the second pass (GenerateVisitedFieldStructs), we generate all type structures in the necessary order. + * (For example: structures for value types must precede any usage of those value types for layout reasons). + */ + + // A cache of field structures that have already been generated, to eliminate duplicate definitions private readonly HashSet VisitedFieldStructs = new HashSet(); + + // A queue of field structures that need to be generated. private readonly List TodoFieldStructs = new List(); + // Walk over dependencies of the given type, to figure out what field structures it depends on private void VisitFieldStructs(TypeInfo ti) { if (VisitedFieldStructs.Contains(ti)) return; @@ -116,12 +132,15 @@ namespace Il2CppInspector.Outputs TodoFieldStructs.Add(ti); } + // 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"); } + // Generate structure fields for each field of a given type private void GenerateFieldList(StringBuilder csrc, Namespace ns, TypeInfo ti) { var namer = ns.MakeNamer((field) => sanitizeIdentifier(field.Name)); foreach (var field in ti.DeclaredFields) { @@ -131,6 +150,7 @@ namespace Il2CppInspector.Outputs } } + // Generate the C structure for a value type, such as an enum or struct private void GenerateValueFieldStruct(StringBuilder csrc, TypeInfo ti) { string name = TypeNamer.GetName(ti); if (ti.IsEnum) { @@ -163,6 +183,7 @@ namespace Il2CppInspector.Outputs } } + // Generate the C structure for a reference type, such as a class or array private void GenerateRefFieldStruct(StringBuilder csrc, TypeInfo ti) { var name = TypeNamer.GetName(ti); if (ti.IsArray || ti.FullName == "System.Array") { @@ -242,6 +263,7 @@ namespace Il2CppInspector.Outputs } } + // "Flush" the list of visited types, generating C structures for each one private void GenerateVisitedFieldStructs(StringBuilder csrc) { foreach (var ti in TodoFieldStructs) { if (ti.IsEnum || ti.IsValueType) @@ -254,7 +276,9 @@ namespace Il2CppInspector.Outputs #endregion #region Class Struct Generation - private Dictionary ConcreteImplementations = new Dictionary(); + + // Concrete implementations for abstract classes, for use in looking up VTable signatures and names + private readonly Dictionary ConcreteImplementations = new Dictionary(); /// /// VTables for abstract types have "null" in place of abstract functions. /// This function searches for concrete implementations so that we can properly @@ -347,6 +371,7 @@ namespace Il2CppInspector.Outputs TodoTypeStructs.Add(ti); } + // Generate the C structure for virtual function calls in a given type (the VTable) private void GenerateVTableStruct(StringBuilder csrc, TypeInfo ti) { MethodBase[] vtable; if (ti.IsInterface) { @@ -380,6 +405,7 @@ namespace Il2CppInspector.Outputs csrc.Append($"}};\n"); } + // Generate the overall Il2CppClass-shaped structure for the given type private void GenerateTypeStruct(StringBuilder csrc, TypeInfo ti) { var name = TypeNamer.GetName(ti); GenerateVTableStruct(csrc, ti); @@ -452,6 +478,7 @@ namespace Il2CppInspector.Outputs } } + // Generate a C declaration for a method private string GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) { string retType; if (method is MethodInfo mi) { diff --git a/Il2CppInspector.Common/Outputs/Namespace.cs b/Il2CppInspector.Common/Outputs/Namespace.cs index c8f734b..694f1e0 100644 --- a/Il2CppInspector.Common/Outputs/Namespace.cs +++ b/Il2CppInspector.Common/Outputs/Namespace.cs @@ -16,8 +16,14 @@ namespace Il2CppInspector.Outputs /// public class Namespace { + // The central data structure that keeps track of which names have been generated + // The value for any given key K is the number of unique objects originally named K, minus 1. + // Each time we see a particular name assigned to a new, different object, we bump its rename count + // and give it a suffix. For example, if we have three different objects all named X, + // we'd name them X, X_1, and X_2, and renameCount["X"] would be 2. private readonly Dictionary renameCount = new Dictionary(); + // Mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names) public void ReserveName(string name) { if (renameCount.ContainsKey(name)) { throw new Exception($"Can't reserve {name}: already taken!"); @@ -25,6 +31,7 @@ namespace Il2CppInspector.Outputs renameCount[name] = 0; } + // Create a Namer object which will give names to objects of type T which are unique within this namespace public Namer MakeNamer(Namer.KeyFunc keyFunc) { return new Namer(this, keyFunc); } @@ -35,8 +42,14 @@ namespace Il2CppInspector.Outputs /// public class Namer { + // Parent namespace private Namespace ns; + + // Names given out by this Namer. private readonly Dictionary names = new Dictionary(); + + // The function which maps a T object to a suitably mangled name + // That name might be further mangled by the Namer to make the name unique within the namespace public delegate string KeyFunc(T t); private readonly KeyFunc keyFunc; @@ -45,12 +58,20 @@ namespace Il2CppInspector.Outputs this.keyFunc = keyFunc; } + // Uniquely name an object within the parent namespace public string GetName(T t) { + // If we've named this particular object before, just return that name string name; if (names.TryGetValue(t, out name)) return name; + // Obtain the mangled name for the object name = keyFunc(t); - // This approach avoids linear scan (quadratic blowup) if there are a lot of similarly-named objects. + // Check if the mangled name has been given to another object - if it has, + // we need to give the object a new suffixed name (e.g. X_1). + // We might need to repeat this process if the new suffixed name also exists. + // Each iteration tacks on another suffix - so we normally expect this to only take + // a single iteration. (It might take multiple iterations in rare cases, e.g. + // another object had the mangled name X_1). if (ns.renameCount.ContainsKey(name)) { int v = ns.renameCount[name] + 1; while (ns.renameCount.ContainsKey(name + "_" + v)) diff --git a/Il2CppInspector.Common/Outputs/UnityHeaders/UnityHeader.cs b/Il2CppInspector.Common/Outputs/UnityHeaders/UnityHeader.cs index 227e0ff..37e419d 100644 --- a/Il2CppInspector.Common/Outputs/UnityHeaders/UnityHeader.cs +++ b/Il2CppInspector.Common/Outputs/UnityHeaders/UnityHeader.cs @@ -9,16 +9,22 @@ using System.Reflection; using System.IO; using System.Linq; using System.Collections.Generic; -using System; namespace Il2CppInspector.Outputs.UnityHeaders { + // Each instance of UnityHeader represents one header file which potentially covers multiple versions of Unity. public class UnityHeader { + // Metadata version of this header. Multiple headers may have the same metadata version public double MetadataVersion { get; } + + // Minimum and maximum Unity version numbers corresponding to this header. Both endpoints are inclusive public UnityVersion MinVersion { get; } public UnityVersion MaxVersion { get; } + + // Filename for the embedded .h resource file containing the header public string HeaderFilename { get; } + private UnityHeader(string headerFilename) { HeaderFilename = headerFilename; var bits = headerFilename.Replace(".h", "").Split("-"); @@ -39,10 +45,10 @@ namespace Il2CppInspector.Outputs.UnityHeaders return res; } - public bool Contains(UnityVersion version) { - return version.CompareTo(MinVersion) >= 0 && (MaxVersion == null || version.CompareTo(MaxVersion) <= 0); - } + // Determine if this header supports the given version of Unity + public bool Contains(UnityVersion version) => version.CompareTo(MinVersion) >= 0 && (MaxVersion == null || version.CompareTo(MaxVersion) <= 0); + // Return the contents of this header file as a string public string GetHeaderText() { string resourceName = typeof(UnityHeader).Namespace + "." + HeaderFilename; Assembly assembly = Assembly.GetCallingAssembly(); @@ -55,6 +61,7 @@ namespace Il2CppInspector.Outputs.UnityHeaders return result; } + // List all header files embedded into this build of Il2Cpp public static IEnumerable GetAllHeaders() { string prefix = typeof(UnityHeader).Namespace + "."; Assembly assembly = Assembly.GetCallingAssembly(); @@ -63,11 +70,13 @@ namespace Il2CppInspector.Outputs.UnityHeaders .Select(s => new UnityHeader(s.Substring(prefix.Length))); } + // Get the header file which supports the given version of Unity public static UnityHeader GetHeaderForVersion(string version) => GetHeaderForVersion(new UnityVersion(version)); - public static UnityHeader GetHeaderForVersion(UnityVersion version) { - return GetAllHeaders().Where(v => v.Contains(version)).First(); - } + public static UnityHeader GetHeaderForVersion(UnityVersion version) => GetAllHeaders().Where(v => v.Contains(version)).First(); + // 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) { List result = new List(); foreach (var v in GetAllHeaders()) { diff --git a/Il2CppInspector.Common/Outputs/UnityHeaders/UnityVersion.cs b/Il2CppInspector.Common/Outputs/UnityHeaders/UnityVersion.cs index f32de5b..7592891 100644 --- a/Il2CppInspector.Common/Outputs/UnityHeaders/UnityVersion.cs +++ b/Il2CppInspector.Common/Outputs/UnityHeaders/UnityVersion.cs @@ -10,8 +10,10 @@ using System.Text.RegularExpressions; namespace Il2CppInspector.Outputs.UnityHeaders { + // Parsed representation of a Unity version number, such as 5.3.0f1 or 2019.3.7. public class UnityVersion : IComparable, IEquatable { + // A sorted enumeration of build types, in order of maturity public enum BuildTypeEnum { Unspecified, @@ -44,6 +46,7 @@ namespace Il2CppInspector.Outputs.UnityHeaders _ => throw new ArgumentException("Unknown build type " + s), }; + // Unity version number is of the form ..[] public int Major { get; } public int Minor { get; } public int Update { get; } @@ -70,6 +73,7 @@ namespace Il2CppInspector.Outputs.UnityHeaders return res; } + // Compare two version numbers, intransitively (due to the Unspecified build type) public int CompareTo(UnityVersion other) { int res; if (0 != (res = Major.CompareTo(other.Major)))