Optimize some of the string operations

This commit is contained in:
LukeFZ
2023-11-30 05:13:19 +01:00
parent 9f6309fb46
commit ab841ccb2b
4 changed files with 1346 additions and 1302 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,95 +1,92 @@
/* /*
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
Copyright 2020 Robert Xiao - https://robertxiao.ca Copyright 2020 Robert Xiao - https://robertxiao.ca
All rights reserved. All rights reserved.
*/ */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Il2CppInspector.Cpp namespace Il2CppInspector.Cpp
{ {
/// <summary> /// <summary>
/// A utility class for managing names in a common namespace. /// A utility class for managing names in a common namespace.
/// </summary> /// </summary>
public class CppNamespace public class CppNamespace
{ {
// The central data structure that keeps track of which names have been generated // 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. // 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 // 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, // 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. // 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>(); 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) // Mark a name as reserved without assigning an object to it (e.g. for keywords and built-in names)
public void ReserveName(string name) { public void ReserveName(string name) {
if (renameCount.ContainsKey(name)) { if (!renameCount.TryAdd(name, 0)) {
throw new Exception($"Can't reserve {name}: already taken!"); 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)
// 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)
public bool TryReserveName(string name) { {
if (renameCount.ContainsKey(name)) return renameCount.TryAdd(name, 0);
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);
// 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>
/// <summary> /// <typeparam name="T"></typeparam>
/// A class for managing objects of a common type within a namespace. public class Namer<T>
/// </summary> {
/// <typeparam name="T"></typeparam> // Parent namespace
public class Namer<T> private CppNamespace ns;
{
// Parent namespace // Names given out by this Namer.
private CppNamespace ns; private readonly Dictionary<T, string> names = new Dictionary<T, string>();
// Names given out by this Namer. // The function which maps a T object to a suitably mangled name
private readonly Dictionary<T, string> names = new Dictionary<T, string>(); // That name might be further mangled by the Namer to make the name unique within the namespace
public delegate string KeyFunc(T t);
// The function which maps a T object to a suitably mangled name private readonly KeyFunc keyFunc;
// That name might be further mangled by the Namer to make the name unique within the namespace
public delegate string KeyFunc(T t); public Namer(CppNamespace ns, KeyFunc keyFunc) {
private readonly KeyFunc keyFunc; this.ns = ns;
this.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
// Uniquely name an object within the parent namespace string name;
public string GetName(T t) { if (names.TryGetValue(t, out name))
// If we've named this particular object before, just return that name return name;
string name; // Obtain the mangled name for the object
if (names.TryGetValue(t, out name)) name = keyFunc(t);
return name; // Check if the mangled name has been given to another object - if it has,
// Obtain the mangled name for the object // we need to give the object a new suffixed name (e.g. X_1).
name = keyFunc(t); // We might need to repeat this process if the new suffixed name also exists.
// Check if the mangled name has been given to another object - if it has, // Each iteration tacks on another suffix - so we normally expect this to only take
// we need to give the object a new suffixed name (e.g. X_1). // a single iteration. (It might take multiple iterations in rare cases, e.g.
// We might need to repeat this process if the new suffixed name also exists. // another object had the mangled name X_1).
// Each iteration tacks on another suffix - so we normally expect this to only take if (ns.renameCount.ContainsKey(name)) {
// a single iteration. (It might take multiple iterations in rare cases, e.g. int v = ns.renameCount[name] + 1;
// another object had the mangled name X_1). while (ns.renameCount.ContainsKey(name + "_" + v))
if (ns.renameCount.ContainsKey(name)) { v++;
int v = ns.renameCount[name] + 1; ns.renameCount[name] = v;
while (ns.renameCount.ContainsKey(name + "_" + v)) name = name + "_" + v;
v++; }
ns.renameCount[name] = v; ns.renameCount[name] = 0;
name = name + "_" + v; names[t] = name;
} return name;
ns.renameCount[name] = 0; }
names[t] = name; }
return name; }
} }
}
}
}

View File

@@ -1,451 +1,458 @@
/* /*
Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved. All rights reserved.
*/ */
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
// NOTE: The types in this file should not be created directly. Always create types using the CppTypeCollection API! // NOTE: The types in this file should not be created directly. Always create types using the CppTypeCollection API!
namespace Il2CppInspector.Cpp namespace Il2CppInspector.Cpp
{ {
// Value type with fields // Value type with fields
public enum ComplexValueType public enum ComplexValueType
{ {
Struct, Struct,
Union, Union,
Enum Enum
} }
// A type with no fields // A type with no fields
public class CppType public class CppType
{ {
// The name of the type // The name of the type
public virtual string Name { get; set; } public virtual string Name { get; set; }
// The logical group this type is part of // The logical group this type is part of
// This is purely for querying types in related groups and has no bearing on the code // This is purely for querying types in related groups and has no bearing on the code
public string Group { get; set; } public string Group { get; set; }
// The size of the C++ type in bits // The size of the C++ type in bits
public virtual int Size { get; set; } public virtual int Size { get; set; }
// The alignment of the type // The alignment of the type
public int AlignmentBytes { get; set; } public int AlignmentBytes { get; set; }
// The size of the C++ type in bytes // The size of the C++ type in bytes
public virtual int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0); public virtual int SizeBytes => (Size / 8) + (Size % 8 > 0 ? 1 : 0);
public CppType(string name = null, int size = 0, int alignmentBytes = 0) { public CppType(string name = null, int size = 0, int alignmentBytes = 0) {
Name = name; Name = name;
Size = size; Size = size;
AlignmentBytes = alignmentBytes; AlignmentBytes = alignmentBytes;
} }
// Generate pointer to this type // Generate pointer to this type
public CppPointerType AsPointer(int WordSize) => new CppPointerType(WordSize, this); public CppPointerType AsPointer(int WordSize) => new CppPointerType(WordSize, this);
// Generate array of this type // Generate array of this type
public CppArrayType AsArray(int Length) => new CppArrayType(this, Length); public CppArrayType AsArray(int Length) => new CppArrayType(this, Length);
// Generate typedef to this type // Generate typedef to this type
public CppAlias AsAlias(string Name) => new CppAlias(Name, this); public CppAlias AsAlias(string Name) => new CppAlias(Name, this);
// Return the type as a field // Return the type as a field
public virtual string ToFieldString(string fieldName, string format = "") => Name + " " + fieldName; public virtual string ToFieldString(string fieldName, string format = "") => Name + " " + fieldName;
public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : ""; public virtual string ToString(string format = "") => format == "o" ? $"/* {SizeBytes:x2} - {Name} */" : "";
public override string ToString() => ToString(); public override string ToString() => ToString();
} }
// A pointer type // A pointer type
public class CppPointerType : CppType public class CppPointerType : CppType
{ {
public override string Name => ElementType.Name + " *"; public override string Name => ElementType.Name + " *";
public CppType ElementType { get; } public CppType ElementType { get; }
public CppPointerType(int WordSize, CppType elementType) : base(null, WordSize) => ElementType = elementType; public CppPointerType(int WordSize, CppType elementType) : base(null, WordSize) => ElementType = elementType;
// Return the type as a field // Return the type as a field
public override string ToFieldString(string fieldName, string format = "") => ElementType.ToFieldString("*" + fieldName, format); public override string ToFieldString(string fieldName, string format = "") => ElementType.ToFieldString("*" + fieldName, format);
public override string ToString(string format = "") => ToFieldString(""); public override string ToString(string format = "") => ToFieldString("");
} }
// An array type // An array type
public class CppArrayType : CppType public class CppArrayType : CppType
{ {
public override string Name => ElementType.Name; public override string Name => ElementType.Name;
public int Length { get; } public int Length { get; }
public CppType ElementType { get; } public CppType ElementType { get; }
// Even an array of 1-bit bitfields must use at least 1 byte each // Even an array of 1-bit bitfields must use at least 1 byte each
public override int Size => SizeBytes * 8; public override int Size => SizeBytes * 8;
public override int SizeBytes => ElementType.SizeBytes * Length; public override int SizeBytes => ElementType.SizeBytes * Length;
public CppArrayType(CppType elementType, int length) : base() { public CppArrayType(CppType elementType, int length) : base() {
ElementType = elementType; ElementType = elementType;
Length = length; Length = length;
} }
// Return the type as a field // Return the type as a field
public override string ToFieldString(string fieldName, string format = "") public override string ToFieldString(string fieldName, string format = "")
=> ElementType.ToFieldString(fieldName, format) + "[" + Length + "]"; => ElementType.ToFieldString(fieldName, format) + "[" + Length + "]";
public override string ToString(string format = "") => ElementType + "[" + Length + "]"; public override string ToString(string format = "") => ElementType + "[" + Length + "]";
} }
// A function pointer type // A function pointer type
public class CppFnPtrType : CppType public class CppFnPtrType : CppType
{ {
// Function return type // Function return type
public CppType ReturnType { get; } public CppType ReturnType { get; }
// Function argument names and types by position (some may have no names) // Function argument names and types by position (some may have no names)
public List<(string Name, CppType Type)> Arguments { get; } public List<(string Name, CppType Type)> Arguments { get; }
// Regex which matches a function pointer // Regex which matches a function pointer
public const string Regex = @"(\S+)\s*\(\s*\*\s*(\S+?)\s*?\)\s*\(\s*(.*)\s*\)"; 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) { public CppFnPtrType(int WordSize, CppType returnType, List<(string Name, CppType Type)> arguments) : base(null, WordSize) {
ReturnType = returnType; ReturnType = returnType;
Arguments = arguments; Arguments = arguments;
} }
// Generate a CppFnPtrType from a text signature (typedef or field) // Generate a CppFnPtrType from a text signature (typedef or field)
public static CppFnPtrType FromSignature(CppTypeCollection types, string text) { public static CppFnPtrType FromSignature(CppTypeCollection types, string text) {
if (text.StartsWith("typedef ")) if (text.StartsWith("typedef "))
text = text.Substring(8); text = text.Substring(8);
if (text.EndsWith(";")) if (text.EndsWith(";"))
text = text[..^1]; text = text[..^1];
var typedef = System.Text.RegularExpressions.Regex.Match(text, Regex + "$"); var typedef = System.Text.RegularExpressions.Regex.Match(text, Regex + "$");
var returnType = types.GetType(typedef.Groups[1].Captures[0].ToString()); var returnType = types.GetType(typedef.Groups[1].Captures[0].ToString());
var fnPtrName = typedef.Groups[2].Captures[0].ToString(); var fnPtrName = typedef.Groups[2].Captures[0].ToString();
var argumentText = typedef.Groups[3].Captures[0].ToString() + ")"; var argumentText = typedef.Groups[3].Captures[0].ToString() + ")";
// Look for each argument one at a time // 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) // 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>(); var arguments = new List<string>();
while (argumentText.Length > 0) { while (argumentText.Length > 0) {
string argument = null; string argument = null;
var originalArgumentText = argumentText; var originalArgumentText = argumentText;
var depth = 0; var depth = 0;
while (depth >= 0) { while (depth >= 0) {
var firstComma = argumentText.IndexOf(","); var firstComma = argumentText.IndexOf(",");
var firstOpenBracket = argumentText.IndexOf("("); var firstOpenBracket = argumentText.IndexOf("(");
var firstCloseBracket = argumentText.IndexOf(")"); var firstCloseBracket = argumentText.IndexOf(")");
if (firstOpenBracket == -1) { if (firstOpenBracket == -1) {
argument += argumentText.Substring(0, 1 + ((firstComma != -1) ? firstComma : firstCloseBracket)); argument += argumentText.Substring(0, 1 + ((firstComma != -1) ? firstComma : firstCloseBracket));
// End of argument if we get a comma or close bracket at zero depth, // 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 // but only for the final close bracket if we are inside a function pointer signature
if (depth == 0 || firstComma == -1) if (depth == 0 || firstComma == -1)
depth--; depth--;
// This condition handles function pointers followed by more arguments, ie. "), " // This condition handles function pointers followed by more arguments, ie. "), "
if (firstComma != -1 && firstCloseBracket < firstComma) if (firstComma != -1 && firstCloseBracket < firstComma)
depth -= 2; depth -= 2;
} else if (firstOpenBracket < firstCloseBracket) { } else if (firstOpenBracket < firstCloseBracket) {
depth++; depth++;
argument += argumentText.Substring(0, firstOpenBracket + 1); argument += argumentText.Substring(0, firstOpenBracket + 1);
} else { } else {
depth--; depth--;
argument += argumentText.Substring(0, firstCloseBracket + 1); argument += argumentText.Substring(0, firstCloseBracket + 1);
} }
argumentText = originalArgumentText.Substring(argument.Length); argumentText = originalArgumentText.Substring(argument.Length);
} }
// Function with no arguments ie. (*foo)() // Function with no arguments ie. (*foo)()
if (argument.Length > 1) { if (argument.Length > 1) {
arguments.Add(argument[..^1].Trim()); arguments.Add(argument[..^1].Trim());
} }
} }
// Split argument names and types // Split argument names and types
var fnPtrArguments = new List<(string, CppType)>(); var fnPtrArguments = new List<(string, CppType)>();
foreach (var argument in arguments) { foreach (var argument in arguments) {
string name; string name;
CppType type; CppType type;
// Function pointer // Function pointer
if (argument.IndexOf("(") != -1) { if (argument.IndexOf("(") != -1) {
type = FromSignature(types, argument); type = FromSignature(types, argument);
name = type.Name; name = type.Name;
// Non-function pointer // Non-function pointer
} else { } else {
name = argument.IndexOf("*") != -1? argument.Substring(argument.LastIndexOf("*") + 1).Trim() : name = argument.IndexOf("*") != -1? argument.Substring(argument.LastIndexOf("*") + 1).Trim() :
argument.IndexOf(" ") != -1? argument.Substring(argument.LastIndexOf(" ") + 1) : ""; argument.IndexOf(" ") != -1? argument.Substring(argument.LastIndexOf(" ") + 1) : "";
type = types.GetType(argument.Substring(0, argument.Length - name.Length)); type = types.GetType(argument.Substring(0, argument.Length - name.Length));
} }
fnPtrArguments.Add((name, type)); fnPtrArguments.Add((name, type));
} }
return new CppFnPtrType(types.WordSize, returnType, fnPtrArguments) {Name = fnPtrName}; return new CppFnPtrType(types.WordSize, returnType, fnPtrArguments) {Name = fnPtrName};
} }
// Output as a named field in a type // Output as a named field in a type
public override string ToFieldString(string name, string format = "") => $"{ReturnType.Name} (*{name})(" 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 : ""))) + 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 // Output as a typedef declaration
public override string ToString(string format = "") => "typedef " + ToFieldString(Name) + ";\n"; public override string ToString(string format = "") => "typedef " + ToFieldString(Name) + ";\n";
// Output as a function signature // Output as a function signature
public string ToSignatureString() => $"{ReturnType.Name} {Name}(" 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 : ""))) + 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 // A named alias for another type
// These are not stored in the type collection but generated on-the-fly for fields by GetType() // These are not stored in the type collection but generated on-the-fly for fields by GetType()
public class CppAlias : CppType public class CppAlias : CppType
{ {
public CppType ElementType { get; } public CppType ElementType { get; }
public override int Size => ElementType.Size; public override int Size => ElementType.Size;
public override int SizeBytes => ElementType.SizeBytes; public override int SizeBytes => ElementType.SizeBytes;
public CppAlias(string name, CppType elementType) : base(name) => ElementType = elementType; public CppAlias(string name, CppType elementType) : base(name) => ElementType = elementType;
public override string ToString(string format = "") => $"typedef {ElementType.ToFieldString(Name)};"; public override string ToString(string format = "") => $"typedef {ElementType.ToFieldString(Name)};";
} }
// A struct, union, enum or class type (type with fields) // A struct, union, enum or class type (type with fields)
public class CppComplexType : CppType, IEnumerable<CppField> public class CppComplexType : CppType, IEnumerable<CppField>
{ {
// Various enumerators // Various enumerators
public List<CppField> this[int byteOffset] => Fields[byteOffset * 8]; 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 CppField this[string fieldName] => Fields.Values.SelectMany(f => f).FirstOrDefault(f => f.Name == fieldName);
public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator(); public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
// Collection which flattens all nested fields, calculating their direct bit offsets from the start of the type // 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 // Unions can still cause some offsets to have multiple values
public class FlattenedFieldsCollection : IEnumerable<CppField> public class FlattenedFieldsCollection : IEnumerable<CppField>
{ {
public SortedDictionary<int, List<CppField>> Fields; public SortedDictionary<int, List<CppField>> Fields;
public FlattenedFieldsCollection(CppComplexType t) => Fields = getFlattenedFields(t); public FlattenedFieldsCollection(CppComplexType t) => Fields = getFlattenedFields(t);
private SortedDictionary<int, List<CppField>> getFlattenedFields(CppComplexType t) { private SortedDictionary<int, List<CppField>> getFlattenedFields(CppComplexType t) {
var flattened = new SortedDictionary<int, List<CppField>>(); var flattened = new SortedDictionary<int, List<CppField>>();
foreach (var field in t.Fields.Values.SelectMany(f => f)) { foreach (var field in t.Fields.Values.SelectMany(f => f)) {
var type = field.Type; var type = field.Type;
while (type is CppAlias aliasType) while (type is CppAlias aliasType)
type = aliasType.ElementType; type = aliasType.ElementType;
if (type is CppComplexType ct) { if (type is CppComplexType ct) {
var baseOffset = field.Offset; var baseOffset = field.Offset;
var fields = ct.Flattened.Fields.Select(kl => new { var fields = ct.Flattened.Fields.Select(kl => new {
Key = kl.Key + baseOffset, Key = kl.Key + baseOffset,
Value = kl.Value.Select(f => new CppField(f.Name, f.Type, f.BitfieldSize) { Offset = f.Offset + baseOffset }).ToList() Value = kl.Value.Select(f => new CppField(f.Name, f.Type, f.BitfieldSize) { Offset = f.Offset + baseOffset }).ToList()
}).ToDictionary(kv => kv.Key, kv => kv.Value); }).ToDictionary(kv => kv.Key, kv => kv.Value);
flattened = new SortedDictionary<int, List<CppField>>(flattened.Union(fields).ToDictionary(kv => kv.Key, kv => kv.Value)); flattened = new SortedDictionary<int, List<CppField>>(flattened.Union(fields).ToDictionary(kv => kv.Key, kv => kv.Value));
} else { } else {
if (flattened.ContainsKey(field.Offset)) if (flattened.ContainsKey(field.Offset))
flattened[field.Offset].Add(field); flattened[field.Offset].Add(field);
else else
flattened.Add(field.Offset, new List<CppField> { field }); flattened.Add(field.Offset, new List<CppField> { field });
} }
} }
return flattened; return flattened;
} }
public List<CppField> this[int byteOffset] => Fields[byteOffset * 8]; 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 CppField this[string fieldName] => Fields.Values.SelectMany(f => f).FirstOrDefault(f => f.Name == fieldName);
public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator(); public IEnumerator<CppField> GetEnumerator() => Fields.Values.SelectMany(f => f).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
} }
private FlattenedFieldsCollection flattenedFields; private FlattenedFieldsCollection flattenedFields;
public FlattenedFieldsCollection Flattened { public FlattenedFieldsCollection Flattened {
get { get {
if (flattenedFields == null) if (flattenedFields == null)
flattenedFields = new FlattenedFieldsCollection(this); flattenedFields = new FlattenedFieldsCollection(this);
return flattenedFields; return flattenedFields;
} }
} }
// The compound type // The compound type
public ComplexValueType ComplexValueType; public ComplexValueType ComplexValueType;
// Dictionary of byte offset in the type to each field // Dictionary of byte offset in the type to each field
// Unions and bitfields can have more than one field at the same offset // 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 SortedDictionary<int, List<CppField>> Fields { get; internal set; } = new SortedDictionary<int, List<CppField>>();
public CppComplexType(ComplexValueType complexValueType) : base("", 0) { public CppComplexType(ComplexValueType complexValueType) : base("", 0) {
ComplexValueType = complexValueType; ComplexValueType = complexValueType;
// An empty class shall always have sizeof() >= 1 // An empty class shall always have sizeof() >= 1
// This will get overwritten the first time a field is added // This will get overwritten the first time a field is added
Size = 8; Size = 8;
} }
// Add a field to the type. Returns the offset of the field in the type // Add a field to the type. Returns the offset of the field in the type
public int AddField(CppField field, int alignmentBytes = 0) { public int AddField(CppField field, int alignmentBytes = 0) {
// Unions and enums always have an offset of zero // Unions and enums always have an offset of zero
// An empty struct has a Size (bits) of 8 so the first field must also be set to zero offset // 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; 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 we just came out of a bitfield, move to the next byte if necessary
if (field.BitfieldSize == 0 && field.Offset % 8 != 0) if (field.BitfieldSize == 0 && field.Offset % 8 != 0)
field.Offset = (field.Offset / 8) * 8 + 8; field.Offset = (field.Offset / 8) * 8 + 8;
// A 2, 4 or 8-byte value etc. must be aligned on an equivalent boundary // 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 // 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 // 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 // https://en.wikipedia.org/wiki/Data_structure_alignment
if (field.BitfieldSize == 0) { if (field.BitfieldSize == 0) {
var firstSimpleType = field.Type; var firstSimpleType = field.Type;
var foundType = false; var foundType = false;
while (!foundType) { while (!foundType) {
var simpleType = firstSimpleType switch { var simpleType = firstSimpleType switch {
CppAlias alias => alias.ElementType, CppAlias alias => alias.ElementType,
CppComplexType { ComplexValueType: ComplexValueType.Struct } complex => complex.Fields.FirstOrDefault().Value?.First().Type, CppComplexType { ComplexValueType: ComplexValueType.Struct } complex => complex.Fields.FirstOrDefault().Value?.First().Type,
CppArrayType array => array.ElementType, CppArrayType array => array.ElementType,
_ => firstSimpleType _ => firstSimpleType
}; };
if (simpleType == firstSimpleType) if (simpleType == firstSimpleType)
foundType = true; foundType = true;
firstSimpleType = simpleType; firstSimpleType = simpleType;
} }
// Empty classes shall always have sizeof() >= 1 and alignment doesn't matter // 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) // Empty classes will be returned as null by the above code (complex? null conditional operator)
// https://www.stroustrup.com/bs_faq2.html#sizeof-empty // https://www.stroustrup.com/bs_faq2.html#sizeof-empty
if (firstSimpleType != null) if (firstSimpleType != null)
if (field.OffsetBytes % firstSimpleType.SizeBytes != 0) if (field.OffsetBytes % firstSimpleType.SizeBytes != 0)
field.Offset += (firstSimpleType.SizeBytes - field.OffsetBytes % firstSimpleType.SizeBytes) * 8; field.Offset += (firstSimpleType.SizeBytes - field.OffsetBytes % firstSimpleType.SizeBytes) * 8;
} }
// Respect alignment directives // Respect alignment directives
if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0) if (alignmentBytes > 0 && field.OffsetBytes % alignmentBytes != 0)
field.Offset += (alignmentBytes - field.OffsetBytes % alignmentBytes) * 8; field.Offset += (alignmentBytes - field.OffsetBytes % alignmentBytes) * 8;
if (field.Type.AlignmentBytes > 0 && field.OffsetBytes % field.Type.AlignmentBytes != 0) if (field.Type.AlignmentBytes > 0 && field.OffsetBytes % field.Type.AlignmentBytes != 0)
field.Offset += (field.Type.AlignmentBytes - field.OffsetBytes % field.Type.AlignmentBytes) * 8; field.Offset += (field.Type.AlignmentBytes - field.OffsetBytes % field.Type.AlignmentBytes) * 8;
if (Fields.ContainsKey(field.Offset)) if (Fields.ContainsKey(field.Offset))
Fields[field.Offset].Add(field); Fields[field.Offset].Add(field);
else else
Fields.Add(field.Offset, new List<CppField> { field }); Fields.Add(field.Offset, new List<CppField> { field });
// Update type size. This lazy evaluation only works if there are no value type forward declarations in the type // 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 // Union size is the size of the largest element in the union
if (ComplexValueType == ComplexValueType.Union) if (ComplexValueType == ComplexValueType.Union)
if (field.Size > Size) if (field.Size > Size)
Size = field.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 // 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) if (ComplexValueType == ComplexValueType.Struct)
Size = field.Offset + field.Size; Size = field.Offset + field.Size;
return Size; return Size;
} }
// Add a field to the type // Add a field to the type
public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0, bool isConst = false) public int AddField(string name, CppType type, int alignmentBytes = 0, int bitfield = 0, bool isConst = false)
=> AddField(new CppField(name, type, bitfield, isConst), alignmentBytes); => AddField(new CppField(name, type, bitfield, isConst), alignmentBytes);
// Return the type as a field // Return the type as a field
public override string ToFieldString(string fieldName, string format = "") public override string ToFieldString(string fieldName, string format = "")
=> (ComplexValueType == ComplexValueType.Struct ? "struct " : "union ") + Name + " " + fieldName; => (ComplexValueType == ComplexValueType.Struct ? "struct " : "union ") + Name + " " + fieldName;
// Summarize all field names and offsets // Summarize all field names and offsets
public override string ToString(string format = "") { public override string ToString(string format = "") {
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union "); sb.Append(ComplexValueType == ComplexValueType.Struct ? "struct " : "union ");
if (AlignmentBytes != 0) if (AlignmentBytes != 0)
sb.Append($"__declspec(align({AlignmentBytes})) "); sb.Append($"__declspec(align({AlignmentBytes})) ");
sb.Append(Name + (Name.Length > 0 ? " " : "")); sb.Append(Name + (Name.Length > 0 ? " " : ""));
sb.Append("{"); sb.Append("{");
foreach (var field in Fields.Values.SelectMany(f => f)) { foreach (var field in Fields.Values.SelectMany(f => f)) {
var fieldString = field.ToString(format); var fieldString = field.ToString(format);
var suffix = ";"; var suffix = ";";
// C-compatible enum field // C-compatible enum field
if (field.Type is CppEnumType) { if (field.Type is CppEnumType) {
var sbEnum = new StringBuilder(); var sbEnum = new StringBuilder();
sbEnum.AppendLine("#if defined(_CPLUSPLUS_)"); sbEnum.AppendLine("#if defined(_CPLUSPLUS_)");
sbEnum.AppendLine(fieldString + ";"); sbEnum.AppendLine(fieldString + ";");
sbEnum.AppendLine("#else"); sbEnum.AppendLine("#else");
sbEnum.AppendLine(field.ToString(format + "c") + ";"); sbEnum.AppendLine(field.ToString(format + "c") + ";");
sbEnum.Append("#endif"); sbEnum.Append("#endif");
fieldString = sbEnum.ToString(); fieldString = sbEnum.ToString();
suffix = ""; suffix = "";
} }
sb.Append("\n " + string.Join("\n ", fieldString.Split('\n')) + suffix);
} sb.Append("\n ");
foreach (var fieldStr in fieldString.Split('\n'))
sb.Append($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); {
sb.Append(fieldStr);
sb.Append("\n"); sb.Append("\n ");
return sb.ToString(); }
} sb.Append(suffix);
} }
// Enumeration type sb.Append($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};");
public class CppEnumType : CppComplexType
{ sb.Append("\n");
// The underlying type of the enum return sb.ToString();
public CppType UnderlyingType { get; } }
}
public override int Size => UnderlyingType.Size;
// Enumeration type
public CppEnumType(CppType underlyingType) : base(ComplexValueType.Enum) => UnderlyingType = underlyingType; public class CppEnumType : CppComplexType
{
public void AddField(string name, object value) => AddField(new CppEnumField(this, name, UnderlyingType, value)); // The underlying type of the enum
public CppType UnderlyingType { get; }
// Return the type as a field
public override string ToFieldString(string fieldName, string format = "") { public override int Size => UnderlyingType.Size;
// C++
if (!format.Contains('c')) public CppEnumType(CppType underlyingType) : base(ComplexValueType.Enum) => UnderlyingType = underlyingType;
return Name + " " + fieldName;
public void AddField(string name, object value) => AddField(new CppEnumField(this, name, UnderlyingType, value));
// For the C-compatible definition, we have an alignment problem when the enum
// does not derive from the architecture integer width. // Return the type as a field
return UnderlyingType.Name + " " + fieldName; public override string ToFieldString(string fieldName, string format = "") {
} // C++
if (!format.Contains('c'))
// Format specifier: 'c' = don't output C++-style enum with base type, use C-compatible code only return Name + " " + fieldName;
public override string ToString(string format = "") {
var sb = new StringBuilder(); // For the C-compatible definition, we have an alignment problem when the enum
// does not derive from the architecture integer width.
// Don't output " : {underlyingType.Name}" because it breaks C return UnderlyingType.Name + " " + fieldName;
if (format.Contains('c')) }
sb.Append($"enum {Name} {{");
else // Format specifier: 'c' = don't output C++-style enum with base type, use C-compatible code only
sb.Append($"enum class {Name} : {UnderlyingType.Name} {{"); public override string ToString(string format = "") {
var sb = new StringBuilder();
foreach (var field in Fields.Values.SelectMany(f => f))
sb.Append("\n " + string.Join("\n ", field.ToString(format).Split('\n')) + ","); // Don't output " : {underlyingType.Name}" because it breaks C
if (format.Contains('c'))
sb.AppendLine($"\n}}{(format == "o"? $" /* Size: 0x{SizeBytes:x2} */" : "")};"); sb.Append($"enum {Name} {{");
return sb.ToString(); 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();
}
}
}

View File

@@ -1,176 +1,185 @@
/* /*
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved. All rights reserved.
*/ */
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace Il2CppInspector.Reflection namespace Il2CppInspector.Reflection
{ {
public static class Extensions public static class Extensions
{ {
// Convert a list of CustomAttributeData objects into C#-friendly attribute usages // Convert a list of CustomAttributeData objects into C#-friendly attribute usages
public static string ToString(this IEnumerable<CustomAttributeData> attributes, Scope scope = null, public static string ToString(this IEnumerable<CustomAttributeData> attributes, Scope scope = null,
string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) { string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) {
var sb = new StringBuilder(); var sb = new StringBuilder();
foreach (var cad in attributes) { foreach (var cad in attributes) {
// Find a constructor that either has no parameters, or all optional parameters // 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)); 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 // 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 commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
var commentEnd = commentStart.Length > 0 && inline? " */" : ""; var commentEnd = commentStart.Length > 0 && inline? " */" : "";
var arguments = ""; var arguments = "";
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592 // Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") { if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
commentStart = ""; commentStart = "";
commentEnd = ""; commentEnd = "";
arguments = "(AttributeTargets.All)"; arguments = "(AttributeTargets.All)";
} }
var name = cad.AttributeType.GetScopedCSharpName(scope); var name = cad.AttributeType.GetScopedCSharpName(scope);
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal); var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
if (suffix != -1) if (suffix != -1)
name = name[..suffix]; name = name[..suffix];
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}"); sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
if (emitPointer) if (emitPointer)
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}"); sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
sb.Append(inline? " ":"\n"); sb.Append(inline? " ":"\n");
} }
return sb.ToString(); return sb.ToString();
} }
// Output a ulong as a 32 or 64-bit hexadecimal address // Output a ulong as a 32 or 64-bit hexadecimal address
public static string ToAddressString(this ulong address) => address <= 0xffff_ffff public static string ToAddressString(this ulong address) => address <= 0xffff_ffff
? string.Format($"0x{(uint)address:X8}") ? string.Format($"0x{(uint)address:X8}")
: string.Format($"0x{address:X16}"); : string.Format($"0x{address:X16}");
public static string ToAddressString(this long address) => ((ulong) address).ToAddressString(); 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 ?? 0) + "-" + ToAddressString(address?.end ?? 0);
public static string ToAddressString(this (ulong start, ulong end) address) => ToAddressString(address.start) + "-" + ToAddressString(address.end); public static string ToAddressString(this (ulong start, ulong end) address) => ToAddressString(address.start) + "-" + ToAddressString(address.end);
// C# string literal escape characters // C# string literal escape characters
// Taken from: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#regular-and-verbatim-string-literals // 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> { private static Dictionary<char, string> escapeChars = new Dictionary<char, string> {
['\''] = @"\'", ['\''] = @"\'",
['"'] = @"\""", ['"'] = @"\""",
['\\'] = @"\\", ['\\'] = @"\\",
['\0'] = @"\0", ['\0'] = @"\0",
['\a'] = @"\a", ['\a'] = @"\a",
['\b'] = @"\b", ['\b'] = @"\b",
['\f'] = @"\f", ['\f'] = @"\f",
['\n'] = @"\n", ['\n'] = @"\n",
['\r'] = @"\r", ['\r'] = @"\r",
['\t'] = @"\t", ['\t'] = @"\t",
['\v'] = @"\v" ['\v'] = @"\v"
}; };
// Output a string in Python-friendly syntax // Output a string in Python-friendly syntax
public static string ToEscapedString(this string str) { public static string ToEscapedString(this string str) {
// Replace standard escape characters // Replace standard escape characters
var s = new StringBuilder(); var s = new StringBuilder();
for (var i = 0; i < str.Length; i++)
// Standard escape characters foreach (var chr in str)
s.Append(escapeChars.ContainsKey(str[i]) ? escapeChars[str[i]] {
// Replace everything else with UTF-16 Unicode if (escapeChars.TryGetValue(chr, out var escaped))
: str[i] < 32 || str[i] > 126 ? @"\u" + $"{(int) str[i]:X4}" s.Append(escaped);
: str[i].ToString()); else if (chr < 32 || chr > 126)
return s.ToString(); {
} s.Append("\\u");
s.Append($"{(int) chr:X4}");
public static string ToCIdentifier(this string str, bool allowScopeQualifiers = false) { }
// replace * with Ptr else
str = str.Replace("*", "Ptr"); s.Append(chr);
// escape non-ASCII characters
var s = new StringBuilder(); }
for (var i = 0; i < str.Length; i++)
if (str[i] < 32 || str[i] > 126) return s.ToString();
s.Append($"u{(int) str[i]:X4}"); }
else
s.Append(str[i]); public static string ToCIdentifier(this string str, bool allowScopeQualifiers = false) {
str = s.ToString(); // replace * with Ptr
// replace illegal characters str = str.Replace("*", "Ptr");
str = Regex.Replace(str, allowScopeQualifiers? @"[^a-zA-Z0-9_\.:]" : "[^a-zA-Z0-9_]", "_"); // escape non-ASCII characters
// ensure identifier starts with a letter or _ (and is non-empty) var s = new StringBuilder();
if (!Regex.IsMatch(str, "^[a-zA-Z_]")) for (var i = 0; i < str.Length; i++)
str = "_" + str; if (str[i] < 32 || str[i] > 126)
return str; s.Append($"u{(int) str[i]:X4}");
} else
s.Append(str[i]);
// Output a value in C#-friendly syntax str = s.ToString();
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) { // replace illegal characters
if (value is bool) str = Regex.Replace(str, allowScopeQualifiers? @"[^a-zA-Z0-9_\.:]" : "[^a-zA-Z0-9_]", "_");
return (bool) value ? "true" : "false"; // ensure identifier starts with a letter or _ (and is non-empty)
if (value is float f) if (!Regex.IsMatch(str, "^[a-zA-Z_]"))
return value switch { str = "_" + str;
float.PositiveInfinity => "1F / 0F", return str;
float.NegativeInfinity => "-1F / 0F", }
float.NaN => "0F / 0F",
_ => f.ToString(CultureInfo.InvariantCulture) + "f" // Output a value in C#-friendly syntax
}; public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
if (value is double d) if (value is bool)
return value switch { return (bool) value ? "true" : "false";
double.PositiveInfinity => "1D / 0D", if (value is float f)
double.NegativeInfinity => "-1D / 0D", return value switch {
double.NaN => "0D / 0D", float.PositiveInfinity => "1F / 0F",
_ => d.ToString(CultureInfo.InvariantCulture) float.NegativeInfinity => "-1F / 0F",
}; float.NaN => "0F / 0F",
if (value is string str) { _ => f.ToString(CultureInfo.InvariantCulture) + "f"
return $"\"{str.ToEscapedString()}\""; };
} if (value is double d)
if (value is char) { return value switch {
var cValue = (int) (char) value; double.PositiveInfinity => "1D / 0D",
if (cValue < 32 || cValue > 126) double.NegativeInfinity => "-1D / 0D",
return $"'\\x{cValue:x4}'"; double.NaN => "0D / 0D",
return $"'{value}'"; _ => d.ToString(CultureInfo.InvariantCulture)
} };
if (type.IsEnum) { if (value is string str) {
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any(); return $"\"{str.ToEscapedString()}\"";
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); if (value is char) {
var cValue = (int) (char) value;
// We don't know what type the enumeration or value is, so we use Object.Equals() to do content-based equality testing if (cValue < 32 || cValue > 126)
if (!flags) { return $"'\\x{cValue:x4}'";
// Defined enum name return $"'{value}'";
if (values.FirstOrDefault(v => v.Value.Equals(value)).Key is string enumValue) }
return typeName + "." + enumValue; if (type.IsEnum) {
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
// Undefined enum value (return a cast) var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v);
return "(" + typeName + ") " + value; 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
// Logical OR a series of flags together if (!flags) {
// Defined enum name
// Values like 0x8000_0000_0000_0000 can't be cast to Int64 if (values.FirstOrDefault(v => v.Value.Equals(value)).Key is string enumValue)
// but values like 0xffff_ffff can't be cast to UInt64 (due to sign extension) return typeName + "." + enumValue;
// 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) { // Undefined enum value (return a cast)
var flagValue = Convert.ToUInt64(value); return "(" + typeName + ") " + value;
var setFlags = values.Where(x => (Convert.ToUInt64(x.Value) & flagValue) == Convert.ToUInt64(x.Value)).Select(x => typeName + "." + x.Key); }
return string.Join(" | ", setFlags);
} // Logical OR a series of flags together
else if (value is sbyte || value is short || value is int || value is long) {
var flagValue = Convert.ToInt64(value); // Values like 0x8000_0000_0000_0000 can't be cast to Int64
var setFlags = values.Where(x => (Convert.ToInt64(x.Value) & flagValue) == Convert.ToInt64(x.Value)).Select(x => typeName + "." + x.Key); // but values like 0xffff_ffff can't be cast to UInt64 (due to sign extension)
return string.Join(" | ", setFlags); // so we're just going to have to try to find a type that doesn't make it explode
} else { if (value is byte || value is ushort || value is uint || value is ulong) {
throw new ArgumentException("Unsupported enum underlying type"); 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);
// Structs and generic type parameters must use 'default' rather than 'null' }
return value?.ToString() ?? (type.IsValueType || type.IsGenericParameter? "default" : "null"); 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");
}
}
}