Comments and minor refactoring into lambdas

This commit is contained in:
Robert Xiao
2020-06-21 01:21:36 -07:00
committed by Katy
parent 3ccbab2461
commit 95ee085374
4 changed files with 70 additions and 9 deletions

View File

@@ -15,14 +15,19 @@ using System.Text.RegularExpressions;
namespace Il2CppInspector.Outputs namespace Il2CppInspector.Outputs
{ {
// Class for generating C header declarations from Reflection objects (TypeInfo, etc.)
public class CppDeclarations public class CppDeclarations
{ {
private readonly Il2CppModel model; private readonly Il2CppModel model;
// Version number and header file to generate structures for
public UnityVersion UnityVersion { get; } public UnityVersion UnityVersion { get; }
public UnityHeader UnityHeader { get; } public UnityHeader UnityHeader { get; }
// How inheritance of type structs should be represented. // 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 public enum InheritanceStyleEnum
{ {
C, // Inheritance structs use C syntax, and will automatically choose MSVC or GCC based on inferred compiler. 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) { public string AsCType(TypeInfo ti) {
// IsArray case handled by TypeNamer.GetName // IsArray case handled by TypeNamer.GetName
if (ti.IsByRef || ti.IsPointer) { if (ti.IsByRef || ti.IsPointer) {
@@ -90,9 +96,19 @@ namespace Il2CppInspector.Outputs
} }
#region Field Struct Generation #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<TypeInfo> VisitedFieldStructs = new HashSet<TypeInfo>(); private readonly HashSet<TypeInfo> VisitedFieldStructs = new HashSet<TypeInfo>();
// A queue of field structures that need to be generated.
private readonly List<TypeInfo> TodoFieldStructs = new List<TypeInfo>(); private readonly List<TypeInfo> TodoFieldStructs = new List<TypeInfo>();
// Walk over dependencies of the given type, to figure out what field structures it depends on
private void VisitFieldStructs(TypeInfo ti) { private void VisitFieldStructs(TypeInfo ti) {
if (VisitedFieldStructs.Contains(ti)) if (VisitedFieldStructs.Contains(ti))
return; return;
@@ -116,12 +132,15 @@ namespace Il2CppInspector.Outputs
TodoFieldStructs.Add(ti); 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) { private void GenerateObjectFields(StringBuilder csrc, TypeInfo ti) {
csrc.Append( csrc.Append(
$" struct {TypeNamer.GetName(ti)}__Class *klass;\n" + $" struct {TypeNamer.GetName(ti)}__Class *klass;\n" +
$" struct MonitorData *monitor;\n"); $" struct MonitorData *monitor;\n");
} }
// Generate structure fields for each field of a given type
private void GenerateFieldList(StringBuilder csrc, Namespace ns, TypeInfo ti) { private void GenerateFieldList(StringBuilder csrc, Namespace ns, TypeInfo ti) {
var namer = ns.MakeNamer<FieldInfo>((field) => sanitizeIdentifier(field.Name)); var namer = ns.MakeNamer<FieldInfo>((field) => sanitizeIdentifier(field.Name));
foreach (var field in ti.DeclaredFields) { 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) { private void GenerateValueFieldStruct(StringBuilder csrc, TypeInfo ti) {
string name = TypeNamer.GetName(ti); string name = TypeNamer.GetName(ti);
if (ti.IsEnum) { 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) { private void GenerateRefFieldStruct(StringBuilder csrc, TypeInfo ti) {
var name = TypeNamer.GetName(ti); var name = TypeNamer.GetName(ti);
if (ti.IsArray || ti.FullName == "System.Array") { 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) { private void GenerateVisitedFieldStructs(StringBuilder csrc) {
foreach (var ti in TodoFieldStructs) { foreach (var ti in TodoFieldStructs) {
if (ti.IsEnum || ti.IsValueType) if (ti.IsEnum || ti.IsValueType)
@@ -254,7 +276,9 @@ namespace Il2CppInspector.Outputs
#endregion #endregion
#region Class Struct Generation #region Class Struct Generation
private Dictionary<TypeInfo, TypeInfo> ConcreteImplementations = new Dictionary<TypeInfo, TypeInfo>();
// Concrete implementations for abstract classes, for use in looking up VTable signatures and names
private readonly Dictionary<TypeInfo, TypeInfo> ConcreteImplementations = new Dictionary<TypeInfo, TypeInfo>();
/// <summary> /// <summary>
/// VTables for abstract types have "null" in place of abstract functions. /// VTables for abstract types have "null" in place of abstract functions.
/// This function searches for concrete implementations so that we can properly /// This function searches for concrete implementations so that we can properly
@@ -347,6 +371,7 @@ namespace Il2CppInspector.Outputs
TodoTypeStructs.Add(ti); TodoTypeStructs.Add(ti);
} }
// Generate the C structure for virtual function calls in a given type (the VTable)
private void GenerateVTableStruct(StringBuilder csrc, TypeInfo ti) { private void GenerateVTableStruct(StringBuilder csrc, TypeInfo ti) {
MethodBase[] vtable; MethodBase[] vtable;
if (ti.IsInterface) { if (ti.IsInterface) {
@@ -380,6 +405,7 @@ namespace Il2CppInspector.Outputs
csrc.Append($"}};\n"); csrc.Append($"}};\n");
} }
// Generate the overall Il2CppClass-shaped structure for the given type
private void GenerateTypeStruct(StringBuilder csrc, TypeInfo ti) { private void GenerateTypeStruct(StringBuilder csrc, TypeInfo ti) {
var name = TypeNamer.GetName(ti); var name = TypeNamer.GetName(ti);
GenerateVTableStruct(csrc, 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) { private string GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) {
string retType; string retType;
if (method is MethodInfo mi) { if (method is MethodInfo mi) {

View File

@@ -16,8 +16,14 @@ namespace Il2CppInspector.Outputs
/// </summary> /// </summary>
public class Namespace 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<string, int> renameCount = new Dictionary<string, int>(); private readonly Dictionary<string, int> renameCount = new Dictionary<string, int>();
// Mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names)
public void ReserveName(string name) { public void ReserveName(string name) {
if (renameCount.ContainsKey(name)) { if (renameCount.ContainsKey(name)) {
throw new Exception($"Can't reserve {name}: already taken!"); throw new Exception($"Can't reserve {name}: already taken!");
@@ -25,6 +31,7 @@ namespace Il2CppInspector.Outputs
renameCount[name] = 0; renameCount[name] = 0;
} }
// Create a Namer object which will give names to objects of type T which are unique within this namespace
public Namer<T> MakeNamer<T>(Namer<T>.KeyFunc keyFunc) { public Namer<T> MakeNamer<T>(Namer<T>.KeyFunc keyFunc) {
return new Namer<T>(this, keyFunc); return new Namer<T>(this, keyFunc);
} }
@@ -35,8 +42,14 @@ namespace Il2CppInspector.Outputs
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public class Namer<T> public class Namer<T>
{ {
// Parent namespace
private Namespace ns; private Namespace ns;
// Names given out by this Namer.
private readonly Dictionary<T, string> names = new Dictionary<T, string>(); private readonly Dictionary<T, string> names = new Dictionary<T, string>();
// 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); public delegate string KeyFunc(T t);
private readonly KeyFunc keyFunc; private readonly KeyFunc keyFunc;
@@ -45,12 +58,20 @@ namespace Il2CppInspector.Outputs
this.keyFunc = keyFunc; this.keyFunc = keyFunc;
} }
// Uniquely name an object within the parent namespace
public string GetName(T t) { public string GetName(T t) {
// If we've named this particular object before, just return that name
string name; string name;
if (names.TryGetValue(t, out name)) if (names.TryGetValue(t, out name))
return name; return name;
// Obtain the mangled name for the object
name = keyFunc(t); 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)) { if (ns.renameCount.ContainsKey(name)) {
int v = ns.renameCount[name] + 1; int v = ns.renameCount[name] + 1;
while (ns.renameCount.ContainsKey(name + "_" + v)) while (ns.renameCount.ContainsKey(name + "_" + v))

View File

@@ -9,16 +9,22 @@ using System.Reflection;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System;
namespace Il2CppInspector.Outputs.UnityHeaders namespace Il2CppInspector.Outputs.UnityHeaders
{ {
// Each instance of UnityHeader represents one header file which potentially covers multiple versions of Unity.
public class UnityHeader public class UnityHeader
{ {
// Metadata version of this header. Multiple headers may have the same metadata version
public double MetadataVersion { get; } public double MetadataVersion { get; }
// Minimum and maximum Unity version numbers corresponding to this header. Both endpoints are inclusive
public UnityVersion MinVersion { get; } public UnityVersion MinVersion { get; }
public UnityVersion MaxVersion { get; } public UnityVersion MaxVersion { get; }
// Filename for the embedded .h resource file containing the header
public string HeaderFilename { get; } public string HeaderFilename { get; }
private UnityHeader(string headerFilename) { private UnityHeader(string headerFilename) {
HeaderFilename = headerFilename; HeaderFilename = headerFilename;
var bits = headerFilename.Replace(".h", "").Split("-"); var bits = headerFilename.Replace(".h", "").Split("-");
@@ -39,10 +45,10 @@ namespace Il2CppInspector.Outputs.UnityHeaders
return res; return res;
} }
public bool Contains(UnityVersion version) { // Determine if this header supports the given version of Unity
return version.CompareTo(MinVersion) >= 0 && (MaxVersion == null || version.CompareTo(MaxVersion) <= 0); 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() { public string GetHeaderText() {
string resourceName = typeof(UnityHeader).Namespace + "." + HeaderFilename; string resourceName = typeof(UnityHeader).Namespace + "." + HeaderFilename;
Assembly assembly = Assembly.GetCallingAssembly(); Assembly assembly = Assembly.GetCallingAssembly();
@@ -55,6 +61,7 @@ namespace Il2CppInspector.Outputs.UnityHeaders
return result; return result;
} }
// List all header files embedded into this build of Il2Cpp
public static IEnumerable<UnityHeader> GetAllHeaders() { public static IEnumerable<UnityHeader> GetAllHeaders() {
string prefix = typeof(UnityHeader).Namespace + "."; string prefix = typeof(UnityHeader).Namespace + ".";
Assembly assembly = Assembly.GetCallingAssembly(); Assembly assembly = Assembly.GetCallingAssembly();
@@ -63,11 +70,13 @@ namespace Il2CppInspector.Outputs.UnityHeaders
.Select(s => new UnityHeader(s.Substring(prefix.Length))); .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(string version) => GetHeaderForVersion(new UnityVersion(version));
public static UnityHeader GetHeaderForVersion(UnityVersion version) { public static UnityHeader GetHeaderForVersion(UnityVersion version) => GetAllHeaders().Where(v => v.Contains(version)).First();
return 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<UnityHeader> GuessHeadersForModel(Reflection.Il2CppModel model) { public static List<UnityHeader> GuessHeadersForModel(Reflection.Il2CppModel model) {
List<UnityHeader> result = new List<UnityHeader>(); List<UnityHeader> result = new List<UnityHeader>();
foreach (var v in GetAllHeaders()) { foreach (var v in GetAllHeaders()) {

View File

@@ -10,8 +10,10 @@ using System.Text.RegularExpressions;
namespace Il2CppInspector.Outputs.UnityHeaders namespace Il2CppInspector.Outputs.UnityHeaders
{ {
// Parsed representation of a Unity version number, such as 5.3.0f1 or 2019.3.7.
public class UnityVersion : IComparable<UnityVersion>, IEquatable<UnityVersion> public class UnityVersion : IComparable<UnityVersion>, IEquatable<UnityVersion>
{ {
// A sorted enumeration of build types, in order of maturity
public enum BuildTypeEnum public enum BuildTypeEnum
{ {
Unspecified, Unspecified,
@@ -44,6 +46,7 @@ namespace Il2CppInspector.Outputs.UnityHeaders
_ => throw new ArgumentException("Unknown build type " + s), _ => throw new ArgumentException("Unknown build type " + s),
}; };
// Unity version number is of the form <Major>.<Minor>.<Update>[<BuildType><BuildNumber>]
public int Major { get; } public int Major { get; }
public int Minor { get; } public int Minor { get; }
public int Update { get; } public int Update { get; }
@@ -70,6 +73,7 @@ namespace Il2CppInspector.Outputs.UnityHeaders
return res; return res;
} }
// Compare two version numbers, intransitively (due to the Unspecified build type)
public int CompareTo(UnityVersion other) { public int CompareTo(UnityVersion other) {
int res; int res;
if (0 != (res = Major.CompareTo(other.Major))) if (0 != (res = Major.CompareTo(other.Major)))