Optimize some of the string operations
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,95 +1,92 @@
|
||||
/*
|
||||
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
Copyright 2020 Robert Xiao - https://robertxiao.ca
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility class for managing names in a common namespace.
|
||||
/// </summary>
|
||||
public class CppNamespace
|
||||
{
|
||||
// The central data structure that keeps track of which names have been generated
|
||||
// The value for any given key K is the number of unique objects originally named K, minus 1.
|
||||
// Each time we see a particular name assigned to a new, different object, we bump its rename count
|
||||
// and give it a suffix. For example, if we have three different objects all named X,
|
||||
// we'd name them X, X_1, and X_2, and renameCount["X"] would be 2.
|
||||
private readonly Dictionary<string, int> renameCount = new Dictionary<string, int>();
|
||||
|
||||
// Mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names)
|
||||
public void ReserveName(string name) {
|
||||
if (renameCount.ContainsKey(name)) {
|
||||
throw new Exception($"Can't reserve {name}: already taken!");
|
||||
}
|
||||
renameCount[name] = 0;
|
||||
}
|
||||
|
||||
// Try to mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names)
|
||||
public bool TryReserveName(string name) {
|
||||
if (renameCount.ContainsKey(name))
|
||||
return false;
|
||||
renameCount[name] = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create a Namer object which will give names to objects of type T which are unique within this namespace
|
||||
public Namer<T> MakeNamer<T>(Namer<T>.KeyFunc keyFunc) {
|
||||
return new Namer<T>(this, keyFunc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class for managing objects of a common type within a namespace.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class Namer<T>
|
||||
{
|
||||
// Parent namespace
|
||||
private CppNamespace ns;
|
||||
|
||||
// Names given out by this Namer.
|
||||
private readonly Dictionary<T, string> names = new Dictionary<T, string>();
|
||||
|
||||
// The function which maps a T object to a suitably mangled name
|
||||
// That name might be further mangled by the Namer to make the name unique within the namespace
|
||||
public delegate string KeyFunc(T t);
|
||||
private readonly KeyFunc keyFunc;
|
||||
|
||||
public Namer(CppNamespace ns, KeyFunc keyFunc) {
|
||||
this.ns = ns;
|
||||
this.keyFunc = keyFunc;
|
||||
}
|
||||
|
||||
// Uniquely name an object within the parent namespace
|
||||
public string GetName(T t) {
|
||||
// If we've named this particular object before, just return that name
|
||||
string name;
|
||||
if (names.TryGetValue(t, out name))
|
||||
return name;
|
||||
// Obtain the mangled name for the object
|
||||
name = keyFunc(t);
|
||||
// Check if the mangled name has been given to another object - if it has,
|
||||
// we need to give the object a new suffixed name (e.g. X_1).
|
||||
// We might need to repeat this process if the new suffixed name also exists.
|
||||
// Each iteration tacks on another suffix - so we normally expect this to only take
|
||||
// a single iteration. (It might take multiple iterations in rare cases, e.g.
|
||||
// another object had the mangled name X_1).
|
||||
if (ns.renameCount.ContainsKey(name)) {
|
||||
int v = ns.renameCount[name] + 1;
|
||||
while (ns.renameCount.ContainsKey(name + "_" + v))
|
||||
v++;
|
||||
ns.renameCount[name] = v;
|
||||
name = name + "_" + v;
|
||||
}
|
||||
ns.renameCount[name] = 0;
|
||||
names[t] = name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
Copyright 2020 Robert Xiao - https://robertxiao.ca
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
/// <summary>
|
||||
/// A utility class for managing names in a common namespace.
|
||||
/// </summary>
|
||||
public class CppNamespace
|
||||
{
|
||||
// The central data structure that keeps track of which names have been generated
|
||||
// The value for any given key K is the number of unique objects originally named K, minus 1.
|
||||
// Each time we see a particular name assigned to a new, different object, we bump its rename count
|
||||
// and give it a suffix. For example, if we have three different objects all named X,
|
||||
// we'd name them X, X_1, and X_2, and renameCount["X"] would be 2.
|
||||
private readonly Dictionary<string, int> renameCount = new Dictionary<string, int>();
|
||||
|
||||
// Mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names)
|
||||
public void ReserveName(string name) {
|
||||
if (!renameCount.TryAdd(name, 0)) {
|
||||
throw new Exception($"Can't reserve {name}: already taken!");
|
||||
}
|
||||
}
|
||||
|
||||
// Try to mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names)
|
||||
public bool TryReserveName(string name)
|
||||
{
|
||||
return renameCount.TryAdd(name, 0);
|
||||
}
|
||||
|
||||
// Create a Namer object which will give names to objects of type T which are unique within this namespace
|
||||
public Namer<T> MakeNamer<T>(Namer<T>.KeyFunc keyFunc) {
|
||||
return new Namer<T>(this, keyFunc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class for managing objects of a common type within a namespace.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class Namer<T>
|
||||
{
|
||||
// Parent namespace
|
||||
private CppNamespace ns;
|
||||
|
||||
// Names given out by this Namer.
|
||||
private readonly Dictionary<T, string> names = new Dictionary<T, string>();
|
||||
|
||||
// The function which maps a T object to a suitably mangled name
|
||||
// That name might be further mangled by the Namer to make the name unique within the namespace
|
||||
public delegate string KeyFunc(T t);
|
||||
private readonly KeyFunc keyFunc;
|
||||
|
||||
public Namer(CppNamespace ns, KeyFunc keyFunc) {
|
||||
this.ns = ns;
|
||||
this.keyFunc = keyFunc;
|
||||
}
|
||||
|
||||
// Uniquely name an object within the parent namespace
|
||||
public string GetName(T t) {
|
||||
// If we've named this particular object before, just return that name
|
||||
string name;
|
||||
if (names.TryGetValue(t, out name))
|
||||
return name;
|
||||
// Obtain the mangled name for the object
|
||||
name = keyFunc(t);
|
||||
// Check if the mangled name has been given to another object - if it has,
|
||||
// we need to give the object a new suffixed name (e.g. X_1).
|
||||
// We might need to repeat this process if the new suffixed name also exists.
|
||||
// Each iteration tacks on another suffix - so we normally expect this to only take
|
||||
// a single iteration. (It might take multiple iterations in rare cases, e.g.
|
||||
// another object had the mangled name X_1).
|
||||
if (ns.renameCount.ContainsKey(name)) {
|
||||
int v = ns.renameCount[name] + 1;
|
||||
while (ns.renameCount.ContainsKey(name + "_" + v))
|
||||
v++;
|
||||
ns.renameCount[name] = v;
|
||||
name = name + "_" + v;
|
||||
}
|
||||
ns.renameCount[name] = 0;
|
||||
names[t] = name;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,451 +1,458 @@
|
||||
/*
|
||||
Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
// NOTE: The types in this file should not be created directly. Always create types using the CppTypeCollection API!
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
// Value type with fields
|
||||
public enum ComplexValueType
|
||||
{
|
||||
Struct,
|
||||
Union,
|
||||
Enum
|
||||
}
|
||||
|
||||
// A type with no fields
|
||||
public class CppType
|
||||
{
|
||||
// The name of the type
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
// The logical group this type is part of
|
||||
// This is purely for querying types in related groups and has no bearing on the code
|
||||
public string Group { get; set; }
|
||||
|
||||
// The size of the C++ type in bits
|
||||
public virtual int Size { get; set; }
|
||||
|
||||
// The alignment of the type
|
||||
public int AlignmentBytes { get; set; }
|
||||
|
||||
// The size of the C++ type in bytes
|
||||
public virtual int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0);
|
||||
|
||||
public CppType(string name = null, int size = 0, int alignmentBytes = 0) {
|
||||
Name = name;
|
||||
Size = size;
|
||||
AlignmentBytes = alignmentBytes;
|
||||
}
|
||||
|
||||
// Generate pointer to this type
|
||||
public CppPointerType AsPointer(int WordSize) => new CppPointerType(WordSize, this);
|
||||
|
||||
// Generate array of this type
|
||||
public CppArrayType AsArray(int Length) => new CppArrayType(this, Length);
|
||||
|
||||
// Generate typedef to this type
|
||||
public CppAlias AsAlias(string Name) => new CppAlias(Name, this);
|
||||
|
||||
// Return the type as a field
|
||||
public virtual string ToFieldString(string fieldName, string format = "") => Name + " " + fieldName;
|
||||
|
||||
public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : "";
|
||||
|
||||
public override string ToString() => ToString();
|
||||
}
|
||||
|
||||
// A pointer type
|
||||
public class CppPointerType : CppType
|
||||
{
|
||||
public override string Name => ElementType.Name + " *";
|
||||
|
||||
public CppType ElementType { get; }
|
||||
|
||||
public CppPointerType(int WordSize, CppType elementType) : base(null, WordSize) => ElementType = elementType;
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "") => ElementType.ToFieldString("*" + fieldName, format);
|
||||
|
||||
public override string ToString(string format = "") => ToFieldString("");
|
||||
}
|
||||
|
||||
// An array type
|
||||
public class CppArrayType : CppType
|
||||
{
|
||||
public override string Name => ElementType.Name;
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public CppType ElementType { get; }
|
||||
|
||||
// Even an array of 1-bit bitfields must use at least 1 byte each
|
||||
public override int Size => SizeBytes * 8;
|
||||
|
||||
public override int SizeBytes => ElementType.SizeBytes * Length;
|
||||
|
||||
public CppArrayType(CppType elementType, int length) : base() {
|
||||
ElementType = elementType;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "")
|
||||
=> ElementType.ToFieldString(fieldName, format) + "[" + Length + "]";
|
||||
|
||||
public override string ToString(string format = "") => ElementType + "[" + Length + "]";
|
||||
}
|
||||
|
||||
// A function pointer type
|
||||
public class CppFnPtrType : CppType
|
||||
{
|
||||
// Function return type
|
||||
public CppType ReturnType { get; }
|
||||
|
||||
// Function argument names and types by position (some may have no names)
|
||||
public List<(string Name, CppType Type)> Arguments { get; }
|
||||
|
||||
// Regex which matches a function pointer
|
||||
public const string Regex = @"(\S+)\s*\(\s*\*\s*(\S+?)\s*?\)\s*\(\s*(.*)\s*\)";
|
||||
|
||||
public CppFnPtrType(int WordSize, CppType returnType, List<(string Name, CppType Type)> arguments) : base(null, WordSize) {
|
||||
ReturnType = returnType;
|
||||
Arguments = arguments;
|
||||
}
|
||||
|
||||
// Generate a CppFnPtrType from a text signature (typedef or field)
|
||||
public static CppFnPtrType FromSignature(CppTypeCollection types, string text) {
|
||||
if (text.StartsWith("typedef "))
|
||||
text = text.Substring(8);
|
||||
|
||||
if (text.EndsWith(";"))
|
||||
text = text[..^1];
|
||||
|
||||
var typedef = System.Text.RegularExpressions.Regex.Match(text, Regex + "$");
|
||||
|
||||
var returnType = types.GetType(typedef.Groups[1].Captures[0].ToString());
|
||||
var fnPtrName = typedef.Groups[2].Captures[0].ToString();
|
||||
|
||||
var argumentText = typedef.Groups[3].Captures[0].ToString() + ")";
|
||||
|
||||
// Look for each argument one at a time
|
||||
// An argument is complete when we have zero bracket depth and either a comma or a close bracket (final argument)
|
||||
var arguments = new List<string>();
|
||||
while (argumentText.Length > 0) {
|
||||
string argument = null;
|
||||
var originalArgumentText = argumentText;
|
||||
var depth = 0;
|
||||
while (depth >= 0) {
|
||||
var firstComma = argumentText.IndexOf(",");
|
||||
var firstOpenBracket = argumentText.IndexOf("(");
|
||||
var firstCloseBracket = argumentText.IndexOf(")");
|
||||
if (firstOpenBracket == -1) {
|
||||
argument += argumentText.Substring(0, 1 + ((firstComma != -1) ? firstComma : firstCloseBracket));
|
||||
// End of argument if we get a comma or close bracket at zero depth,
|
||||
// but only for the final close bracket if we are inside a function pointer signature
|
||||
if (depth == 0 || firstComma == -1)
|
||||
depth--;
|
||||
// This condition handles function pointers followed by more arguments, ie. "), "
|
||||
if (firstComma != -1 && firstCloseBracket < firstComma)
|
||||
depth -= 2;
|
||||
} else if (firstOpenBracket < firstCloseBracket) {
|
||||
depth++;
|
||||
argument += argumentText.Substring(0, firstOpenBracket + 1);
|
||||
} else {
|
||||
depth--;
|
||||
argument += argumentText.Substring(0, firstCloseBracket + 1);
|
||||
}
|
||||
argumentText = originalArgumentText.Substring(argument.Length);
|
||||
}
|
||||
|
||||
// Function with no arguments ie. (*foo)()
|
||||
if (argument.Length > 1) {
|
||||
arguments.Add(argument[..^1].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Split argument names and types
|
||||
var fnPtrArguments = new List<(string, CppType)>();
|
||||
|
||||
foreach (var argument in arguments) {
|
||||
string name;
|
||||
CppType type;
|
||||
|
||||
// Function pointer
|
||||
if (argument.IndexOf("(") != -1) {
|
||||
type = FromSignature(types, argument);
|
||||
name = type.Name;
|
||||
|
||||
// Non-function pointer
|
||||
} else {
|
||||
name = argument.IndexOf("*") != -1? argument.Substring(argument.LastIndexOf("*") + 1).Trim() :
|
||||
argument.IndexOf(" ") != -1? argument.Substring(argument.LastIndexOf(" ") + 1) : "";
|
||||
type = types.GetType(argument.Substring(0, argument.Length - name.Length));
|
||||
}
|
||||
fnPtrArguments.Add((name, type));
|
||||
}
|
||||
return new CppFnPtrType(types.WordSize, returnType, fnPtrArguments) {Name = fnPtrName};
|
||||
}
|
||||
|
||||
// Output as a named field in a type
|
||||
public override string ToFieldString(string name, string format = "") => $"{ReturnType.Name} (*{name})("
|
||||
+ string.Join(", ", Arguments.Select(a => a.Type is CppFnPtrType fn ? fn.ToFieldString(a.Name, format) : a.Type.Name + (a.Name.Length > 0 ? " " + a.Name : "")))
|
||||
+ ")";
|
||||
|
||||
// Output as a typedef declaration
|
||||
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 is CppFnPtrType fn? fn.ToFieldString(a.Name) : a.Type.Name + (a.Name.Length > 0? " " + a.Name : "")))
|
||||
+ ")";
|
||||
}
|
||||
|
||||
// A named alias for another type
|
||||
// These are not stored in the type collection but generated on-the-fly for fields by GetType()
|
||||
public class CppAlias : CppType
|
||||
{
|
||||
public CppType ElementType { get; }
|
||||
|
||||
public override int Size => ElementType.Size;
|
||||
|
||||
public override int SizeBytes => ElementType.SizeBytes;
|
||||
|
||||
public CppAlias(string name, CppType elementType) : base(name) => ElementType = elementType;
|
||||
|
||||
public override string ToString(string format = "") => $"typedef {ElementType.ToFieldString(Name)};";
|
||||
}
|
||||
|
||||
// A struct, union, enum or class type (type with fields)
|
||||
public class CppComplexType : CppType, IEnumerable<CppField>
|
||||
{
|
||||
// Various enumerators
|
||||
public List<CppField> this[int byteOffset] => Fields[byteOffset * 8];
|
||||
|
||||
public CppField this[string fieldName] => Fields.Values.SelectMany(f => f).FirstOrDefault(f => f.Name == fieldName);
|
||||
|
||||
public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
// Collection which flattens all nested fields, calculating their direct bit offsets from the start of the type
|
||||
// Unions can still cause some offsets to have multiple values
|
||||
public class FlattenedFieldsCollection : IEnumerable<CppField>
|
||||
{
|
||||
public SortedDictionary<int, List<CppField>> Fields;
|
||||
|
||||
public FlattenedFieldsCollection(CppComplexType t) => Fields = getFlattenedFields(t);
|
||||
|
||||
private SortedDictionary<int, List<CppField>> getFlattenedFields(CppComplexType t) {
|
||||
var flattened = new SortedDictionary<int, List<CppField>>();
|
||||
|
||||
foreach (var field in t.Fields.Values.SelectMany(f => f)) {
|
||||
var type = field.Type;
|
||||
while (type is CppAlias aliasType)
|
||||
type = aliasType.ElementType;
|
||||
|
||||
if (type is CppComplexType ct) {
|
||||
var baseOffset = field.Offset;
|
||||
var fields = ct.Flattened.Fields.Select(kl => new {
|
||||
Key = kl.Key + baseOffset,
|
||||
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));
|
||||
} else {
|
||||
if (flattened.ContainsKey(field.Offset))
|
||||
flattened[field.Offset].Add(field);
|
||||
else
|
||||
flattened.Add(field.Offset, new List<CppField> { field });
|
||||
}
|
||||
}
|
||||
return flattened;
|
||||
}
|
||||
|
||||
public List<CppField> this[int byteOffset] => Fields[byteOffset * 8];
|
||||
|
||||
public CppField this[string fieldName] => Fields.Values.SelectMany(f => f).FirstOrDefault(f => f.Name == fieldName);
|
||||
|
||||
public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
private FlattenedFieldsCollection flattenedFields;
|
||||
|
||||
public FlattenedFieldsCollection Flattened {
|
||||
get {
|
||||
if (flattenedFields == null)
|
||||
flattenedFields = new FlattenedFieldsCollection(this);
|
||||
return flattenedFields;
|
||||
}
|
||||
}
|
||||
|
||||
// The compound type
|
||||
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(ComplexValueType complexValueType) : base("", 0) {
|
||||
ComplexValueType = complexValueType;
|
||||
|
||||
// An empty class shall always have sizeof() >= 1
|
||||
// This will get overwritten the first time a field is added
|
||||
Size = 8;
|
||||
}
|
||||
|
||||
// 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
|
||||
// An empty struct has a Size (bits) of 8 so the first field must also be set to zero offset
|
||||
field.Offset = ComplexValueType == ComplexValueType.Struct ? (Fields.Any()? Size : 0) : 0;
|
||||
|
||||
// If we just came out of a bitfield, move to the next byte if necessary
|
||||
if (field.BitfieldSize == 0 && field.Offset % 8 != 0)
|
||||
field.Offset = (field.Offset / 8) * 8 + 8;
|
||||
|
||||
// A 2, 4 or 8-byte value etc. must be aligned on an equivalent boundary
|
||||
// The same goes for the first entry in a struct, union or array
|
||||
// This block searches depth-first for the first field or element in any child types to find the required alignment boundary
|
||||
// https://en.wikipedia.org/wiki/Data_structure_alignment
|
||||
if (field.BitfieldSize == 0) {
|
||||
var firstSimpleType = field.Type;
|
||||
var foundType = false;
|
||||
while (!foundType) {
|
||||
var simpleType = firstSimpleType switch {
|
||||
CppAlias alias => alias.ElementType,
|
||||
CppComplexType { ComplexValueType: ComplexValueType.Struct } complex => complex.Fields.FirstOrDefault().Value?.First().Type,
|
||||
CppArrayType array => array.ElementType,
|
||||
_ => firstSimpleType
|
||||
};
|
||||
if (simpleType == firstSimpleType)
|
||||
foundType = true;
|
||||
firstSimpleType = simpleType;
|
||||
}
|
||||
|
||||
// Empty classes shall always have sizeof() >= 1 and alignment doesn't matter
|
||||
// Empty classes will be returned as null by the above code (complex? null conditional operator)
|
||||
// https://www.stroustrup.com/bs_faq2.html#sizeof-empty
|
||||
if (firstSimpleType != null)
|
||||
if (field.OffsetBytes % firstSimpleType.SizeBytes != 0)
|
||||
field.Offset += (firstSimpleType.SizeBytes - field.OffsetBytes % firstSimpleType.SizeBytes) * 8;
|
||||
}
|
||||
|
||||
// Respect alignment directives
|
||||
if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0)
|
||||
field.Offset += (alignmentBytes - field.OffsetBytes % alignmentBytes) * 8;
|
||||
|
||||
if (field.Type.AlignmentBytes > 0 && field.OffsetBytes % field.Type.AlignmentBytes != 0)
|
||||
field.Offset += (field.Type.AlignmentBytes - field.OffsetBytes % field.Type.AlignmentBytes) * 8;
|
||||
|
||||
if (Fields.ContainsKey(field.Offset))
|
||||
Fields[field.Offset].Add(field);
|
||||
else
|
||||
Fields.Add(field.Offset, new List<CppField> { field });
|
||||
|
||||
// Update type size. This lazy evaluation only works if there are no value type forward declarations in the type
|
||||
// Union size is the size of the largest element in the union
|
||||
if (ComplexValueType == ComplexValueType.Union)
|
||||
if (field.Size > Size)
|
||||
Size = field.Size;
|
||||
|
||||
// For structs we look for the last item and add the size; adding the sizes without offsets might fail because of alignment padding
|
||||
if (ComplexValueType == ComplexValueType.Struct)
|
||||
Size = field.Offset + field.Size;
|
||||
|
||||
return Size;
|
||||
}
|
||||
|
||||
// Add a field to the type
|
||||
public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0, bool isConst = false)
|
||||
=> AddField(new CppField(name, type, bitfield, isConst), alignmentBytes);
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "")
|
||||
=> (ComplexValueType == ComplexValueType.Struct ? "struct " : "union ") + Name + " " + fieldName;
|
||||
|
||||
// Summarize all field names and offsets
|
||||
public override string ToString(string format = "") {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union ");
|
||||
|
||||
if (AlignmentBytes != 0)
|
||||
sb.Append($"__declspec(align({AlignmentBytes})) ");
|
||||
|
||||
sb.Append(Name + (Name.Length > 0 ? " " : ""));
|
||||
|
||||
sb.Append("{");
|
||||
foreach (var field in Fields.Values.SelectMany(f => f)) {
|
||||
var fieldString = field.ToString(format);
|
||||
var suffix = ";";
|
||||
// C-compatible enum field
|
||||
if (field.Type is CppEnumType) {
|
||||
var sbEnum = new StringBuilder();
|
||||
sbEnum.AppendLine("#if defined(_CPLUSPLUS_)");
|
||||
sbEnum.AppendLine(fieldString + ";");
|
||||
sbEnum.AppendLine("#else");
|
||||
sbEnum.AppendLine(field.ToString(format + "c") + ";");
|
||||
sbEnum.Append("#endif");
|
||||
fieldString = sbEnum.ToString();
|
||||
suffix = "";
|
||||
}
|
||||
sb.Append("\n " + string.Join("\n ", fieldString.Split('\n')) + suffix);
|
||||
}
|
||||
|
||||
sb.Append($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||
|
||||
sb.Append("\n");
|
||||
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, object value) => AddField(new CppEnumField(this, name, UnderlyingType, value));
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "") {
|
||||
// C++
|
||||
if (!format.Contains('c'))
|
||||
return Name + " " + fieldName;
|
||||
|
||||
// For the C-compatible definition, we have an alignment problem when the enum
|
||||
// does not derive from the architecture integer width.
|
||||
return UnderlyingType.Name + " " + fieldName;
|
||||
}
|
||||
|
||||
// Format specifier: 'c' = don't output C++-style enum with base type, use C-compatible code only
|
||||
public override string ToString(string format = "") {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Don't output " : {underlyingType.Name}" because it breaks C
|
||||
if (format.Contains('c'))
|
||||
sb.Append($"enum {Name} {{");
|
||||
else
|
||||
sb.Append($"enum class {Name} : {UnderlyingType.Name} {{");
|
||||
|
||||
foreach (var field in Fields.Values.SelectMany(f => f))
|
||||
sb.Append("\n " + string.Join("\n ", field.ToString(format).Split('\n')) + ",");
|
||||
|
||||
sb.AppendLine($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
// NOTE: The types in this file should not be created directly. Always create types using the CppTypeCollection API!
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
// Value type with fields
|
||||
public enum ComplexValueType
|
||||
{
|
||||
Struct,
|
||||
Union,
|
||||
Enum
|
||||
}
|
||||
|
||||
// A type with no fields
|
||||
public class CppType
|
||||
{
|
||||
// The name of the type
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
// The logical group this type is part of
|
||||
// This is purely for querying types in related groups and has no bearing on the code
|
||||
public string Group { get; set; }
|
||||
|
||||
// The size of the C++ type in bits
|
||||
public virtual int Size { get; set; }
|
||||
|
||||
// The alignment of the type
|
||||
public int AlignmentBytes { get; set; }
|
||||
|
||||
// The size of the C++ type in bytes
|
||||
public virtual int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0);
|
||||
|
||||
public CppType(string name = null, int size = 0, int alignmentBytes = 0) {
|
||||
Name = name;
|
||||
Size = size;
|
||||
AlignmentBytes = alignmentBytes;
|
||||
}
|
||||
|
||||
// Generate pointer to this type
|
||||
public CppPointerType AsPointer(int WordSize) => new CppPointerType(WordSize, this);
|
||||
|
||||
// Generate array of this type
|
||||
public CppArrayType AsArray(int Length) => new CppArrayType(this, Length);
|
||||
|
||||
// Generate typedef to this type
|
||||
public CppAlias AsAlias(string Name) => new CppAlias(Name, this);
|
||||
|
||||
// Return the type as a field
|
||||
public virtual string ToFieldString(string fieldName, string format = "") => Name + " " + fieldName;
|
||||
|
||||
public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : "";
|
||||
|
||||
public override string ToString() => ToString();
|
||||
}
|
||||
|
||||
// A pointer type
|
||||
public class CppPointerType : CppType
|
||||
{
|
||||
public override string Name => ElementType.Name + " *";
|
||||
|
||||
public CppType ElementType { get; }
|
||||
|
||||
public CppPointerType(int WordSize, CppType elementType) : base(null, WordSize) => ElementType = elementType;
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "") => ElementType.ToFieldString("*" + fieldName, format);
|
||||
|
||||
public override string ToString(string format = "") => ToFieldString("");
|
||||
}
|
||||
|
||||
// An array type
|
||||
public class CppArrayType : CppType
|
||||
{
|
||||
public override string Name => ElementType.Name;
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
public CppType ElementType { get; }
|
||||
|
||||
// Even an array of 1-bit bitfields must use at least 1 byte each
|
||||
public override int Size => SizeBytes * 8;
|
||||
|
||||
public override int SizeBytes => ElementType.SizeBytes * Length;
|
||||
|
||||
public CppArrayType(CppType elementType, int length) : base() {
|
||||
ElementType = elementType;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "")
|
||||
=> ElementType.ToFieldString(fieldName, format) + "[" + Length + "]";
|
||||
|
||||
public override string ToString(string format = "") => ElementType + "[" + Length + "]";
|
||||
}
|
||||
|
||||
// A function pointer type
|
||||
public class CppFnPtrType : CppType
|
||||
{
|
||||
// Function return type
|
||||
public CppType ReturnType { get; }
|
||||
|
||||
// Function argument names and types by position (some may have no names)
|
||||
public List<(string Name, CppType Type)> Arguments { get; }
|
||||
|
||||
// Regex which matches a function pointer
|
||||
public const string Regex = @"(\S+)\s*\(\s*\*\s*(\S+?)\s*?\)\s*\(\s*(.*)\s*\)";
|
||||
|
||||
public CppFnPtrType(int WordSize, CppType returnType, List<(string Name, CppType Type)> arguments) : base(null, WordSize) {
|
||||
ReturnType = returnType;
|
||||
Arguments = arguments;
|
||||
}
|
||||
|
||||
// Generate a CppFnPtrType from a text signature (typedef or field)
|
||||
public static CppFnPtrType FromSignature(CppTypeCollection types, string text) {
|
||||
if (text.StartsWith("typedef "))
|
||||
text = text.Substring(8);
|
||||
|
||||
if (text.EndsWith(";"))
|
||||
text = text[..^1];
|
||||
|
||||
var typedef = System.Text.RegularExpressions.Regex.Match(text, Regex + "$");
|
||||
|
||||
var returnType = types.GetType(typedef.Groups[1].Captures[0].ToString());
|
||||
var fnPtrName = typedef.Groups[2].Captures[0].ToString();
|
||||
|
||||
var argumentText = typedef.Groups[3].Captures[0].ToString() + ")";
|
||||
|
||||
// Look for each argument one at a time
|
||||
// An argument is complete when we have zero bracket depth and either a comma or a close bracket (final argument)
|
||||
var arguments = new List<string>();
|
||||
while (argumentText.Length > 0) {
|
||||
string argument = null;
|
||||
var originalArgumentText = argumentText;
|
||||
var depth = 0;
|
||||
while (depth >= 0) {
|
||||
var firstComma = argumentText.IndexOf(",");
|
||||
var firstOpenBracket = argumentText.IndexOf("(");
|
||||
var firstCloseBracket = argumentText.IndexOf(")");
|
||||
if (firstOpenBracket == -1) {
|
||||
argument += argumentText.Substring(0, 1 + ((firstComma != -1) ? firstComma : firstCloseBracket));
|
||||
// End of argument if we get a comma or close bracket at zero depth,
|
||||
// but only for the final close bracket if we are inside a function pointer signature
|
||||
if (depth == 0 || firstComma == -1)
|
||||
depth--;
|
||||
// This condition handles function pointers followed by more arguments, ie. "), "
|
||||
if (firstComma != -1 && firstCloseBracket < firstComma)
|
||||
depth -= 2;
|
||||
} else if (firstOpenBracket < firstCloseBracket) {
|
||||
depth++;
|
||||
argument += argumentText.Substring(0, firstOpenBracket + 1);
|
||||
} else {
|
||||
depth--;
|
||||
argument += argumentText.Substring(0, firstCloseBracket + 1);
|
||||
}
|
||||
argumentText = originalArgumentText.Substring(argument.Length);
|
||||
}
|
||||
|
||||
// Function with no arguments ie. (*foo)()
|
||||
if (argument.Length > 1) {
|
||||
arguments.Add(argument[..^1].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
// Split argument names and types
|
||||
var fnPtrArguments = new List<(string, CppType)>();
|
||||
|
||||
foreach (var argument in arguments) {
|
||||
string name;
|
||||
CppType type;
|
||||
|
||||
// Function pointer
|
||||
if (argument.IndexOf("(") != -1) {
|
||||
type = FromSignature(types, argument);
|
||||
name = type.Name;
|
||||
|
||||
// Non-function pointer
|
||||
} else {
|
||||
name = argument.IndexOf("*") != -1? argument.Substring(argument.LastIndexOf("*") + 1).Trim() :
|
||||
argument.IndexOf(" ") != -1? argument.Substring(argument.LastIndexOf(" ") + 1) : "";
|
||||
type = types.GetType(argument.Substring(0, argument.Length - name.Length));
|
||||
}
|
||||
fnPtrArguments.Add((name, type));
|
||||
}
|
||||
return new CppFnPtrType(types.WordSize, returnType, fnPtrArguments) {Name = fnPtrName};
|
||||
}
|
||||
|
||||
// Output as a named field in a type
|
||||
public override string ToFieldString(string name, string format = "") => $"{ReturnType.Name} (*{name})("
|
||||
+ string.Join(", ", Arguments.Select(a => a.Type is CppFnPtrType fn ? fn.ToFieldString(a.Name, format) : a.Type.Name + (a.Name.Length > 0 ? " " + a.Name : "")))
|
||||
+ ")";
|
||||
|
||||
// Output as a typedef declaration
|
||||
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 is CppFnPtrType fn? fn.ToFieldString(a.Name) : a.Type.Name + (a.Name.Length > 0? " " + a.Name : "")))
|
||||
+ ")";
|
||||
}
|
||||
|
||||
// A named alias for another type
|
||||
// These are not stored in the type collection but generated on-the-fly for fields by GetType()
|
||||
public class CppAlias : CppType
|
||||
{
|
||||
public CppType ElementType { get; }
|
||||
|
||||
public override int Size => ElementType.Size;
|
||||
|
||||
public override int SizeBytes => ElementType.SizeBytes;
|
||||
|
||||
public CppAlias(string name, CppType elementType) : base(name) => ElementType = elementType;
|
||||
|
||||
public override string ToString(string format = "") => $"typedef {ElementType.ToFieldString(Name)};";
|
||||
}
|
||||
|
||||
// A struct, union, enum or class type (type with fields)
|
||||
public class CppComplexType : CppType, IEnumerable<CppField>
|
||||
{
|
||||
// Various enumerators
|
||||
public List<CppField> this[int byteOffset] => Fields[byteOffset * 8];
|
||||
|
||||
public CppField this[string fieldName] => Fields.Values.SelectMany(f => f).FirstOrDefault(f => f.Name == fieldName);
|
||||
|
||||
public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
// Collection which flattens all nested fields, calculating their direct bit offsets from the start of the type
|
||||
// Unions can still cause some offsets to have multiple values
|
||||
public class FlattenedFieldsCollection : IEnumerable<CppField>
|
||||
{
|
||||
public SortedDictionary<int, List<CppField>> Fields;
|
||||
|
||||
public FlattenedFieldsCollection(CppComplexType t) => Fields = getFlattenedFields(t);
|
||||
|
||||
private SortedDictionary<int, List<CppField>> getFlattenedFields(CppComplexType t) {
|
||||
var flattened = new SortedDictionary<int, List<CppField>>();
|
||||
|
||||
foreach (var field in t.Fields.Values.SelectMany(f => f)) {
|
||||
var type = field.Type;
|
||||
while (type is CppAlias aliasType)
|
||||
type = aliasType.ElementType;
|
||||
|
||||
if (type is CppComplexType ct) {
|
||||
var baseOffset = field.Offset;
|
||||
var fields = ct.Flattened.Fields.Select(kl => new {
|
||||
Key = kl.Key + baseOffset,
|
||||
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));
|
||||
} else {
|
||||
if (flattened.ContainsKey(field.Offset))
|
||||
flattened[field.Offset].Add(field);
|
||||
else
|
||||
flattened.Add(field.Offset, new List<CppField> { field });
|
||||
}
|
||||
}
|
||||
return flattened;
|
||||
}
|
||||
|
||||
public List<CppField> this[int byteOffset] => Fields[byteOffset * 8];
|
||||
|
||||
public CppField this[string fieldName] => Fields.Values.SelectMany(f => f).FirstOrDefault(f => f.Name == fieldName);
|
||||
|
||||
public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
||||
private FlattenedFieldsCollection flattenedFields;
|
||||
|
||||
public FlattenedFieldsCollection Flattened {
|
||||
get {
|
||||
if (flattenedFields == null)
|
||||
flattenedFields = new FlattenedFieldsCollection(this);
|
||||
return flattenedFields;
|
||||
}
|
||||
}
|
||||
|
||||
// The compound type
|
||||
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(ComplexValueType complexValueType) : base("", 0) {
|
||||
ComplexValueType = complexValueType;
|
||||
|
||||
// An empty class shall always have sizeof() >= 1
|
||||
// This will get overwritten the first time a field is added
|
||||
Size = 8;
|
||||
}
|
||||
|
||||
// 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
|
||||
// An empty struct has a Size (bits) of 8 so the first field must also be set to zero offset
|
||||
field.Offset = ComplexValueType == ComplexValueType.Struct ? (Fields.Any()? Size : 0) : 0;
|
||||
|
||||
// If we just came out of a bitfield, move to the next byte if necessary
|
||||
if (field.BitfieldSize == 0 && field.Offset % 8 != 0)
|
||||
field.Offset = (field.Offset / 8) * 8 + 8;
|
||||
|
||||
// A 2, 4 or 8-byte value etc. must be aligned on an equivalent boundary
|
||||
// The same goes for the first entry in a struct, union or array
|
||||
// This block searches depth-first for the first field or element in any child types to find the required alignment boundary
|
||||
// https://en.wikipedia.org/wiki/Data_structure_alignment
|
||||
if (field.BitfieldSize == 0) {
|
||||
var firstSimpleType = field.Type;
|
||||
var foundType = false;
|
||||
while (!foundType) {
|
||||
var simpleType = firstSimpleType switch {
|
||||
CppAlias alias => alias.ElementType,
|
||||
CppComplexType { ComplexValueType: ComplexValueType.Struct } complex => complex.Fields.FirstOrDefault().Value?.First().Type,
|
||||
CppArrayType array => array.ElementType,
|
||||
_ => firstSimpleType
|
||||
};
|
||||
if (simpleType == firstSimpleType)
|
||||
foundType = true;
|
||||
firstSimpleType = simpleType;
|
||||
}
|
||||
|
||||
// Empty classes shall always have sizeof() >= 1 and alignment doesn't matter
|
||||
// Empty classes will be returned as null by the above code (complex? null conditional operator)
|
||||
// https://www.stroustrup.com/bs_faq2.html#sizeof-empty
|
||||
if (firstSimpleType != null)
|
||||
if (field.OffsetBytes % firstSimpleType.SizeBytes != 0)
|
||||
field.Offset += (firstSimpleType.SizeBytes - field.OffsetBytes % firstSimpleType.SizeBytes) * 8;
|
||||
}
|
||||
|
||||
// Respect alignment directives
|
||||
if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0)
|
||||
field.Offset += (alignmentBytes - field.OffsetBytes % alignmentBytes) * 8;
|
||||
|
||||
if (field.Type.AlignmentBytes > 0 && field.OffsetBytes % field.Type.AlignmentBytes != 0)
|
||||
field.Offset += (field.Type.AlignmentBytes - field.OffsetBytes % field.Type.AlignmentBytes) * 8;
|
||||
|
||||
if (Fields.ContainsKey(field.Offset))
|
||||
Fields[field.Offset].Add(field);
|
||||
else
|
||||
Fields.Add(field.Offset, new List<CppField> { field });
|
||||
|
||||
// Update type size. This lazy evaluation only works if there are no value type forward declarations in the type
|
||||
// Union size is the size of the largest element in the union
|
||||
if (ComplexValueType == ComplexValueType.Union)
|
||||
if (field.Size > Size)
|
||||
Size = field.Size;
|
||||
|
||||
// For structs we look for the last item and add the size; adding the sizes without offsets might fail because of alignment padding
|
||||
if (ComplexValueType == ComplexValueType.Struct)
|
||||
Size = field.Offset + field.Size;
|
||||
|
||||
return Size;
|
||||
}
|
||||
|
||||
// Add a field to the type
|
||||
public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0, bool isConst = false)
|
||||
=> AddField(new CppField(name, type, bitfield, isConst), alignmentBytes);
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "")
|
||||
=> (ComplexValueType == ComplexValueType.Struct ? "struct " : "union ") + Name + " " + fieldName;
|
||||
|
||||
// Summarize all field names and offsets
|
||||
public override string ToString(string format = "") {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union ");
|
||||
|
||||
if (AlignmentBytes != 0)
|
||||
sb.Append($"__declspec(align({AlignmentBytes})) ");
|
||||
|
||||
sb.Append(Name + (Name.Length > 0 ? " " : ""));
|
||||
|
||||
sb.Append("{");
|
||||
foreach (var field in Fields.Values.SelectMany(f => f)) {
|
||||
var fieldString = field.ToString(format);
|
||||
var suffix = ";";
|
||||
// C-compatible enum field
|
||||
if (field.Type is CppEnumType) {
|
||||
var sbEnum = new StringBuilder();
|
||||
sbEnum.AppendLine("#if defined(_CPLUSPLUS_)");
|
||||
sbEnum.AppendLine(fieldString + ";");
|
||||
sbEnum.AppendLine("#else");
|
||||
sbEnum.AppendLine(field.ToString(format + "c") + ";");
|
||||
sbEnum.Append("#endif");
|
||||
fieldString = sbEnum.ToString();
|
||||
suffix = "";
|
||||
}
|
||||
|
||||
sb.Append("\n ");
|
||||
foreach (var fieldStr in fieldString.Split('\n'))
|
||||
{
|
||||
sb.Append(fieldStr);
|
||||
sb.Append("\n ");
|
||||
}
|
||||
sb.Append(suffix);
|
||||
}
|
||||
|
||||
sb.Append($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||
|
||||
sb.Append("\n");
|
||||
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, object value) => AddField(new CppEnumField(this, name, UnderlyingType, value));
|
||||
|
||||
// Return the type as a field
|
||||
public override string ToFieldString(string fieldName, string format = "") {
|
||||
// C++
|
||||
if (!format.Contains('c'))
|
||||
return Name + " " + fieldName;
|
||||
|
||||
// For the C-compatible definition, we have an alignment problem when the enum
|
||||
// does not derive from the architecture integer width.
|
||||
return UnderlyingType.Name + " " + fieldName;
|
||||
}
|
||||
|
||||
// Format specifier: 'c' = don't output C++-style enum with base type, use C-compatible code only
|
||||
public override string ToString(string format = "") {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Don't output " : {underlyingType.Name}" because it breaks C
|
||||
if (format.Contains('c'))
|
||||
sb.Append($"enum {Name} {{");
|
||||
else
|
||||
sb.Append($"enum class {Name} : {UnderlyingType.Name} {{");
|
||||
|
||||
foreach (var field in Fields.Values.SelectMany(f => f))
|
||||
sb.Append("\n " + string.Join("\n ", field.ToString(format).Split('\n')) + ",");
|
||||
|
||||
sb.AppendLine($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,176 +1,185 @@
|
||||
/*
|
||||
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Il2CppInspector.Reflection
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
// Convert a list of CustomAttributeData objects into C#-friendly attribute usages
|
||||
public static string ToString(this IEnumerable<CustomAttributeData> attributes, Scope scope = null,
|
||||
string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var cad in attributes) {
|
||||
// Find a constructor that either has no parameters, or all optional parameters
|
||||
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
|
||||
|
||||
// IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile
|
||||
var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
|
||||
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
|
||||
var arguments = "";
|
||||
|
||||
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
||||
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
|
||||
commentStart = "";
|
||||
commentEnd = "";
|
||||
arguments = "(AttributeTargets.All)";
|
||||
}
|
||||
|
||||
var name = cad.AttributeType.GetScopedCSharpName(scope);
|
||||
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||
if (suffix != -1)
|
||||
name = name[..suffix];
|
||||
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
||||
if (emitPointer)
|
||||
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
|
||||
sb.Append(inline? " ":"\n");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Output a ulong as a 32 or 64-bit hexadecimal address
|
||||
public static string ToAddressString(this ulong address) => address <= 0xffff_ffff
|
||||
? string.Format($"0x{(uint)address:X8}")
|
||||
: string.Format($"0x{address:X16}");
|
||||
|
||||
public static string ToAddressString(this long address) => ((ulong) address).ToAddressString();
|
||||
|
||||
public static string ToAddressString(this (ulong start, ulong end)? address) => ToAddressString(address?.start ?? 0) + "-" + ToAddressString(address?.end ?? 0);
|
||||
|
||||
public static string ToAddressString(this (ulong start, ulong end) address) => ToAddressString(address.start) + "-" + ToAddressString(address.end);
|
||||
|
||||
// C# string literal escape characters
|
||||
// Taken from: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#regular-and-verbatim-string-literals
|
||||
private static Dictionary<char, string> escapeChars = new Dictionary<char, string> {
|
||||
['\''] = @"\'",
|
||||
['"'] = @"\""",
|
||||
['\\'] = @"\\",
|
||||
['\0'] = @"\0",
|
||||
['\a'] = @"\a",
|
||||
['\b'] = @"\b",
|
||||
['\f'] = @"\f",
|
||||
['\n'] = @"\n",
|
||||
['\r'] = @"\r",
|
||||
['\t'] = @"\t",
|
||||
['\v'] = @"\v"
|
||||
};
|
||||
|
||||
// Output a string in Python-friendly syntax
|
||||
public static string ToEscapedString(this string str) {
|
||||
// Replace standard escape characters
|
||||
var s = new StringBuilder();
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
// Standard escape characters
|
||||
s.Append(escapeChars.ContainsKey(str[i]) ? escapeChars[str[i]]
|
||||
// Replace everything else with UTF-16 Unicode
|
||||
: str[i] < 32 || str[i] > 126 ? @"\u" + $"{(int) str[i]:X4}"
|
||||
: str[i].ToString());
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
public static string ToCIdentifier(this string str, bool allowScopeQualifiers = false) {
|
||||
// replace * with Ptr
|
||||
str = str.Replace("*", "Ptr");
|
||||
// escape non-ASCII characters
|
||||
var s = new StringBuilder();
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
if (str[i] < 32 || str[i] > 126)
|
||||
s.Append($"u{(int) str[i]:X4}");
|
||||
else
|
||||
s.Append(str[i]);
|
||||
str = s.ToString();
|
||||
// replace illegal characters
|
||||
str = Regex.Replace(str, allowScopeQualifiers? @"[^a-zA-Z0-9_\.:]" : "[^a-zA-Z0-9_]", "_");
|
||||
// ensure identifier starts with a letter or _ (and is non-empty)
|
||||
if (!Regex.IsMatch(str, "^[a-zA-Z_]"))
|
||||
str = "_" + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
// Output a value in C#-friendly syntax
|
||||
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
|
||||
if (value is bool)
|
||||
return (bool) value ? "true" : "false";
|
||||
if (value is float f)
|
||||
return value switch {
|
||||
float.PositiveInfinity => "1F / 0F",
|
||||
float.NegativeInfinity => "-1F / 0F",
|
||||
float.NaN => "0F / 0F",
|
||||
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
||||
};
|
||||
if (value is double d)
|
||||
return value switch {
|
||||
double.PositiveInfinity => "1D / 0D",
|
||||
double.NegativeInfinity => "-1D / 0D",
|
||||
double.NaN => "0D / 0D",
|
||||
_ => d.ToString(CultureInfo.InvariantCulture)
|
||||
};
|
||||
if (value is string str) {
|
||||
return $"\"{str.ToEscapedString()}\"";
|
||||
}
|
||||
if (value is char) {
|
||||
var cValue = (int) (char) value;
|
||||
if (cValue < 32 || cValue > 126)
|
||||
return $"'\\x{cValue:x4}'";
|
||||
return $"'{value}'";
|
||||
}
|
||||
if (type.IsEnum) {
|
||||
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
|
||||
var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v);
|
||||
var typeName = type.GetScopedCSharpName(usingScope);
|
||||
|
||||
// We don't know what type the enumeration or value is, so we use Object.Equals() to do content-based equality testing
|
||||
if (!flags) {
|
||||
// Defined enum name
|
||||
if (values.FirstOrDefault(v => v.Value.Equals(value)).Key is string enumValue)
|
||||
return typeName + "." + enumValue;
|
||||
|
||||
// Undefined enum value (return a cast)
|
||||
return "(" + typeName + ") " + value;
|
||||
}
|
||||
|
||||
// Logical OR a series of flags together
|
||||
|
||||
// Values like 0x8000_0000_0000_0000 can't be cast to Int64
|
||||
// but values like 0xffff_ffff can't be cast to UInt64 (due to sign extension)
|
||||
// so we're just going to have to try to find a type that doesn't make it explode
|
||||
if (value is byte || value is ushort || value is uint || value is ulong) {
|
||||
var flagValue = Convert.ToUInt64(value);
|
||||
var setFlags = values.Where(x => (Convert.ToUInt64(x.Value) & flagValue) == Convert.ToUInt64(x.Value)).Select(x => typeName + "." + x.Key);
|
||||
return string.Join(" | ", setFlags);
|
||||
}
|
||||
else if (value is sbyte || value is short || value is int || value is long) {
|
||||
var flagValue = Convert.ToInt64(value);
|
||||
var setFlags = values.Where(x => (Convert.ToInt64(x.Value) & flagValue) == Convert.ToInt64(x.Value)).Select(x => typeName + "." + x.Key);
|
||||
return string.Join(" | ", setFlags);
|
||||
} else {
|
||||
throw new ArgumentException("Unsupported enum underlying type");
|
||||
}
|
||||
}
|
||||
// Structs and generic type parameters must use 'default' rather than 'null'
|
||||
return value?.ToString() ?? (type.IsValueType || type.IsGenericParameter? "default" : "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Il2CppInspector.Reflection
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
// Convert a list of CustomAttributeData objects into C#-friendly attribute usages
|
||||
public static string ToString(this IEnumerable<CustomAttributeData> attributes, Scope scope = null,
|
||||
string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) {
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var cad in attributes) {
|
||||
// Find a constructor that either has no parameters, or all optional parameters
|
||||
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
|
||||
|
||||
// IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile
|
||||
var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
|
||||
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
|
||||
var arguments = "";
|
||||
|
||||
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
||||
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
|
||||
commentStart = "";
|
||||
commentEnd = "";
|
||||
arguments = "(AttributeTargets.All)";
|
||||
}
|
||||
|
||||
var name = cad.AttributeType.GetScopedCSharpName(scope);
|
||||
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||
if (suffix != -1)
|
||||
name = name[..suffix];
|
||||
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
||||
if (emitPointer)
|
||||
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
|
||||
sb.Append(inline? " ":"\n");
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Output a ulong as a 32 or 64-bit hexadecimal address
|
||||
public static string ToAddressString(this ulong address) => address <= 0xffff_ffff
|
||||
? string.Format($"0x{(uint)address:X8}")
|
||||
: string.Format($"0x{address:X16}");
|
||||
|
||||
public static string ToAddressString(this long address) => ((ulong) address).ToAddressString();
|
||||
|
||||
public static string ToAddressString(this (ulong start, ulong end)? address) => ToAddressString(address?.start ?? 0) + "-" + ToAddressString(address?.end ?? 0);
|
||||
|
||||
public static string ToAddressString(this (ulong start, ulong end) address) => ToAddressString(address.start) + "-" + ToAddressString(address.end);
|
||||
|
||||
// C# string literal escape characters
|
||||
// Taken from: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#regular-and-verbatim-string-literals
|
||||
private static Dictionary<char, string> escapeChars = new Dictionary<char, string> {
|
||||
['\''] = @"\'",
|
||||
['"'] = @"\""",
|
||||
['\\'] = @"\\",
|
||||
['\0'] = @"\0",
|
||||
['\a'] = @"\a",
|
||||
['\b'] = @"\b",
|
||||
['\f'] = @"\f",
|
||||
['\n'] = @"\n",
|
||||
['\r'] = @"\r",
|
||||
['\t'] = @"\t",
|
||||
['\v'] = @"\v"
|
||||
};
|
||||
|
||||
// Output a string in Python-friendly syntax
|
||||
public static string ToEscapedString(this string str) {
|
||||
// Replace standard escape characters
|
||||
var s = new StringBuilder();
|
||||
|
||||
foreach (var chr in str)
|
||||
{
|
||||
if (escapeChars.TryGetValue(chr, out var escaped))
|
||||
s.Append(escaped);
|
||||
else if (chr < 32 || chr > 126)
|
||||
{
|
||||
s.Append("\\u");
|
||||
s.Append($"{(int) chr:X4}");
|
||||
}
|
||||
else
|
||||
s.Append(chr);
|
||||
|
||||
}
|
||||
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
public static string ToCIdentifier(this string str, bool allowScopeQualifiers = false) {
|
||||
// replace * with Ptr
|
||||
str = str.Replace("*", "Ptr");
|
||||
// escape non-ASCII characters
|
||||
var s = new StringBuilder();
|
||||
for (var i = 0; i < str.Length; i++)
|
||||
if (str[i] < 32 || str[i] > 126)
|
||||
s.Append($"u{(int) str[i]:X4}");
|
||||
else
|
||||
s.Append(str[i]);
|
||||
str = s.ToString();
|
||||
// replace illegal characters
|
||||
str = Regex.Replace(str, allowScopeQualifiers? @"[^a-zA-Z0-9_\.:]" : "[^a-zA-Z0-9_]", "_");
|
||||
// ensure identifier starts with a letter or _ (and is non-empty)
|
||||
if (!Regex.IsMatch(str, "^[a-zA-Z_]"))
|
||||
str = "_" + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
// Output a value in C#-friendly syntax
|
||||
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
|
||||
if (value is bool)
|
||||
return (bool) value ? "true" : "false";
|
||||
if (value is float f)
|
||||
return value switch {
|
||||
float.PositiveInfinity => "1F / 0F",
|
||||
float.NegativeInfinity => "-1F / 0F",
|
||||
float.NaN => "0F / 0F",
|
||||
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
||||
};
|
||||
if (value is double d)
|
||||
return value switch {
|
||||
double.PositiveInfinity => "1D / 0D",
|
||||
double.NegativeInfinity => "-1D / 0D",
|
||||
double.NaN => "0D / 0D",
|
||||
_ => d.ToString(CultureInfo.InvariantCulture)
|
||||
};
|
||||
if (value is string str) {
|
||||
return $"\"{str.ToEscapedString()}\"";
|
||||
}
|
||||
if (value is char) {
|
||||
var cValue = (int) (char) value;
|
||||
if (cValue < 32 || cValue > 126)
|
||||
return $"'\\x{cValue:x4}'";
|
||||
return $"'{value}'";
|
||||
}
|
||||
if (type.IsEnum) {
|
||||
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
|
||||
var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v);
|
||||
var typeName = type.GetScopedCSharpName(usingScope);
|
||||
|
||||
// We don't know what type the enumeration or value is, so we use Object.Equals() to do content-based equality testing
|
||||
if (!flags) {
|
||||
// Defined enum name
|
||||
if (values.FirstOrDefault(v => v.Value.Equals(value)).Key is string enumValue)
|
||||
return typeName + "." + enumValue;
|
||||
|
||||
// Undefined enum value (return a cast)
|
||||
return "(" + typeName + ") " + value;
|
||||
}
|
||||
|
||||
// Logical OR a series of flags together
|
||||
|
||||
// Values like 0x8000_0000_0000_0000 can't be cast to Int64
|
||||
// but values like 0xffff_ffff can't be cast to UInt64 (due to sign extension)
|
||||
// so we're just going to have to try to find a type that doesn't make it explode
|
||||
if (value is byte || value is ushort || value is uint || value is ulong) {
|
||||
var flagValue = Convert.ToUInt64(value);
|
||||
var setFlags = values.Where(x => (Convert.ToUInt64(x.Value) & flagValue) == Convert.ToUInt64(x.Value)).Select(x => typeName + "." + x.Key);
|
||||
return string.Join(" | ", setFlags);
|
||||
}
|
||||
else if (value is sbyte || value is short || value is int || value is long) {
|
||||
var flagValue = Convert.ToInt64(value);
|
||||
var setFlags = values.Where(x => (Convert.ToInt64(x.Value) & flagValue) == Convert.ToInt64(x.Value)).Select(x => typeName + "." + x.Key);
|
||||
return string.Join(" | ", setFlags);
|
||||
} else {
|
||||
throw new ArgumentException("Unsupported enum underlying type");
|
||||
}
|
||||
}
|
||||
// Structs and generic type parameters must use 'default' rather than 'null'
|
||||
return value?.ToString() ?? (type.IsValueType || type.IsGenericParameter? "default" : "null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user