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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
21
Il2CppInspector.Common/Cpp/CppCompilerType.cs
Normal file
21
Il2CppInspector.Common/Cpp/CppCompilerType.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user