C++: Many small quality-of-life code improvements
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
@@ -40,20 +41,30 @@ namespace Il2CppInspector.Cpp
|
||||
public List<Export> Exports { get; }
|
||||
public int WordSize => ILModel.Package.BinaryImage.Bits;
|
||||
|
||||
public CppApplicationModel(Il2CppModel model, UnityVersion unityVersion, CppCompiler.Type compiler = CppCompiler.Type.BinaryFormat) {
|
||||
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;
|
||||
UnityVersion = unityVersion;
|
||||
|
||||
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.FromUnityVersion(unityVersion, WordSize);
|
||||
Types = CppTypes.FromUnityHeaders(unityHeader, WordSize);
|
||||
|
||||
// TODO: Process every type in the binary
|
||||
var decl = new CppDeclarationGenerator(ILModel, UnityVersion);
|
||||
//var decl = new CppDeclarationGenerator(this);
|
||||
|
||||
// Add addresses of IL2CPP API function exports
|
||||
Exports = model.Package.Binary.Image.GetExports().ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,16 @@ namespace Il2CppInspector.Cpp
|
||||
public class CppField
|
||||
{
|
||||
// The name of the field
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
// The type of the field
|
||||
public CppType Type { get; }
|
||||
|
||||
// The offset of the field into the type
|
||||
public int Offset { get; set; }
|
||||
public int Offset { get; internal set; }
|
||||
|
||||
// The size of the field in bits
|
||||
public int BitfieldSize { get; }
|
||||
|
||||
// The offset of the field into the type in bytes
|
||||
public int OffsetBytes => Offset / 8;
|
||||
@@ -23,17 +29,18 @@ namespace Il2CppInspector.Cpp
|
||||
|
||||
public int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0);
|
||||
|
||||
// The size of the field in bits
|
||||
public int BitfieldSize { get; set; }
|
||||
|
||||
// The LSB of the bitfield
|
||||
public int BitfieldLSB => Offset % 8;
|
||||
|
||||
// The MSB of the bitfield
|
||||
public int BitfieldMSB => BitfieldLSB + Size - 1;
|
||||
|
||||
// The type of the field
|
||||
public CppType Type { get; set; }
|
||||
// Initialize field
|
||||
public CppField(string name, CppType type, int bitfieldSize = 0) {
|
||||
Name = name;
|
||||
Type = type;
|
||||
BitfieldSize = bitfieldSize;
|
||||
}
|
||||
|
||||
// C++ representation of field
|
||||
public virtual string ToString(string format = "") {
|
||||
@@ -67,7 +74,9 @@ namespace Il2CppInspector.Cpp
|
||||
public class CppEnumField : CppField
|
||||
{
|
||||
// The value of this key name
|
||||
public ulong Value { get; set; }
|
||||
public ulong Value { get; }
|
||||
|
||||
public CppEnumField(string name, CppType type, ulong value) : base(name, type) => Value = value;
|
||||
|
||||
public override string ToString(string format = "") => Name + " = " + Value;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ using Il2CppInspector.Cpp.UnityHeaders;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
// Compound type
|
||||
public enum CompoundType
|
||||
// Value type with fields
|
||||
public enum ComplexValueType
|
||||
{
|
||||
Struct,
|
||||
Union,
|
||||
@@ -50,6 +50,11 @@ namespace Il2CppInspector.Cpp
|
||||
// Generate typedef to this type
|
||||
public CppAlias AsAlias(string Name) => new CppAlias(Name, this);
|
||||
|
||||
// Helper factories
|
||||
public static CppComplexType NewStruct(string name = "") => new CppComplexType(ComplexValueType.Struct) {Name = name};
|
||||
public static CppComplexType NewUnion(string name = "") => new CppComplexType(ComplexValueType.Union) {Name = name};
|
||||
public static CppEnumType NewEnum(CppType underlyingType, string name = "") => new CppEnumType(underlyingType) {Name = name};
|
||||
|
||||
public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : $"/* {Name} */";
|
||||
|
||||
public override string ToString() => ToString();
|
||||
@@ -173,12 +178,7 @@ namespace Il2CppInspector.Cpp
|
||||
var baseOffset = field.Offset;
|
||||
var fields = ct.Flattened.Fields.Select(kl => new {
|
||||
Key = kl.Key + baseOffset,
|
||||
Value = kl.Value.Select(f => new CppField {
|
||||
Name = f.Name,
|
||||
Type = f.Type,
|
||||
BitfieldSize = f.BitfieldSize,
|
||||
Offset = f.Offset + baseOffset
|
||||
}).ToList()
|
||||
Value = kl.Value.Select(f => new CppField(f.Name, f.Type, f.BitfieldSize) { Offset = f.Offset + baseOffset }).ToList()
|
||||
}).ToDictionary(kv => kv.Key, kv => kv.Value);
|
||||
|
||||
flattened = new SortedDictionary<int, List<CppField>>(flattened.Union(fields).ToDictionary(kv => kv.Key, kv => kv.Value));
|
||||
@@ -211,17 +211,17 @@ namespace Il2CppInspector.Cpp
|
||||
}
|
||||
|
||||
// The compound type
|
||||
public CompoundType CompoundType;
|
||||
public ComplexValueType ComplexValueType;
|
||||
|
||||
// Dictionary of byte offset in the type to each field
|
||||
// Unions and bitfields can have more than one field at the same offset
|
||||
public SortedDictionary<int, List<CppField>> Fields { get; internal set; } = new SortedDictionary<int, List<CppField>>();
|
||||
|
||||
public CppComplexType(CompoundType compoundType) : base("", 0) => CompoundType = compoundType;
|
||||
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 =>
|
||||
CompoundType == CompoundType.Union
|
||||
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;
|
||||
@@ -231,7 +231,7 @@ namespace Il2CppInspector.Cpp
|
||||
// Add a field to the type. Returns the offset of the field in the type
|
||||
public int AddField(CppField field, int alignmentBytes = 0) {
|
||||
// Unions and enums always have an offset of zero
|
||||
field.Offset = CompoundType == CompoundType.Struct ? Size : 0;
|
||||
field.Offset = ComplexValueType == ComplexValueType.Struct ? Size : 0;
|
||||
|
||||
// If we just came out of a bitfield, move to the next byte if necessary
|
||||
if (field.BitfieldSize == 0 && field.Offset % 8 != 0)
|
||||
@@ -249,25 +249,23 @@ namespace Il2CppInspector.Cpp
|
||||
return Size;
|
||||
}
|
||||
|
||||
// Add a field to the type
|
||||
public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0)
|
||||
=> AddField(new CppField(name, type, bitfield), alignmentBytes);
|
||||
|
||||
// Summarize all field names and offsets
|
||||
public override string ToString(string format = "") {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (Name.Length > 0)
|
||||
sb.Append("typedef ");
|
||||
sb.Append(CompoundType == CompoundType.Struct ? "struct " : CompoundType == CompoundType.Enum? "enum " : "union ");
|
||||
sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union ");
|
||||
sb.Append(Name + (Name.Length > 0 ? " " : ""));
|
||||
|
||||
var delimiter = CompoundType == CompoundType.Enum ? "," : ";";
|
||||
|
||||
if (Fields.Any()) {
|
||||
sb.Append("{");
|
||||
foreach (var field in Fields.Values.SelectMany(f => f))
|
||||
sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + delimiter);
|
||||
|
||||
// Chop off final comma
|
||||
if (CompoundType == CompoundType.Enum)
|
||||
sb = sb.Remove(sb.Length - 1, 1);
|
||||
sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ";");
|
||||
|
||||
sb.Append($"\n}}{(Name.Length > 0? " " + Name : "")}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||
}
|
||||
@@ -279,4 +277,39 @@ namespace Il2CppInspector.Cpp
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
// Enumeration type
|
||||
public class CppEnumType : CppComplexType
|
||||
{
|
||||
// The underlying type of the enum
|
||||
public CppType UnderlyingType { get; }
|
||||
|
||||
public override int Size => UnderlyingType.Size;
|
||||
|
||||
public CppEnumType(CppType underlyingType) : base(ComplexValueType.Enum) => UnderlyingType = underlyingType;
|
||||
|
||||
public void AddField(string name, ulong value) => AddField(new CppEnumField(name, UnderlyingType, value));
|
||||
|
||||
public override string ToString(string format = "") {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append($"typedef enum {Name} : {UnderlyingType.Name}");
|
||||
|
||||
if (Fields.Any()) {
|
||||
sb.Append(" {");
|
||||
foreach (var field in Fields.Values.SelectMany(f => f))
|
||||
sb.Append("\n\t" + string.Join("\n\t", field.ToString(format).Split('\n')) + ",");
|
||||
|
||||
// Chop off final comma
|
||||
sb = sb.Remove(sb.Length - 1, 1);
|
||||
sb.Append($"\n}} {Name}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||
}
|
||||
// Forward declaration
|
||||
else {
|
||||
sb.Append($"{Name};");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace Il2CppInspector.Cpp
|
||||
if (externDecl.Success) {
|
||||
var declType = externDecl.Groups[1].Captures[0].ToString();
|
||||
|
||||
Types.Add(declType, new CppComplexType(CompoundType.Struct) {Name = declType});
|
||||
Types.Add(declType, CppType.NewStruct(declType));
|
||||
|
||||
Debug.WriteLine($"[EXTERN DECL ] {line}");
|
||||
continue;
|
||||
@@ -213,7 +213,7 @@ namespace Il2CppInspector.Cpp
|
||||
|
||||
// Sometimes we might get multiple forward declarations for the same type
|
||||
if (!Types.ContainsKey(declType))
|
||||
Types.Add(declType, new CppComplexType(CompoundType.Struct) {Name = declType});
|
||||
Types.Add(declType, CppType.NewStruct(declType));
|
||||
|
||||
// Sometimes the alias might be the same name as the type (this is usually the case)
|
||||
if (!Types.ContainsKey(alias))
|
||||
@@ -261,7 +261,7 @@ namespace Il2CppInspector.Cpp
|
||||
// typedef struct <optional-type-name>
|
||||
if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1
|
||||
&& currentType.Count == 0) {
|
||||
currentType.Push(new CppComplexType(CompoundType.Struct));
|
||||
currentType.Push(CppType.NewStruct());
|
||||
|
||||
if (line.StartsWith("struct "))
|
||||
currentType.Peek().Name = line.Split(' ')[1];
|
||||
@@ -273,7 +273,7 @@ namespace Il2CppInspector.Cpp
|
||||
// Start of union
|
||||
// typedef union <optional-type-name>
|
||||
if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
||||
currentType.Push(new CppComplexType(CompoundType.Union));
|
||||
currentType.Push(CppType.NewUnion());
|
||||
|
||||
Debug.WriteLine($"\n[UNION START ] {line}");
|
||||
continue;
|
||||
@@ -282,7 +282,7 @@ namespace Il2CppInspector.Cpp
|
||||
// Start of enum
|
||||
// typedef enum <optional-type-name>
|
||||
if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
||||
currentType.Push(new CppComplexType(CompoundType.Enum));
|
||||
currentType.Push(NewDefaultEnum());
|
||||
nextEnumValue = 0;
|
||||
|
||||
Debug.WriteLine($"\n[ENUM START ] {line}");
|
||||
@@ -294,7 +294,7 @@ namespace Il2CppInspector.Cpp
|
||||
// union <optional-type-name>
|
||||
var words = line.Split(' ');
|
||||
if ((words[0] == "union" || words[0] == "struct") && words.Length <= 2) {
|
||||
currentType.Push(new CppComplexType(words[0] == "struct"? CompoundType.Struct : CompoundType.Union));
|
||||
currentType.Push(words[0] == "struct" ? CppType.NewStruct() : CppType.NewUnion());
|
||||
|
||||
Debug.WriteLine($"[FIELD START ] {line}");
|
||||
continue;
|
||||
@@ -339,7 +339,7 @@ namespace Il2CppInspector.Cpp
|
||||
// Otherwise it's a field name in the current type
|
||||
else {
|
||||
var parent = currentType.Peek();
|
||||
parent.AddField(new CppField { Name = name, Type = ct });
|
||||
parent.AddField(name, ct, alignment);
|
||||
|
||||
Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {name}");
|
||||
}
|
||||
@@ -354,7 +354,7 @@ namespace Il2CppInspector.Cpp
|
||||
var name = fieldFnPtr.Groups[2].Captures[0].ToString();
|
||||
|
||||
var ct = currentType.Peek();
|
||||
ct.AddField(new CppField {Name = name, Type = fnPtrType}, alignment);
|
||||
ct.AddField(name, fnPtrType, alignment);
|
||||
|
||||
Debug.WriteLine($"[FIELD FNPTR ] {line} -- {name}");
|
||||
continue;
|
||||
@@ -395,7 +395,7 @@ namespace Il2CppInspector.Cpp
|
||||
if (arraySize > 0)
|
||||
type = type.AsArray(arraySize);
|
||||
|
||||
ct.AddField(new CppField {Name = name, Type = type, BitfieldSize = bitfield}, alignment);
|
||||
ct.AddField(name, type, alignment, bitfield);
|
||||
|
||||
if (bitfield == 0)
|
||||
Debug.WriteLine($"[FIELD {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- {name}");
|
||||
@@ -423,7 +423,7 @@ namespace Il2CppInspector.Cpp
|
||||
}
|
||||
|
||||
var ct = currentType.Peek();
|
||||
ct.AddField(new CppEnumField {Name = name, Type = WordSize == 32 ? Types["uint32_t"] : Types["uint64_t"], Value = value});
|
||||
((CppEnumType) ct).AddField(name, value);
|
||||
|
||||
Debug.WriteLine($"[ENUM VALUE ] {line} -- {name} = {value}");
|
||||
continue;
|
||||
@@ -468,6 +468,14 @@ namespace Il2CppInspector.Cpp
|
||||
// Add a type externally
|
||||
public void Add(CppType type) => Types.Add(type.Name, type);
|
||||
|
||||
// Add a field to a type specifying the field type and/or declaring type name as a string
|
||||
// Convenient when the field type is a pointer, or to avoid referencing this.Types or this.WordSize externally
|
||||
public int AddField(CppComplexType declaringType, string fieldName, string typeName)
|
||||
=> declaringType.AddField(fieldName, GetType(typeName));
|
||||
|
||||
// Create an empty enum with the default underlying type for the architecture (32 or 64-bit)
|
||||
public CppEnumType NewDefaultEnum() => CppType.NewEnum(Types["int"]);
|
||||
|
||||
// Generate a populated CppTypes object from a set of Unity headers
|
||||
public static CppTypes FromUnityVersion(UnityVersion version, int wordSize = 32)
|
||||
=> FromUnityHeaders(UnityHeader.GetHeaderForVersion(version), wordSize);
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Il2CppInspector
|
||||
var cppTypes = CppTypes.FromUnityHeaders(unityHeader);
|
||||
|
||||
foreach (var cppType in cppTypes.Types)
|
||||
Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value + "\n");
|
||||
Debug.WriteLine("// " + cppType.Key + "\n" + cppType.Value.ToString("o") + "\n");
|
||||
}
|
||||
|
||||
// Do a few sanity checks taken from real applications
|
||||
|
||||
Reference in New Issue
Block a user