C++: Many small quality-of-life code improvements

This commit is contained in:
Katy Coe
2020-07-05 04:02:43 +02:00
parent 2412da4f8b
commit 3db319711c
5 changed files with 106 additions and 45 deletions

View File

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

View File

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

View File

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

View File

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