C++: Split CppType, CppField, CppTypes into separate files
This commit is contained in:
74
Il2CppInspector.Common/Cpp/CppField.cs
Normal file
74
Il2CppInspector.Common/Cpp/CppField.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Il2CppInspector.Cpp
|
||||||
|
{
|
||||||
|
// A field in a C++ type
|
||||||
|
public class CppField
|
||||||
|
{
|
||||||
|
// The name of the field
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
// The offset of the field into the type
|
||||||
|
public int Offset { get; set; }
|
||||||
|
|
||||||
|
// The offset of the field into the type in bytes
|
||||||
|
public int OffsetBytes => Offset / 8;
|
||||||
|
|
||||||
|
// The size of the field
|
||||||
|
public int Size => (BitfieldSize > 0 ? BitfieldSize : Type.Size);
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
// C++ representation of field
|
||||||
|
public virtual string ToString(string format = "") {
|
||||||
|
var offset = format == "o" ? $"/* 0x{OffsetBytes:x2} - 0x{OffsetBytes + SizeBytes - 1:x2} (0x{SizeBytes:x2}) */" : "";
|
||||||
|
|
||||||
|
var field = Type switch {
|
||||||
|
// nested anonymous types
|
||||||
|
CppComplexType t when string.IsNullOrEmpty(t.Name) => (format == "o"? "\n" : "") + t.ToString(format)[..^1] + (Name.Length > 0? " " + Name : ""),
|
||||||
|
// function pointers
|
||||||
|
CppFnPtrType t when string.IsNullOrEmpty(t.Name) => (format == "o"? " " : "") + t.FieldToString(Name),
|
||||||
|
// regular fields
|
||||||
|
_ => $"{(format == "o"? " ":"")}{Type.Name} {Name}" + (BitfieldSize > 0? $" : {BitfieldSize}" : "")
|
||||||
|
};
|
||||||
|
|
||||||
|
var suffix = "";
|
||||||
|
|
||||||
|
// arrays
|
||||||
|
if (Type is CppArrayType a)
|
||||||
|
suffix += "[" + a.Length + "]";
|
||||||
|
|
||||||
|
// bitfields
|
||||||
|
if (BitfieldSize > 0 && format == "o")
|
||||||
|
suffix += $" /* bits {BitfieldLSB} - {BitfieldMSB} */";
|
||||||
|
|
||||||
|
return offset + field + suffix;
|
||||||
|
}
|
||||||
|
public override string ToString() => ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// An enum key and value pair
|
||||||
|
public class CppEnumField : CppField
|
||||||
|
{
|
||||||
|
// The value of this key name
|
||||||
|
public ulong Value { get; set; }
|
||||||
|
|
||||||
|
public override string ToString(string format = "") => Name + " = " + Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +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.
|
||||||
*/
|
*/
|
||||||
@@ -279,540 +279,4 @@ namespace Il2CppInspector.Cpp
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A field in a C++ type
|
|
||||||
public class CppField
|
|
||||||
{
|
|
||||||
// The name of the field
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
// The offset of the field into the type
|
|
||||||
public int Offset { get; set; }
|
|
||||||
|
|
||||||
// The offset of the field into the type in bytes
|
|
||||||
public int OffsetBytes => Offset / 8;
|
|
||||||
|
|
||||||
// The size of the field
|
|
||||||
public int Size => (BitfieldSize > 0 ? BitfieldSize : Type.Size);
|
|
||||||
|
|
||||||
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; }
|
|
||||||
|
|
||||||
// C++ representation of field
|
|
||||||
public virtual string ToString(string format = "") {
|
|
||||||
var offset = format == "o" ? $"/* 0x{OffsetBytes:x2} - 0x{OffsetBytes + SizeBytes - 1:x2} (0x{SizeBytes:x2}) */" : "";
|
|
||||||
|
|
||||||
var field = Type switch {
|
|
||||||
// nested anonymous types
|
|
||||||
CppComplexType t when string.IsNullOrEmpty(t.Name) => (format == "o"? "\n" : "") + t.ToString(format)[..^1] + (Name.Length > 0? " " + Name : ""),
|
|
||||||
// function pointers
|
|
||||||
CppFnPtrType t when string.IsNullOrEmpty(t.Name) => (format == "o"? " " : "") + t.FieldToString(Name),
|
|
||||||
// regular fields
|
|
||||||
_ => $"{(format == "o"? " ":"")}{Type.Name} {Name}" + (BitfieldSize > 0? $" : {BitfieldSize}" : "")
|
|
||||||
};
|
|
||||||
|
|
||||||
var suffix = "";
|
|
||||||
|
|
||||||
// arrays
|
|
||||||
if (Type is CppArrayType a)
|
|
||||||
suffix += "[" + a.Length + "]";
|
|
||||||
|
|
||||||
// bitfields
|
|
||||||
if (BitfieldSize > 0 && format == "o")
|
|
||||||
suffix += $" /* bits {BitfieldLSB} - {BitfieldMSB} */";
|
|
||||||
|
|
||||||
return offset + field + suffix;
|
|
||||||
}
|
|
||||||
public override string ToString() => ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// An enum key and value pair
|
|
||||||
public class CppEnumField : CppField
|
|
||||||
{
|
|
||||||
// The value of this key name
|
|
||||||
public ulong Value { get; set; }
|
|
||||||
|
|
||||||
public override string ToString(string format = "") => Name + " = " + Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A collection of C++ types
|
|
||||||
public class CppTypes : IEnumerable<CppType>
|
|
||||||
{
|
|
||||||
// All of the types
|
|
||||||
public Dictionary<string, CppType> Types { get; }
|
|
||||||
|
|
||||||
public CppType this[string s] => Types[s];
|
|
||||||
|
|
||||||
public IEnumerator<CppType> GetEnumerator() => Types.Values.GetEnumerator();
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
|
||||||
|
|
||||||
// Architecture width in bits (32/64) - to determine pointer sizes
|
|
||||||
public int WordSize { get; }
|
|
||||||
|
|
||||||
private static readonly List<CppType> primitiveTypes = new List<CppType> {
|
|
||||||
new CppType("uint8_t", 8),
|
|
||||||
new CppType("uint16_t", 16),
|
|
||||||
new CppType("uint32_t", 32),
|
|
||||||
new CppType("uint64_t", 64),
|
|
||||||
new CppType("int8_t", 8),
|
|
||||||
new CppType("int16_t", 16),
|
|
||||||
new CppType("int32_t", 32),
|
|
||||||
new CppType("int64_t", 64),
|
|
||||||
new CppType("char", 8),
|
|
||||||
new CppType("int", 32),
|
|
||||||
new CppType("float", 32),
|
|
||||||
new CppType("double", 64),
|
|
||||||
new CppType("bool", 8),
|
|
||||||
new CppType("void", 0)
|
|
||||||
};
|
|
||||||
|
|
||||||
public CppTypes(int wordSize) {
|
|
||||||
if (wordSize != 32 && wordSize != 64)
|
|
||||||
throw new ArgumentOutOfRangeException("Architecture word size must be 32 or 64-bit to generate C++ data");
|
|
||||||
|
|
||||||
WordSize = wordSize;
|
|
||||||
Types = primitiveTypes.ToDictionary(t => t.Name, t => t);
|
|
||||||
|
|
||||||
// This is all compiler-dependent, let's hope for the best!
|
|
||||||
Add(new CppType("long", WordSize));
|
|
||||||
Add(new CppType("intptr_t", WordSize));
|
|
||||||
Add(new CppType("uintptr_t", WordSize));
|
|
||||||
Add(new CppType("size_t", WordSize));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a block of C++ source code, adding any types found
|
|
||||||
public void AddFromDeclarationText(string text) {
|
|
||||||
using StringReader lines = new StringReader(text);
|
|
||||||
|
|
||||||
var rgxExternDecl = new Regex(@"struct (\S+);");
|
|
||||||
var rgxTypedefForwardDecl = new Regex(@"typedef struct (\S+) (\S+);");
|
|
||||||
var rgxTypedefFnPtr = new Regex(@"typedef\s+(?:struct )?" + CppFnPtrType.Regex + ";");
|
|
||||||
var rgxTypedef = new Regex(@"typedef (\S+?)\s*\**\s*(\S+);");
|
|
||||||
var rgxFieldFnPtr = new Regex(CppFnPtrType.Regex + @";");
|
|
||||||
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 rgxStripKeywords = new Regex(@"\b(?:const|unsigned|volatile)\b");
|
|
||||||
var rgxCompressPtrs = new Regex(@"\*\s+\*");
|
|
||||||
|
|
||||||
var rgxArrayField = new Regex(@"(\S+?)\[([0-9]+)\]");
|
|
||||||
|
|
||||||
var rgxAlignment = new Regex(@"__attribute__\(\(aligned\(([0-9]+)\)\)\)");
|
|
||||||
var rgxIsBitDirective = new Regex(@"#ifdef\s+IS_(32|64)BIT");
|
|
||||||
var rgxSingleLineComment = new Regex(@"/\*.*?\*/");
|
|
||||||
|
|
||||||
var currentType = new Stack<CppComplexType>();
|
|
||||||
bool falseIfBlock = false;
|
|
||||||
bool inComment = false;
|
|
||||||
bool inMethod = false;
|
|
||||||
var nextEnumValue = 0ul;
|
|
||||||
string line;
|
|
||||||
|
|
||||||
while ((line = lines.ReadLine()) != null) {
|
|
||||||
|
|
||||||
// Remove comments
|
|
||||||
if (line.Contains("//"))
|
|
||||||
line = line.Substring(0, line.IndexOf("//", StringComparison.Ordinal));
|
|
||||||
|
|
||||||
// End of multi-line comment?
|
|
||||||
if (line.Contains("*/") && inComment) {
|
|
||||||
inComment = false;
|
|
||||||
line = line.Substring(line.IndexOf("*/", StringComparison.Ordinal) + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inComment) {
|
|
||||||
Debug.WriteLine($"[COMMENT ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all single-line comments
|
|
||||||
line = rgxSingleLineComment.Replace(line, "");
|
|
||||||
|
|
||||||
// Start of multi-line comment?
|
|
||||||
if (line.Contains("/*") && !inComment) {
|
|
||||||
inComment = true;
|
|
||||||
line = line.Substring(0, line.IndexOf("/*"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore global variables
|
|
||||||
if (line.StartsWith("const ") && currentType.Count == 0) {
|
|
||||||
Debug.WriteLine($"[GLOBAL ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore methods
|
|
||||||
// Note: This is a very lazy way of processing early version IL2CPP headers
|
|
||||||
if (line != "}" && inMethod) {
|
|
||||||
Debug.WriteLine($"[METHOD ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == "}" && inMethod) {
|
|
||||||
inMethod = false;
|
|
||||||
|
|
||||||
Debug.WriteLine($"[METHOD END ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.StartsWith("static inline ")) {
|
|
||||||
inMethod = true;
|
|
||||||
|
|
||||||
Debug.WriteLine($"[METHOD START ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove keywords we don't care about
|
|
||||||
line = rgxStripKeywords.Replace(line, "");
|
|
||||||
|
|
||||||
// Remove whitespace in multiple indirections
|
|
||||||
line = rgxCompressPtrs.Replace(line, "**");
|
|
||||||
|
|
||||||
// Process __attribute((aligned(x)))
|
|
||||||
var alignment = 0;
|
|
||||||
var alignmentMatch = rgxAlignment.Match(line);
|
|
||||||
if (alignmentMatch.Success) {
|
|
||||||
alignment = int.Parse(alignmentMatch.Groups[1].Captures[0].ToString());
|
|
||||||
line = rgxAlignment.Replace(line, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
line = line.Trim();
|
|
||||||
|
|
||||||
// Ignore blank lines
|
|
||||||
if (line.Length == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Process #ifs before anything else
|
|
||||||
// Doesn't handle nesting but we probably don't need to (use a Stack if we do)
|
|
||||||
var ifdef = rgxIsBitDirective.Match(line);
|
|
||||||
if (ifdef.Success) {
|
|
||||||
var bits = int.Parse(ifdef.Groups[1].Captures[0].ToString());
|
|
||||||
if (bits != WordSize)
|
|
||||||
falseIfBlock = true;
|
|
||||||
|
|
||||||
Debug.WriteLine($"[IF ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line == "#else") {
|
|
||||||
falseIfBlock = !falseIfBlock;
|
|
||||||
|
|
||||||
Debug.WriteLine($"[ELSE ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (line == "#endif") {
|
|
||||||
falseIfBlock = false;
|
|
||||||
|
|
||||||
Debug.WriteLine($"[ENDIF ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (falseIfBlock) {
|
|
||||||
Debug.WriteLine($"[FALSE ] {line}");
|
|
||||||
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, new CppComplexType(CompoundType.Struct) {Name = declType});
|
|
||||||
|
|
||||||
Debug.WriteLine($"[EXTERN DECL ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward declaration
|
|
||||||
// typedef struct <struct-type> <alias>
|
|
||||||
var typedef = rgxTypedefForwardDecl.Match(line);
|
|
||||||
if (typedef.Success) {
|
|
||||||
var alias = typedef.Groups[2].Captures[0].ToString();
|
|
||||||
var declType = typedef.Groups[1].Captures[0].ToString();
|
|
||||||
|
|
||||||
// Sometimes we might get multiple forward declarations for the same type
|
|
||||||
if (!Types.ContainsKey(declType))
|
|
||||||
Types.Add(declType, new CppComplexType(CompoundType.Struct) {Name = declType});
|
|
||||||
|
|
||||||
// Sometimes the alias might be the same name as the type (this is usually the case)
|
|
||||||
if (!Types.ContainsKey(alias))
|
|
||||||
Types.Add(alias, Types[declType].AsAlias(alias));
|
|
||||||
|
|
||||||
Debug.WriteLine($"[FORWARD DECL ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function pointer
|
|
||||||
// typedef <retType> (*<alias>)(<args>);
|
|
||||||
typedef = rgxTypedefFnPtr.Match(line);
|
|
||||||
if (typedef.Success) {
|
|
||||||
var alias = typedef.Groups[2].Captures[0].ToString();
|
|
||||||
|
|
||||||
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
|
||||||
fnPtrType.Name = alias;
|
|
||||||
|
|
||||||
Types.Add(alias, fnPtrType);
|
|
||||||
|
|
||||||
Debug.WriteLine($"[TYPEDEF FNPTR] {line} -- Adding method pointer typedef to {alias}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alias
|
|
||||||
// typedef <targetType>[*..] <alias>;
|
|
||||||
typedef = rgxTypedef.Match(line);
|
|
||||||
if (typedef.Success) {
|
|
||||||
var alias = typedef.Groups[2].Captures[0].ToString();
|
|
||||||
var existingType = typedef.Groups[1].Captures[0].ToString();
|
|
||||||
|
|
||||||
// Potential multiple indirection
|
|
||||||
var type = Types[existingType];
|
|
||||||
var pointers = line.Count(c => c == '*');
|
|
||||||
for (int i = 0; i < pointers; i++)
|
|
||||||
type = type.AsPointer(WordSize);
|
|
||||||
|
|
||||||
Types.Add(alias, type.AsAlias(alias));
|
|
||||||
|
|
||||||
Debug.WriteLine($"[TYPEDEF {(pointers > 0? "PTR":"VAL")} ] {line} -- Adding typedef from {type.Name} to {alias}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start of struct
|
|
||||||
// typedef struct <optional-type-name>
|
|
||||||
if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1
|
|
||||||
&& currentType.Count == 0) {
|
|
||||||
currentType.Push(new CppComplexType(CompoundType.Struct));
|
|
||||||
|
|
||||||
if (line.StartsWith("struct "))
|
|
||||||
currentType.Peek().Name = line.Split(' ')[1];
|
|
||||||
|
|
||||||
Debug.WriteLine($"\n[STRUCT START ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start of union
|
|
||||||
// typedef union <optional-type-name>
|
|
||||||
if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
|
||||||
currentType.Push(new CppComplexType(CompoundType.Union));
|
|
||||||
|
|
||||||
Debug.WriteLine($"\n[UNION START ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start of enum
|
|
||||||
// typedef enum <optional-type-name>
|
|
||||||
if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
|
||||||
currentType.Push(new CppComplexType(CompoundType.Enum));
|
|
||||||
nextEnumValue = 0;
|
|
||||||
|
|
||||||
Debug.WriteLine($"\n[ENUM START ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nested complex field
|
|
||||||
// struct <optional-type-name>
|
|
||||||
// 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));
|
|
||||||
|
|
||||||
Debug.WriteLine($"[FIELD START ] {line}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of already named struct
|
|
||||||
if (line == "};" && currentType.Count == 1) {
|
|
||||||
var ct = currentType.Pop();
|
|
||||||
if (!Types.ContainsKey(ct.Name))
|
|
||||||
Types.Add(ct.Name, ct);
|
|
||||||
else
|
|
||||||
((CppComplexType) Types[ct.Name]).Fields = ct.Fields;
|
|
||||||
|
|
||||||
Debug.WriteLine($"[STRUCT END ] {line} -- {ct.Name}\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// End of complex field, complex type or enum
|
|
||||||
// end of [typedef] struct/union/enum
|
|
||||||
if (line.StartsWith("}") && line.EndsWith(";")) {
|
|
||||||
var name = line[1..^1].Trim();
|
|
||||||
var ct = currentType.Pop();
|
|
||||||
|
|
||||||
// End of top-level typedef, so it's a type name
|
|
||||||
if (currentType.Count == 0) {
|
|
||||||
ct.Name = name;
|
|
||||||
|
|
||||||
if (!Types.ContainsKey(name))
|
|
||||||
Types.Add(name, ct);
|
|
||||||
|
|
||||||
// We will have to copy the type data if the type was forward declared,
|
|
||||||
// because other types are already referencing it; replacing it in the
|
|
||||||
// collection will not replace the references to the empty version in
|
|
||||||
// other types
|
|
||||||
else {
|
|
||||||
((CppComplexType) Types[name]).Fields = ct.Fields;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.WriteLine($"[STRUCT END ] {line} -- {name}\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise it's a field name in the current type
|
|
||||||
else {
|
|
||||||
var parent = currentType.Peek();
|
|
||||||
parent.AddField(new CppField { Name = name, Type = ct });
|
|
||||||
|
|
||||||
Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {name}");
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function pointer field
|
|
||||||
var fieldFnPtr = rgxFieldFnPtr.Match(line);
|
|
||||||
if (fieldFnPtr.Success) {
|
|
||||||
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
|
||||||
|
|
||||||
var name = fieldFnPtr.Groups[2].Captures[0].ToString();
|
|
||||||
|
|
||||||
var ct = currentType.Peek();
|
|
||||||
ct.AddField(new CppField {Name = name, Type = fnPtrType}, alignment);
|
|
||||||
|
|
||||||
Debug.WriteLine($"[FIELD FNPTR ] {line} -- {name}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer or value field
|
|
||||||
var field = rgxField.Match(line);
|
|
||||||
|
|
||||||
if (field.Success) {
|
|
||||||
var names = field.Groups[2].Captures[0].ToString();
|
|
||||||
var typeName = field.Groups[1].Captures[0].ToString();
|
|
||||||
|
|
||||||
// Multiple fields can be separated by commas
|
|
||||||
foreach (var fieldName in names.Split(',')) {
|
|
||||||
string name = fieldName.Trim();
|
|
||||||
|
|
||||||
// Array
|
|
||||||
var array = rgxArrayField.Match(name);
|
|
||||||
int arraySize = 0;
|
|
||||||
if (array.Success && array.Groups[2].Captures.Count > 0) {
|
|
||||||
arraySize = int.Parse(array.Groups[2].Captures[0].ToString());
|
|
||||||
name = array.Groups[1].Captures[0].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bitfield
|
|
||||||
int bitfield = 0;
|
|
||||||
if (field.Groups[3].Captures.Count > 0)
|
|
||||||
bitfield = int.Parse(field.Groups[3].Captures[0].ToString());
|
|
||||||
|
|
||||||
// Potential multiple indirection
|
|
||||||
var type = Types[typeName];
|
|
||||||
var pointers = line.Count(c => c == '*');
|
|
||||||
for (int i = 0; i < pointers; i++)
|
|
||||||
type = type.AsPointer(WordSize);
|
|
||||||
|
|
||||||
var ct = currentType.Peek();
|
|
||||||
|
|
||||||
if (arraySize > 0)
|
|
||||||
type = type.AsArray(arraySize);
|
|
||||||
|
|
||||||
ct.AddField(new CppField {Name = name, Type = type, BitfieldSize = bitfield}, alignment);
|
|
||||||
|
|
||||||
if (bitfield == 0)
|
|
||||||
Debug.WriteLine($"[FIELD {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- {name}");
|
|
||||||
else
|
|
||||||
Debug.WriteLine($"[BITFIELD ] {line} -- {name} : {bitfield}");
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enum value field
|
|
||||||
var enumValue = rgxEnumValue.Match(line);
|
|
||||||
if (enumValue.Success) {
|
|
||||||
var name = enumValue.Groups[1].Captures[0].ToString();
|
|
||||||
|
|
||||||
var value = nextEnumValue++;
|
|
||||||
if (enumValue.Groups[2].Captures.Count > 0) {
|
|
||||||
// Convert the text to a ulong even if it's hexadecimal with a 0x prefix
|
|
||||||
var valueText = enumValue.Groups[2].Captures[0].ToString();
|
|
||||||
var conv = new System.ComponentModel.UInt64Converter();
|
|
||||||
|
|
||||||
// Handle bit shift operator
|
|
||||||
var values = valueText.Split("<<").Select(t => (ulong) conv.ConvertFromInvariantString(t.Trim())).ToArray();
|
|
||||||
value = values.Length == 1 ? values[0] : values[0] << (int)values[1];
|
|
||||||
nextEnumValue = value + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ct = currentType.Peek();
|
|
||||||
ct.AddField(new CppEnumField {Name = name, Type = WordSize == 32 ? Types["uint32_t"] : Types["uint64_t"], Value = value});
|
|
||||||
|
|
||||||
Debug.WriteLine($"[ENUM VALUE ] {line} -- {name} = {value}");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we're not ignoring anything we shouldn't
|
|
||||||
Debug.WriteLine($"[IGNORE ] {line}");
|
|
||||||
|
|
||||||
// Block opens
|
|
||||||
if (line == "{")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Global variables
|
|
||||||
if (line.StartsWith("static"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Pragma directives
|
|
||||||
if (line.StartsWith("#pragma"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
if (line.StartsWith("extern"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
throw new InvalidOperationException("Could not understand C++ code: " + line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a type from its name, handling pointer types
|
|
||||||
public CppType GetType(string typeName) {
|
|
||||||
var baseName = typeName.Replace("*", "");
|
|
||||||
var indirectionCount = typeName.Length - baseName.Length;
|
|
||||||
|
|
||||||
var type = Types[baseName.Trim()];
|
|
||||||
for (int i = 0; i < indirectionCount; i++)
|
|
||||||
type = type.AsPointer(WordSize);
|
|
||||||
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a type externally
|
|
||||||
public void Add(CppType type) => Types.Add(type.Name, type);
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
public static CppTypes FromUnityHeaders(UnityHeader header, int wordSize = 32) {
|
|
||||||
var cppTypes = new CppTypes(wordSize);
|
|
||||||
|
|
||||||
// Add junk from config files we haven't included
|
|
||||||
cppTypes.Add(new CppType("Il2CppIManagedObjectHolder"));
|
|
||||||
cppTypes.Add(new CppType("Il2CppIUnknown"));
|
|
||||||
|
|
||||||
// Process Unity headers
|
|
||||||
var headers = header.GetHeaderText();
|
|
||||||
cppTypes.AddFromDeclarationText(headers);
|
|
||||||
|
|
||||||
return cppTypes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
489
Il2CppInspector.Common/Cpp/CppTypes.cs
Normal file
489
Il2CppInspector.Common/Cpp/CppTypes.cs
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
/*
|
||||||
|
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.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Il2CppInspector.Cpp.UnityHeaders;
|
||||||
|
|
||||||
|
namespace Il2CppInspector.Cpp
|
||||||
|
{
|
||||||
|
// A collection of C++ types
|
||||||
|
public class CppTypes : IEnumerable<CppType>
|
||||||
|
{
|
||||||
|
// All of the types
|
||||||
|
public Dictionary<string, CppType> Types { get; }
|
||||||
|
|
||||||
|
public CppType this[string s] => Types[s];
|
||||||
|
|
||||||
|
public IEnumerator<CppType> GetEnumerator() => Types.Values.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
// Architecture width in bits (32/64) - to determine pointer sizes
|
||||||
|
public int WordSize { get; }
|
||||||
|
|
||||||
|
private static readonly List<CppType> primitiveTypes = new List<CppType> {
|
||||||
|
new CppType("uint8_t", 8),
|
||||||
|
new CppType("uint16_t", 16),
|
||||||
|
new CppType("uint32_t", 32),
|
||||||
|
new CppType("uint64_t", 64),
|
||||||
|
new CppType("int8_t", 8),
|
||||||
|
new CppType("int16_t", 16),
|
||||||
|
new CppType("int32_t", 32),
|
||||||
|
new CppType("int64_t", 64),
|
||||||
|
new CppType("char", 8),
|
||||||
|
new CppType("int", 32),
|
||||||
|
new CppType("float", 32),
|
||||||
|
new CppType("double", 64),
|
||||||
|
new CppType("bool", 8),
|
||||||
|
new CppType("void", 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
public CppTypes(int wordSize) {
|
||||||
|
if (wordSize != 32 && wordSize != 64)
|
||||||
|
throw new ArgumentOutOfRangeException("Architecture word size must be 32 or 64-bit to generate C++ data");
|
||||||
|
|
||||||
|
WordSize = wordSize;
|
||||||
|
Types = primitiveTypes.ToDictionary(t => t.Name, t => t);
|
||||||
|
|
||||||
|
// This is all compiler-dependent, let's hope for the best!
|
||||||
|
Add(new CppType("long", WordSize));
|
||||||
|
Add(new CppType("intptr_t", WordSize));
|
||||||
|
Add(new CppType("uintptr_t", WordSize));
|
||||||
|
Add(new CppType("size_t", WordSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Code parser
|
||||||
|
// Parse a block of C++ source code, adding any types found
|
||||||
|
public void AddFromDeclarationText(string text) {
|
||||||
|
using StringReader lines = new StringReader(text);
|
||||||
|
|
||||||
|
var rgxExternDecl = new Regex(@"struct (\S+);");
|
||||||
|
var rgxTypedefForwardDecl = new Regex(@"typedef struct (\S+) (\S+);");
|
||||||
|
var rgxTypedefFnPtr = new Regex(@"typedef\s+(?:struct )?" + CppFnPtrType.Regex + ";");
|
||||||
|
var rgxTypedef = new Regex(@"typedef (\S+?)\s*\**\s*(\S+);");
|
||||||
|
var rgxFieldFnPtr = new Regex(CppFnPtrType.Regex + @";");
|
||||||
|
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 rgxStripKeywords = new Regex(@"\b(?:const|unsigned|volatile)\b");
|
||||||
|
var rgxCompressPtrs = new Regex(@"\*\s+\*");
|
||||||
|
|
||||||
|
var rgxArrayField = new Regex(@"(\S+?)\[([0-9]+)\]");
|
||||||
|
|
||||||
|
var rgxAlignment = new Regex(@"__attribute__\(\(aligned\(([0-9]+)\)\)\)");
|
||||||
|
var rgxIsBitDirective = new Regex(@"#ifdef\s+IS_(32|64)BIT");
|
||||||
|
var rgxSingleLineComment = new Regex(@"/\*.*?\*/");
|
||||||
|
|
||||||
|
var currentType = new Stack<CppComplexType>();
|
||||||
|
bool falseIfBlock = false;
|
||||||
|
bool inComment = false;
|
||||||
|
bool inMethod = false;
|
||||||
|
var nextEnumValue = 0ul;
|
||||||
|
string line;
|
||||||
|
|
||||||
|
while ((line = lines.ReadLine()) != null) {
|
||||||
|
|
||||||
|
// Remove comments
|
||||||
|
if (line.Contains("//"))
|
||||||
|
line = line.Substring(0, line.IndexOf("//", StringComparison.Ordinal));
|
||||||
|
|
||||||
|
// End of multi-line comment?
|
||||||
|
if (line.Contains("*/") && inComment) {
|
||||||
|
inComment = false;
|
||||||
|
line = line.Substring(line.IndexOf("*/", StringComparison.Ordinal) + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inComment) {
|
||||||
|
Debug.WriteLine($"[COMMENT ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all single-line comments
|
||||||
|
line = rgxSingleLineComment.Replace(line, "");
|
||||||
|
|
||||||
|
// Start of multi-line comment?
|
||||||
|
if (line.Contains("/*") && !inComment) {
|
||||||
|
inComment = true;
|
||||||
|
line = line.Substring(0, line.IndexOf("/*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore global variables
|
||||||
|
if (line.StartsWith("const ") && currentType.Count == 0) {
|
||||||
|
Debug.WriteLine($"[GLOBAL ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore methods
|
||||||
|
// Note: This is a very lazy way of processing early version IL2CPP headers
|
||||||
|
if (line != "}" && inMethod) {
|
||||||
|
Debug.WriteLine($"[METHOD ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line == "}" && inMethod) {
|
||||||
|
inMethod = false;
|
||||||
|
|
||||||
|
Debug.WriteLine($"[METHOD END ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.StartsWith("static inline ")) {
|
||||||
|
inMethod = true;
|
||||||
|
|
||||||
|
Debug.WriteLine($"[METHOD START ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove keywords we don't care about
|
||||||
|
line = rgxStripKeywords.Replace(line, "");
|
||||||
|
|
||||||
|
// Remove whitespace in multiple indirections
|
||||||
|
line = rgxCompressPtrs.Replace(line, "**");
|
||||||
|
|
||||||
|
// Process __attribute((aligned(x)))
|
||||||
|
var alignment = 0;
|
||||||
|
var alignmentMatch = rgxAlignment.Match(line);
|
||||||
|
if (alignmentMatch.Success) {
|
||||||
|
alignment = int.Parse(alignmentMatch.Groups[1].Captures[0].ToString());
|
||||||
|
line = rgxAlignment.Replace(line, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.Trim();
|
||||||
|
|
||||||
|
// Ignore blank lines
|
||||||
|
if (line.Length == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Process #ifs before anything else
|
||||||
|
// Doesn't handle nesting but we probably don't need to (use a Stack if we do)
|
||||||
|
var ifdef = rgxIsBitDirective.Match(line);
|
||||||
|
if (ifdef.Success) {
|
||||||
|
var bits = int.Parse(ifdef.Groups[1].Captures[0].ToString());
|
||||||
|
if (bits != WordSize)
|
||||||
|
falseIfBlock = true;
|
||||||
|
|
||||||
|
Debug.WriteLine($"[IF ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line == "#else") {
|
||||||
|
falseIfBlock = !falseIfBlock;
|
||||||
|
|
||||||
|
Debug.WriteLine($"[ELSE ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line == "#endif") {
|
||||||
|
falseIfBlock = false;
|
||||||
|
|
||||||
|
Debug.WriteLine($"[ENDIF ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (falseIfBlock) {
|
||||||
|
Debug.WriteLine($"[FALSE ] {line}");
|
||||||
|
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, new CppComplexType(CompoundType.Struct) {Name = declType});
|
||||||
|
|
||||||
|
Debug.WriteLine($"[EXTERN DECL ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward declaration
|
||||||
|
// typedef struct <struct-type> <alias>
|
||||||
|
var typedef = rgxTypedefForwardDecl.Match(line);
|
||||||
|
if (typedef.Success) {
|
||||||
|
var alias = typedef.Groups[2].Captures[0].ToString();
|
||||||
|
var declType = typedef.Groups[1].Captures[0].ToString();
|
||||||
|
|
||||||
|
// Sometimes we might get multiple forward declarations for the same type
|
||||||
|
if (!Types.ContainsKey(declType))
|
||||||
|
Types.Add(declType, new CppComplexType(CompoundType.Struct) {Name = declType});
|
||||||
|
|
||||||
|
// Sometimes the alias might be the same name as the type (this is usually the case)
|
||||||
|
if (!Types.ContainsKey(alias))
|
||||||
|
Types.Add(alias, Types[declType].AsAlias(alias));
|
||||||
|
|
||||||
|
Debug.WriteLine($"[FORWARD DECL ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function pointer
|
||||||
|
// typedef <retType> (*<alias>)(<args>);
|
||||||
|
typedef = rgxTypedefFnPtr.Match(line);
|
||||||
|
if (typedef.Success) {
|
||||||
|
var alias = typedef.Groups[2].Captures[0].ToString();
|
||||||
|
|
||||||
|
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
||||||
|
fnPtrType.Name = alias;
|
||||||
|
|
||||||
|
Types.Add(alias, fnPtrType);
|
||||||
|
|
||||||
|
Debug.WriteLine($"[TYPEDEF FNPTR] {line} -- Adding method pointer typedef to {alias}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alias
|
||||||
|
// typedef <targetType>[*..] <alias>;
|
||||||
|
typedef = rgxTypedef.Match(line);
|
||||||
|
if (typedef.Success) {
|
||||||
|
var alias = typedef.Groups[2].Captures[0].ToString();
|
||||||
|
var existingType = typedef.Groups[1].Captures[0].ToString();
|
||||||
|
|
||||||
|
// Potential multiple indirection
|
||||||
|
var type = Types[existingType];
|
||||||
|
var pointers = line.Count(c => c == '*');
|
||||||
|
for (int i = 0; i < pointers; i++)
|
||||||
|
type = type.AsPointer(WordSize);
|
||||||
|
|
||||||
|
Types.Add(alias, type.AsAlias(alias));
|
||||||
|
|
||||||
|
Debug.WriteLine($"[TYPEDEF {(pointers > 0? "PTR":"VAL")} ] {line} -- Adding typedef from {type.Name} to {alias}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of struct
|
||||||
|
// typedef struct <optional-type-name>
|
||||||
|
if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1
|
||||||
|
&& currentType.Count == 0) {
|
||||||
|
currentType.Push(new CppComplexType(CompoundType.Struct));
|
||||||
|
|
||||||
|
if (line.StartsWith("struct "))
|
||||||
|
currentType.Peek().Name = line.Split(' ')[1];
|
||||||
|
|
||||||
|
Debug.WriteLine($"\n[STRUCT START ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of union
|
||||||
|
// typedef union <optional-type-name>
|
||||||
|
if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
||||||
|
currentType.Push(new CppComplexType(CompoundType.Union));
|
||||||
|
|
||||||
|
Debug.WriteLine($"\n[UNION START ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of enum
|
||||||
|
// typedef enum <optional-type-name>
|
||||||
|
if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) {
|
||||||
|
currentType.Push(new CppComplexType(CompoundType.Enum));
|
||||||
|
nextEnumValue = 0;
|
||||||
|
|
||||||
|
Debug.WriteLine($"\n[ENUM START ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested complex field
|
||||||
|
// struct <optional-type-name>
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
Debug.WriteLine($"[FIELD START ] {line}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of already named struct
|
||||||
|
if (line == "};" && currentType.Count == 1) {
|
||||||
|
var ct = currentType.Pop();
|
||||||
|
if (!Types.ContainsKey(ct.Name))
|
||||||
|
Types.Add(ct.Name, ct);
|
||||||
|
else
|
||||||
|
((CppComplexType) Types[ct.Name]).Fields = ct.Fields;
|
||||||
|
|
||||||
|
Debug.WriteLine($"[STRUCT END ] {line} -- {ct.Name}\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End of complex field, complex type or enum
|
||||||
|
// end of [typedef] struct/union/enum
|
||||||
|
if (line.StartsWith("}") && line.EndsWith(";")) {
|
||||||
|
var name = line[1..^1].Trim();
|
||||||
|
var ct = currentType.Pop();
|
||||||
|
|
||||||
|
// End of top-level typedef, so it's a type name
|
||||||
|
if (currentType.Count == 0) {
|
||||||
|
ct.Name = name;
|
||||||
|
|
||||||
|
if (!Types.ContainsKey(name))
|
||||||
|
Types.Add(name, ct);
|
||||||
|
|
||||||
|
// We will have to copy the type data if the type was forward declared,
|
||||||
|
// because other types are already referencing it; replacing it in the
|
||||||
|
// collection will not replace the references to the empty version in
|
||||||
|
// other types
|
||||||
|
else {
|
||||||
|
((CppComplexType) Types[name]).Fields = ct.Fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"[STRUCT END ] {line} -- {name}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise it's a field name in the current type
|
||||||
|
else {
|
||||||
|
var parent = currentType.Peek();
|
||||||
|
parent.AddField(new CppField { Name = name, Type = ct });
|
||||||
|
|
||||||
|
Debug.WriteLine($"[FIELD END ] {line} -- {ct.Name} {name}");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function pointer field
|
||||||
|
var fieldFnPtr = rgxFieldFnPtr.Match(line);
|
||||||
|
if (fieldFnPtr.Success) {
|
||||||
|
var fnPtrType = CppFnPtrType.FromSignature(this, line);
|
||||||
|
|
||||||
|
var name = fieldFnPtr.Groups[2].Captures[0].ToString();
|
||||||
|
|
||||||
|
var ct = currentType.Peek();
|
||||||
|
ct.AddField(new CppField {Name = name, Type = fnPtrType}, alignment);
|
||||||
|
|
||||||
|
Debug.WriteLine($"[FIELD FNPTR ] {line} -- {name}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer or value field
|
||||||
|
var field = rgxField.Match(line);
|
||||||
|
|
||||||
|
if (field.Success) {
|
||||||
|
var names = field.Groups[2].Captures[0].ToString();
|
||||||
|
var typeName = field.Groups[1].Captures[0].ToString();
|
||||||
|
|
||||||
|
// Multiple fields can be separated by commas
|
||||||
|
foreach (var fieldName in names.Split(',')) {
|
||||||
|
string name = fieldName.Trim();
|
||||||
|
|
||||||
|
// Array
|
||||||
|
var array = rgxArrayField.Match(name);
|
||||||
|
int arraySize = 0;
|
||||||
|
if (array.Success && array.Groups[2].Captures.Count > 0) {
|
||||||
|
arraySize = int.Parse(array.Groups[2].Captures[0].ToString());
|
||||||
|
name = array.Groups[1].Captures[0].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bitfield
|
||||||
|
int bitfield = 0;
|
||||||
|
if (field.Groups[3].Captures.Count > 0)
|
||||||
|
bitfield = int.Parse(field.Groups[3].Captures[0].ToString());
|
||||||
|
|
||||||
|
// Potential multiple indirection
|
||||||
|
var type = Types[typeName];
|
||||||
|
var pointers = line.Count(c => c == '*');
|
||||||
|
for (int i = 0; i < pointers; i++)
|
||||||
|
type = type.AsPointer(WordSize);
|
||||||
|
|
||||||
|
var ct = currentType.Peek();
|
||||||
|
|
||||||
|
if (arraySize > 0)
|
||||||
|
type = type.AsArray(arraySize);
|
||||||
|
|
||||||
|
ct.AddField(new CppField {Name = name, Type = type, BitfieldSize = bitfield}, alignment);
|
||||||
|
|
||||||
|
if (bitfield == 0)
|
||||||
|
Debug.WriteLine($"[FIELD {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- {name}");
|
||||||
|
else
|
||||||
|
Debug.WriteLine($"[BITFIELD ] {line} -- {name} : {bitfield}");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum value field
|
||||||
|
var enumValue = rgxEnumValue.Match(line);
|
||||||
|
if (enumValue.Success) {
|
||||||
|
var name = enumValue.Groups[1].Captures[0].ToString();
|
||||||
|
|
||||||
|
var value = nextEnumValue++;
|
||||||
|
if (enumValue.Groups[2].Captures.Count > 0) {
|
||||||
|
// Convert the text to a ulong even if it's hexadecimal with a 0x prefix
|
||||||
|
var valueText = enumValue.Groups[2].Captures[0].ToString();
|
||||||
|
var conv = new System.ComponentModel.UInt64Converter();
|
||||||
|
|
||||||
|
// Handle bit shift operator
|
||||||
|
var values = valueText.Split("<<").Select(t => (ulong) conv.ConvertFromInvariantString(t.Trim())).ToArray();
|
||||||
|
value = values.Length == 1 ? values[0] : values[0] << (int)values[1];
|
||||||
|
nextEnumValue = value + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ct = currentType.Peek();
|
||||||
|
ct.AddField(new CppEnumField {Name = name, Type = WordSize == 32 ? Types["uint32_t"] : Types["uint64_t"], Value = value});
|
||||||
|
|
||||||
|
Debug.WriteLine($"[ENUM VALUE ] {line} -- {name} = {value}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we're not ignoring anything we shouldn't
|
||||||
|
Debug.WriteLine($"[IGNORE ] {line}");
|
||||||
|
|
||||||
|
// Block opens
|
||||||
|
if (line == "{")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Global variables
|
||||||
|
if (line.StartsWith("static"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Pragma directives
|
||||||
|
if (line.StartsWith("#pragma"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Imports
|
||||||
|
if (line.StartsWith("extern"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
throw new InvalidOperationException("Could not understand C++ code: " + line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// Get a type from its name, handling pointer types
|
||||||
|
public CppType GetType(string typeName) {
|
||||||
|
var baseName = typeName.Replace("*", "");
|
||||||
|
var indirectionCount = typeName.Length - baseName.Length;
|
||||||
|
|
||||||
|
var type = Types[baseName.Trim()];
|
||||||
|
for (int i = 0; i < indirectionCount; i++)
|
||||||
|
type = type.AsPointer(WordSize);
|
||||||
|
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a type externally
|
||||||
|
public void Add(CppType type) => Types.Add(type.Name, type);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
public static CppTypes FromUnityHeaders(UnityHeader header, int wordSize = 32) {
|
||||||
|
var cppTypes = new CppTypes(wordSize);
|
||||||
|
|
||||||
|
// Add junk from config files we haven't included
|
||||||
|
cppTypes.Add(new CppType("Il2CppIManagedObjectHolder"));
|
||||||
|
cppTypes.Add(new CppType("Il2CppIUnknown"));
|
||||||
|
|
||||||
|
// Process Unity headers
|
||||||
|
var headers = header.GetHeaderText();
|
||||||
|
cppTypes.AddFromDeclarationText(headers);
|
||||||
|
|
||||||
|
return cppTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user