AppModel: First iteration of ApplicationModel API

Integrate with C++ scaffolding
Add new tests
Rename Il2CppModel to TypeModel
Incomplete IDAPython integration
CLI and GUI support
Update README.md
This commit is contained in:
Katy Coe
2020-07-09 03:48:50 +02:00
parent 9fff9678aa
commit 873a6c98f6
25 changed files with 809 additions and 588 deletions

View File

@@ -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<Export> 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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<string, string> primitiveTypeMap = new Dictionary<string, string> {
["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<FieldInfo>((field) => field.Name.ToCIdentifier());
private void GenerateFieldList(CppComplexType type, CppNamespace ns, TypeInfo ti) {
var namer = ns.MakeNamer<FieldInfo>(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<CppType> GenerateVisitedFieldStructs() {
var structs = new List<CppType>(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<FieldInfo>((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);
}
/// <summary>
@@ -441,15 +435,18 @@ namespace Il2CppInspector.Cpp
/// Type declarations that have previously been generated by this instance of CppDeclarationGenerator will not be generated again.
/// </summary>
/// <returns>A string containing C type declarations</returns>
public string GenerateRemainingTypeDeclarations() {
var csrc = new StringBuilder();
GenerateVisitedFieldStructs(csrc);
public List<CppType> 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<ParameterInfo>((pi) => pi.Name == "" ? "arg" : pi.Name.ToCIdentifier());
var paramList = new List<string>();
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};
}
/// <summary>
@@ -516,21 +513,9 @@ namespace Il2CppInspector.Cpp
/// </summary>
/// <param name="mi"></param>
/// <returns></returns>
public string GenerateMethodDeclaration(MethodBase method) {
public CppFnPtrType GenerateMethodDeclaration(MethodBase method) {
return GenerateMethodDeclaration(method, GlobalNamer.GetName(method), method.DeclaringType);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="mi">Method to generate (only the signature will be used)</param>
/// <param name="name">Name of the function pointer</param>
/// <returns></returns>
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<TypeInfo>((ti) => {

View File

@@ -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;
}

View File

@@ -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<int, List<CppField>>();
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<CppField> { 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();
}
}

View File

@@ -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<CppType>
public class CppTypeCollection : IEnumerable<CppType>
{
// All of the types
public Dictionary<string, CppType> Types { get; }
// All of the literal typedef aliases
public Dictionary<string, CppType> TypedefAliases { get; } = new Dictionary<string, CppType>();
public CppType this[string s] => Types[s];
public IEnumerator<CppType> 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<string, ComplexValueType> complexTypeMap = new Dictionary<string, ComplexValueType> {
["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<CppType> primitiveTypes = new List<CppType> {
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 <external-type>;
// 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 <struct-type> <alias>
var typedef = rgxTypedefForwardDecl.Match(line);
if (typedef.Success) {
var alias = typedef.Groups[2].Captures[0].ToString();
var declType = typedef.Groups[1].Captures[0].ToString();
// <struct|union> <external-type>;
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 <struct|union> <existing-type> <alias>
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 <retType> (*<alias>)(<args>);
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 <targetType>[*..] <alias>;
typedef = rgxTypedef.Match(line);
// Type (pointer) alias
// typedef <targetType[*..]> <alias>;
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 <optional-type-name>
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] <struct|union|enum> [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 <optional-type-name>
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 <optional-type-name>
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 <optional-type-name>
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<CppType> 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();

View File

@@ -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<UnityHeader> GuessHeadersForModel(Reflection.Il2CppModel model) {
public static List<UnityHeader> GuessHeadersForModel(Reflection.TypeModel model) {
List<UnityHeader> result = new List<UnityHeader>();
foreach (var v in GetAllHeaders()) {
if (v.MetadataVersion != model.Package.BinaryImage.Version)