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:
@@ -8,9 +8,10 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
using Il2CppInspector.Cpp;
|
using Il2CppInspector.Cpp;
|
||||||
using Il2CppInspector.Reflection;
|
|
||||||
using Il2CppInspector.Outputs;
|
|
||||||
using Il2CppInspector.Cpp.UnityHeaders;
|
using Il2CppInspector.Cpp.UnityHeaders;
|
||||||
|
using Il2CppInspector.Model;
|
||||||
|
using Il2CppInspector.Outputs;
|
||||||
|
using Il2CppInspector.Reflection;
|
||||||
|
|
||||||
namespace Il2CppInspector.CLI
|
namespace Il2CppInspector.CLI
|
||||||
{
|
{
|
||||||
@@ -70,8 +71,8 @@ namespace Il2CppInspector.CLI
|
|||||||
[Option('j', "project", Required = false, HelpText = "Create a Visual Studio solution and projects. Implies --layout tree, --must-compile and --separate-attributes")]
|
[Option('j', "project", Required = false, HelpText = "Create a Visual Studio solution and projects. Implies --layout tree, --must-compile and --separate-attributes")]
|
||||||
public bool CreateSolution { get; set; }
|
public bool CreateSolution { get; set; }
|
||||||
|
|
||||||
[Option("cpp-compiler", Required = false, HelpText = "Compiler to make C++ output compatible with (MSVC or GCC); selects based on binary executable type by default", Default = Cpp.CppCompiler.Type.BinaryFormat)]
|
[Option("cpp-compiler", Required = false, HelpText = "Compiler to target for C++ output (MSVC or GCC); selects based on binary executable type by default", Default = CppCompilerType.BinaryFormat)]
|
||||||
public CppCompiler.Type CppCompiler { get; set; }
|
public CppCompilerType CppCompiler { get; set; }
|
||||||
|
|
||||||
[Option("unity-path", Required = false, HelpText = "Path to Unity editor (when using --project). Wildcards select last matching folder in alphanumeric order", Default = @"C:\Program Files\Unity\Hub\Editor\*")]
|
[Option("unity-path", Required = false, HelpText = "Path to Unity editor (when using --project). Wildcards select last matching folder in alphanumeric order", Default = @"C:\Program Files\Unity\Hub\Editor\*")]
|
||||||
public string UnityPath { get; set; }
|
public string UnityPath { get; set; }
|
||||||
@@ -182,9 +183,14 @@ namespace Il2CppInspector.CLI
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var il2cpp in il2cppInspectors) {
|
foreach (var il2cpp in il2cppInspectors) {
|
||||||
// Create model
|
// Create model
|
||||||
Il2CppModel model;
|
TypeModel model;
|
||||||
using (new Benchmark("Create type model"))
|
using (new Benchmark("Create .NET type model"))
|
||||||
model = new Il2CppModel(il2cpp);
|
model = new TypeModel(il2cpp);
|
||||||
|
|
||||||
|
AppModel appModel;
|
||||||
|
using (new Benchmark("Create C++ application model")) {
|
||||||
|
appModel = new AppModel(model).Build(options.UnityVersion, options.CppCompiler);
|
||||||
|
}
|
||||||
|
|
||||||
// C# signatures output
|
// C# signatures output
|
||||||
using (new Benchmark("Generate C# code")) {
|
using (new Benchmark("Generate C# code")) {
|
||||||
@@ -243,19 +249,12 @@ namespace Il2CppInspector.CLI
|
|||||||
|
|
||||||
// IDA Python script output
|
// IDA Python script output
|
||||||
using (new Benchmark("Generate IDAPython script")) {
|
using (new Benchmark("Generate IDAPython script")) {
|
||||||
var idaWriter = new IDAPythonScript(model) {
|
new IDAPythonScript(appModel).WriteScriptToFile(options.PythonOutFile);
|
||||||
UnityVersion = options.UnityVersion
|
|
||||||
};
|
|
||||||
idaWriter.WriteScriptToFile(options.PythonOutFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// C++ output
|
// C++ output
|
||||||
using (new Benchmark("Generate C++ code")) {
|
using (new Benchmark("Generate C++ code")) {
|
||||||
var cppWriter = new CppScaffolding(model) {
|
new CppScaffolding(appModel).WriteCppToFile(options.CppOutFile);
|
||||||
UnityVersion = options.UnityVersion,
|
|
||||||
Compiler = options.CppCompiler
|
|
||||||
};
|
|
||||||
cppWriter.WriteCppToFile(options.CppOutFile);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,42 +8,32 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Il2CppInspector.Cpp.UnityHeaders;
|
using Il2CppInspector.Cpp.UnityHeaders;
|
||||||
|
using Il2CppInspector.Model;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
|
|
||||||
namespace Il2CppInspector.Cpp
|
namespace Il2CppInspector.Cpp
|
||||||
{
|
{
|
||||||
// Class for generating C header declarations from Reflection objects (TypeInfo, etc.)
|
// 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
|
// Version number and header file to generate structures for
|
||||||
public UnityVersion UnityVersion { get; }
|
public UnityVersion UnityVersion => appModel.UnityVersion;
|
||||||
public UnityHeader UnityHeader { get; }
|
|
||||||
|
|
||||||
// How inheritance of type structs should be represented.
|
// How inheritance of type structs should be represented.
|
||||||
// Different C++ compilers lay out C++ class structures differently,
|
// Different C++ compilers lay out C++ class structures differently,
|
||||||
// meaning that the compiler must be known in order to generate class type structures
|
// meaning that the compiler must be known in order to generate class type structures
|
||||||
// with the correct layout.
|
// with the correct layout.
|
||||||
public CppCompiler.Type InheritanceStyle;
|
public CppCompilerType InheritanceStyle;
|
||||||
|
|
||||||
public CppDeclarationGenerator(Il2CppModel model, UnityVersion version) {
|
public CppDeclarationGenerator(AppModel appModel) {
|
||||||
this.model = model;
|
this.appModel = appModel;
|
||||||
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}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InitializeNaming();
|
InitializeNaming();
|
||||||
InitializeConcreteImplementations();
|
InitializeConcreteImplementations();
|
||||||
@@ -53,34 +43,38 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// C type declaration used to name variables of the given C# type
|
// 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
|
// IsArray case handled by TypeNamer.GetName
|
||||||
if (ti.IsByRef || ti.IsPointer) {
|
if (ti.IsByRef || ti.IsPointer) {
|
||||||
return $"{AsCType(ti.ElementType)} *";
|
return AsCType(ti.ElementType).AsPointer(types.WordSize);
|
||||||
} 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";
|
|
||||||
}
|
}
|
||||||
|
if (ti.IsValueType) {
|
||||||
|
if (ti.IsPrimitive && primitiveTypeMap.ContainsKey(ti.Name)) {
|
||||||
|
return types.GetType(primitiveTypeMap[ti.Name]);
|
||||||
}
|
}
|
||||||
return $"struct {TypeNamer.GetName(ti)}";
|
return types.GetType(TypeNamer.GetName(ti));
|
||||||
} else if (ti.IsEnum) {
|
|
||||||
return $"enum {TypeNamer.GetName(ti)}";
|
|
||||||
}
|
}
|
||||||
return $"struct {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
|
// 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)
|
// 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
|
// The two fields are inlined so that we can specialize the klass member for each type object
|
||||||
private void GenerateObjectFields(StringBuilder csrc, TypeInfo ti) {
|
private CppComplexType GenerateObjectStruct(string name, TypeInfo ti) {
|
||||||
csrc.Append(
|
var type = types.Struct(name);
|
||||||
$" struct {TypeNamer.GetName(ti)}__Class *klass;\n" +
|
types.AddField(type, "klass", TypeNamer.GetName(ti) + "__Class *");
|
||||||
$" struct MonitorData *monitor;\n");
|
types.AddField(type, "monitor", "MonitorData *");
|
||||||
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate structure fields for each field of a given type
|
// Generate structure fields for each field of a given type
|
||||||
private void GenerateFieldList(StringBuilder csrc, CppNamespace ns, TypeInfo ti) {
|
private void GenerateFieldList(CppComplexType type, CppNamespace ns, TypeInfo ti) {
|
||||||
var namer = ns.MakeNamer<FieldInfo>((field) => field.Name.ToCIdentifier());
|
var namer = ns.MakeNamer<FieldInfo>(field => field.Name.ToCIdentifier());
|
||||||
foreach (var field in ti.DeclaredFields) {
|
foreach (var field in ti.DeclaredFields) {
|
||||||
if (field.IsLiteral || field.IsStatic)
|
if (field.IsLiteral || field.IsStatic)
|
||||||
continue;
|
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
|
// 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);
|
string name = TypeNamer.GetName(ti);
|
||||||
|
|
||||||
if (ti.IsEnum) {
|
if (ti.IsEnum) {
|
||||||
// Enums should be represented using enum syntax
|
// Enums should be represented using enum syntax
|
||||||
// They otherwise behave like value types
|
// 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) {
|
foreach (var field in ti.DeclaredFields) {
|
||||||
if (field.Name != "value__")
|
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
|
// Use System.Enum base type as klass
|
||||||
csrc.Append($"struct {name}__Boxed {{\n");
|
boxedType = GenerateObjectStruct(name + "__Boxed", ti.BaseType);
|
||||||
GenerateObjectFields(csrc, ti.BaseType);
|
boxedType.AddField("value", AsCType(ti));
|
||||||
csrc.Append($" {AsCType(ti)} value;\n");
|
|
||||||
csrc.Append($"}};\n");
|
|
||||||
} else {
|
} else {
|
||||||
// This structure is passed by value, so it doesn't include Il2CppObject fields.
|
// This structure is passed by value, so it doesn't include Il2CppObject fields.
|
||||||
csrc.Append($"struct {name} {{\n");
|
valueType = types.Struct(name);
|
||||||
GenerateFieldList(csrc, CreateNamespace(), ti);
|
GenerateFieldList(valueType, CreateNamespace(), ti);
|
||||||
csrc.Append($"}};\n");
|
|
||||||
|
|
||||||
// Also generate the boxed form of the structure which includes the Il2CppObject header.
|
// Also generate the boxed form of the structure which includes the Il2CppObject header.
|
||||||
csrc.Append($"struct {name}__Boxed {{\n");
|
boxedType = GenerateObjectStruct(name + "__Boxed", ti);
|
||||||
GenerateObjectFields(csrc, ti);
|
boxedType.AddField("fields", AsCType(ti));
|
||||||
csrc.Append($" {AsCType(ti)} fields;\n");
|
|
||||||
csrc.Append($"}};\n");
|
|
||||||
}
|
}
|
||||||
|
return (valueType, boxedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate the C structure for a reference type, such as a class or array
|
// 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);
|
var name = TypeNamer.GetName(ti);
|
||||||
|
|
||||||
if (ti.IsArray || ti.FullName == "System.Array") {
|
if (ti.IsArray || ti.FullName == "System.Array") {
|
||||||
var klassType = ti.IsArray ? ti : ti.BaseType;
|
var klassType = ti.IsArray ? ti : ti.BaseType;
|
||||||
var elementType = ti.IsArray ? AsCType(ti.ElementType) : "void *";
|
var elementType = ti.IsArray ? AsCType(ti.ElementType) : types.GetType("void *");
|
||||||
csrc.Append($"struct {name} {{\n");
|
var type = GenerateObjectStruct(name, klassType);
|
||||||
GenerateObjectFields(csrc, klassType);
|
types.AddField(type, "bounds", "Il2CppArrayBounds *");
|
||||||
csrc.Append(
|
types.AddField(type, "max_length", "il2cpp_array_size_t");
|
||||||
$" struct Il2CppArrayBounds *bounds;\n" +
|
type.AddField("vector", elementType.AsArray(32));
|
||||||
$" il2cpp_array_size_t max_length;\n" +
|
return (type, null);
|
||||||
$" {elementType} vector[32];\n");
|
|
||||||
csrc.Append($"}};\n");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generate a list of all base classes starting from the root */
|
/* Generate a list of all base classes starting from the root */
|
||||||
@@ -202,7 +193,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
|
|
||||||
var ns = CreateNamespace();
|
var ns = CreateNamespace();
|
||||||
|
|
||||||
if (InheritanceStyle == CppCompiler.Type.MSVC) {
|
if (InheritanceStyle == CppCompilerType.MSVC) {
|
||||||
/* MSVC style: classes directly contain their base class as the first member.
|
/* 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. */
|
* This causes all classes to be aligned to the alignment of their base class. */
|
||||||
TypeInfo firstNonEmpty = null;
|
TypeInfo firstNonEmpty = null;
|
||||||
@@ -214,56 +205,63 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
if (firstNonEmpty == null) {
|
if (firstNonEmpty == null) {
|
||||||
/* This struct is completely empty. Omit __Fields entirely. */
|
/* This struct is completely empty. Omit __Fields entirely. */
|
||||||
csrc.Append($"struct {name} {{\n");
|
return (GenerateObjectStruct(name, ti), null);
|
||||||
GenerateObjectFields(csrc, ti);
|
|
||||||
csrc.Append($"}};\n");
|
|
||||||
} else {
|
} else {
|
||||||
|
CppComplexType fieldType;
|
||||||
if (firstNonEmpty == ti) {
|
if (firstNonEmpty == ti) {
|
||||||
/* All base classes are empty, so this class forms the root of a new hierarchy.
|
/* 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
|
* 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. */
|
* in the hierarchy because we want to customize the type of the klass parameter. */
|
||||||
var align = model.Package.BinaryImage.Bits == 32 ? 4 : 8;
|
var align = model.Package.BinaryImage.Bits == 32 ? 4 : 8;
|
||||||
csrc.Append($"struct __declspec(align({align})) {name}__Fields {{\n");
|
fieldType = types.Struct(name + "__Fields", align);
|
||||||
GenerateFieldList(csrc, ns, ti);
|
GenerateFieldList(fieldType, ns, ti);
|
||||||
csrc.Append($"}};\n");
|
|
||||||
} else {
|
} else {
|
||||||
/* Include the base class fields. Alignment will be dictated by the hierarchy. */
|
/* Include the base class fields. Alignment will be dictated by the hierarchy. */
|
||||||
ns.ReserveName("_");
|
ns.ReserveName("_");
|
||||||
csrc.Append($"struct {name}__Fields {{\n");
|
fieldType = types.Struct(name + "__Fields");
|
||||||
csrc.Append($" struct {TypeNamer.GetName(ti.BaseType)}__Fields _;\n");
|
var baseFieldType = types[TypeNamer.GetName(ti.BaseType) + "__Fields"];
|
||||||
GenerateFieldList(csrc, ns, ti);
|
fieldType.AddField("_", baseFieldType);
|
||||||
csrc.Append($"}};\n");
|
GenerateFieldList(fieldType, ns, ti);
|
||||||
}
|
}
|
||||||
csrc.Append($"struct {name} {{\n");
|
|
||||||
GenerateObjectFields(csrc, ti);
|
var type = GenerateObjectStruct(name, ti);
|
||||||
csrc.Append($" struct {name}__Fields fields;\n");
|
types.AddField(type, "fields", name + "__Fields");
|
||||||
csrc.Append($"}};\n");
|
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.
|
/* 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
|
* This saves space (fields are "packed") but requires us to repeat fields from
|
||||||
* base classes. */
|
* base classes. */
|
||||||
ns.ReserveName("klass");
|
ns.ReserveName("klass");
|
||||||
ns.ReserveName("monitor");
|
ns.ReserveName("monitor");
|
||||||
|
|
||||||
csrc.Append($"struct {name} {{\n");
|
var type = GenerateObjectStruct(name, ti);
|
||||||
GenerateObjectFields(csrc, ti);
|
|
||||||
foreach (var bti in baseClasses)
|
foreach (var bti in baseClasses)
|
||||||
GenerateFieldList(csrc, ns, bti);
|
GenerateFieldList(type, ns, bti);
|
||||||
csrc.Append($"}};\n");
|
return (type, null);
|
||||||
}
|
}
|
||||||
|
throw new InvalidOperationException("Could not generate ref field struct");
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Flush" the list of visited types, generating C structures for each one
|
// "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) {
|
foreach (var ti in TodoFieldStructs) {
|
||||||
if (ti.IsEnum || ti.IsValueType)
|
if (ti.IsEnum || ti.IsValueType) {
|
||||||
GenerateValueFieldStruct(csrc, ti);
|
var (valueType, boxedType) = GenerateValueFieldStruct(ti);
|
||||||
else
|
structs.Add(valueType);
|
||||||
GenerateRefFieldStruct(csrc, ti);
|
structs.Add(boxedType);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var (objectOrArrayType, fieldsType) = GenerateRefFieldStruct(ti);
|
||||||
|
if (fieldsType != null)
|
||||||
|
structs.Add(fieldsType);
|
||||||
|
structs.Add(objectOrArrayType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TodoFieldStructs.Clear();
|
TodoFieldStructs.Clear();
|
||||||
|
return structs;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -365,7 +363,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate the C structure for virtual function calls in a given type (the VTable)
|
// 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;
|
MethodBase[] vtable;
|
||||||
if (ti.IsInterface) {
|
if (ti.IsInterface) {
|
||||||
/* Interface vtables are just all of the interface methods.
|
/* Interface vtables are just all of the interface methods.
|
||||||
@@ -385,55 +383,51 @@ namespace Il2CppInspector.Cpp
|
|||||||
// Previous versions used `MethodInfo **vtable`.
|
// Previous versions used `MethodInfo **vtable`.
|
||||||
// TODO: Consider adding function types. This considerably increases the script size
|
// TODO: Consider adding function types. This considerably increases the script size
|
||||||
// but can significantly help with reverse-engineering certain binaries.
|
// 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) {
|
if (UnityVersion.CompareTo("5.3.6") < 0) {
|
||||||
for (int i = 0; i < vtable.Length; i++) {
|
for (int i = 0; i < vtable.Length; i++) {
|
||||||
csrc.Append($" MethodInfo *{namer.GetName(i)};\n");
|
types.AddField(vtableStruct, namer.GetName(i), "MethodInfo *");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < vtable.Length; i++) {
|
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
|
// 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);
|
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());
|
var namer = CreateNamespace().MakeNamer<FieldInfo>((field) => field.Name.ToCIdentifier());
|
||||||
foreach (var field in ti.DeclaredFields) {
|
foreach (var field in ti.DeclaredFields) {
|
||||||
if (field.IsLiteral || !field.IsStatic)
|
if (field.IsLiteral || !field.IsStatic)
|
||||||
continue;
|
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 */
|
/* TODO: type the rgctx_data */
|
||||||
|
var cls = types.Struct(name + "__Class");
|
||||||
|
types.AddField(cls, "_0", "Il2CppClass_0");
|
||||||
|
|
||||||
if (UnityVersion.CompareTo("5.5.0") < 0) {
|
if (UnityVersion.CompareTo("5.5.0") < 0) {
|
||||||
csrc.Append(
|
cls.AddField("vtable", vtable.AsPointer(types.WordSize));
|
||||||
$"struct {name}__Class {{\n" +
|
types.AddField(cls, "interfaceOffsets", "Il2CppRuntimeInterfaceOffsetPair *");
|
||||||
$" struct Il2CppClass_0 _0;\n" +
|
cls.AddField("static_fields", statics.AsPointer(types.WordSize));
|
||||||
$" struct {name}__VTable *vtable;\n" +
|
types.AddField(cls, "rgctx_data", "Il2CppRGCTXData *", true);
|
||||||
$" Il2CppRuntimeInterfaceOffsetPair *interfaceOffsets;\n" +
|
types.AddField(cls, "_1", "Il2CppClass_1");
|
||||||
$" struct {name}__StaticFields *static_fields;\n" +
|
|
||||||
$" const Il2CppRGCTXData *rgctx_data;\n" +
|
|
||||||
$" struct Il2CppClass_1 _1;\n" +
|
|
||||||
$"}};\n");
|
|
||||||
} else {
|
} else {
|
||||||
csrc.Append(
|
types.AddField(cls, "interfaceOffsets", "Il2CppRuntimeInterfaceOffsetPair *");
|
||||||
$"struct {name}__Class {{\n" +
|
cls.AddField("static_fields", statics.AsPointer(types.WordSize));
|
||||||
$" struct Il2CppClass_0 _0;\n" +
|
types.AddField(cls, "rgctx_data", "Il2CppRGCTXData *", true);
|
||||||
$" Il2CppRuntimeInterfaceOffsetPair *interfaceOffsets;\n" +
|
types.AddField(cls, "_1", "Il2CppClass_1");
|
||||||
$" struct {name}__StaticFields *static_fields;\n" +
|
cls.AddField("vtable", vtable);
|
||||||
$" const Il2CppRGCTXData *rgctx_data;\n" +
|
|
||||||
$" struct Il2CppClass_1 _1;\n" +
|
|
||||||
$" struct {name}__VTable vtable;\n" +
|
|
||||||
$"}};\n");
|
|
||||||
}
|
}
|
||||||
|
return (cls, statics, vtable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Type declarations that have previously been generated by this instance of CppDeclarationGenerator will not be generated again.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>A string containing C type declarations</returns>
|
/// <returns>A string containing C type declarations</returns>
|
||||||
public string GenerateRemainingTypeDeclarations() {
|
public List<CppType> GenerateRemainingTypeDeclarations() {
|
||||||
var csrc = new StringBuilder();
|
var decl = GenerateVisitedFieldStructs();
|
||||||
GenerateVisitedFieldStructs(csrc);
|
|
||||||
|
|
||||||
foreach (var ti in TodoTypeStructs)
|
foreach (var ti in TodoTypeStructs) {
|
||||||
GenerateTypeStruct(csrc, ti);
|
var (cls, statics, vtable) = GenerateTypeStruct(ti);
|
||||||
|
decl.Add(vtable);
|
||||||
|
decl.Add(statics);
|
||||||
|
decl.Add(cls);
|
||||||
|
}
|
||||||
TodoTypeStructs.Clear();
|
TodoTypeStructs.Clear();
|
||||||
|
|
||||||
return csrc.ToString();
|
return decl;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -473,40 +470,40 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate a C declaration for a method
|
// Generate a C declaration for a method
|
||||||
private string GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) {
|
private CppFnPtrType GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) {
|
||||||
string retType;
|
CppType retType;
|
||||||
if (method is MethodInfo mi) {
|
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 {
|
} else {
|
||||||
retType = "void";
|
retType = types["void"];
|
||||||
}
|
}
|
||||||
|
|
||||||
var paramNs = CreateNamespace();
|
var paramNs = CreateNamespace();
|
||||||
paramNs.ReserveName("method");
|
paramNs.ReserveName("method");
|
||||||
var paramNamer = paramNs.MakeNamer<ParameterInfo>((pi) => pi.Name == "" ? "arg" : pi.Name.ToCIdentifier());
|
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
|
// Figure out the "this" param
|
||||||
if (method.IsStatic) {
|
if (method.IsStatic) {
|
||||||
// In older versions, static methods took a dummy this parameter
|
// In older versions, static methods took a dummy this parameter
|
||||||
if (UnityVersion.CompareTo("2018.3.0") < 0)
|
if (UnityVersion.CompareTo("2018.3.0") < 0)
|
||||||
paramList.Add("void *this");
|
paramList.Add(("this", types.GetType("void *")));
|
||||||
} else {
|
} else {
|
||||||
if (declaringType.IsValueType) {
|
if (declaringType.IsValueType) {
|
||||||
// Methods for structs take the boxed object as the this param
|
// 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 {
|
} else {
|
||||||
paramList.Add($"{AsCType(declaringType)} this");
|
paramList.Add(("this", AsCType(declaringType)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var pi in method.DeclaredParameters) {
|
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>
|
/// <summary>
|
||||||
@@ -516,21 +513,9 @@ namespace Il2CppInspector.Cpp
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="mi"></param>
|
/// <param name="mi"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string GenerateMethodDeclaration(MethodBase method) {
|
public CppFnPtrType GenerateMethodDeclaration(MethodBase method) {
|
||||||
return GenerateMethodDeclaration(method, GlobalNamer.GetName(method), method.DeclaringType);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Naming
|
#region Naming
|
||||||
@@ -539,7 +524,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
private void InitializeNaming() {
|
private void InitializeNaming() {
|
||||||
TypeNamespace = CreateNamespace();
|
TypeNamespace = CreateNamespace();
|
||||||
// Type names that may appear in the header
|
// 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);
|
TypeNamespace.ReserveName(typeName);
|
||||||
}
|
}
|
||||||
TypeNamer = TypeNamespace.MakeNamer<TypeInfo>((ti) => {
|
TypeNamer = TypeNamespace.MakeNamer<TypeInfo>((ti) => {
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ namespace Il2CppInspector.Cpp
|
|||||||
// The type of the field
|
// The type of the field
|
||||||
public CppType Type { get; }
|
public CppType Type { get; }
|
||||||
|
|
||||||
|
// Whether the field is const
|
||||||
|
public bool IsConst { get; }
|
||||||
|
|
||||||
// The offset of the field into the type
|
// The offset of the field into the type
|
||||||
public int Offset { get; internal set; }
|
public int Offset { get; internal set; }
|
||||||
|
|
||||||
@@ -36,36 +39,36 @@ namespace Il2CppInspector.Cpp
|
|||||||
public int BitfieldMSB => BitfieldLSB + Size - 1;
|
public int BitfieldMSB => BitfieldLSB + Size - 1;
|
||||||
|
|
||||||
// Initialize field
|
// 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;
|
Name = name;
|
||||||
Type = type;
|
Type = type;
|
||||||
BitfieldSize = bitfieldSize;
|
BitfieldSize = bitfieldSize;
|
||||||
|
IsConst = isConst;
|
||||||
}
|
}
|
||||||
|
|
||||||
// C++ representation of field
|
// C++ representation of field
|
||||||
public virtual string ToString(string format = "") {
|
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 {
|
var field = Type switch {
|
||||||
// nested anonymous types
|
// nested anonymous types (trim semi-colon and newline from end)
|
||||||
CppComplexType t when string.IsNullOrEmpty(t.Name) => (format == "o"? "\n" : "") + t.ToString(format)[..^1] + (Name.Length > 0? " " + Name : ""),
|
CppComplexType t when string.IsNullOrEmpty(t.Name) => (format == "o"? "\n" : "")
|
||||||
|
+ t.ToString(format)[..^2] + (Name.Length > 0? " " + Name : ""),
|
||||||
// function pointers
|
// 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
|
// regular fields
|
||||||
_ => $"{(format == "o"? " ":"")}{Type.Name} {Name}" + (BitfieldSize > 0? $" : {BitfieldSize}" : "")
|
_ => $"{Type.ToFieldString(Name)}" + (BitfieldSize > 0? $" : {BitfieldSize}" : "")
|
||||||
};
|
};
|
||||||
|
|
||||||
var suffix = "";
|
var suffix = "";
|
||||||
|
|
||||||
// arrays
|
|
||||||
if (Type is CppArrayType a)
|
|
||||||
suffix += "[" + a.Length + "]";
|
|
||||||
|
|
||||||
// bitfields
|
// bitfields
|
||||||
if (BitfieldSize > 0 && format == "o")
|
if (BitfieldSize > 0 && format == "o")
|
||||||
suffix += $" /* bits {BitfieldLSB} - {BitfieldMSB} */";
|
suffix += $" /* bits {BitfieldLSB} - {BitfieldMSB} */";
|
||||||
|
|
||||||
return offset + field + suffix;
|
return offset + prefix + field + suffix;
|
||||||
}
|
}
|
||||||
public override string ToString() => ToString();
|
public override string ToString() => ToString();
|
||||||
}
|
}
|
||||||
@@ -74,9 +77,9 @@ namespace Il2CppInspector.Cpp
|
|||||||
public class CppEnumField : CppField
|
public class CppEnumField : CppField
|
||||||
{
|
{
|
||||||
// The value of this key name
|
// 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;
|
public override string ToString(string format = "") => Name + " = " + Value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,12 @@
|
|||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
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
|
namespace Il2CppInspector.Cpp
|
||||||
{
|
{
|
||||||
@@ -30,15 +27,23 @@ namespace Il2CppInspector.Cpp
|
|||||||
// The name of the type
|
// The name of the type
|
||||||
public virtual string Name { get; set; }
|
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
|
// The size of the C++ type in bits
|
||||||
public virtual int Size { get; set; }
|
public virtual int Size { get; set; }
|
||||||
|
|
||||||
|
// The alignment of the type
|
||||||
|
public int AlignmentBytes { get; set; }
|
||||||
|
|
||||||
// The size of the C++ type in bytes
|
// The size of the C++ type in bytes
|
||||||
public virtual int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0);
|
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;
|
Name = name;
|
||||||
Size = size;
|
Size = size;
|
||||||
|
AlignmentBytes = alignmentBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate pointer to this type
|
// Generate pointer to this type
|
||||||
@@ -50,12 +55,10 @@ namespace Il2CppInspector.Cpp
|
|||||||
// Generate typedef to this type
|
// Generate typedef to this type
|
||||||
public CppAlias AsAlias(string Name) => new CppAlias(Name, this);
|
public CppAlias AsAlias(string Name) => new CppAlias(Name, this);
|
||||||
|
|
||||||
// Helper factories
|
// Return the type as a field
|
||||||
public static CppComplexType NewStruct(string name = "") => new CppComplexType(ComplexValueType.Struct) {Name = name};
|
public virtual string ToFieldString(string fieldName) => Name + " " + fieldName;
|
||||||
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};
|
|
||||||
|
|
||||||
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();
|
public override string ToString() => ToString();
|
||||||
}
|
}
|
||||||
@@ -68,6 +71,9 @@ namespace Il2CppInspector.Cpp
|
|||||||
public CppType ElementType { get; }
|
public CppType ElementType { get; }
|
||||||
|
|
||||||
public CppPointerType(int WordSize, CppType elementType) : base(null, WordSize) => ElementType = elementType;
|
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
|
// An array type
|
||||||
@@ -89,6 +95,9 @@ namespace Il2CppInspector.Cpp
|
|||||||
Length = length;
|
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 + "]";
|
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)
|
// 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 "))
|
if (text.StartsWith("typedef "))
|
||||||
text = text.Substring(8);
|
text = text.Substring(8);
|
||||||
|
|
||||||
@@ -131,13 +140,17 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Output as a named field in a type
|
// 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
|
// 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 class CppAlias : CppType
|
||||||
{
|
{
|
||||||
public CppType ElementType { get; }
|
public CppType ElementType { get; }
|
||||||
@@ -148,7 +161,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
|
|
||||||
public CppAlias(string name, CppType elementType) : base(name) => ElementType = elementType;
|
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)
|
// A struct, union, enum or class type (type with fields)
|
||||||
@@ -174,7 +187,11 @@ namespace Il2CppInspector.Cpp
|
|||||||
var flattened = new SortedDictionary<int, List<CppField>>();
|
var flattened = new SortedDictionary<int, List<CppField>>();
|
||||||
|
|
||||||
foreach (var field in t.Fields.Values.SelectMany(f => f)) {
|
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 baseOffset = field.Offset;
|
||||||
var fields = ct.Flattened.Fields.Select(kl => new {
|
var fields = ct.Flattened.Fields.Select(kl => new {
|
||||||
Key = kl.Key + baseOffset,
|
Key = kl.Key + baseOffset,
|
||||||
@@ -219,15 +236,6 @@ namespace Il2CppInspector.Cpp
|
|||||||
|
|
||||||
public CppComplexType(ComplexValueType complexValueType) : base("", 0) => ComplexValueType = complexValueType;
|
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
|
// Add a field to the type. Returns the offset of the field in the type
|
||||||
public int AddField(CppField field, int alignmentBytes = 0) {
|
public int AddField(CppField field, int alignmentBytes = 0) {
|
||||||
// Unions and enums always have an offset of zero
|
// Unions and enums always have an offset of zero
|
||||||
@@ -241,39 +249,52 @@ namespace Il2CppInspector.Cpp
|
|||||||
if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0)
|
if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0)
|
||||||
field.Offset += (alignmentBytes - field.OffsetBytes % alignmentBytes) * 8;
|
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))
|
if (Fields.ContainsKey(field.Offset))
|
||||||
Fields[field.Offset].Add(field);
|
Fields[field.Offset].Add(field);
|
||||||
else
|
else
|
||||||
Fields.Add(field.Offset, new List<CppField> { field });
|
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;
|
return Size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a field to the type
|
// Add a field to the type
|
||||||
public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0)
|
public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0, bool isConst = false)
|
||||||
=> AddField(new CppField(name, type, bitfield), alignmentBytes);
|
=> 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
|
// Summarize all field names and offsets
|
||||||
public override string ToString(string format = "") {
|
public override string ToString(string format = "") {
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
if (Name.Length > 0)
|
|
||||||
sb.Append("typedef ");
|
|
||||||
sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union ");
|
sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union ");
|
||||||
|
|
||||||
|
if (AlignmentBytes != 0)
|
||||||
|
sb.Append($"__declspec(align({AlignmentBytes})) ");
|
||||||
|
|
||||||
sb.Append(Name + (Name.Length > 0 ? " " : ""));
|
sb.Append(Name + (Name.Length > 0 ? " " : ""));
|
||||||
|
|
||||||
if (Fields.Any()) {
|
|
||||||
sb.Append("{");
|
sb.Append("{");
|
||||||
foreach (var field in Fields.Values.SelectMany(f => f))
|
foreach (var field in Fields.Values.SelectMany(f => f))
|
||||||
sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ";");
|
sb.Append("\n " + string.Join("\n ", field.ToString(format).Split('\n')) + ";");
|
||||||
|
|
||||||
sb.Append($"\n}}{(Name.Length > 0? " " + Name : "")}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
sb.Append($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||||
}
|
|
||||||
// Forward declaration
|
|
||||||
else {
|
|
||||||
sb.Append($"{Name};");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
sb.Append("\n");
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,27 +309,20 @@ namespace Il2CppInspector.Cpp
|
|||||||
|
|
||||||
public CppEnumType(CppType underlyingType) : base(ComplexValueType.Enum) => UnderlyingType = underlyingType;
|
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 = "") {
|
public override string ToString(string format = "") {
|
||||||
var sb = new StringBuilder();
|
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))
|
foreach (var field in Fields.Values.SelectMany(f => f))
|
||||||
sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ",");
|
sb.Append("\n " + string.Join("\n ", 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};");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
sb.AppendLine($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -16,11 +17,14 @@ using Il2CppInspector.Cpp.UnityHeaders;
|
|||||||
namespace Il2CppInspector.Cpp
|
namespace Il2CppInspector.Cpp
|
||||||
{
|
{
|
||||||
// A collection of C++ types
|
// A collection of C++ types
|
||||||
public class CppTypes : IEnumerable<CppType>
|
public class CppTypeCollection : IEnumerable<CppType>
|
||||||
{
|
{
|
||||||
// All of the types
|
// All of the types
|
||||||
public Dictionary<string, CppType> Types { get; }
|
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 CppType this[string s] => Types[s];
|
||||||
|
|
||||||
public IEnumerator<CppType> GetEnumerator() => Types.Values.GetEnumerator();
|
public IEnumerator<CppType> GetEnumerator() => Types.Values.GetEnumerator();
|
||||||
@@ -29,6 +33,16 @@ namespace Il2CppInspector.Cpp
|
|||||||
// Architecture width in bits (32/64) - to determine pointer sizes
|
// Architecture width in bits (32/64) - to determine pointer sizes
|
||||||
public int WordSize { get; }
|
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> {
|
private static readonly List<CppType> primitiveTypes = new List<CppType> {
|
||||||
new CppType("uint8_t", 8),
|
new CppType("uint8_t", 8),
|
||||||
new CppType("uint16_t", 16),
|
new CppType("uint16_t", 16),
|
||||||
@@ -46,7 +60,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
new CppType("void", 0)
|
new CppType("void", 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
public CppTypes(int wordSize) {
|
public CppTypeCollection(int wordSize) {
|
||||||
if (wordSize != 32 && wordSize != 64)
|
if (wordSize != 32 && wordSize != 64)
|
||||||
throw new ArgumentOutOfRangeException("Architecture word size must be 32 or 64-bit to generate C++ data");
|
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("intptr_t", WordSize));
|
||||||
Add(new CppType("uintptr_t", WordSize));
|
Add(new CppType("uintptr_t", WordSize));
|
||||||
Add(new CppType("size_t", WordSize));
|
Add(new CppType("size_t", WordSize));
|
||||||
|
|
||||||
|
foreach (var type in Types.Values)
|
||||||
|
type.Group = "primitive";
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Code parser
|
#region Code parser
|
||||||
@@ -65,20 +82,22 @@ namespace Il2CppInspector.Cpp
|
|||||||
public void AddFromDeclarationText(string text) {
|
public void AddFromDeclarationText(string text) {
|
||||||
using StringReader lines = new StringReader(text);
|
using StringReader lines = new StringReader(text);
|
||||||
|
|
||||||
var rgxExternDecl = new Regex(@"struct (\S+);");
|
var rgxForwardDecl = new Regex(@"(struct|union) (\S+);");
|
||||||
var rgxTypedefForwardDecl = new Regex(@"typedef struct (\S+) (\S+);");
|
var rgxTypedefAlias = new Regex(@"typedef (struct|union) (\S+) (\S+);");
|
||||||
var rgxTypedefFnPtr = new Regex(@"typedef\s+(?:struct )?" + CppFnPtrType.Regex + ";");
|
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 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 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 rgxStripKeywords = new Regex(@"\b(?:const|unsigned|volatile)\b");
|
||||||
var rgxCompressPtrs = new Regex(@"\*\s+\*");
|
var rgxCompressPtrs = new Regex(@"\*\s+\*");
|
||||||
|
|
||||||
var rgxArrayField = new Regex(@"(\S+?)\[([0-9]+)\]");
|
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 rgxIsBitDirective = new Regex(@"#ifdef\s+IS_(32|64)BIT");
|
||||||
var rgxSingleLineComment = new Regex(@"/\*.*?\*/");
|
var rgxSingleLineComment = new Regex(@"/\*.*?\*/");
|
||||||
|
|
||||||
@@ -86,10 +105,11 @@ namespace Il2CppInspector.Cpp
|
|||||||
bool falseIfBlock = false;
|
bool falseIfBlock = false;
|
||||||
bool inComment = false;
|
bool inComment = false;
|
||||||
bool inMethod = false;
|
bool inMethod = false;
|
||||||
|
bool inTypedef = false;
|
||||||
var nextEnumValue = 0ul;
|
var nextEnumValue = 0ul;
|
||||||
string line;
|
string rawLine, line;
|
||||||
|
|
||||||
while ((line = lines.ReadLine()) != null) {
|
while ((rawLine = line = lines.ReadLine()) != null) {
|
||||||
|
|
||||||
// Remove comments
|
// Remove comments
|
||||||
if (line.Contains("//"))
|
if (line.Contains("//"))
|
||||||
@@ -191,101 +211,103 @@ namespace Il2CppInspector.Cpp
|
|||||||
continue;
|
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
|
// Forward declaration
|
||||||
// typedef struct <struct-type> <alias>
|
// <struct|union> <external-type>;
|
||||||
var typedef = rgxTypedefForwardDecl.Match(line);
|
var externDecl = rgxForwardDecl.Match(line);
|
||||||
if (typedef.Success) {
|
if (externDecl.Success) {
|
||||||
var alias = typedef.Groups[2].Captures[0].ToString();
|
var complexType = complexTypeMap[externDecl.Groups[1].Captures[0].ToString()];
|
||||||
var declType = typedef.Groups[1].Captures[0].ToString();
|
var declType = externDecl.Groups[2].Captures[0].ToString();
|
||||||
|
|
||||||
// Sometimes we might get multiple forward declarations for the same type
|
switch (complexType) {
|
||||||
if (!Types.ContainsKey(declType))
|
case ComplexValueType.Struct: Struct(declType); break;
|
||||||
Types.Add(declType, CppType.NewStruct(declType));
|
case ComplexValueType.Union: Union(declType); break;
|
||||||
|
}
|
||||||
// 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));
|
|
||||||
|
|
||||||
Debug.WriteLine($"[FORWARD DECL ] {line}");
|
Debug.WriteLine($"[FORWARD DECL ] {line}");
|
||||||
continue;
|
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 <retType> (*<alias>)(<args>);
|
||||||
typedef = rgxTypedefFnPtr.Match(line);
|
typedef = rgxTypedefFnPtr.Match(line);
|
||||||
if (typedef.Success) {
|
if (typedef.Success) {
|
||||||
var alias = typedef.Groups[2].Captures[0].ToString();
|
var alias = typedef.Groups[2].Captures[0].ToString();
|
||||||
|
|
||||||
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
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}");
|
Debug.WriteLine($"[TYPEDEF FNPTR] {line} -- Adding method pointer typedef to {alias}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias
|
// Type (pointer) alias
|
||||||
// typedef <targetType>[*..] <alias>;
|
// typedef <targetType[*..]> <alias>;
|
||||||
typedef = rgxTypedef.Match(line);
|
typedef = rgxTypedefPtr.Match(line);
|
||||||
if (typedef.Success) {
|
if (typedef.Success) {
|
||||||
var alias = typedef.Groups[2].Captures[0].ToString();
|
var alias = typedef.Groups[2].Captures[0].ToString();
|
||||||
var existingType = typedef.Groups[1].Captures[0].ToString();
|
var existingType = typedef.Groups[1].Captures[0].ToString();
|
||||||
|
|
||||||
// Potential multiple indirection
|
// Potential multiple indirection
|
||||||
var type = Types[existingType];
|
var type = GetType(existingType);
|
||||||
|
|
||||||
|
TypedefAliases.TryAdd(alias, type);
|
||||||
|
|
||||||
var pointers = line.Count(c => c == '*');
|
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}");
|
Debug.WriteLine($"[TYPEDEF {(pointers > 0? "PTR":"VAL")} ] {line} -- Adding typedef from {type.Name} to {alias}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start of struct
|
// Start of struct/union/enum
|
||||||
// typedef struct <optional-type-name>
|
// [typedef] <struct|union|enum> [optional-tag-name]
|
||||||
if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1
|
var definition = rgxDefinition.Match(line);
|
||||||
&& currentType.Count == 0) {
|
if (definition.Success && line.IndexOf(";", StringComparison.Ordinal) == -1 && currentType.Count == 0) {
|
||||||
currentType.Push(CppType.NewStruct());
|
// Must have a name if not a typedef, might have a name if it is
|
||||||
|
var split = line.Split(' ');
|
||||||
|
|
||||||
if (line.StartsWith("struct "))
|
if (split[0] == "typedef")
|
||||||
currentType.Peek().Name = line.Split(' ')[1];
|
split = split.Skip(1).ToArray();
|
||||||
|
|
||||||
Debug.WriteLine($"\n[STRUCT START ] {line}");
|
var name = split.Length > 1 && split[1] != "{" ? split[1] : "";
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start of union
|
currentType.Push(complexTypeMap[split[0]] switch {
|
||||||
// typedef union <optional-type-name>
|
ComplexValueType.Struct => Struct(name),
|
||||||
if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
ComplexValueType.Union => Union(name),
|
||||||
currentType.Push(CppType.NewUnion());
|
ComplexValueType.Enum => NewDefaultEnum(name),
|
||||||
|
_ => throw new InvalidOperationException("Unknown complex type")
|
||||||
|
});
|
||||||
|
|
||||||
Debug.WriteLine($"\n[UNION START ] {line}");
|
// Remember we have to set an alias later
|
||||||
continue;
|
inTypedef = line.StartsWith("typedef ");
|
||||||
}
|
|
||||||
|
|
||||||
// Start of enum
|
// Reset next enum value if needed
|
||||||
// typedef enum <optional-type-name>
|
|
||||||
if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
|
||||||
currentType.Push(NewDefaultEnum());
|
|
||||||
nextEnumValue = 0;
|
nextEnumValue = 0;
|
||||||
|
|
||||||
Debug.WriteLine($"\n[ENUM START ] {line}");
|
Debug.WriteLine($"\n[COMPLEX START] {line}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,17 +316,17 @@ namespace Il2CppInspector.Cpp
|
|||||||
// union <optional-type-name>
|
// union <optional-type-name>
|
||||||
var words = line.Split(' ');
|
var words = line.Split(' ');
|
||||||
if ((words[0] == "union" || words[0] == "struct") && words.Length <= 2) {
|
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}");
|
Debug.WriteLine($"[FIELD START ] {line}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of already named struct
|
// End of already named (non-typedef) struct
|
||||||
if (line == "};" && currentType.Count == 1) {
|
if (line == "};" && currentType.Count == 1) {
|
||||||
var ct = currentType.Pop();
|
var ct = currentType.Pop();
|
||||||
if (!Types.ContainsKey(ct.Name))
|
if (!Types.ContainsKey(ct.Name))
|
||||||
Types.Add(ct.Name, ct);
|
Add(ct);
|
||||||
else
|
else
|
||||||
((CppComplexType) Types[ct.Name]).Fields = ct.Fields;
|
((CppComplexType) Types[ct.Name]).Fields = ct.Fields;
|
||||||
|
|
||||||
@@ -313,35 +335,42 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
|
|
||||||
// End of complex field, complex type or enum
|
// 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(";")) {
|
if (line.StartsWith("}") && line.EndsWith(";")) {
|
||||||
var name = line[1..^1].Trim();
|
var fieldNameOrTypedefAlias = line[1..^1].Trim();
|
||||||
var ct = currentType.Pop();
|
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) {
|
if (currentType.Count == 0) {
|
||||||
ct.Name = name;
|
|
||||||
|
|
||||||
if (!Types.ContainsKey(name))
|
if (inTypedef)
|
||||||
Types.Add(name, ct);
|
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,
|
// 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
|
// because other types are already referencing it; replacing it in the
|
||||||
// collection will not replace the references to the empty version in
|
// collection will not replace the references to the empty version in
|
||||||
// other types
|
// other types
|
||||||
else {
|
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
|
// Otherwise it's a field name in the current type
|
||||||
else {
|
else {
|
||||||
var parent = currentType.Peek();
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -350,6 +379,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
var fieldFnPtr = rgxFieldFnPtr.Match(line);
|
var fieldFnPtr = rgxFieldFnPtr.Match(line);
|
||||||
if (fieldFnPtr.Success) {
|
if (fieldFnPtr.Success) {
|
||||||
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
||||||
|
fnPtrType.Group = currentGroup;
|
||||||
|
|
||||||
var name = fieldFnPtr.Groups[2].Captures[0].ToString();
|
var name = fieldFnPtr.Groups[2].Captures[0].ToString();
|
||||||
|
|
||||||
@@ -365,7 +395,8 @@ namespace Il2CppInspector.Cpp
|
|||||||
|
|
||||||
if (field.Success) {
|
if (field.Success) {
|
||||||
var names = field.Groups[2].Captures[0].ToString();
|
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
|
// Multiple fields can be separated by commas
|
||||||
foreach (var fieldName in names.Split(',')) {
|
foreach (var fieldName in names.Split(',')) {
|
||||||
@@ -384,21 +415,20 @@ namespace Il2CppInspector.Cpp
|
|||||||
if (field.Groups[3].Captures.Count > 0)
|
if (field.Groups[3].Captures.Count > 0)
|
||||||
bitfield = int.Parse(field.Groups[3].Captures[0].ToString());
|
bitfield = int.Parse(field.Groups[3].Captures[0].ToString());
|
||||||
|
|
||||||
// Potential multiple indirection
|
// Potential multiple indirection or use of alias
|
||||||
var type = Types[typeName];
|
var type = GetType(typeName);
|
||||||
var pointers = line.Count(c => c == '*');
|
|
||||||
for (int i = 0; i < pointers; i++)
|
|
||||||
type = type.AsPointer(WordSize);
|
|
||||||
|
|
||||||
var ct = currentType.Peek();
|
var ct = currentType.Peek();
|
||||||
|
|
||||||
if (arraySize > 0)
|
if (arraySize > 0)
|
||||||
type = type.AsArray(arraySize);
|
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}");
|
Debug.WriteLine($"[FIELD {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- {name}");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
Debug.WriteLine($"[BITFIELD ] {line} -- {name} : {bitfield}");
|
Debug.WriteLine($"[BITFIELD ] {line} -- {name} : {bitfield}");
|
||||||
}
|
}
|
||||||
@@ -414,7 +444,7 @@ namespace Il2CppInspector.Cpp
|
|||||||
if (enumValue.Groups[2].Captures.Count > 0) {
|
if (enumValue.Groups[2].Captures.Count > 0) {
|
||||||
// Convert the text to a ulong even if it's hexadecimal with a 0x prefix
|
// Convert the text to a ulong even if it's hexadecimal with a 0x prefix
|
||||||
var valueText = enumValue.Groups[2].Captures[0].ToString();
|
var valueText = enumValue.Groups[2].Captures[0].ToString();
|
||||||
var conv = new System.ComponentModel.UInt64Converter();
|
var conv = new UInt64Converter();
|
||||||
|
|
||||||
// Handle bit shift operator
|
// Handle bit shift operator
|
||||||
var values = valueText.Split("<<").Select(t => (ulong) conv.ConvertFromInvariantString(t.Trim())).ToArray();
|
var values = valueText.Split("<<").Select(t => (ulong) conv.ConvertFromInvariantString(t.Trim())).ToArray();
|
||||||
@@ -453,39 +483,95 @@ namespace Il2CppInspector.Cpp
|
|||||||
}
|
}
|
||||||
#endregion
|
#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) {
|
public CppType GetType(string typeName) {
|
||||||
|
|
||||||
|
// Separate type name from pointers
|
||||||
var baseName = typeName.Replace("*", "");
|
var baseName = typeName.Replace("*", "");
|
||||||
var indirectionCount = typeName.Length - baseName.Length;
|
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++)
|
for (int i = 0; i < indirectionCount; i++)
|
||||||
type = type.AsPointer(WordSize);
|
type = type.AsPointer(WordSize);
|
||||||
|
|
||||||
return type;
|
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
|
// 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
|
// 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
|
// 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)
|
public int AddField(CppComplexType declaringType, string fieldName, string typeName, bool isConst = false)
|
||||||
=> declaringType.AddField(fieldName, GetType(typeName));
|
=> 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)
|
// 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
|
// Generate a populated CppTypeCollection object from a set of Unity headers
|
||||||
public static CppTypes FromUnityVersion(UnityVersion version, int wordSize = 32)
|
public static CppTypeCollection FromUnityVersion(UnityVersion version, int wordSize = 32)
|
||||||
=> FromUnityHeaders(UnityHeader.GetHeaderForVersion(version), wordSize);
|
=> FromUnityHeaders(UnityHeader.GetHeaderForVersion(version), wordSize);
|
||||||
|
|
||||||
public static CppTypes FromUnityHeaders(UnityHeader header, int wordSize = 32) {
|
public static CppTypeCollection FromUnityHeaders(UnityHeader header, int wordSize = 32) {
|
||||||
var cppTypes = new CppTypes(wordSize);
|
var cppTypes = new CppTypeCollection(wordSize);
|
||||||
|
|
||||||
|
cppTypes.SetGroup("il2cpp");
|
||||||
|
|
||||||
// Add junk from config files we haven't included
|
// Add junk from config files we haven't included
|
||||||
cppTypes.Add(new CppType("Il2CppIManagedObjectHolder"));
|
cppTypes.TypedefAliases.Add("Il2CppIManagedObjectHolder", cppTypes["void"].AsPointer(wordSize));
|
||||||
cppTypes.Add(new CppType("Il2CppIUnknown"));
|
cppTypes.TypedefAliases.Add("Il2CppIUnknown", cppTypes["void"].AsPointer(wordSize));
|
||||||
|
|
||||||
// Process Unity headers
|
// Process Unity headers
|
||||||
var headers = header.GetHeaderText();
|
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
|
Copyright 2020 Robert Xiao - https://robertxiao.ca
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
@@ -77,7 +77,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
|||||||
// Guess which header file(s) correspond to the given metadata+binary.
|
// Guess which header file(s) correspond to the given metadata+binary.
|
||||||
// Note that this may match multiple headers due to structural changes between versions
|
// Note that this may match multiple headers due to structural changes between versions
|
||||||
// that are not reflected in the metadata version.
|
// 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>();
|
List<UnityHeader> result = new List<UnityHeader>();
|
||||||
foreach (var v in GetAllHeaders()) {
|
foreach (var v in GetAllHeaders()) {
|
||||||
if (v.MetadataVersion != model.Package.BinaryImage.Version)
|
if (v.MetadataVersion != model.Package.BinaryImage.Version)
|
||||||
|
|||||||
188
Il2CppInspector.Common/Model/AppModel.cs
Normal file
188
Il2CppInspector.Common/Model/AppModel.cs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Il2CppInspector.Cpp;
|
||||||
|
using Il2CppInspector.Cpp.UnityHeaders;
|
||||||
|
using Il2CppInspector.Reflection;
|
||||||
|
|
||||||
|
namespace Il2CppInspector.Model
|
||||||
|
{
|
||||||
|
// Class that represents a composite IL/C++ type
|
||||||
|
public class AppType
|
||||||
|
{
|
||||||
|
// 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 ulong VirtualAddress { get; internal set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class that represents a composite IL/C++ method
|
||||||
|
public class AppMethod
|
||||||
|
{
|
||||||
|
// The corresponding C++ function pointer type
|
||||||
|
public CppFnPtrType CppFnPtrType { get; internal set; }
|
||||||
|
|
||||||
|
// The corresponding .NET method
|
||||||
|
public MethodBase ILMethod { get; internal set; }
|
||||||
|
|
||||||
|
// The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method
|
||||||
|
public ulong MethodInfoPtrAddress { get; internal set; }
|
||||||
|
|
||||||
|
// The VA of the method code itself, or 0 if unknown/not compiled
|
||||||
|
public ulong MethodCodeAddress => ILMethod.VirtualAddress?.Start ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 AppModel : IEnumerable<CppType>
|
||||||
|
{
|
||||||
|
// The C++ compiler to target
|
||||||
|
public CppCompilerType TargetCompiler { get; private set; }
|
||||||
|
|
||||||
|
// The Unity version used to build the binary
|
||||||
|
public UnityVersion UnityVersion { get; set; } // TODO: Change to private set after integrating IDA output
|
||||||
|
|
||||||
|
// The Unity IL2CPP C++ headers for the binary
|
||||||
|
// Use this for code output
|
||||||
|
public UnityHeader UnityHeader { get; set; } // TODO: Change to private set after integrating IDA output
|
||||||
|
|
||||||
|
// All of the C++ types used in the application including Unity internal types
|
||||||
|
// NOTE: This is for querying individual types for static analysis
|
||||||
|
// To generate code output, use DeclarationOrderedTypes
|
||||||
|
public CppTypeCollection TypeCollection { get; set; } // TODO: Change to private set after integrating IDA output
|
||||||
|
|
||||||
|
// All of the C++ types used in the application (.NET type translations only)
|
||||||
|
// The types are ordered to enable the production of code output without forward dependencies
|
||||||
|
public List<CppType> DependencyOrderedTypes { get; private set; }
|
||||||
|
|
||||||
|
// The .NET type model for the application
|
||||||
|
public TypeModel ILModel { get; }
|
||||||
|
|
||||||
|
// All of the function exports for the binary
|
||||||
|
public List<Export> Exports { get; }
|
||||||
|
|
||||||
|
// Delegated C++ types iterator
|
||||||
|
public IEnumerator<CppType> GetEnumerator() => TypeCollection.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable) TypeCollection).GetEnumerator();
|
||||||
|
|
||||||
|
// The C++ declaration generator for this binary
|
||||||
|
// TODO: Make this private once IDA output integration is completed
|
||||||
|
internal CppDeclarationGenerator declarationGenerator;
|
||||||
|
|
||||||
|
// Convenience properties
|
||||||
|
|
||||||
|
// The word size of the binary in bits
|
||||||
|
public int WordSize => ILModel.Package.BinaryImage.Bits;
|
||||||
|
|
||||||
|
// The IL2CPP package for this application
|
||||||
|
public Il2CppInspector Package => ILModel.Package;
|
||||||
|
|
||||||
|
// The compiler used to build the binary
|
||||||
|
public CppCompilerType SourceCompiler => declarationGenerator.InheritanceStyle;
|
||||||
|
|
||||||
|
// The Unity header text including word size define
|
||||||
|
public string UnityHeaderText => (WordSize == 32 ? "#define IS_32BIT\n" : "") + UnityHeader.GetHeaderText();
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
public AppModel(TypeModel model) {
|
||||||
|
// Save .NET type model
|
||||||
|
ILModel = model;
|
||||||
|
|
||||||
|
// Get addresses of IL2CPP API function exports
|
||||||
|
Exports = model.Package.Binary.Image.GetExports()?.ToList() ?? new List<Export>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the application model targeting a specific version of Unity and C++ compiler
|
||||||
|
// If no Unity version is specified, it will be guessed from the contents of the IL2CPP binary
|
||||||
|
// The C++ compiler used to actually build the original IL2CPP binary will always be guessed based on the binary file format
|
||||||
|
// (via the constructor of CppDeclarationGenerator, in InheritanceStyle)
|
||||||
|
// If no target C++ compiler is specified, it will be set to match the one assumed to have been used to compile the binary
|
||||||
|
public AppModel Build(UnityVersion unityVersion = null, CppCompilerType compiler = CppCompilerType.BinaryFormat) {
|
||||||
|
// Set target compiler
|
||||||
|
TargetCompiler = compiler == CppCompilerType.BinaryFormat ? CppCompiler.GuessFromImage(ILModel.Package.BinaryImage) : compiler;
|
||||||
|
|
||||||
|
// Determine Unity version and get headers
|
||||||
|
UnityHeader = unityVersion != null ? UnityHeader.GetHeaderForVersion(unityVersion) : UnityHeader.GuessHeadersForModel(ILModel)[0];
|
||||||
|
UnityVersion = unityVersion ?? UnityHeader.MinVersion;
|
||||||
|
|
||||||
|
// Check for matching metadata and binary versions
|
||||||
|
if (UnityHeader.MetadataVersion != ILModel.Package.BinaryImage.Version) {
|
||||||
|
Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeader.MetadataVersion})" +
|
||||||
|
$" does not match metadata version {ILModel.Package.BinaryImage.Version}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start creation of type model by parsing all of the Unity IL2CPP headers
|
||||||
|
// Calling declarationGenerator.GenerateRemainingTypeDeclarations() below will automatically add to this collection
|
||||||
|
TypeCollection = CppTypeCollection.FromUnityHeaders(UnityHeader, WordSize);
|
||||||
|
|
||||||
|
// Initialize declaration generator to process every type in the binary
|
||||||
|
declarationGenerator = new CppDeclarationGenerator(this);
|
||||||
|
|
||||||
|
// Initialize ordered type list for code output
|
||||||
|
DependencyOrderedTypes = new List<CppType>();
|
||||||
|
|
||||||
|
// Add method definitions to C++ type model
|
||||||
|
TypeCollection.SetGroup("type_definitions");
|
||||||
|
|
||||||
|
foreach (var method in ILModel.MethodsByDefinitionIndex.Where(m => m.VirtualAddress.HasValue)) {
|
||||||
|
declarationGenerator.IncludeMethod(method);
|
||||||
|
DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add generic methods to C++ type model
|
||||||
|
TypeCollection.SetGroup("types_from_generics");
|
||||||
|
|
||||||
|
foreach (var method in ILModel.GenericMethods.Values.Where(m => m.VirtualAddress.HasValue)) {
|
||||||
|
declarationGenerator.IncludeMethod(method);
|
||||||
|
DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add metadata usage types to C++ type model
|
||||||
|
// Not supported in il2cpp <19
|
||||||
|
TypeCollection.SetGroup("types_from_usages");
|
||||||
|
|
||||||
|
if (Package.MetadataUsages != null)
|
||||||
|
foreach (var usage in Package.MetadataUsages) {
|
||||||
|
switch (usage.Type) {
|
||||||
|
case MetadataUsageType.Type:
|
||||||
|
case MetadataUsageType.TypeInfo:
|
||||||
|
var type = ILModel.GetMetadataUsageType(usage);
|
||||||
|
declarationGenerator.IncludeType(type);
|
||||||
|
DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||||
|
break;
|
||||||
|
case MetadataUsageType.MethodDef:
|
||||||
|
case MetadataUsageType.MethodRef:
|
||||||
|
var method = ILModel.GetMetadataUsageMethod(usage);
|
||||||
|
declarationGenerator.IncludeMethod(method);
|
||||||
|
DependencyOrderedTypes.AddRange(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Build composite types
|
||||||
|
|
||||||
|
// This is to allow this method to be chained after a new expression
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all the types for a group
|
||||||
|
public IEnumerable<CppType> GetTypeGroup(string groupName) => TypeCollection.GetTypeGroup(groupName);
|
||||||
|
public IEnumerable<CppType> GetDependencyOrderedTypeGroup(string groupName) => DependencyOrderedTypes.Where(t => t.Group == groupName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ namespace Il2CppInspector.Outputs
|
|||||||
{
|
{
|
||||||
public class CSharpCodeStubs
|
public class CSharpCodeStubs
|
||||||
{
|
{
|
||||||
private readonly Il2CppModel model;
|
private readonly TypeModel model;
|
||||||
private Exception lastException;
|
private Exception lastException;
|
||||||
|
|
||||||
// Namespace prefixes whose contents should be skipped
|
// Namespace prefixes whose contents should be skipped
|
||||||
@@ -41,7 +41,7 @@ namespace Il2CppInspector.Outputs
|
|||||||
private HashSet<CustomAttributeData> usedAssemblyAttributes = new HashSet<CustomAttributeData>();
|
private HashSet<CustomAttributeData> usedAssemblyAttributes = new HashSet<CustomAttributeData>();
|
||||||
private readonly object usedAssemblyAttributesLock = new object();
|
private readonly object usedAssemblyAttributesLock = new object();
|
||||||
|
|
||||||
public CSharpCodeStubs(Il2CppModel model) => this.model = model;
|
public CSharpCodeStubs(TypeModel model) => this.model = model;
|
||||||
|
|
||||||
// Get the last error that occurred and clear the error state
|
// Get the last error that occurred and clear the error state
|
||||||
public Exception GetAndClearLastException() {
|
public Exception GetAndClearLastException() {
|
||||||
|
|||||||
@@ -2,61 +2,44 @@
|
|||||||
// Copyright (c) 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
// Copyright (c) 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
// All rights reserved
|
// All rights reserved
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
using Il2CppInspector.Cpp;
|
using Il2CppInspector.Cpp;
|
||||||
using Il2CppInspector.Cpp.UnityHeaders;
|
using Il2CppInspector.Model;
|
||||||
|
|
||||||
namespace Il2CppInspector.Outputs
|
namespace Il2CppInspector.Outputs
|
||||||
{
|
{
|
||||||
public class CppScaffolding
|
public class CppScaffolding
|
||||||
{
|
{
|
||||||
private readonly Il2CppModel model;
|
private readonly AppModel model;
|
||||||
public CppCompiler.Type Compiler = CppCompiler.Type.BinaryFormat;
|
|
||||||
private StreamWriter writer;
|
private StreamWriter writer;
|
||||||
public UnityVersion UnityVersion;
|
|
||||||
private CppDeclarationGenerator declGenerator;
|
|
||||||
|
|
||||||
private readonly Regex rgxGCCalign = new Regex(@"__attribute__\s*?\(\s*?\(\s*?aligned\s*?\(\s*?([0-9]+)\s*?\)\s*?\)\s*?\)");
|
private readonly Regex rgxGCCalign = new Regex(@"__attribute__\s*?\(\s*?\(\s*?aligned\s*?\(\s*?([0-9]+)\s*?\)\s*?\)\s*?\)");
|
||||||
private readonly Regex rgxMSVCalign = new Regex(@"__declspec\s*?\(\s*?align\s*?\(\s*?([0-9]+)\s*?\)\s*?\)");
|
private readonly Regex rgxMSVCalign = new Regex(@"__declspec\s*?\(\s*?align\s*?\(\s*?([0-9]+)\s*?\)\s*?\)");
|
||||||
|
|
||||||
public CppScaffolding(Il2CppModel model) => this.model = model;
|
public CppScaffolding(AppModel model) => this.model = model;
|
||||||
|
|
||||||
public void WriteCppToFile(string outputFile) {
|
public void WriteCppToFile(string outputFile) {
|
||||||
declGenerator = new CppDeclarationGenerator(model, UnityVersion);
|
|
||||||
UnityVersion = declGenerator.UnityVersion;
|
|
||||||
|
|
||||||
// Can be overridden in the object initializer
|
|
||||||
if (Compiler == CppCompiler.Type.BinaryFormat)
|
|
||||||
Compiler = CppCompiler.GuessFromImage(model.Package.BinaryImage);
|
|
||||||
|
|
||||||
using var fs = new FileStream(outputFile, FileMode.Create);
|
using var fs = new FileStream(outputFile, FileMode.Create);
|
||||||
writer = new StreamWriter(fs, Encoding.UTF8);
|
writer = new StreamWriter(fs, Encoding.UTF8);
|
||||||
|
|
||||||
writeLine("// Generated C++ file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty");
|
writeLine("// Generated C++ file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty");
|
||||||
writeLine("// Target Unity version: " + declGenerator.UnityHeader);
|
writeLine("// Target Unity version: " + model.UnityHeader);
|
||||||
writeLine("");
|
writeLine("");
|
||||||
|
|
||||||
// TODO: The implementation of C++ header output is temporary and will be replaced by a C++ type model in a later version
|
|
||||||
writeSectionHeader("IL2CPP internal types");
|
writeSectionHeader("IL2CPP internal types");
|
||||||
writeUnityHeaders();
|
writeCode(model.UnityHeaderText);
|
||||||
|
|
||||||
// Prevent conflicts with symbols that are in scope for compilers by default
|
// Prevent conflicts with symbols that are in scope for compilers by default
|
||||||
writeCode("namespace app {");
|
writeCode("namespace app {");
|
||||||
writeLine("");
|
writeLine("");
|
||||||
|
|
||||||
writeSectionHeader("Application type definitions");
|
writeTypesForGroup("Application type definitions", "type_definitions");
|
||||||
writeTypesForMethods(model.MethodsByDefinitionIndex);
|
writeTypesForGroup("Application generic method type usages", "types_from_generics");
|
||||||
|
writeTypesForGroup("Application type usages", "types_from_usages");
|
||||||
writeSectionHeader("Application generic method type usages");
|
|
||||||
writeTypesForMethods(model.GenericMethods.Values);
|
|
||||||
|
|
||||||
writeSectionHeader("Application type usages");
|
|
||||||
writeUsages();
|
|
||||||
|
|
||||||
writeCode("}");
|
writeCode("}");
|
||||||
|
|
||||||
@@ -65,43 +48,19 @@ namespace Il2CppInspector.Outputs
|
|||||||
|
|
||||||
private void writeUnityHeaders() {
|
private void writeUnityHeaders() {
|
||||||
var prefix = (model.Package.BinaryImage.Bits == 32) ? "#define IS_32BIT\n" : "";
|
var prefix = (model.Package.BinaryImage.Bits == 32) ? "#define IS_32BIT\n" : "";
|
||||||
writeCode(prefix + declGenerator.UnityHeader.GetHeaderText());
|
writeCode(prefix + model.UnityHeader.GetHeaderText());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeTypesForMethods(IEnumerable<MethodBase> methods) {
|
private void writeTypesForGroup(string header, string group) {
|
||||||
foreach (var method in methods.Where(m => m.VirtualAddress.HasValue)) {
|
writeSectionHeader(header);
|
||||||
declGenerator.IncludeMethod(method);
|
foreach (var cppType in model.GetDependencyOrderedTypeGroup(group))
|
||||||
writeCode(declGenerator.GenerateRemainingTypeDeclarations());
|
writeCode(cppType.ToString());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeUsages() {
|
|
||||||
// Not supported in il2cpp <19
|
|
||||||
if (model.Package.MetadataUsages == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var usage in model.Package.MetadataUsages) {
|
|
||||||
switch (usage.Type) {
|
|
||||||
case MetadataUsageType.Type:
|
|
||||||
case MetadataUsageType.TypeInfo:
|
|
||||||
var type = model.GetMetadataUsageType(usage);
|
|
||||||
declGenerator.IncludeType(type);
|
|
||||||
writeCode(declGenerator.GenerateRemainingTypeDeclarations());
|
|
||||||
break;
|
|
||||||
case MetadataUsageType.MethodDef:
|
|
||||||
case MetadataUsageType.MethodRef:
|
|
||||||
var method = model.GetMetadataUsageMethod(usage);
|
|
||||||
declGenerator.IncludeMethod(method);
|
|
||||||
writeCode(declGenerator.GenerateRemainingTypeDeclarations());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeCode(string text) {
|
private void writeCode(string text) {
|
||||||
if (Compiler == CppCompiler.Type.MSVC)
|
if (model.TargetCompiler == CppCompilerType.MSVC)
|
||||||
text = rgxGCCalign.Replace(text, @"__declspec(align($1))");
|
text = rgxGCCalign.Replace(text, @"__declspec(align($1))");
|
||||||
if (Compiler == CppCompiler.Type.GCC)
|
if (model.TargetCompiler == CppCompilerType.GCC)
|
||||||
text = rgxMSVCalign.Replace(text, @"__attribute__((aligned($1)))");
|
text = rgxMSVCalign.Replace(text, @"__attribute__((aligned($1)))");
|
||||||
|
|
||||||
var lines = text.Replace("\r", "").Split('\n');
|
var lines = text.Replace("\r", "").Split('\n');
|
||||||
|
|||||||
@@ -10,28 +10,34 @@ using System.IO;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
using Il2CppInspector.Cpp;
|
using Il2CppInspector.Cpp;
|
||||||
using Il2CppInspector.Cpp.UnityHeaders;
|
using Il2CppInspector.Model;
|
||||||
|
|
||||||
namespace Il2CppInspector.Outputs
|
namespace Il2CppInspector.Outputs
|
||||||
{
|
{
|
||||||
public class IDAPythonScript
|
public class IDAPythonScript
|
||||||
{
|
{
|
||||||
private readonly Il2CppModel model;
|
// TODO: Make this readonly when we've integrated with ApplicationModel
|
||||||
|
private AppModel model;
|
||||||
private StreamWriter writer;
|
private StreamWriter writer;
|
||||||
public UnityVersion UnityVersion;
|
// TODO: Remove when integrated with ApplicationModel
|
||||||
private CppDeclarationGenerator declGenerator;
|
private CppDeclarationGenerator declGenerator;
|
||||||
|
|
||||||
public IDAPythonScript(Il2CppModel model) => this.model = model;
|
public IDAPythonScript(AppModel model) => this.model = model;
|
||||||
|
|
||||||
public void WriteScriptToFile(string outputFile) {
|
public void WriteScriptToFile(string outputFile) {
|
||||||
declGenerator = new CppDeclarationGenerator(model, UnityVersion);
|
// TODO: Integrate with ApplicationModel - use this hack so we can use CppDeclarationGenerator without disturbing the model passed in
|
||||||
UnityVersion = declGenerator.UnityVersion;
|
var internalModel = new AppModel(model.ILModel);
|
||||||
|
internalModel.UnityVersion = model.UnityVersion;
|
||||||
|
internalModel.UnityHeader = model.UnityHeader;
|
||||||
|
internalModel.TypeCollection = CppTypeCollection.FromUnityHeaders(model.UnityHeader, model.WordSize);
|
||||||
|
model = internalModel;
|
||||||
|
declGenerator = new CppDeclarationGenerator(model);
|
||||||
|
|
||||||
using var fs = new FileStream(outputFile, FileMode.Create);
|
using var fs = new FileStream(outputFile, FileMode.Create);
|
||||||
writer = new StreamWriter(fs, Encoding.UTF8);
|
writer = new StreamWriter(fs, Encoding.UTF8);
|
||||||
|
|
||||||
writeLine("# Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty");
|
writeLine("# Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty");
|
||||||
writeLine("# Target Unity version: " + declGenerator.UnityHeader.ToString());
|
writeLine("# Target Unity version: " + model.UnityHeader);
|
||||||
writeLine("print('Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty')");
|
writeLine("print('Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty')");
|
||||||
writeSectionHeader("Preamble");
|
writeSectionHeader("Preamble");
|
||||||
writePreamble();
|
writePreamble();
|
||||||
@@ -81,27 +87,26 @@ typedef __int16 int16_t;
|
|||||||
typedef __int32 int32_t;
|
typedef __int32 int32_t;
|
||||||
typedef __int64 int64_t;
|
typedef __int64 int64_t;
|
||||||
");
|
");
|
||||||
|
// IL2CPP internal types
|
||||||
var prefix = (model.Package.BinaryImage.Bits == 32) ? "#define IS_32BIT\n" : "";
|
writeDecls(model.UnityHeaderText);
|
||||||
writeDecls(prefix + declGenerator.UnityHeader.GetHeaderText());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeMethods() {
|
private void writeMethods() {
|
||||||
writeSectionHeader("Method definitions");
|
writeSectionHeader("Method definitions");
|
||||||
writeMethods(model.MethodsByDefinitionIndex);
|
writeMethods(model.ILModel.MethodsByDefinitionIndex);
|
||||||
|
|
||||||
writeSectionHeader("Constructed generic methods");
|
writeSectionHeader("Constructed generic methods");
|
||||||
writeMethods(model.GenericMethods.Values);
|
writeMethods(model.ILModel.GenericMethods.Values);
|
||||||
|
|
||||||
writeSectionHeader("Custom attributes generators");
|
writeSectionHeader("Custom attributes generators");
|
||||||
foreach (var method in model.AttributesByIndices.Values.Where(m => m.VirtualAddress.HasValue)) {
|
foreach (var method in model.ILModel.AttributesByIndices.Values.Where(m => m.VirtualAddress.HasValue)) {
|
||||||
var address = method.VirtualAddress.Value.Start;
|
var address = method.VirtualAddress.Value.Start;
|
||||||
writeName(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator");
|
writeName(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator");
|
||||||
writeComment(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator(CustomAttributesCache *)");
|
writeComment(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator(CustomAttributesCache *)");
|
||||||
}
|
}
|
||||||
|
|
||||||
writeSectionHeader("Method.Invoke thunks");
|
writeSectionHeader("Method.Invoke thunks");
|
||||||
foreach (var method in model.MethodInvokers.Where(m => m != null)) {
|
foreach (var method in model.ILModel.MethodInvokers.Where(m => m != null)) {
|
||||||
var address = method.VirtualAddress.Start;
|
var address = method.VirtualAddress.Start;
|
||||||
writeName(address, method.Name);
|
writeName(address, method.Name);
|
||||||
writeComment(address, method);
|
writeComment(address, method);
|
||||||
@@ -113,7 +118,7 @@ typedef __int64 int64_t;
|
|||||||
declGenerator.IncludeMethod(method);
|
declGenerator.IncludeMethod(method);
|
||||||
writeDecls(declGenerator.GenerateRemainingTypeDeclarations());
|
writeDecls(declGenerator.GenerateRemainingTypeDeclarations());
|
||||||
var address = method.VirtualAddress.Value.Start;
|
var address = method.VirtualAddress.Value.Start;
|
||||||
writeTypedName(address, declGenerator.GenerateMethodDeclaration(method), declGenerator.GlobalNamer.GetName(method));
|
writeTypedName(address, declGenerator.GenerateMethodDeclaration(method).ToSignatureString(), declGenerator.GlobalNamer.GetName(method));
|
||||||
writeComment(address, method);
|
writeComment(address, method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,20 +146,20 @@ typedef __int64 int64_t;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var stringType = declGenerator.AsCType(model.TypesByFullName["System.String"]);
|
var stringType = declGenerator.AsCType(model.ILModel.TypesByFullName["System.String"]);
|
||||||
foreach (var usage in model.Package.MetadataUsages) {
|
foreach (var usage in model.Package.MetadataUsages) {
|
||||||
var address = usage.VirtualAddress;
|
var address = usage.VirtualAddress;
|
||||||
string name;
|
string name;
|
||||||
|
|
||||||
switch (usage.Type) {
|
switch (usage.Type) {
|
||||||
case MetadataUsageType.StringLiteral:
|
case MetadataUsageType.StringLiteral:
|
||||||
var str = model.GetMetadataUsageName(usage);
|
var str = model.ILModel.GetMetadataUsageName(usage);
|
||||||
writeTypedName(address, stringType, $"StringLiteral_{stringToIdentifier(str)}");
|
writeTypedName(address, stringType.ToString(), $"StringLiteral_{stringToIdentifier(str)}");
|
||||||
writeComment(address, str);
|
writeComment(address, str);
|
||||||
break;
|
break;
|
||||||
case MetadataUsageType.Type:
|
case MetadataUsageType.Type:
|
||||||
case MetadataUsageType.TypeInfo:
|
case MetadataUsageType.TypeInfo:
|
||||||
var type = model.GetMetadataUsageType(usage);
|
var type = model.ILModel.GetMetadataUsageType(usage);
|
||||||
declGenerator.IncludeType(type);
|
declGenerator.IncludeType(type);
|
||||||
writeDecls(declGenerator.GenerateRemainingTypeDeclarations());
|
writeDecls(declGenerator.GenerateRemainingTypeDeclarations());
|
||||||
|
|
||||||
@@ -167,7 +172,7 @@ typedef __int64 int64_t;
|
|||||||
break;
|
break;
|
||||||
case MetadataUsageType.MethodDef:
|
case MetadataUsageType.MethodDef:
|
||||||
case MetadataUsageType.MethodRef:
|
case MetadataUsageType.MethodRef:
|
||||||
var method = model.GetMetadataUsageMethod(usage);
|
var method = model.ILModel.GetMetadataUsageMethod(usage);
|
||||||
declGenerator.IncludeMethod(method);
|
declGenerator.IncludeMethod(method);
|
||||||
writeDecls(declGenerator.GenerateRemainingTypeDeclarations());
|
writeDecls(declGenerator.GenerateRemainingTypeDeclarations());
|
||||||
|
|
||||||
@@ -220,6 +225,9 @@ typedef __int64 int64_t;
|
|||||||
writeLine("idc.parse_decls('''" + declString + "''')");
|
writeLine("idc.parse_decls('''" + declString + "''')");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Temporary compatibility function, remove when integrated with ApplicationModel
|
||||||
|
private void writeDecls(List<CppType> types) => writeDecls(string.Join("\n", types.Select(t => t.ToString())));
|
||||||
|
|
||||||
private void writeName(ulong address, string name) {
|
private void writeName(ulong address, string name) {
|
||||||
writeLine($"SetName({address.ToAddressString()}, r'{name.ToEscapedString()}')");
|
writeLine($"SetName({address.ToAddressString()}, r'{name.ToEscapedString()}')");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
Copyright 2017-2019 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -12,7 +11,7 @@ namespace Il2CppInspector.Reflection {
|
|||||||
public class Assembly
|
public class Assembly
|
||||||
{
|
{
|
||||||
// IL2CPP-specific data
|
// IL2CPP-specific data
|
||||||
public Il2CppModel Model { get; }
|
public TypeModel Model { get; }
|
||||||
public Il2CppImageDefinition ImageDefinition { get; }
|
public Il2CppImageDefinition ImageDefinition { get; }
|
||||||
public Il2CppAssemblyDefinition AssemblyDefinition { get; }
|
public Il2CppAssemblyDefinition AssemblyDefinition { get; }
|
||||||
public Il2CppCodeGenModule ModuleDefinition { get; }
|
public Il2CppCodeGenModule ModuleDefinition { get; }
|
||||||
@@ -37,7 +36,7 @@ namespace Il2CppInspector.Reflection {
|
|||||||
public TypeInfo GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName);
|
public TypeInfo GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName);
|
||||||
|
|
||||||
// Initialize from specified assembly index in package
|
// Initialize from specified assembly index in package
|
||||||
public Assembly(Il2CppModel model, int imageIndex) {
|
public Assembly(TypeModel model, int imageIndex) {
|
||||||
Model = model;
|
Model = model;
|
||||||
ImageDefinition = Model.Package.Images[imageIndex];
|
ImageDefinition = Model.Package.Images[imageIndex];
|
||||||
AssemblyDefinition = Model.Package.Assemblies[ImageDefinition.assemblyIndex];
|
AssemblyDefinition = Model.Package.Assemblies[ImageDefinition.assemblyIndex];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
@@ -15,7 +15,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
public class CustomAttributeData
|
public class CustomAttributeData
|
||||||
{
|
{
|
||||||
// IL2CPP-specific data
|
// IL2CPP-specific data
|
||||||
public Il2CppModel Model => AttributeType.Assembly.Model;
|
public TypeModel Model => AttributeType.Assembly.Model;
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
|
|
||||||
// The type of the attribute
|
// The type of the attribute
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The invokers use Object for all reference types, and SByte for booleans
|
// The invokers use Object for all reference types, and SByte for booleans
|
||||||
private TypeInfo mapParameterType(Il2CppModel model, TypeInfo type) => type switch {
|
private TypeInfo mapParameterType(TypeModel model, TypeInfo type) => type switch {
|
||||||
{ IsValueType: false } => model.TypesByFullName["System.Object"],
|
{ IsValueType: false } => model.TypesByFullName["System.Object"],
|
||||||
{ FullName: "System.Boolean" } => model.TypesByFullName["System.SByte"],
|
{ FullName: "System.Boolean" } => model.TypesByFullName["System.SByte"],
|
||||||
_ => type
|
_ => type
|
||||||
|
|||||||
@@ -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
|
Copyright 2020 Robert Xiao - https://robertxiao.ca
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
@@ -12,7 +12,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Il2CppInspector.Reflection
|
namespace Il2CppInspector.Reflection
|
||||||
{
|
{
|
||||||
public class Il2CppModel
|
public class TypeModel
|
||||||
{
|
{
|
||||||
public Il2CppInspector Package { get; }
|
public Il2CppInspector Package { get; }
|
||||||
public List<Assembly> Assemblies { get; } = new List<Assembly>();
|
public List<Assembly> Assemblies { get; } = new List<Assembly>();
|
||||||
@@ -62,7 +62,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
&& m.GetGenericArguments().SequenceEqual(typeArguments));
|
&& m.GetGenericArguments().SequenceEqual(typeArguments));
|
||||||
|
|
||||||
// Create type model
|
// Create type model
|
||||||
public Il2CppModel(Il2CppInspector package) {
|
public TypeModel(Il2CppInspector package) {
|
||||||
Package = package;
|
Package = package;
|
||||||
TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length];
|
TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length];
|
||||||
TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count];
|
TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count];
|
||||||
@@ -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
|
Copyright 2020 Robert Xiao - https://robertxiao.ca
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
@@ -11,7 +11,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
/// A class which lazily refers to a TypeInfo instance
|
/// A class which lazily refers to a TypeInfo instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class TypeRef {
|
internal class TypeRef {
|
||||||
private Il2CppModel model;
|
private TypeModel model;
|
||||||
private int referenceIndex = -1;
|
private int referenceIndex = -1;
|
||||||
private int definitionIndex = -1;
|
private int definitionIndex = -1;
|
||||||
private TypeInfo typeInfo = null;
|
private TypeInfo typeInfo = null;
|
||||||
@@ -28,10 +28,10 @@ namespace Il2CppInspector.Reflection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TypeRef FromReferenceIndex(Il2CppModel model, int index)
|
public static TypeRef FromReferenceIndex(TypeModel model, int index)
|
||||||
=> new TypeRef { model = model, referenceIndex = index };
|
=> new TypeRef { model = model, referenceIndex = index };
|
||||||
|
|
||||||
public static TypeRef FromDefinitionIndex(Il2CppModel model, int index)
|
public static TypeRef FromDefinitionIndex(TypeModel model, int index)
|
||||||
=> new TypeRef { model = model, definitionIndex = index };
|
=> new TypeRef { model = model, definitionIndex = index };
|
||||||
|
|
||||||
public static TypeRef FromTypeInfo(TypeInfo type)
|
public static TypeRef FromTypeInfo(TypeInfo type)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Il2CppInspector;
|
using Il2CppInspector;
|
||||||
|
using Il2CppInspector.Model;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
using Inspector = Il2CppInspector.Il2CppInspector;
|
using Inspector = Il2CppInspector.Il2CppInspector;
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ namespace Il2CppInspectorGUI
|
|||||||
{
|
{
|
||||||
private Metadata metadata;
|
private Metadata metadata;
|
||||||
|
|
||||||
public List<Il2CppModel> Il2CppModels { get; } = new List<Il2CppModel>();
|
public List<AppModel> AppModels { get; } = new List<AppModel>();
|
||||||
|
|
||||||
public Exception LastException { get; private set; }
|
public Exception LastException { get; private set; }
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ namespace Il2CppInspectorGUI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Multi-image binaries may contain more than one Il2Cpp image
|
// Multi-image binaries may contain more than one Il2Cpp image
|
||||||
Il2CppModels.Clear();
|
AppModels.Clear();
|
||||||
foreach (var image in stream.Images) {
|
foreach (var image in stream.Images) {
|
||||||
OnStatusUpdate?.Invoke(this, "Analyzing IL2CPP data");
|
OnStatusUpdate?.Invoke(this, "Analyzing IL2CPP data");
|
||||||
|
|
||||||
@@ -93,14 +94,18 @@ namespace Il2CppInspectorGUI
|
|||||||
var inspector = new Inspector(binary, metadata);
|
var inspector = new Inspector(binary, metadata);
|
||||||
|
|
||||||
// Build type model
|
// Build type model
|
||||||
OnStatusUpdate?.Invoke(this, "Building type model");
|
OnStatusUpdate?.Invoke(this, "Building .NET type model");
|
||||||
Il2CppModels.Add(new Il2CppModel(inspector));
|
var typeModel = new TypeModel(inspector);
|
||||||
|
|
||||||
|
// Initialize (but don't build) application model
|
||||||
|
// We will build the model after the user confirms the Unity version and target compiler
|
||||||
|
AppModels.Add(new AppModel(typeModel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Unsupported architecture; ignore it
|
// Unsupported architecture; ignore it
|
||||||
catch (NotImplementedException) { }
|
catch (NotImplementedException) { }
|
||||||
}
|
}
|
||||||
if (!Il2CppModels.Any()) {
|
if (!AppModels.Any()) {
|
||||||
throw new InvalidOperationException("Could not auto-detect any IL2CPP binary images in the file. This may mean the binary file is packed, encrypted or obfuscated, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data. Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!");
|
throw new InvalidOperationException("Could not auto-detect any IL2CPP binary images in the file. This may mean the binary file is packed, encrypted or obfuscated, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data. Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ using Microsoft.Win32;
|
|||||||
using Il2CppInspector;
|
using Il2CppInspector;
|
||||||
using Il2CppInspector.Cpp;
|
using Il2CppInspector.Cpp;
|
||||||
using Il2CppInspector.GUI;
|
using Il2CppInspector.GUI;
|
||||||
|
using Il2CppInspector.Model;
|
||||||
using Il2CppInspector.Outputs;
|
using Il2CppInspector.Outputs;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
using Ookii.Dialogs.Wpf;
|
using Ookii.Dialogs.Wpf;
|
||||||
@@ -112,7 +113,7 @@ namespace Il2CppInspectorGUI
|
|||||||
// Binary loaded successfully
|
// Binary loaded successfully
|
||||||
areaBusyIndicator.Visibility = Visibility.Hidden;
|
areaBusyIndicator.Visibility = Visibility.Hidden;
|
||||||
|
|
||||||
lstImages.ItemsSource = app.Il2CppModels;
|
lstImages.ItemsSource = app.AppModels;
|
||||||
lstImages.SelectedIndex = 0;
|
lstImages.SelectedIndex = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -144,7 +145,7 @@ namespace Il2CppInspectorGUI
|
|||||||
// Package loaded successfully
|
// Package loaded successfully
|
||||||
areaBusyIndicator.Visibility = Visibility.Hidden;
|
areaBusyIndicator.Visibility = Visibility.Hidden;
|
||||||
|
|
||||||
lstImages.ItemsSource = app.Il2CppModels;
|
lstImages.ItemsSource = app.AppModels;
|
||||||
lstImages.SelectedIndex = 0;
|
lstImages.SelectedIndex = 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -175,10 +176,10 @@ namespace Il2CppInspectorGUI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get selected image
|
// Get selected image
|
||||||
var model = (Il2CppModel)((ListBox)sender).SelectedItem;
|
var model = (AppModel)((ListBox)sender).SelectedItem;
|
||||||
|
|
||||||
// Get namespaces
|
// Get namespaces
|
||||||
var namespaces = model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace).Select(n => n.Key);
|
var namespaces = model.ILModel.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace).Select(n => n.Key);
|
||||||
|
|
||||||
// Break namespaces down into a tree
|
// Break namespaces down into a tree
|
||||||
var namespaceTree = deconstructNamespaces(namespaces);
|
var namespaceTree = deconstructNamespaces(namespaces);
|
||||||
@@ -204,7 +205,7 @@ namespace Il2CppInspectorGUI
|
|||||||
var prevCppSelection = cboCppUnityVersion.SelectedItem;
|
var prevCppSelection = cboCppUnityVersion.SelectedItem;
|
||||||
cboUnityVersion.Items.Clear();
|
cboUnityVersion.Items.Clear();
|
||||||
cboCppUnityVersion.Items.Clear();
|
cboCppUnityVersion.Items.Clear();
|
||||||
foreach (var version in UnityHeader.GuessHeadersForModel(model)) {
|
foreach (var version in UnityHeader.GuessHeadersForModel(model.ILModel)) {
|
||||||
cboUnityVersion.Items.Add(version);
|
cboUnityVersion.Items.Add(version);
|
||||||
cboCppUnityVersion.Items.Add(version);
|
cboCppUnityVersion.Items.Add(version);
|
||||||
}
|
}
|
||||||
@@ -287,7 +288,7 @@ namespace Il2CppInspectorGUI
|
|||||||
/// Perform export
|
/// Perform export
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async void BtnExport_OnClick(object sender, RoutedEventArgs e) {
|
private async void BtnExport_OnClick(object sender, RoutedEventArgs e) {
|
||||||
var model = (Il2CppModel) lstImages.SelectedItem;
|
var model = (AppModel) lstImages.SelectedItem;
|
||||||
|
|
||||||
var unityPath = txtUnityPath.Text;
|
var unityPath = txtUnityPath.Text;
|
||||||
var unityAssembliesPath = txtUnityScriptPath.Text;
|
var unityAssembliesPath = txtUnityScriptPath.Text;
|
||||||
@@ -318,7 +319,7 @@ namespace Il2CppInspectorGUI
|
|||||||
// Get options
|
// Get options
|
||||||
var excludedNamespaces = constructExcludedNamespaces((IEnumerable<CheckboxNode>) trvNamespaces.ItemsSource);
|
var excludedNamespaces = constructExcludedNamespaces((IEnumerable<CheckboxNode>) trvNamespaces.ItemsSource);
|
||||||
|
|
||||||
var writer = new CSharpCodeStubs(model) {
|
var writer = new CSharpCodeStubs(model.ILModel) {
|
||||||
ExcludedNamespaces = excludedNamespaces.ToList(),
|
ExcludedNamespaces = excludedNamespaces.ToList(),
|
||||||
SuppressMetadata = cbSuppressMetadata.IsChecked == true,
|
SuppressMetadata = cbSuppressMetadata.IsChecked == true,
|
||||||
MustCompile = cbMustCompile.IsChecked == true
|
MustCompile = cbMustCompile.IsChecked == true
|
||||||
@@ -404,14 +405,14 @@ namespace Il2CppInspectorGUI
|
|||||||
|
|
||||||
var outFile = scriptSaveFileDialog.FileName;
|
var outFile = scriptSaveFileDialog.FileName;
|
||||||
|
|
||||||
txtBusyStatus.Text = "Generating IDAPython script...";
|
|
||||||
areaBusyIndicator.Visibility = Visibility.Visible;
|
areaBusyIndicator.Visibility = Visibility.Visible;
|
||||||
var selectedVersion = ((UnityHeader)cboUnityVersion.SelectedItem)?.MinVersion;
|
var selectedVersion = ((UnityHeader)cboUnityVersion.SelectedItem)?.MinVersion;
|
||||||
await Task.Run(() => {
|
await Task.Run(() => {
|
||||||
var idaWriter = new IDAPythonScript(model) {
|
OnStatusUpdate(this, "Building C++ application model");
|
||||||
UnityVersion = selectedVersion,
|
model.Build(selectedVersion, CppCompilerType.GCC);
|
||||||
};
|
|
||||||
idaWriter.WriteScriptToFile(outFile);
|
OnStatusUpdate(this, "Generating IDAPython script");
|
||||||
|
new IDAPythonScript(model).WriteScriptToFile(outFile);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -430,16 +431,15 @@ namespace Il2CppInspectorGUI
|
|||||||
|
|
||||||
var cppOutFile = cppSaveFileDialog.FileName;
|
var cppOutFile = cppSaveFileDialog.FileName;
|
||||||
|
|
||||||
txtBusyStatus.Text = "Generating C++ scaffolding...";
|
|
||||||
areaBusyIndicator.Visibility = Visibility.Visible;
|
areaBusyIndicator.Visibility = Visibility.Visible;
|
||||||
var selectedCppUnityVersion = ((UnityHeader)cboCppUnityVersion.SelectedItem)?.MinVersion;
|
var selectedCppUnityVersion = ((UnityHeader)cboCppUnityVersion.SelectedItem)?.MinVersion;
|
||||||
var cppCompiler = (CppCompiler.Type) Enum.Parse(typeof(CppCompiler.Type), cboCppCompiler.SelectionBoxItem.ToString());
|
var cppCompiler = (CppCompilerType) Enum.Parse(typeof(CppCompilerType), cboCppCompiler.SelectionBoxItem.ToString());
|
||||||
await Task.Run(() => {
|
await Task.Run(() => {
|
||||||
var cppWriter = new CppScaffolding(model) {
|
OnStatusUpdate(this, "Building C++ application model");
|
||||||
UnityVersion = selectedCppUnityVersion,
|
model.Build(selectedCppUnityVersion, cppCompiler);
|
||||||
Compiler = cppCompiler
|
|
||||||
};
|
OnStatusUpdate(this, "Generating C++ scaffolding");
|
||||||
cppWriter.WriteCppToFile(cppOutFile);
|
new CppScaffolding(model).WriteCppToFile(cppOutFile);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Il2CppInspector
|
|||||||
public partial class FixedTests
|
public partial class FixedTests
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestCppTypes() {
|
public void TestCppTypeDeclarations() {
|
||||||
// NOTE: This test doesn't check for correct results, only that parsing doesn't fail!
|
// NOTE: This test doesn't check for correct results, only that parsing doesn't fail!
|
||||||
|
|
||||||
var unityAllHeaders = UnityHeader.GetAllHeaders();
|
var unityAllHeaders = UnityHeader.GetAllHeaders();
|
||||||
@@ -30,16 +30,15 @@ namespace Il2CppInspector
|
|||||||
// Ensure we can interpret every header from every version of Unity without errors
|
// Ensure we can interpret every header from every version of Unity without errors
|
||||||
// This will throw InvalidOperationException if there is a problem
|
// This will throw InvalidOperationException if there is a problem
|
||||||
foreach (var unityHeader in unityAllHeaders) {
|
foreach (var unityHeader in unityAllHeaders) {
|
||||||
var cppTypes = CppTypes.FromUnityHeaders(unityHeader);
|
var cppTypes = CppTypeCollection.FromUnityHeaders(unityHeader);
|
||||||
|
|
||||||
foreach (var cppType in cppTypes.Types)
|
foreach (var cppType in cppTypes.Types)
|
||||||
Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value.ToString("o") + "\n");
|
Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value.ToString("o"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do a few sanity checks taken from real applications
|
// Do a few sanity checks taken from real applications
|
||||||
// NOTE: Does not provide full code coverage!
|
// NOTE: Does not provide full code coverage!
|
||||||
|
|
||||||
var cppTypes2 = CppTypes.FromUnityVersion(new UnityVersion("2019.3.1f1"), 64);
|
var cppTypes2 = CppTypeCollection.FromUnityVersion(new UnityVersion("2019.3.1f1"), 64);
|
||||||
|
|
||||||
CppComplexType ct;
|
CppComplexType ct;
|
||||||
CppField field;
|
CppField field;
|
||||||
@@ -82,6 +81,39 @@ namespace Il2CppInspector
|
|||||||
field = fields["vtable"];
|
field = fields["vtable"];
|
||||||
|
|
||||||
Assert.AreEqual(field.OffsetBytes, 0x128);
|
Assert.AreEqual(field.OffsetBytes, 0x128);
|
||||||
|
|
||||||
|
// Bitfield
|
||||||
|
ct = (CppComplexType) cppTypes2["Il2CppType"];
|
||||||
|
|
||||||
|
field = ct.Fields[0xB * 8 + 7].First();
|
||||||
|
|
||||||
|
Assert.AreEqual(field.Name, "pinned");
|
||||||
|
|
||||||
|
// Nested fields
|
||||||
|
ct = (CppComplexType) cppTypes2["Il2CppWin32Decimal"];
|
||||||
|
fields = ct.Flattened;
|
||||||
|
|
||||||
|
field = fields[0x08].First();
|
||||||
|
|
||||||
|
Assert.AreEqual(field.Name, "lo32");
|
||||||
|
|
||||||
|
field = fields[0x08].Last();
|
||||||
|
|
||||||
|
Assert.AreEqual(field.Name, "lo64");
|
||||||
|
|
||||||
|
field = fields[0x0C].First();
|
||||||
|
|
||||||
|
Assert.AreEqual(field.Name, "mid32");
|
||||||
|
|
||||||
|
// Pointer alias
|
||||||
|
var alias = (CppAlias) cppTypes2.GetType("Il2CppHString");
|
||||||
|
|
||||||
|
Assert.AreEqual(alias.ElementType.GetType(), typeof(CppPointerType));
|
||||||
|
Assert.AreEqual(alias.ElementType.Name, "Il2CppHString__ *");
|
||||||
|
|
||||||
|
// Typedef struct with no tag
|
||||||
|
Assert.True(cppTypes2.Types.ContainsKey("Il2CppGenericMethodIndices"));
|
||||||
|
Assert.True(((CppComplexType)cppTypes2["Il2CppGenericMethodIndices"]).ComplexValueType == ComplexValueType.Struct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
Copyright 2019-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
@@ -25,7 +25,7 @@ namespace Il2CppInspector
|
|||||||
|
|
||||||
// Build model
|
// Build model
|
||||||
var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\GenericTypes-ARMv7.so", testPath + @"\global-metadata.dat");
|
var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\GenericTypes-ARMv7.so", testPath + @"\global-metadata.dat");
|
||||||
var model = new Il2CppModel(inspectors[0]);
|
var model = new TypeModel(inspectors[0]);
|
||||||
|
|
||||||
var asm = model.GetAssembly("GenericTypes.dll");
|
var asm = model.GetAssembly("GenericTypes.dll");
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
|
Copyright 2019 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
@@ -23,7 +23,7 @@ namespace Il2CppInspector
|
|||||||
|
|
||||||
// Build model
|
// Build model
|
||||||
var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\References-ARMv7.so", testPath + @"\global-metadata.dat");
|
var inspectors = Il2CppInspector.LoadFromFile(testPath + @"\References-ARMv7.so", testPath + @"\global-metadata.dat");
|
||||||
var model = new Il2CppModel(inspectors[0]);
|
var model = new TypeModel(inspectors[0]);
|
||||||
|
|
||||||
var asm = model.GetAssembly("References.dll");
|
var asm = model.GetAssembly("References.dll");
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Model;
|
||||||
using Il2CppInspector.Outputs;
|
using Il2CppInspector.Outputs;
|
||||||
|
using Il2CppInspector.Reflection;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace Il2CppInspector
|
namespace Il2CppInspector
|
||||||
@@ -20,6 +20,8 @@ namespace Il2CppInspector
|
|||||||
private void runTest(string testPath) {
|
private void runTest(string testPath) {
|
||||||
// Android
|
// Android
|
||||||
var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so";
|
var testFile = testPath + @"\" + Path.GetFileName(testPath) + ".so";
|
||||||
|
if (!File.Exists(testFile))
|
||||||
|
testFile = testPath + @"\libil2cpp.so";
|
||||||
// Windows
|
// Windows
|
||||||
if (!File.Exists(testFile))
|
if (!File.Exists(testFile))
|
||||||
testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll";
|
testFile = testPath + @"\" + Path.GetFileName(testPath) + ".dll";
|
||||||
@@ -28,9 +30,6 @@ namespace Il2CppInspector
|
|||||||
// iOS
|
// iOS
|
||||||
if (!File.Exists(testFile))
|
if (!File.Exists(testFile))
|
||||||
testFile = testPath + @"\" + Path.GetFileName(testPath);
|
testFile = testPath + @"\" + Path.GetFileName(testPath);
|
||||||
// Android
|
|
||||||
if (!File.Exists(testFile))
|
|
||||||
testFile = testPath + @"\libil2cpp.so";
|
|
||||||
|
|
||||||
var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat");
|
var inspectors = Il2CppInspector.LoadFromFile(testFile, testPath + @"\global-metadata.dat");
|
||||||
|
|
||||||
@@ -44,7 +43,8 @@ namespace Il2CppInspector
|
|||||||
// Dump each image in the binary separately
|
// Dump each image in the binary separately
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var il2cpp in inspectors) {
|
foreach (var il2cpp in inspectors) {
|
||||||
var model = new Il2CppModel(il2cpp);
|
var model = new TypeModel(il2cpp);
|
||||||
|
var appModel = new AppModel(model).Build();
|
||||||
var nameSuffix = i++ > 0 ? "-" + (i - 1) : "";
|
var nameSuffix = i++ > 0 ? "-" + (i - 1) : "";
|
||||||
|
|
||||||
new CSharpCodeStubs(model) {
|
new CSharpCodeStubs(model) {
|
||||||
@@ -53,10 +53,10 @@ namespace Il2CppInspector
|
|||||||
MustCompile = true
|
MustCompile = true
|
||||||
}.WriteSingleFile(testPath + $@"\test-result{nameSuffix}.cs");
|
}.WriteSingleFile(testPath + $@"\test-result{nameSuffix}.cs");
|
||||||
|
|
||||||
new IDAPythonScript(model)
|
new IDAPythonScript(appModel)
|
||||||
.WriteScriptToFile(testPath + $@"\test-ida-result{nameSuffix}.py");
|
.WriteScriptToFile(testPath + $@"\test-ida-result{nameSuffix}.py");
|
||||||
|
|
||||||
new CppScaffolding(model)
|
new CppScaffolding(appModel)
|
||||||
.WriteCppToFile(testPath + $@"\test-result{nameSuffix}.h");
|
.WriteCppToFile(testPath + $@"\test-result{nameSuffix}.h");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ namespace Il2CppInspector
|
|||||||
var suffix = (i > 0 ? "-" + i : "");
|
var suffix = (i > 0 ? "-" + i : "");
|
||||||
|
|
||||||
compareFiles(testPath, suffix + ".cs", $"test-result{suffix}.cs");
|
compareFiles(testPath, suffix + ".cs", $"test-result{suffix}.cs");
|
||||||
compareFiles(testPath, suffix + ".py", $"test-ida-result{suffix}.py");
|
|
||||||
compareFiles(testPath, suffix + ".h", $"test-result{suffix}.h");
|
compareFiles(testPath, suffix + ".h", $"test-result{suffix}.h");
|
||||||
|
compareFiles(testPath, suffix + ".py", $"test-ida-result{suffix}.py");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
24
README.md
24
README.md
@@ -14,7 +14,7 @@ Main features:
|
|||||||
|
|
||||||
* Create IL2CPP binaries from arbitrary C# source code without a Unity project
|
* Create IL2CPP binaries from arbitrary C# source code without a Unity project
|
||||||
|
|
||||||
* .NET Reflection-style API to allow you to query the IL2CPP type model, easily create new output modules and integrate Il2CppInspector with your own applications
|
* Three major APIs for use in custom static analysis projects
|
||||||
|
|
||||||
* Supports all major file formats and processor architectures
|
* Supports all major file formats and processor architectures
|
||||||
|
|
||||||
@@ -48,10 +48,14 @@ Nice to have:
|
|||||||
* Static symbol table scanning for Mach-O binaries
|
* Static symbol table scanning for Mach-O binaries
|
||||||
* Automatically defeats certain basic obfuscation methods
|
* Automatically defeats certain basic obfuscation methods
|
||||||
|
|
||||||
Reusable class libraries:
|
Reusable class library APIs:
|
||||||
|
|
||||||
|
* **Il2CppInspector** - low-level access to the binary image and metadata
|
||||||
|
* **TypeModel** - high-level .NET Reflection-like query API for all of the .NET types in the source project as a tree model
|
||||||
|
* **ApplicationModel** - access to all of the C++ types and methods, plus the IL2CPP API exports, with detailed address and offset data and mappings to their .NET equivalents
|
||||||
|
|
||||||
|
Use these APIs to easily query IL2CPP types, create new output modules and integrate Il2CppInspector with your own static analysis applications.
|
||||||
|
|
||||||
* **Il2CppInspector** for low-level access to IL2CPP binaries and metadata
|
|
||||||
* **Il2CppModel** for high-level .NET Reflection-style access to IL2CPP types and data as a tree model
|
|
||||||
* Test chassis for automated integration testing of IL2CPP binaries
|
* Test chassis for automated integration testing of IL2CPP binaries
|
||||||
|
|
||||||
Class library targets .NET Standard 2.1. Application targets .NET Core 3.0. Built with Visual Studio 2019.
|
Class library targets .NET Standard 2.1. Application targets .NET Core 3.0. Built with Visual Studio 2019.
|
||||||
@@ -207,9 +211,17 @@ The auto-generated tests generate a file in the test IL2CPP binary's folder call
|
|||||||
|
|
||||||
To learn more about this feature, see the section entitled **Using Il2CppInspector to generate IL2CPP code** in [IL2CPP Reverse Engineering Part 1](https://katyscode.wordpress.com/2020/06/24/il2cpp-part-1/).
|
To learn more about this feature, see the section entitled **Using Il2CppInspector to generate IL2CPP code** in [IL2CPP Reverse Engineering Part 1](https://katyscode.wordpress.com/2020/06/24/il2cpp-part-1/).
|
||||||
|
|
||||||
### Using the class library
|
### Using the APIs for programmatic analysis
|
||||||
|
|
||||||
To utilize Il2CppInspector in your own programs, add a reference to `Il2CppInspector.Common.dll` and add a using statement for the namespace `Il2CppInspector.Reflection`. See the source code for further details.
|
To utilize Il2CppInspector in your own projects, add a reference to `Il2CppInspector.Common.dll`.
|
||||||
|
|
||||||
|
Include the following `using` directives:
|
||||||
|
|
||||||
|
* `using Il2CppInspector` to use `Il2CppInspector`.
|
||||||
|
* `using Il2CppInspector.Reflection` to use `TypeModel`.
|
||||||
|
* `using Il2CppInspector.Model` to use `ApplicationModel`.
|
||||||
|
|
||||||
|
See the source code for further details.
|
||||||
|
|
||||||
### Version support
|
### Version support
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user