Refactor solution layout

This commit is contained in:
Katy Coe
2020-02-06 02:51:42 +01:00
parent 66b8e30586
commit e971cb8502
49 changed files with 72 additions and 50 deletions

View File

@@ -0,0 +1,83 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace Il2CppInspector.Reflection {
public class Assembly
{
// IL2CPP-specific data
public Il2CppModel Model { get; }
public Il2CppImageDefinition ImageDefinition { get; }
public Il2CppAssemblyDefinition AssemblyDefinition { get; }
public Il2CppCodeGenModule ModuleDefinition { get; }
public int Index { get; }
// Custom attributes for this assembly
public IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
// Fully qualified name of the assembly
public string FullName { get; }
// Display name of the assembly
public string ShortName { get; }
// Entry point method for the assembly
public MethodInfo EntryPoint => throw new NotImplementedException();
// List of types defined in the assembly
public List<TypeInfo> DefinedTypes { get; } = new List<TypeInfo>();
// Get a type from its string name (including namespace)
public TypeInfo GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName);
// Initialize from specified assembly index in package
public Assembly(Il2CppModel model, int imageIndex) {
Model = model;
ImageDefinition = Model.Package.Images[imageIndex];
AssemblyDefinition = Model.Package.Assemblies[ImageDefinition.assemblyIndex];
if (AssemblyDefinition.imageIndex != imageIndex)
throw new InvalidOperationException("Assembly/image index mismatch");
Index = ImageDefinition.assemblyIndex;
ShortName = Model.Package.Strings[ImageDefinition.nameIndex];
// Get full assembly name
var nameDef = AssemblyDefinition.aname;
var name = Model.Package.Strings[nameDef.nameIndex];
var culture = Model.Package.Strings[nameDef.cultureIndex];
if (string.IsNullOrEmpty(culture))
culture = "neutral";
var pkt = BitConverter.ToString(nameDef.publicKeyToken).Replace("-", "");
if (pkt == "0000000000000000")
pkt = "null";
var version = string.Format($"{nameDef.major}.{nameDef.minor}.{nameDef.build}.{nameDef.revision}");
FullName = string.Format($"{name}, Version={version}, Culture={culture}, PublicKeyToken={pkt.ToLower()}");
if (ImageDefinition.entryPointIndex != -1) {
// TODO: Generate EntryPoint method from entryPointIndex
}
// Find corresponding module (we'll need this for method pointers)
ModuleDefinition = Model.Package.Modules?[ShortName];
// Generate types in DefinedTypes from typeStart to typeStart+typeCount-1
for (var t = ImageDefinition.typeStart; t < ImageDefinition.typeStart + ImageDefinition.typeCount; t++) {
var type = new TypeInfo(t, this);
// Don't add empty module definitions
if (type.Name != "<Module>")
DefinedTypes.Add(type);
}
}
public override string ToString() => FullName;
}
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Il2CppInspector.Reflection
{
public static class Constants
{
// All C# reserved keywords
// From: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/
public static readonly string[] Keywords = {
"abstract", "as", "base", "bool",
"break", "byte", "case", "catch",
"char", "checked", "class", "const",
"continue", "decimal", "default", "delegate",
"do", "double", "else", "enum",
"event", "explicit", "extern", "false",
"finally", "fixed", "float", "for",
"foreach", "goto", "if", "implicit",
"in", "int", "interface", "internal",
"is", "lock", "long", "namespace",
"new", "null", "object", "operator",
"out", "override", "params", "private",
"protected", "public", "readonly", "ref",
"return", "sbyte", "sealed", "short",
"sizeof", "stackalloc", "static", "string",
"struct", "switch", "this", "throw",
"true", "try", "typeof", "uint",
"ulong", "unchecked", "unsafe", "ushort",
"using", /* "using static", */ "virtual", "void",
"volatile", "while"
};
}
}

View File

@@ -0,0 +1,31 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class ConstructorInfo : MethodBase
{
// IL names of constructor and static constructor
public static readonly string ConstructorName = ".ctor";
public static readonly string TypeConstructorName = ".cctor";
public override MemberTypes MemberType => MemberTypes.Constructor;
public ConstructorInfo(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(pkg, methodIndex, declaringType) { }
public ConstructorInfo(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(model, spec, declaringType) { }
public override string ToString() => DeclaringType.Name + GetFullTypeParametersString()
+ "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")";
public override string GetSignatureString() => Name + GetFullTypeParametersString()
+ "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")";
}
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppInspector.Reflection
{
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?view=netframework-4.8
public class CustomAttributeData
{
// IL2CPP-specific data
public Il2CppModel Model => AttributeType.Assembly.Model;
public int Index { get; set; }
// The type of the attribute
public TypeInfo AttributeType { get; set; }
public (ulong Start, ulong End)? VirtualAddress =>
// The last one will be wrong but there is no way to calculate it
(Model.Package.CustomAttributeGenerators[Index], Model.Package.FunctionAddresses[Model.Package.CustomAttributeGenerators[Index]]);
public override string ToString() => "[" + AttributeType.FullName + "]";
// Get all the custom attributes for a given assembly, type, member or parameter
private static IEnumerable<CustomAttributeData> getCustomAttributes(Assembly asm, int customAttributeIndex) {
if (customAttributeIndex < 0)
yield break;
var pkg = asm.Model.Package;
// Attribute type ranges weren't included before v21 (customASttributeGenerators was though)
if (pkg.Version < 21)
yield break;
var range = pkg.AttributeTypeRanges[customAttributeIndex];
for (var i = range.start; i < range.start + range.count; i++) {
var typeIndex = pkg.AttributeTypeIndices[i];
if (asm.Model.AttributesByIndices.TryGetValue(i, out var attribute)) {
yield return attribute;
continue;
}
attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] };
asm.Model.AttributesByIndices.TryAdd(i, attribute);
yield return attribute;
}
}
private static readonly object gcaLock = new object();
private static IList<CustomAttributeData> getCustomAttributes(Assembly asm, uint token, int customAttributeIndex) {
// Force the generation of the collection to be thread-safe
// Convert the result into a list for thread-safe enumeration
lock (gcaLock) {
return getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList();
}
}
public static IList<CustomAttributeData> GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.AssemblyDefinition.token, asm.AssemblyDefinition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.Definition.token, evt.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.Definition.token, field.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.Definition.token, method.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.Definition.token, param.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(PropertyInfo prop)
=> prop.Definition != null ? getCustomAttributes(prop.Assembly, prop.Definition.token, prop.Definition.customAttributeIndex) : new List<CustomAttributeData>();
public static IList<CustomAttributeData> GetCustomAttributes(TypeInfo type) => getCustomAttributes(type.Assembly, type.Definition.token, type.Definition.customAttributeIndex);
}
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class EventInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppEventDefinition Definition { get; }
public int Index { get; }
// Information/flags about the event
public EventAttributes Attributes { get; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
// Methods for the event
public MethodInfo AddMethod { get; }
public MethodInfo RemoveMethod { get; }
public MethodInfo RaiseMethod { get; }
// Event handler delegate type
private int eventTypeReference;
public TypeInfo EventHandlerType => Assembly.Model.TypesByReferenceIndex[eventTypeReference];
// True if the event has a special name
public bool IsSpecialName => (Attributes & EventAttributes.SpecialName) == EventAttributes.SpecialName;
public override MemberTypes MemberType => MemberTypes.Event;
public EventInfo(Il2CppInspector pkg, int eventIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Events[eventIndex];
Index = eventIndex;
Name = pkg.Strings[Definition.nameIndex];
eventTypeReference = Definition.typeIndex;
var eventType = pkg.TypeReferences[eventTypeReference];
if ((eventType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME) == Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME)
Attributes |= EventAttributes.SpecialName;
// NOTE: This relies on methods being added to TypeInfo.DeclaredMethods in the same order they are defined in the Il2Cpp metadata
// add, remove and raise are method indices from the first method of the declaring type
if (Definition.add >= 0)
AddMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.add);
if (Definition.remove >= 0)
RemoveMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.remove);
if (Definition.raise >= 0)
RaiseMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.raise);
}
}
}

View File

@@ -0,0 +1,125 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
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 (ulong start, ulong end)? address) => ToAddressString(address?.start ?? 0) + "-" + ToAddressString(address?.end ?? 0);
// 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();
}
// 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)
return value + "f";
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
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);
}
// Structs and generic type parameters must use 'default' rather than 'null'
return value?.ToString() ?? (type.IsValueType || type.IsGenericParameter? "default" : "null");
}
}
}

View File

@@ -0,0 +1,150 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection {
public class FieldInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppFieldDefinition Definition { get; }
public int Index { get; }
public long Offset { get; }
public ulong DefaultValueMetadataAddress { get; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0;
public object DefaultValue { get; }
public string DefaultValueString => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType) : "";
// Information/flags about the field
public FieldAttributes Attributes { get; }
// Type of field
private readonly int fieldTypeReference;
public TypeInfo FieldType => Assembly.Model.TypesByReferenceIndex[fieldTypeReference];
// For the Is* definitions below, see:
// https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo.isfamilyandassembly?view=netframework-4.7.1#System_Reflection_FieldInfo_IsFamilyAndAssembly
// True if the field is declared as internal
public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly;
// True if the field is declared as protected
public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family;
// True if the field is declared as 'protected private' (always false)
public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem;
// True if the field is declared as protected public
public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem;
// True if the field is declared as readonly
public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly;
// True if the field is const
public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal;
// True if the field has the NonSerialized attribute
public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized;
// True if the field is extern
public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl;
// True if the field is declared a private
public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private;
// True if the field is declared as public
public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public;
// True if the field has a special name
public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) == FieldAttributes.SpecialName;
// True if the field is declared as static
public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static;
public override MemberTypes MemberType => MemberTypes.Field;
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Fields[fieldIndex];
Index = fieldIndex;
Offset = pkg.FieldOffsets[fieldIndex];
Name = pkg.Strings[Definition.nameIndex];
fieldTypeReference = Definition.typeIndex;
var fieldType = pkg.TypeReferences[fieldTypeReference];
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_PRIVATE)
Attributes |= FieldAttributes.Private;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_PUBLIC)
Attributes |= FieldAttributes.Public;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_FAM_AND_ASSEM)
Attributes |= FieldAttributes.FamANDAssem;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_ASSEMBLY)
Attributes |= FieldAttributes.Assembly;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_FAMILY)
Attributes |= FieldAttributes.Family;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_FAM_OR_ASSEM)
Attributes |= FieldAttributes.FamORAssem;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_STATIC) == Il2CppConstants.FIELD_ATTRIBUTE_STATIC)
Attributes |= FieldAttributes.Static;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_INIT_ONLY) == Il2CppConstants.FIELD_ATTRIBUTE_INIT_ONLY)
Attributes |= FieldAttributes.InitOnly;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_LITERAL) == Il2CppConstants.FIELD_ATTRIBUTE_LITERAL)
Attributes |= FieldAttributes.Literal;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_NOT_SERIALIZED) == Il2CppConstants.FIELD_ATTRIBUTE_NOT_SERIALIZED)
Attributes |= FieldAttributes.NotSerialized;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME) == Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME)
Attributes |= FieldAttributes.SpecialName;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_PINVOKE_IMPL) == Il2CppConstants.FIELD_ATTRIBUTE_PINVOKE_IMPL)
Attributes |= FieldAttributes.PinvokeImpl;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_HAS_DEFAULT) != 0)
Attributes |= FieldAttributes.HasDefault;
// Default initialization value if present
if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) {
DefaultValue = value.variant;
DefaultValueMetadataAddress = value.address;
}
}
public string GetAccessModifierString() => this switch {
{ IsPrivate: true } => "private ",
{ IsPublic: true } => "public ",
{ IsFamily: true } => "protected ",
{ IsAssembly: true } => "internal ",
{ IsFamilyOrAssembly: true } => "protected internal ",
{ IsFamilyAndAssembly: true } => "private protected ",
_ => ""
};
public string GetModifierString() {
var modifiers = new StringBuilder(GetAccessModifierString());
if (FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
modifiers.Append("unsafe ");
if (IsLiteral)
modifiers.Append("const ");
// All const fields are also static by implication
else if (IsStatic)
modifiers.Append("static ");
if (IsInitOnly)
modifiers.Append("readonly ");
if (IsPinvokeImpl)
modifiers.Append("extern ");
if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
modifiers.Append("fixed ");
return modifiers.ToString();
}
}
}

View File

@@ -0,0 +1,260 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Il2CppInspector.Reflection
{
public class Il2CppModel
{
public Il2CppInspector Package { get; }
public List<Assembly> Assemblies { get; } = new List<Assembly>();
// List of all types from TypeDefs ordered by their TypeDefinitionIndex
public TypeInfo[] TypesByDefinitionIndex { get; }
// List of all types from TypeRefs ordered by instanceIndex
public TypeInfo[] TypesByReferenceIndex { get; }
// List of all types from MethodSpecs (closed generic types that can be instantiated)
public Dictionary<int, TypeInfo> TypesByMethodSpecClassIndex { get; } = new Dictionary<int, TypeInfo>();
// List of all methods from MethodSpecs (closed generic methods that can be called; does not need to be in a generic class)
public Dictionary<Il2CppMethodSpec, MethodBase> GenericMethods { get; } = new Dictionary<Il2CppMethodSpec, MethodBase>();
// List of all type definitions by fully qualified name (TypeDefs only)
public Dictionary<string, TypeInfo> TypesByFullName { get; } = new Dictionary<string, TypeInfo>();
// Every type
public IEnumerable<TypeInfo> Types => new IEnumerable<TypeInfo>[] {TypesByDefinitionIndex, TypesByReferenceIndex, TypesByMethodSpecClassIndex.Values}
.SelectMany(t => t).Distinct();
// List of all methods ordered by their MethodDefinitionIndex
public MethodBase[] MethodsByDefinitionIndex { get; }
// List of all Method.Invoke functions by invoker index
public MethodInvoker[] MethodInvokers { get; }
// List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices
public ConcurrentDictionary<int, CustomAttributeData> AttributesByIndices { get; } = new ConcurrentDictionary<int, CustomAttributeData>();
// Get an assembly by its image name
public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name);
// Get a type by its fully qualified name including generic type arguments, array brackets etc.
// In other words, rather than only being able to fetch a type definition such as in Assembly.GetType(),
// this method can also find reference types, types created from TypeRefs and constructed types from MethodSpecs
public TypeInfo GetType(string fullName) => Types.FirstOrDefault(t => fullName == t.Namespace + "." + t.Name);
// Get a concrete instantiation of a generic method from its fully qualified name and type arguments
public MethodBase GetGenericMethod(string fullName, params TypeInfo[] typeArguments) =>
GenericMethods.Values.First(m => fullName == m.DeclaringType.Namespace + "." + m.DeclaringType.BaseName + "." + m.Name
&& m.GetGenericArguments().SequenceEqual(typeArguments));
// Create type model
public Il2CppModel(Il2CppInspector package) {
Package = package;
TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length];
TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count];
MethodsByDefinitionIndex = new MethodBase[package.Methods.Length];
MethodInvokers = new MethodInvoker[package.MethodInvokePointers.Length];
// Recursively create hierarchy of assemblies and types from TypeDefs
// No code that executes here can access any type through a TypeRef (ie. via TypesByReferenceIndex)
for (var image = 0; image < package.Images.Length; image++)
Assemblies.Add(new Assembly(this, image));
// Create and reference types from TypeRefs
// Note that you can't resolve any TypeRefs until all the TypeDefs have been processed
for (int typeRefIndex = 0; typeRefIndex < package.TypeReferences.Count; typeRefIndex++) {
var typeRef = Package.TypeReferences[typeRefIndex];
var referencedType = resolveTypeReference(typeRef);
TypesByReferenceIndex[typeRefIndex] = referencedType;
}
// Create types and methods from MethodSpec (which incorporates TypeSpec in IL2CPP)
foreach (var spec in Package.MethodSpecs) {
TypeInfo declaringType;
// Concrete instance of a generic class
// If the class index is not specified, we will later create a generic method in a non-generic class
if (spec.classIndexIndex != -1) {
if (!TypesByMethodSpecClassIndex.ContainsKey(spec.classIndexIndex))
TypesByMethodSpecClassIndex.Add(spec.classIndexIndex, new TypeInfo(this, spec));
declaringType = TypesByMethodSpecClassIndex[spec.classIndexIndex];
}
else
declaringType = MethodsByDefinitionIndex[spec.methodDefinitionIndex].DeclaringType;
// Concrete instance of a generic method
if (spec.methodIndexIndex != -1) {
// Method or constructor
var concreteMethod = new MethodInfo(this, spec, declaringType);
if (concreteMethod.Name == ConstructorInfo.ConstructorName || concreteMethod.Name == ConstructorInfo.TypeConstructorName)
GenericMethods.Add(spec, new ConstructorInfo(this, spec, declaringType));
else
GenericMethods.Add(spec, concreteMethod);
}
}
// Create method invokers (one per signature, in invoker index order)
foreach (var method in MethodsByDefinitionIndex) {
var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition);
if (index != -1) {
if (MethodInvokers[index] == null)
MethodInvokers[index] = new MethodInvoker(method, index);
method.Invoker = MethodInvokers[index];
}
}
// TODO: Some invokers are not initialized or missing, need to find out why
// Create method invokers sourced from generic method invoker indices
foreach (var spec in GenericMethods.Keys) {
if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) {
if (MethodInvokers[index] == null)
MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index);
GenericMethods[spec].Invoker = MethodInvokers[index];
}
}
}
// Get generic arguments from either a type or method instanceIndex from a MethodSpec
public List<TypeInfo> ResolveGenericArguments(int instanceIndex) => ResolveGenericArguments(Package.GenericInstances[instanceIndex]);
public List<TypeInfo> ResolveGenericArguments(Il2CppGenericInst inst) {
// Get list of pointers to type parameters (both unresolved and concrete)
var genericTypeArguments = Package.BinaryImage.ReadMappedWordArray(inst.type_argv, (int)inst.type_argc);
return genericTypeArguments.Select(a => GetTypeFromVirtualAddress((ulong) a)).ToList();
}
private TypeInfo resolveTypeReference(Il2CppType typeRef) {
TypeInfo underlyingType;
switch (typeRef.type) {
// Classes defined in the metadata (reference to a TypeDef)
case Il2CppTypeEnum.IL2CPP_TYPE_CLASS:
case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE:
underlyingType = TypesByDefinitionIndex[typeRef.datapoint]; // klassIndex
break;
// Constructed types
case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST:
case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY:
case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY:
case Il2CppTypeEnum.IL2CPP_TYPE_PTR:
// Generic type and generic method parameters
case Il2CppTypeEnum.IL2CPP_TYPE_VAR:
case Il2CppTypeEnum.IL2CPP_TYPE_MVAR:
underlyingType = new TypeInfo(this, typeRef);
break;
// Primitive types
default:
underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type);
break;
}
// Create a reference type if necessary
return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType;
}
// Basic primitive types are specified via a flag value
private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) {
if ((int) t >= Il2CppConstants.FullNameTypeString.Count)
return null;
var fqn = Il2CppConstants.FullNameTypeString[(int) t];
return TypesByFullName[fqn];
}
// Get a TypeRef by its virtual address
// These are always nested types from references within another TypeRef
public TypeInfo GetTypeFromVirtualAddress(ulong ptr) {
var typeRefIndex = Package.TypeReferenceIndicesByAddress[ptr];
if (TypesByReferenceIndex[typeRefIndex] != null)
return TypesByReferenceIndex[typeRefIndex];
var type = Package.TypeReferences[typeRefIndex];
var referencedType = resolveTypeReference(type);
TypesByReferenceIndex[typeRefIndex] = referencedType;
return referencedType;
}
// The attribute index is an index into AttributeTypeRanges, each of which is a start-end range index into AttributeTypeIndices, each of which is a TypeIndex
public int GetCustomAttributeIndex(Assembly asm, uint token, int customAttributeIndex) {
// Prior to v24.1, Type, Field, Parameter, Method, Event, Property, Assembly definitions had their own customAttributeIndex field
if (Package.Version <= 24.0)
return customAttributeIndex;
// From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image
if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue(token, out var index))
return -1;
return index;
}
// Get the name of a metadata typeRef
public string GetMetadataUsageName(MetadataUsage usage) {
switch (usage.Type) {
case MetadataUsageType.TypeInfo:
case MetadataUsageType.Type:
return GetMetadataUsageType(usage).Name;
case MetadataUsageType.MethodDef:
var method = GetMetadataUsageMethod(usage);
return $"{method.DeclaringType.Name}.{method.Name}";
case MetadataUsageType.FieldInfo:
var fieldRef = Package.FieldRefs[usage.SourceIndex];
var type = GetMetadataUsageType(usage);
var field = type.DeclaredFields.First(f => f.Index == type.Definition.fieldStart + fieldRef.fieldIndex);
return $"{type.Name}.{field.Name}";
case MetadataUsageType.StringLiteral:
return Package.StringLiterals[usage.SourceIndex];
case MetadataUsageType.MethodRef:
type = GetMetadataUsageType(usage);
method = GetMetadataUsageMethod(usage);
return $"{type.Name}.{method.Name}";
}
throw new NotImplementedException("Unknown metadata usage type: " + usage.Type);
}
// Get the type used in a metadata usage
public TypeInfo GetMetadataUsageType(MetadataUsage usage) => usage.Type switch {
MetadataUsageType.Type => TypesByReferenceIndex[usage.SourceIndex],
MetadataUsageType.TypeInfo => TypesByReferenceIndex[usage.SourceIndex],
MetadataUsageType.MethodDef => GetMetadataUsageMethod(usage).DeclaringType,
MetadataUsageType.FieldInfo => TypesByReferenceIndex[Package.FieldRefs[usage.SourceIndex].typeIndex],
MetadataUsageType.MethodRef => Package.MethodSpecs[usage.SourceIndex].classIndexIndex != -1?
TypesByMethodSpecClassIndex[Package.MethodSpecs[usage.SourceIndex].classIndexIndex] :
GetMetadataUsageMethod(usage).DeclaringType,
_ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type")
};
// Get the method used in a metadata usage
public MethodBase GetMetadataUsageMethod(MetadataUsage usage) => usage.Type switch {
MetadataUsageType.MethodDef => MethodsByDefinitionIndex[usage.SourceIndex],
MetadataUsageType.MethodRef => Package.MethodSpecs[usage.SourceIndex].methodIndexIndex != -1?
GenericMethods[Package.MethodSpecs[usage.SourceIndex]] :
MethodsByDefinitionIndex[Package.MethodSpecs[usage.SourceIndex].methodDefinitionIndex],
_ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type")
};
}
}

View File

@@ -0,0 +1,48 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection {
public abstract class MemberInfo
{
// Assembly that this member is defined in. Only set when MemberType == TypeInfo
public Assembly Assembly { get; protected set; }
// Custom attributes for this member
public abstract IEnumerable<CustomAttributeData> CustomAttributes { get; }
public CustomAttributeData[] GetCustomAttributes(string fullTypeName) => CustomAttributes.Where(a => a.AttributeType.FullName == fullTypeName).ToArray();
// Type that this type is declared in for nested types
protected int declaringTypeDefinitionIndex { private get; set; } = -1;
public TypeInfo DeclaringType => declaringTypeDefinitionIndex != -1? Assembly.Model.TypesByDefinitionIndex[declaringTypeDefinitionIndex] : null;
// What sort of member this is, eg. method, field etc.
public abstract MemberTypes MemberType { get; }
// Name of the member
public virtual string Name { get; protected set; }
// Name of the member with @ prepended if the name is a C# reserved keyword
public string CSharpSafeName => Constants.Keywords.Contains(Name) ? "@" + Name : Name;
// For top-level members in an assembly (ie. non-nested types)
protected MemberInfo(Assembly asm) => Assembly = asm;
// For lower level members, eg. fields, properties etc. and nested types
protected MemberInfo(TypeInfo declaringType = null) {
if (declaringType != null) {
Assembly = declaringType.Assembly;
declaringTypeDefinitionIndex = declaringType.Index;
}
}
public override string ToString() => Name;
}
}

View File

@@ -0,0 +1,280 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection
{
public abstract class MethodBase : MemberInfo
{
// IL2CPP-specific data
public Il2CppMethodDefinition Definition { get; }
public int Index { get; }
public (ulong Start, ulong End)? VirtualAddress { get; }
// Method.Invoke implementation
public MethodInvoker Invoker { get; set; }
// Information/flags about the method
public MethodAttributes Attributes { get; protected set; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public List<ParameterInfo> DeclaredParameters { get; } = new List<ParameterInfo>();
public bool IsAbstract => (Attributes & MethodAttributes.Abstract) == MethodAttributes.Abstract;
public bool IsAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Assembly;
public bool IsConstructor => MemberType == MemberTypes.Constructor;
public bool IsFamily => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family;
public bool IsFamilyAndAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamANDAssem;
public bool IsFamilyOrAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem;
public bool IsFinal => (Attributes & MethodAttributes.Final) == MethodAttributes.Final;
public bool IsHideBySig => (Attributes & MethodAttributes.HideBySig) == MethodAttributes.HideBySig;
public bool IsPrivate => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private;
public bool IsPublic => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
public bool IsSpecialName => (Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName;
public bool IsStatic => (Attributes & MethodAttributes.Static) == MethodAttributes.Static;
public bool IsVirtual => (Attributes & MethodAttributes.Virtual) == MethodAttributes.Virtual;
public virtual bool RequiresUnsafeContext => DeclaredParameters.Any(p => p.ParameterType.RequiresUnsafeContext);
// True if the method contains unresolved generic type parameters, or if it is a non-generic method in an open ganeric type
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.containsgenericparameters?view=netframework-4.8
public bool ContainsGenericParameters => DeclaringType.ContainsGenericParameters || genericArguments.Any(ga => ga.ContainsGenericParameters);
// For a generic method definition: the list of generic type parameters
// For an open generic method: a mix of generic type parameters and generic type arguments
// For a closed generic method: the list of generic type arguments
private readonly List<TypeInfo> genericArguments = new List<TypeInfo>();
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.getgenericarguments?view=netframework-4.8
public List<TypeInfo> GetGenericArguments() => genericArguments;
// This was added in .NET Core 2.1 and isn't properly documented yet
public bool IsConstructedGenericMethod => IsGenericMethod && genericArguments.All(ga => !ga.ContainsGenericParameters);
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.isgenericmethod?view=netframework-4.8
public bool IsGenericMethod { get; }
public bool IsGenericMethodDefinition => genericArguments.Any() && genericArguments.All(a => a.IsGenericMethodParameter);
// TODO: GetMethodBody()
public string CSharpName =>
// Operator overload or user-defined conversion operator
OperatorMethodNames.ContainsKey(Name)? "operator " + OperatorMethodNames[Name]
// Explicit interface implementation
: (IsVirtual && IsFinal && (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot && Name.IndexOf('.') != -1)?
((Func<string>)(() => {
// This is some shenanigans because IL2CPP does not use a consistent naming scheme for explicit interface implementation method names
var implementingInterface = DeclaringType.ImplementedInterfaces.FirstOrDefault(i => Name.StartsWith(i.Namespace + "." + i.CSharpName + "."))
?? DeclaringType.ImplementedInterfaces.FirstOrDefault(i => Name.StartsWith(i.Namespace + "." + i.CSharpTypeDeclarationName.Replace(" ", "") + "."));
// TODO: There are some combinations we haven't dealt with so use this test as a safety valve
if (implementingInterface == null)
return Name;
return implementingInterface.CSharpName + Name.Substring(Name.LastIndexOf('.'));
}))()
// Regular method
: Name;
// Initialize a method from a method definition (MethodDef)
protected MethodBase(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(declaringType) {
Definition = pkg.Methods[methodIndex];
Index = methodIndex;
Name = pkg.Strings[Definition.nameIndex];
// Find method pointer
VirtualAddress = pkg.GetMethodPointer(Assembly.ModuleDefinition, Definition);
// Add to global method definition list
Assembly.Model.MethodsByDefinitionIndex[Index] = this;
// Generic method definition?
if (Definition.genericContainerIndex >= 0) {
IsGenericMethod = true;
// Store the generic type parameters for later instantiation
var container = pkg.GenericContainers[Definition.genericContainerIndex];
genericArguments = pkg.GenericParameters.Skip((int)container.genericParameterStart).Take(container.type_argc).Select(p => new TypeInfo(this, p)).ToList();
}
// Set method attributes
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_PRIVATE)
Attributes |= MethodAttributes.Private;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_PUBLIC)
Attributes |= MethodAttributes.Public;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_FAM_AND_ASSEM)
Attributes |= MethodAttributes.FamANDAssem;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_ASSEM)
Attributes |= MethodAttributes.Assembly;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_FAMILY)
Attributes |= MethodAttributes.Family;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_FAM_OR_ASSEM)
Attributes |= MethodAttributes.FamORAssem;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_VIRTUAL) != 0)
Attributes |= MethodAttributes.Virtual;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_ABSTRACT) != 0)
Attributes |= MethodAttributes.Abstract;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_STATIC) != 0)
Attributes |= MethodAttributes.Static;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_FINAL) != 0)
Attributes |= MethodAttributes.Final;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_HIDE_BY_SIG) != 0)
Attributes |= MethodAttributes.HideBySig;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_NEW_SLOT)
Attributes |= MethodAttributes.NewSlot;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_PINVOKE_IMPL) != 0)
Attributes |= MethodAttributes.PinvokeImpl;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_SPECIAL_NAME) != 0)
Attributes |= MethodAttributes.SpecialName;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_UNMANAGED_EXPORT) != 0)
Attributes |= MethodAttributes.UnmanagedExport;
// Add arguments
for (var p = Definition.parameterStart; p < Definition.parameterStart + Definition.parameterCount; p++)
DeclaredParameters.Add(new ParameterInfo(pkg, p, this));
}
// Initialize a method from a concrete generic method (MethodSpec)
protected MethodBase(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(declaringType) {
var methodDef = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex];
Name = methodDef.Name;
Attributes = methodDef.Attributes;
IsGenericMethod = true;
genericArguments = model.ResolveGenericArguments(spec.methodIndexIndex);
// Substitute matching generic type parameters with concrete type arguments
foreach (var p in methodDef.DeclaredParameters) {
if (!p.ParameterType.IsGenericMethodParameter)
DeclaredParameters.Add(p);
else
DeclaredParameters.Add(new ParameterInfo(model, p, genericArguments[p.ParameterType.GenericParameterPosition]));
}
VirtualAddress = model.Package.GetGenericMethodPointer(spec);
}
public string GetAccessModifierString() => this switch {
// Static constructors can not have an access level modifier
{ IsConstructor: true, IsStatic: true } => "",
// Finalizers can not have an access level modifier
{ Name: "Finalize", IsVirtual: true, IsFamily: true } => "",
// Explicit interface implementations do not have an access level modifier
{ IsVirtual: true, IsFinal: true, Attributes: var a } when (a & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot && Name.IndexOf('.') != -1 => "",
{ IsPrivate: true } => "private ",
{ IsPublic: true } => "public ",
{ IsFamily: true } => "protected ",
{ IsAssembly: true } => "internal ",
{ IsFamilyOrAssembly: true } => "protected internal ",
{ IsFamilyAndAssembly: true } => "private protected ",
_ => ""
};
public string GetModifierString() {
// Interface methods and properties have no visible modifiers (they are always declared 'public abstract')
if (DeclaringType.IsInterface)
return string.Empty;
var modifiers = new StringBuilder(GetAccessModifierString());
if (RequiresUnsafeContext)
modifiers.Append("unsafe ");
if (IsAbstract)
modifiers.Append("abstract ");
// Methods that implement interfaces are IsVirtual && IsFinal with MethodAttributes.NewSlot (don't show 'virtual sealed' for these)
if (IsFinal && (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot)
modifiers.Append("sealed override ");
// All abstract, override and sealed methods are also virtual by nature
if (IsVirtual && !IsAbstract && !IsFinal && Name != "Finalize")
modifiers.Append((Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot ? "virtual " : "override ");
if (IsStatic)
modifiers.Append("static ");
if ((Attributes & MethodAttributes.PinvokeImpl) != 0)
modifiers.Append("extern ");
// Method hiding
if ((DeclaringType.BaseType?.GetAllMethods().Any(m => m.GetSignatureString() == GetSignatureString() && m.IsHideBySig) ?? false)
&& (((Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot && !IsVirtual)
|| (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot))
modifiers.Append("new ");
if (Name == "op_Implicit")
modifiers.Append("implicit ");
if (Name == "op_Explicit")
modifiers.Append("explicit ");
// Will include a trailing space
return modifiers.ToString();
}
// Get C# syntax-friendly list of parameters
public string GetParametersString(Scope usingScope, bool emitPointer = false, bool commentAttributes = false)
=> string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(usingScope, emitPointer, commentAttributes)));
public string GetTypeParametersString(Scope usingScope) => !GetGenericArguments().Any()? "" :
"<" + string.Join(", ", GetGenericArguments().Select(p => p.GetScopedCSharpName(usingScope))) + ">";
public string GetFullTypeParametersString() => !GetGenericArguments().Any()? "" :
"[" + string.Join(",", GetGenericArguments().Select(p => p.Name)) + "]";
public abstract string GetSignatureString();
// List of operator overload metadata names
// https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/operator-overloads
public static Dictionary<string, string> OperatorMethodNames = new Dictionary<string, string> {
["op_Implicit"] = "",
["op_Explicit"] = "",
["op_Addition"] = "+",
["op_Subtraction"] = "-",
["op_Multiply"] = "*",
["op_Division"] = "/",
["op_Modulus"] = "%",
["op_ExclusiveOr"] = "^",
["op_BitwiseAnd"] = "&",
["op_BitwiseOr"] = "|",
["op_LogicalAnd"] = "&&",
["op_LogicalOr"] = "||",
["op_Assign"] = "=",
["op_LeftShift"] = "<<",
["op_RightShift"] = ">>",
["op_SignedLeftShift"] = "", // Listed as N/A in the documentation
["op_SignedRightShift"] = "", // Listed as N/A in the documentation
["op_Equality"] = "==",
["op_Inequality"] = "!=",
["op_GreaterThan"] = ">",
["op_LessThan"] = "<",
["op_GreaterThanOrEqual"] = ">=",
["op_LessThanOrEqual"] = "<=",
["op_MultiplicationAssignment"] = "*=",
["op_SubtractionAssignment"] = "-=",
["op_ExclusiveOrAssignment"] = "^=",
["op_LeftShiftAssignment"] = "<<=", // Doesn't seem to be any right shift assignment`in documentation
["op_ModulusAssignment"] = "%=",
["op_AdditionAssignment"] = "+=",
["op_BitwiseAndAssignment"] = "&=",
["op_BitwiseOrAssignment"] = "|=",
["op_Comma"] = ",",
["op_DivisionAssignment"] = "*/=",
["op_Decrement"] = "--",
["op_Increment"] = "++",
["op_UnaryNegation"] = "-",
["op_UnaryPlus"] = "+",
["op_OnesComplement"] = "~"
};
}
}

View File

@@ -0,0 +1,47 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class MethodInfo : MethodBase
{
public override MemberTypes MemberType => MemberTypes.Method;
// Info about the return parameter
public ParameterInfo ReturnParameter { get; }
// Return type of the method
private readonly int returnTypeReference;
public TypeInfo ReturnType => Assembly.Model.TypesByReferenceIndex[returnTypeReference];
public override bool RequiresUnsafeContext => base.RequiresUnsafeContext || ReturnType.RequiresUnsafeContext;
// IL2CPP doesn't seem to retain return type custom attributes
public MethodInfo(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(pkg, methodIndex, declaringType) {
// Add return parameter
returnTypeReference = Definition.returnType;
ReturnParameter = new ParameterInfo(pkg, -1, this);
}
public MethodInfo(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(model, spec, declaringType) {
var methodDef = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex];
// Add return parameter
returnTypeReference = methodDef.Definition.returnType;
ReturnParameter = ((MethodInfo) methodDef).ReturnParameter;
}
public override string ToString() => ReturnType.Name + " " + Name + GetFullTypeParametersString() + "(" + string.Join(", ",
DeclaredParameters.Select(x => x.ParameterType.IsByRef? x.ParameterType.Name.TrimEnd('&') + " ByRef" : x.ParameterType.Name)) + ")";
public override string GetSignatureString() => ReturnParameter.GetSignatureString() + " " + Name + GetFullTypeParametersString()
+ "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")";
}
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppInspector.Reflection
{
// Class representing a MethodBase.Invoke() method for a specific signature
// Every IL2CPP invoker has the signature:
// void* RuntimeInvoker_{RequiresObject (True/False)}{ReturnType}_{ParameterTypes}
// (Il2CppMethodPointer pointer, const RuntimeMethod* methodMetadata, void* obj, void** args)
public class MethodInvoker
{
// IL2CPP invoker index
public int Index { get; }
// Virtual address of the invoker function
public (ulong Start, ulong End) VirtualAddress { get; }
// If false, the first argument to the called function pointers must be the object instance
public bool IsStatic { get; }
// Return type
public TypeInfo ReturnType { get; }
// Argument types
public TypeInfo[] ParameterTypes { get; }
// Find the correct method invoker for a method with a specific signature
public MethodInvoker(MethodBase exampleMethod, int index) {
var model = exampleMethod.Assembly.Model;
var package = exampleMethod.Assembly.Model.Package;
Index = index;
IsStatic = exampleMethod.IsStatic;
ReturnType = exampleMethod.IsConstructor ? model.TypesByFullName["System.Void"] : mapParameterType(model, ((MethodInfo) exampleMethod).ReturnType);
ParameterTypes = exampleMethod.DeclaredParameters.Select(p => mapParameterType(model, p.ParameterType)).ToArray();
var start = package.MethodInvokePointers[Index];
VirtualAddress = (start & 0xffff_ffff_ffff_fffe, package.FunctionAddresses[start]);
}
// The invokers use Object for all reference types, and SByte for booleans
private TypeInfo mapParameterType(Il2CppModel model, TypeInfo type) => type switch {
{ IsValueType: false } => model.TypesByFullName["System.Object"],
{ FullName: "System.Boolean" } => model.TypesByFullName["System.SByte"],
_ => type
};
public string Name => $"RunTimeInvoker_{!IsStatic}{ReturnType.BaseName}_" + string.Join("_", ParameterTypes.Select(p => p.BaseName));
// Display as a C++ method signature
public string Signature => Name + "(Il2CppMethodPointer pointer, const RuntimeMethod* methodMetadata, void* obj, void** args)";
public override string ToString() => Signature;
}
}

View File

@@ -0,0 +1,133 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class ParameterInfo
{
// IL2CPP-specific data
public Il2CppParameterDefinition Definition { get; }
public int Index { get; }
public ulong DefaultValueMetadataAddress { get; }
// Information/flags about the parameter
public ParameterAttributes Attributes { get; }
// Custom attributes for this parameter
public IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
// True if the parameter has a default value
public bool HasDefaultValue => (Attributes & ParameterAttributes.HasDefault) != 0;
// Default value for the parameter
public object DefaultValue { get; }
public bool IsIn => (Attributes & ParameterAttributes.In) != 0;
public bool IsOptional => (Attributes & ParameterAttributes.Optional) != 0;
public bool IsOut => (Attributes & ParameterAttributes.Out) != 0;
public bool IsRetval => (Attributes & ParameterAttributes.Retval) != 0;
// The method in which the parameter is defined
public MethodBase DeclaringMethod { get; }
// Name of parameter
public string Name { get; }
public string CSharpSafeName => Constants.Keywords.Contains(Name) ? "@" + Name : Name;
// Type of this parameter
private readonly int paramTypeReference;
public TypeInfo ParameterType => DeclaringMethod.Assembly.Model.TypesByReferenceIndex[paramTypeReference];
// Zero-indexed position of the parameter in parameter list
public int Position { get; }
// Create a parameter. Specify paramIndex == -1 for a return type parameter
public ParameterInfo(Il2CppInspector pkg, int paramIndex, MethodBase declaringMethod) {
Index = paramIndex;
DeclaringMethod = declaringMethod;
if (paramIndex == -1) {
Position = -1;
paramTypeReference = declaringMethod.Definition.returnType;
Attributes |= ParameterAttributes.Retval;
return;
}
Definition = pkg.Params[Index];
Name = pkg.Strings[Definition.nameIndex];
// Handle unnamed/obfuscated parameter names
if (string.IsNullOrEmpty(Name))
Name = string.Format($"param_{Index:x8}");
Position = paramIndex - declaringMethod.Definition.parameterStart;
paramTypeReference = Definition.typeIndex;
var paramType = pkg.TypeReferences[paramTypeReference];
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_HAS_DEFAULT) != 0)
Attributes |= ParameterAttributes.HasDefault;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_OPTIONAL) != 0)
Attributes |= ParameterAttributes.Optional;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_IN) != 0)
Attributes |= ParameterAttributes.In;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_OUT) != 0)
Attributes |= ParameterAttributes.Out;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_RESERVED_MASK) != 0)
Attributes |= ParameterAttributes.ReservedMask;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_HAS_FIELD_MARSHAL) != 0)
Attributes |= ParameterAttributes.HasFieldMarshal;
if (Position == -1)
Attributes |= ParameterAttributes.Retval;
// Default initialization value if present
if (pkg.ParameterDefaultValue.TryGetValue(paramIndex, out (ulong address, object variant) value)) {
DefaultValue = value.variant;
DefaultValueMetadataAddress = value.address;
}
}
// Create a concrete type parameter from a generic type parameter
public ParameterInfo(Il2CppModel model, ParameterInfo generic, TypeInfo concrete) {
DeclaringMethod = generic.DeclaringMethod;
Name = generic.Name;
Position = generic.Position;
Attributes = generic.Attributes;
// Search for the concrete type's TypeRef index to store as the parameter type reference index
paramTypeReference = Array.IndexOf(model.TypesByReferenceIndex, concrete);
DefaultValue = generic.DefaultValue;
DefaultValueMetadataAddress = generic.DefaultValueMetadataAddress;
}
// ref will be handled as part of the type name
public string GetModifierString() =>
(IsIn ? "in " : "")
+ (IsOut ? "out " : "")
+ (!IsIn && !IsOut && ParameterType.IsByRef ? "ref " : "");
private string getCSharpSignatureString(Scope scope) => $"{GetModifierString()}{ParameterType.GetScopedCSharpName(scope, omitRef: true)}";
public string GetSignatureString() => $"{GetModifierString()}{ParameterType.FullName}";
public string GetParameterString(Scope usingScope, bool emitPointer = false, bool compileAttributes = false) => IsRetval? null :
$"{CustomAttributes.ToString(usingScope, inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}"
+ (Position == 0 && DeclaringMethod.GetCustomAttributes("System.Runtime.CompilerServices.ExtensionAttribute").Any()? "this ":"")
+ $"{getCSharpSignatureString(usingScope)} {CSharpSafeName}"
+ (IsOptional? " = " + DefaultValue.ToCSharpValue(ParameterType, usingScope)
+ (emitPointer && !(DefaultValue is null)? $" /* Metadata: 0x{(uint) DefaultValueMetadataAddress:X8} */" : "") : "");
public string GetReturnParameterString(Scope scope) => !IsRetval? null : getCSharpSignatureString(scope);
public override string ToString() => ParameterType.Name + " " + Name;
}
}

View File

@@ -0,0 +1,72 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection {
public class PropertyInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppPropertyDefinition Definition { get; }
public int Index { get; }
public bool CanRead => GetMethod != null;
public bool CanWrite => SetMethod != null;
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public MethodInfo GetMethod { get; }
public MethodInfo SetMethod { get; }
public bool IsAutoProperty => DeclaringType.DeclaredFields.Any(f => f.Name == $"<{Name}>k__BackingField");
public override string Name { get; protected set; }
public string CSharpName {
get {
// Explicit interface implementation
if (DeclaringType.ImplementedInterfaces
.FirstOrDefault(i => CSharpSafeName.IndexOf("." + i.CSharpName, StringComparison.Ordinal) != -1) is TypeInfo @interface)
return CSharpSafeName.Substring(CSharpSafeName.IndexOf("." + @interface.CSharpName, StringComparison.Ordinal) + 1);
// Regular method
return Name;
}
}
public TypeInfo PropertyType => GetMethod?.ReturnType ?? SetMethod.DeclaredParameters[^1].ParameterType;
public override MemberTypes MemberType => MemberTypes.Property;
public PropertyInfo(Il2CppInspector pkg, int propIndex, TypeInfo declaringType) :
base(declaringType) {
Index = propIndex;
Definition = pkg.Properties[propIndex];
Name = pkg.Strings[Definition.nameIndex];
// prop.get and prop.set are method indices from the first method of the declaring type
if (Definition.get >= 0)
GetMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.get);
if (Definition.set >= 0)
SetMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.set);
}
// Create a property based on a get and set method
public PropertyInfo(MethodInfo getter, MethodInfo setter, TypeInfo declaringType) :
base(declaringType) {
Index = -1;
Definition = null;
Name = (getter ?? setter).Name.Replace(".get_", ".").Replace(".set_", ".");
GetMethod = getter;
SetMethod = setter;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
namespace Il2CppInspector.Reflection
{
// A code scope with which to evaluate how to output type references
public class Scope
{
// The scope we are currently in
public TypeInfo Current;
// The list of namespace using directives in the file
public IEnumerable<string> Namespaces;
}
}

View File

@@ -0,0 +1,960 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection {
public class TypeInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppTypeDefinition Definition { get; }
public int Index { get; } = -1;
// Information/flags about the type
// Undefined if the Type represents a generic type parameter
public TypeAttributes Attributes { get; }
// Type that this type inherits from
private readonly int baseTypeReference = -1;
public TypeInfo BaseType => IsPointer? null :
baseTypeReference != -1?
Assembly.Model.TypesByReferenceIndex[baseTypeReference]
: IsArray? Assembly.Model.TypesByFullName["System.Array"]
: Namespace != "System" || BaseName != "Object" ? Assembly.Model.TypesByFullName["System.Object"]
: null;
// True if the type contains unresolved generic type parameters
public bool ContainsGenericParameters => IsGenericParameter || genericArguments.Any(ga => ga.ContainsGenericParameters);
public string BaseName => base.Name;
// Get rid of generic backticks
public string UnmangledBaseName => base.Name.IndexOf("`", StringComparison.Ordinal) == -1 ? base.Name : base.Name.Remove(base.Name.IndexOf("`", StringComparison.Ordinal));
// C# colloquial name of the type (if available)
public string CSharpName {
get {
var s = Namespace + "." + base.Name;
var i = Il2CppConstants.FullNameTypeString.IndexOf(s);
var n = (i != -1 ? Il2CppConstants.CSharpTypeString[i] : base.Name);
if (n?.IndexOf("`", StringComparison.Ordinal) != -1)
n = n?.Remove(n.IndexOf("`", StringComparison.Ordinal));
n += (GetGenericArguments().Any()? "<" + string.Join(", ", GetGenericArguments().Select(x => x.CSharpName)) + ">" : "");
if (s == "System.Nullable`1" && GetGenericArguments().Any())
n = GetGenericArguments()[0].CSharpName + "?";
if (HasElementType)
n = ElementType.CSharpName;
if ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant)
n = "out " + n;
if ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant)
n = "in " + n;
if (IsByRef)
n = "ref " + n;
return n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : "");
}
}
// C# name as it would be written in a type declaration
public string CSharpTypeDeclarationName {
get {
var ga = IsNested ? GetGenericArguments().Where(p => DeclaringType.GetGenericArguments().All(dp => dp.Name != p.Name)) : GetGenericArguments();
return (IsByRef ? "ref " : "")
+ (HasElementType
? ElementType.CSharpTypeDeclarationName
: ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant ? "in " : "")
+ ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant ? "out " : "")
+ (base.Name.IndexOf("`", StringComparison.Ordinal) == -1 ? base.Name : base.Name.Remove(base.Name.IndexOf("`", StringComparison.Ordinal)))
+ (ga.Any()? "<" + string.Join(", ", ga.Select(x => (!x.IsGenericTypeParameter ? x.Namespace + "." : "") + x.CSharpTypeDeclarationName)) + ">" : ""))
+ (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsPointer ? "*" : "");
}
}
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public List<ConstructorInfo> DeclaredConstructors { get; } = new List<ConstructorInfo>();
public List<EventInfo> DeclaredEvents { get; } = new List<EventInfo>();
public List<FieldInfo> DeclaredFields { get; } = new List<FieldInfo>();
public List<MemberInfo> DeclaredMembers => new IEnumerable<MemberInfo>[] {
DeclaredConstructors, DeclaredEvents, DeclaredFields, DeclaredMethods,
DeclaredNestedTypes?.ToList() ?? new List<TypeInfo>(), DeclaredProperties
}.SelectMany(m => m).ToList();
public List<MethodInfo> DeclaredMethods { get; } = new List<MethodInfo>();
private readonly int[] declaredNestedTypes;
public IEnumerable<TypeInfo> DeclaredNestedTypes => declaredNestedTypes.Select(x => Assembly.Model.TypesByDefinitionIndex[x]);
public List<PropertyInfo> DeclaredProperties { get; } = new List<PropertyInfo>();
// Get a field by its name
public FieldInfo GetField(string name) => DeclaredFields.FirstOrDefault(f => f.Name == name);
private readonly int genericConstraintIndex;
private readonly int genericConstraintCount;
// Get type constraints on a generic parameter
public TypeInfo[] GetGenericParameterConstraints() {
var types = new TypeInfo[genericConstraintCount];
for (int c = 0; c < genericConstraintCount; c++)
types[c] = Assembly.Model.TypesByReferenceIndex[Assembly.Model.Package.GenericConstraintIndices[genericConstraintIndex + c]];
return types;
}
// Get a method by its name
public MethodInfo GetMethod(string name) => DeclaredMethods.FirstOrDefault(m => m.Name == name);
// Get all methods with same name (overloads)
public MethodInfo[] GetMethods(string name) => DeclaredMethods.Where(m => m.Name == name).ToArray();
// Get methods including inherited methods
public MethodInfo[] GetAllMethods() {
var methods = new List<IEnumerable<MethodInfo>>();
// Specifically return a list in order of most derived to least derived
for (var type = this; type != null; type = type.BaseType)
methods.Add(type.DeclaredMethods);
return methods.SelectMany(m => m).ToArray();
}
// Get a property by its name
public PropertyInfo GetProperty(string name) => DeclaredProperties.FirstOrDefault(p => p.Name == name);
// Method that the type is declared in if this is a type parameter of a generic method
// TODO: Make a unit test from this: https://docs.microsoft.com/en-us/dotnet/api/system.type.declaringmethod?view=netframework-4.8
public MethodBase DeclaringMethod;
// IsGenericTypeParameter and IsGenericMethodParameter from https://github.com/dotnet/corefx/issues/23883
public bool IsGenericTypeParameter => IsGenericParameter && DeclaringMethod == null;
public bool IsGenericMethodParameter => IsGenericParameter && DeclaringMethod != null;
// Gets the type of the object encompassed or referred to by the current array, pointer or reference type
public TypeInfo ElementType { get; }
// Type name including namespace
// Fully qualified generic type names from the C# compiler use backtick and arity rather than a list of generic arguments
public string FullName =>
IsGenericParameter? null :
HasElementType && ElementType.IsGenericParameter? null :
(HasElementType? ElementType.FullName :
(DeclaringType != null? DeclaringType.FullName + "+" : Namespace + (Namespace.Length > 0? "." : ""))
+ base.Name)
+ (IsArray? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsByRef? "&" : "")
+ (IsPointer? "*" : "");
// Returns the minimally qualified type name required to refer to this type within the specified scope
private string getScopedFullName(Scope scope) {
// This is the type to be used (generic type parameters have a null FullName)
var usedType = FullName?.Replace('+', '.') ?? Name;
// This is the scope in which this type is currently being used
// If Scope.Current is null, our scope is at the assembly level
var usingScope = scope.Current?.FullName.Replace('+', '.') ?? "";
// This is the scope in which this type's definition is located
var declaringScope = DeclaringType?.FullName.Replace('+', '.') ?? Namespace;
// Are we in the same scope as the scope the type is defined in? Save ourselves a bunch of work if so
if (usingScope == declaringScope)
return base.Name;
// We're also in the same scope the type is defined in if we're looking for a nested type
// that is declared in a type we derive from
for (var b = scope.Current?.BaseType; b != null; b = b.BaseType)
if (b.FullName.Replace('+', '.') == declaringScope)
return base.Name;
// Find first difference in the declaring scope from the using scope, moving one namespace/type name at a time
var diff = 1;
usingScope += ".";
declaringScope += ".";
while (usingScope.IndexOf('.', diff) == declaringScope.IndexOf('.', diff)
&& usingScope.IndexOf('.', diff) != -1
&& usingScope.Substring(0, usingScope.IndexOf('.', diff))
== declaringScope.Substring(0, declaringScope.IndexOf('.', diff)))
diff = usingScope.IndexOf('.', diff) + 1;
usingScope = usingScope.Remove(usingScope.Length - 1);
declaringScope = declaringScope.Remove(declaringScope.Length - 1);
// This is the mutual root namespace and optionally nested types that the two scopes share
var mutualRootScope = usingScope.Substring(0, diff - 1);
// Determine if the using scope is a child of the declaring scope (always a child if declaring scope is empty)
var usingScopeIsChildOfDeclaringScope = string.IsNullOrEmpty(declaringScope) || (usingScope + ".").StartsWith(declaringScope + ".");
// Determine using directive to use
var usingDirective =
// If the scope of usage is inside the scope in which the type is declared, no additional scope is needed
// but we still need to check for ancestor conflicts below
usingScopeIsChildOfDeclaringScope? declaringScope
// Check to see if there is a namespace in our using directives which brings this type into scope
// Sort by descending order of length to search the deepest namespaces first
: scope.Namespaces.OrderByDescending(n => n.Length).FirstOrDefault(n => declaringScope == n || declaringScope.StartsWith(n + "."));
// minimallyScopedName will eventually contain the least qualified name needed to access the type
// Initially we set it as follows:
// - The non-mutual part of the declaring scope if there is a mutual root scope
// - The fully-qualified type name if there is no mutual root scope
// - The leaf name if the declaring scope and mutual root scope are the same
// The first two must be checked in this order to avoid a . at the start
// when the mutual root scope and declaring scope are both empty
var minimallyScopedName =
declaringScope == mutualRootScope? base.Name :
string.IsNullOrEmpty(mutualRootScope)? declaringScope + '.' + base.Name :
declaringScope.Substring(mutualRootScope.Length + 1) + '.' + base.Name;
// Find the outermost type name if the wanted type is a nested type (if we need it below)
string outerTypeName = "";
if (!usingScopeIsChildOfDeclaringScope)
for (var d = this; d != null; d = d.DeclaringType)
outerTypeName = d.BaseName;
// Are there any ancestor nested types or namespaces in the using scope with the same name as the wanted type's unqualified name?
// If so, the ancestor name will hide the type we are trying to reference, so we need to provide a higher-level scope
// If the using scope is a child of the declaring scope, we can try every parent scope until we find one that doesn't hide the type
// Otherwise, we just try the unqualified outer (least nested) type name to make sure it's accessible
// and revert to the fully qualified name if it's hidden
var nsAndTypeHierarchy = usingScopeIsChildOfDeclaringScope?
usingDirective.Split('.').Append(minimallyScopedName).ToArray()
: new [] {outerTypeName};
var hidden = true;
var foundTypeInAncestorScope = false;
string testTypeName = "";
for (var depth = nsAndTypeHierarchy.Length - 1; depth >= 0 && hidden; depth--) {
testTypeName = nsAndTypeHierarchy[depth] + (testTypeName.Length > 0? "." : "") + testTypeName;
hidden = false;
for (var d = scope.Current; d != null && !hidden && !foundTypeInAncestorScope; d = d.DeclaringType) {
// If neither condition is true, the wanted type is not hidden by the type we are testing
foundTypeInAncestorScope = d.FullName == FullName;
hidden = !foundTypeInAncestorScope && d.BaseName == testTypeName;
}
// We found the shortest non-hidden scope we can use
// For a child scope, use the shortest found scope
// Otherwise, we've confirmed the outer nested type name is not hidden so go ahead and use the nested type name without a namespace
if (!hidden)
minimallyScopedName = usingScopeIsChildOfDeclaringScope? testTypeName : Name.Replace('+', '.');
// If the wanted type is an unhidden ancestor, we don't need any additional scope at all
if (foundTypeInAncestorScope)
minimallyScopedName = base.Name;
}
// If there are multiple using directives that would allow the same minimally scoped name to be used,
// then the minimally scoped name is ambiguous and we can't use it
// Note that if the wanted type is an unhidden outer class relative to the using scope, this takes precedence and there can be no ambiguity
if (!foundTypeInAncestorScope) {
// Only test the outermost type name
outerTypeName = minimallyScopedName.Split('.')[0];
// Take matching type names from all namespaces in scope
var matchingNamespaces = scope.Namespaces.Where(n => Assembly.Model.TypesByFullName.ContainsKey(n + "." + outerTypeName)).ToList();
// The global namespace is in scope so take every matching type from that too
if (Assembly.Model.TypesByFullName.ContainsKey(outerTypeName))
matchingNamespaces.Add("");
// More than one possible matching namespace? If so, the type reference is ambiguous
if (matchingNamespaces.Count > 1) {
// TODO: This can be improved to cut off a new mutual root that doesn't cause ambiguity
minimallyScopedName = usedType;
}
// No matching namespaces, not hidden, no mutual root scope in the file and no using directive?
// If so, the type's namespace is completely out of scope so use the fully-qualified type name
if (matchingNamespaces.Count == 0 && !hidden && string.IsNullOrEmpty(mutualRootScope) && usingDirective == null)
minimallyScopedName = usedType;
}
return minimallyScopedName;
}
// C#-friendly type name as it should be used in the scope of a given type
public string GetScopedCSharpName(Scope usingScope = null, bool omitRef = false) {
// Unscoped name if no using scope specified
if (usingScope == null)
return CSharpName;
// Generic parameters don't have a scope
if (IsGenericParameter)
return CSharpName;
var s = Namespace + "." + base.Name;
// Built-in keyword type names do not require a scope
var i = Il2CppConstants.FullNameTypeString.IndexOf(s);
var n = i != -1 ? Il2CppConstants.CSharpTypeString[i] : getScopedFullName(usingScope);
// Unmangle generic type names
if (n?.IndexOf("`", StringComparison.Ordinal) != -1)
n = n?.Remove(n.IndexOf("`", StringComparison.Ordinal));
// Generic type parameters and type arguments
var g = string.Join(", ", getGenericTypeParameters(usingScope).Select(x => x.GetScopedCSharpName(usingScope)));
if (!string.IsNullOrEmpty(g))
n += "<" + g + ">";
// Nullable types
if (s == "System.Nullable`1" && GetGenericArguments().Any())
n = GetGenericArguments()[0].GetScopedCSharpName(usingScope) + "?";
// Arrays, pointers, references
if (HasElementType)
n = ElementType.GetScopedCSharpName(usingScope);
return (IsByRef && !omitRef? "ref " : "") + n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : "");
}
// Get the generic type parameters for a specific usage of this type based on its scope,
// or all generic type parameters if no scope specified
private IEnumerable<TypeInfo> getGenericTypeParameters(Scope scope = null) {
var ga = GetGenericArguments();
// If no scope or empty scope specified, or no type parameters, stop here
if (scope?.Current == null || !ga.Any())
return ga;
// In order to elide generic type parameters, the using scope must be a parent of the declaring scope
// Determine if the using scope is a parent of the declaring scope (always a child if using scope is empty)
var usingScopeIsParent = false;
for (var s = DeclaringType; s != null && !usingScopeIsParent; s = s.DeclaringType)
if (s == scope.Current)
usingScopeIsParent = true;
if (!usingScopeIsParent)
return ga;
// Get the generic type parameters available in the using scope
// (no need to recurse because every nested type inherits all of the generic type parameters of all of its ancestors)
var gasInScope = scope.Current.GetGenericArguments();
// Return all of the generic type parameters this type uses minus those already in scope
return ga.Where(p => gasInScope.All(pp => pp.Name != p.Name));
}
public GenericParameterAttributes GenericParameterAttributes { get; }
// Generic parameter position in list of non-concrete type parameters
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.genericparameterposition?view=netframework-4.8
private int genericParameterPosition;
public int GenericParameterPosition {
get => IsGenericParameter ? genericParameterPosition : throw new InvalidOperationException("The current type does not represent a type parameter");
private set => genericParameterPosition = value;
}
// For a generic type definition: the list of generic type parameters
// For an open generic type: a mix of generic type parameters and generic type arguments
// For a closed generic type: the list of generic type arguments
private readonly List<TypeInfo> genericArguments = new List<TypeInfo>();
public List<TypeInfo> GenericTypeParameters => IsGenericTypeDefinition ? genericArguments : new List<TypeInfo>();
public List<TypeInfo> GenericTypeArguments => !IsGenericTypeDefinition ? genericArguments : new List<TypeInfo>();
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.getgenericarguments?view=netframework-4.8
public List<TypeInfo> GetGenericArguments() => genericArguments;
// True if an array, pointer or reference, otherwise false
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.haselementtype?view=netframework-4.8
public bool HasElementType => ElementType != null;
private readonly int[] implementedInterfaceReferences;
public IEnumerable<TypeInfo> ImplementedInterfaces => implementedInterfaceReferences.Select(x => Assembly.Model.TypesByReferenceIndex[x]);
public bool IsAbstract => (Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract;
public bool IsArray { get; }
public bool IsByRef { get; }
public bool IsClass => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class;
public bool IsEnum => enumUnderlyingTypeReference != -1;
public bool IsGenericParameter { get; }
public bool IsGenericType { get; }
public bool IsGenericTypeDefinition => genericArguments.Any() && genericArguments.All(a => a.IsGenericTypeParameter);
public bool IsImport => (Attributes & TypeAttributes.Import) == TypeAttributes.Import;
public bool IsInterface => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface;
public bool IsNested => (MemberType & MemberTypes.NestedType) == MemberTypes.NestedType;
public bool IsNestedAssembly => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedAssembly;
public bool IsNestedFamANDAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamANDAssem;
public bool IsNestedFamily => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily;
public bool IsNestedFamORAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem;
public bool IsNestedPrivate => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPrivate;
public bool IsNestedPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic;
public bool IsNotPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NotPublic;
public bool IsPointer { get; }
// Primitive types table: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table (we exclude Object and String)
public bool IsPrimitive => Namespace == "System" && new[] { "Boolean", "Byte", "SByte", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64", "IntPtr", "UIntPtr", "Char", "Decimal", "Double", "Single" }.Contains(Name);
public bool IsPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public;
public bool IsSealed => (Attributes & TypeAttributes.Sealed) == TypeAttributes.Sealed;
public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable;
public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName;
public bool IsValueType => BaseType?.FullName == "System.ValueType";
// Helper function for determining if using this type as a field, parameter etc. requires that field or method to be declared as unsafe
public bool RequiresUnsafeContext => IsPointer || (HasElementType && ElementType.RequiresUnsafeContext);
// May get overridden by Il2CppType-based constructor below
public override MemberTypes MemberType { get; } = MemberTypes.TypeInfo;
private string @namespace;
public string Namespace {
get => !string.IsNullOrEmpty(@namespace) ? @namespace : DeclaringType?.Namespace ?? "";
set => @namespace = value;
}
// Number of dimensions of an array
private readonly int arrayRank;
public int GetArrayRank() => arrayRank;
public string[] GetEnumNames() => IsEnum? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.Name).ToArray() : throw new InvalidOperationException("Type is not an enumeration");
// The underlying type of an enumeration (int by default)
private readonly int enumUnderlyingTypeReference = -1;
private TypeInfo enumUnderlyingType;
public TypeInfo GetEnumUnderlyingType() {
if (!IsEnum)
return null;
enumUnderlyingType ??= Assembly.Model.TypesByReferenceIndex[enumUnderlyingTypeReference];
return enumUnderlyingType;
}
public Array GetEnumValues() => IsEnum? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.DefaultValue).ToArray() : throw new InvalidOperationException("Type is not an enumeration");
// Initialize type from TypeDef using specified index in metadata
public TypeInfo(int typeIndex, Assembly owner) : base(owner) {
var pkg = Assembly.Model.Package;
Definition = pkg.TypeDefinitions[typeIndex];
Index = typeIndex;
Namespace = pkg.Strings[Definition.namespaceIndex];
Name = pkg.Strings[Definition.nameIndex];
// Derived type?
if (Definition.parentIndex >= 0)
baseTypeReference = Definition.parentIndex;
// Nested type?
if (Definition.declaringTypeIndex >= 0) {
declaringTypeDefinitionIndex = (int) pkg.TypeReferences[Definition.declaringTypeIndex].datapoint;
MemberType |= MemberTypes.NestedType;
}
// Generic type definition?
if (Definition.genericContainerIndex >= 0) {
IsGenericType = true;
IsGenericParameter = false;
// Store the generic type parameters for later instantiation
var container = pkg.GenericContainers[Definition.genericContainerIndex];
genericArguments = pkg.GenericParameters.Skip((int) container.genericParameterStart).Take(container.type_argc).Select(p => new TypeInfo(this, p)).ToList();
}
// Add to global type definition list
Assembly.Model.TypesByDefinitionIndex[Index] = this;
Assembly.Model.TypesByFullName[FullName] = this;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_SERIALIZABLE) != 0)
Attributes |= TypeAttributes.Serializable;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_PUBLIC)
Attributes |= TypeAttributes.Public;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NOT_PUBLIC)
Attributes |= TypeAttributes.NotPublic;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_PUBLIC)
Attributes |= TypeAttributes.NestedPublic;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_PRIVATE)
Attributes |= TypeAttributes.NestedPrivate;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_ASSEMBLY)
Attributes |= TypeAttributes.NestedAssembly;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_FAMILY)
Attributes |= TypeAttributes.NestedFamily;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_FAM_AND_ASSEM)
Attributes |= TypeAttributes.NestedFamANDAssem;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_FAM_OR_ASSEM)
Attributes |= TypeAttributes.NestedFamORAssem;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_ABSTRACT) != 0)
Attributes |= TypeAttributes.Abstract;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_SEALED) != 0)
Attributes |= TypeAttributes.Sealed;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_SPECIAL_NAME) != 0)
Attributes |= TypeAttributes.SpecialName;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_IMPORT) != 0)
Attributes |= TypeAttributes.Import;
// TypeAttributes.Class == 0 so we only care about setting TypeAttributes.Interface (it's a non-interface class by default)
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_INTERFACE) != 0)
Attributes |= TypeAttributes.Interface;
// Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum)
if (((Definition.bitfield >> 1) & 1) == 1)
enumUnderlyingTypeReference = Definition.elementTypeIndex;
// Pass-by-reference type
// NOTE: This should actually always evaluate to false in the current implementation
IsByRef = Index == Definition.byrefTypeIndex;
// Add all implemented interfaces
implementedInterfaceReferences = new int[Definition.interfaces_count];
for (var i = 0; i < Definition.interfaces_count; i++)
implementedInterfaceReferences[i] = pkg.InterfaceUsageIndices[Definition.interfacesStart + i];
// Add all nested types
declaredNestedTypes = new int[Definition.nested_type_count];
for (var n = 0; n < Definition.nested_type_count; n++)
declaredNestedTypes[n] = pkg.NestedTypeIndices[Definition.nestedTypesStart + n];
// Add all fields
for (var f = Definition.fieldStart; f < Definition.fieldStart + Definition.field_count; f++)
DeclaredFields.Add(new FieldInfo(pkg, f, this));
// Add all methods
for (var m = Definition.methodStart; m < Definition.methodStart + Definition.method_count; m++) {
var method = new MethodInfo(pkg, m, this);
if (method.Name == ConstructorInfo.ConstructorName || method.Name == ConstructorInfo.TypeConstructorName)
DeclaredConstructors.Add(new ConstructorInfo(pkg, m, this));
else
DeclaredMethods.Add(method);
}
// Add all properties
for (var p = Definition.propertyStart; p < Definition.propertyStart + Definition.property_count; p++)
DeclaredProperties.Add(new PropertyInfo(pkg, p, this));
// There are rare cases when explicitly implemented interface properties
// are only given as methods in the metadata. Find these and add them as properties
var eip = DeclaredMethods.Where(m => m.Name.Contains(".get_") || m.Name.Contains(".set_"))
.Except(DeclaredProperties.Select(p => p.GetMethod))
.Except(DeclaredProperties.Select(p => p.SetMethod));
// Build a paired list of getters and setters
var pairedEip = new List<(MethodInfo get, MethodInfo set)>();
foreach (var p in eip) {
// Discern property name
var n = p.Name.Replace(".get_", ".").Replace(".set_", ".");
// Find setter with no matching getter
if (p.Name.Contains(".get_"))
if (pairedEip.FirstOrDefault(pe => pe.get == null && pe.set.Name == p.Name.Replace(".get_", ".set_")) is (MethodInfo get, MethodInfo set) method) {
pairedEip.Remove(method);
pairedEip.Add((p, method.set));
}
else
pairedEip.Add((p, null));
// Find getter with no matching setter
if (p.Name.Contains(".set_"))
if (pairedEip.FirstOrDefault(pe => pe.set == null && pe.get.Name == p.Name.Replace(".set_", ".get_")) is (MethodInfo get, MethodInfo set) method) {
pairedEip.Remove(method);
pairedEip.Add((method.get, p));
}
else
pairedEip.Add((null, p));
}
foreach (var prop in pairedEip)
DeclaredProperties.Add(new PropertyInfo(prop.get, prop.set, this));
// Add all events
for (var e = Definition.eventStart; e < Definition.eventStart + Definition.event_count; e++)
DeclaredEvents.Add(new EventInfo(pkg, e, this));
}
// Initialize type from type reference (TypeRef)
// Much of the following is adapted from il2cpp::vm::Class::FromIl2CppType
public TypeInfo(Il2CppModel model, Il2CppType pType) {
var image = model.Package.BinaryImage;
// Open and closed generic types
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) {
// TODO: Replace with array load from Il2CppMetadataRegistration.genericClasses
var generic = image.ReadMappedObject<Il2CppGenericClass>(pType.datapoint); // Il2CppGenericClass *
// We have seen one test case where the TypeRef can point to no generic instance
// This is going to leave the TypeInfo in an undefined state
if (generic.typeDefinitionIndex == 0x0000_0000_ffff_ffff)
return;
var genericTypeDef = model.TypesByDefinitionIndex[generic.typeDefinitionIndex];
Assembly = genericTypeDef.Assembly;
Namespace = genericTypeDef.Namespace;
Name = genericTypeDef.BaseName;
Attributes |= TypeAttributes.Class;
// Derived type?
if (genericTypeDef.Definition.parentIndex >= 0)
baseTypeReference = genericTypeDef.Definition.parentIndex;
// Nested type?
if (genericTypeDef.Definition.declaringTypeIndex >= 0) {
declaringTypeDefinitionIndex = (int)model.Package.TypeReferences[genericTypeDef.Definition.declaringTypeIndex].datapoint;
MemberType |= MemberTypes.NestedType;
}
IsGenericType = true;
IsGenericParameter = false;
// Get the instantiation
// TODO: Replace with array load from Il2CppMetadataRegistration.genericInsts
var genericInstance = image.ReadMappedObject<Il2CppGenericInst>(generic.context.class_inst);
if (generic.context.method_inst != 0)
throw new InvalidOperationException("Generic method instance cannot be non-null when processing a generic class instance");
// Find all the type parameters (both unresolved and concrete)
// This will cause new types to be generated with the VAR and MVAR types below
genericArguments = model.ResolveGenericArguments(genericInstance);
}
// TODO: Set DeclaringType for the two below
// Array with known dimensions and bounds
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_ARRAY) {
var descriptor = image.ReadMappedObject<Il2CppArrayType>(pType.datapoint);
ElementType = model.GetTypeFromVirtualAddress(descriptor.etype);
Assembly = ElementType.Assembly;
Namespace = ElementType.Namespace;
Name = ElementType.Name;
IsArray = true;
arrayRank = descriptor.rank;
}
// Dynamically allocated array or pointer type
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY || pType.type == Il2CppTypeEnum.IL2CPP_TYPE_PTR) {
ElementType = model.GetTypeFromVirtualAddress(pType.datapoint);
Assembly = ElementType.Assembly;
Namespace = ElementType.Namespace;
Name = ElementType.Name;
IsPointer = (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_PTR);
IsArray = !IsPointer;
// Heap arrays always have one dimension
arrayRank = 1;
}
// Generic type parameter
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_VAR || pType.type == Il2CppTypeEnum.IL2CPP_TYPE_MVAR) {
var paramType = model.Package.GenericParameters[pType.datapoint]; // genericParameterIndex
var container = model.Package.GenericContainers[paramType.ownerIndex];
var ownerType = model.TypesByDefinitionIndex[
container.is_method == 1
? model.Package.Methods[container.ownerIndex].declaringType
: container.ownerIndex];
Assembly = ownerType.Assembly;
Namespace = "";
Name = model.Package.Strings[paramType.nameIndex];
Attributes |= TypeAttributes.Class;
// Derived type?
if (ownerType.Definition.parentIndex >= 0)
baseTypeReference = ownerType.Definition.parentIndex;
// Nested type always - sets DeclaringType used below
declaringTypeDefinitionIndex = ownerType.Index;
MemberType |= MemberTypes.NestedType;
// All generic method type parameters have a declared method
if (container.is_method == 1)
DeclaringMethod = model.MethodsByDefinitionIndex[container.ownerIndex];
// Set position in argument list
GenericParameterPosition = paramType.num;
IsGenericParameter = true;
IsGenericType = false;
}
}
// Initialize a type from a concrete generic instance (TypeSpec)
public TypeInfo(Il2CppModel model, Il2CppMethodSpec spec) {
var genericTypeDefinition = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex].DeclaringType;
// Same visibility attributes as generic type definition
Attributes = genericTypeDefinition.Attributes;
// Even though this isn't a TypeDef, we have to set this so that DeclaringType works in later references
Index = genericTypeDefinition.Index;
// Same name as generic type definition
Assembly = genericTypeDefinition.Assembly;
Namespace = genericTypeDefinition.Namespace;
Name = genericTypeDefinition.BaseName; // use BaseName to exclude the type parameters so we can supply our own
IsGenericParameter = false;
IsGenericType = true;
// Resolve type arguments
genericArguments = model.ResolveGenericArguments(spec.classIndexIndex);
/* TODO: This is a bare definition at the moment. We need to iterate over all the members of genericTypeDefinition
* and replace the matching generic type parameters with our concrete type parameters,
* as well as setting the various TypeInfo properties here
*/
}
// Initialize a type that is a generic parameter of a generic type
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8
public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) {
// Same visibility attributes as declaring type
Attributes = declaringType.Attributes;
// Same namespace as declaring type
Namespace = declaringType.Namespace;
// Special constraints
GenericParameterAttributes = (GenericParameterAttributes) param.flags;
// Type constraints
genericConstraintIndex = param.constraintsStart;
genericConstraintCount = param.constraintsCount;
// Base type of object (set by default)
// TODO: BaseType should be set to base type constraint
// TODO: ImplementedInterfaces should be set to interface types constraints
// Name of parameter
Name = Assembly.Model.Package.Strings[param.nameIndex];
// Position
GenericParameterPosition = param.num;
IsGenericParameter = true;
IsGenericType = false;
}
// Initialize a type that is a generic parameter of a generic method
public TypeInfo(MethodBase declaringMethod, Il2CppGenericParameter param) : this(declaringMethod.DeclaringType, param)
=> DeclaringMethod = declaringMethod;
// Initialize a type that is a reference to the specified type
private TypeInfo(TypeInfo underlyingType) {
ElementType = underlyingType;
IsByRef = true;
// No base type or declaring type for reference types
Assembly = ElementType.Assembly;
Definition = ElementType.Definition;
Index = ElementType.Index;
Namespace = ElementType.Namespace;
Name = ElementType.Name;
Attributes = ElementType.Attributes;
}
public TypeInfo MakeByRefType() => new TypeInfo(this);
// Get all the other types directly referenced by this type (single level depth; no recursion)
public List<TypeInfo> GetAllTypeReferences() {
var refs = new HashSet<TypeInfo>();
// Fixed attributes
if (IsImport)
refs.Add(Assembly.Model.TypesByFullName["System.Runtime.InteropServices.ComVisibleAttribute"]);
if (IsSerializable)
refs.Add(Assembly.Model.TypesByFullName["System.SerializableAttribute"]);
// Constructor, event, field, method, nested type, property attributes
var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes);
refs.UnionWith(attrs.Select(a => a.AttributeType));
// Events
refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType));
// Fields
refs.UnionWith(DeclaredFields.Select(f => f.FieldType));
// Properties (return type of getters or argument type of setters)
refs.UnionWith(DeclaredProperties.Select(p => p.PropertyType));
// Nested types
refs.UnionWith(DeclaredNestedTypes);
refs.UnionWith(DeclaredNestedTypes.SelectMany(n => n.GetAllTypeReferences()));
// Constructors
refs.UnionWith(DeclaredConstructors.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType));
// Methods (includes event add/remove/raise, property get/set methods and extension methods)
refs.UnionWith(DeclaredMethods.Select(m => m.ReturnParameter.ParameterType));
refs.UnionWith(DeclaredMethods.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType));
// Method generic type parameters and constraints
refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments()));
refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments())
.SelectMany(p => p.GetGenericParameterConstraints()));
// Type declaration attributes
refs.UnionWith(CustomAttributes.Select(a => a.AttributeType));
// Parent type
if (BaseType != null)
refs.Add(BaseType);
// Declaring type
if (DeclaringType != null)
refs.Add(DeclaringType);
// Element type
if (HasElementType)
refs.Add(ElementType);
// Enum type
if (IsEnum)
refs.Add(GetEnumUnderlyingType());
// Generic type parameters and constraints
refs.UnionWith(GetGenericArguments());
refs.UnionWith(GetGenericParameterConstraints());
// Generic type constraints of type parameters in generic type definition
refs.UnionWith(GenericTypeParameters.SelectMany(p => p.GetGenericParameterConstraints()));
// Implemented interfaces
refs.UnionWith(ImplementedInterfaces);
// Repeatedly replace arrays, pointers and references with their element types
while (refs.Any(r => r.HasElementType))
refs = refs.Select(r => r.HasElementType ? r.ElementType : r).ToHashSet();
// Type arguments in generic types that may have been a field, method parameter etc.
IEnumerable<TypeInfo> genericArguments = refs.ToList();
do {
genericArguments = genericArguments.SelectMany(r => r.GetGenericArguments());
refs.UnionWith(genericArguments);
} while (genericArguments.Any());
// Remove anonymous types
refs.RemoveWhere(r => string.IsNullOrEmpty(r.FullName));
IEnumerable<TypeInfo> refList = refs;
// Eliminated named duplicates (the HashSet removes instance duplicates)
refList = refList.GroupBy(r => r.FullName).Select(p => p.First());
// Remove System.Object
refList = refList.Where(r => r.FullName != "System.Object");
return refList.ToList();
}
// Display name of object
public override string Name => IsGenericParameter ? base.Name :
(HasElementType? ElementType.Name :
(DeclaringType != null ? DeclaringType.Name + "+" : "")
+ base.Name
+ (GetGenericArguments().Any()? "[" + string.Join(",", GetGenericArguments().Select(x => x.Namespace != Namespace? x.FullName ?? x.Name : x.Name)) + "]" : ""))
+ (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsByRef ? "&" : "")
+ (IsPointer ? "*" : "");
public string GetAccessModifierString() => this switch {
{ IsPublic: true } => "public ",
{ IsNotPublic: true } => "internal ",
{ IsNestedPublic: true } => "public ",
{ IsNestedPrivate: true } => "private ",
{ IsNestedFamily: true } => "protected ",
{ IsNestedAssembly: true } => "internal ",
{ IsNestedFamORAssem: true } => "protected internal ",
{ IsNestedFamANDAssem: true } => "private protected ",
_ => throw new InvalidOperationException("Unknown type access modifier")
};
public string GetModifierString() {
var modifiers = new StringBuilder(GetAccessModifierString());
// An abstract sealed class is a static class
if (IsAbstract && IsSealed)
modifiers.Append("static ");
else {
if (IsAbstract && !IsInterface)
modifiers.Append("abstract ");
if (IsSealed && !IsValueType && !IsEnum)
modifiers.Append("sealed ");
}
if (IsInterface)
modifiers.Append("interface ");
else if (IsValueType)
modifiers.Append("struct ");
else if (IsEnum)
modifiers.Append("enum ");
else
modifiers.Append("class ");
return modifiers.ToString();
}
public string GetTypeConstraintsString(Scope scope) {
if (!IsGenericParameter)
return string.Empty;
var typeConstraints = GetGenericParameterConstraints();
if (GenericParameterAttributes == GenericParameterAttributes.None && typeConstraints.Length == 0)
return string.Empty;
// Check if we are in a nested type, and if so, exclude ourselves if we are a generic type parameter from the outer type
// All constraints are inherited automatically by all nested types so we only have to look at the immediate outer type
if (DeclaringMethod == null && DeclaringType.IsNested && DeclaringType.DeclaringType.GetGenericArguments().Any(p => p.Name == Name))
return string.Empty;
// Check if we are in an overriding method, and if so, exclude ourselves if we are a generic type parameter from the base method
// All constraints are inherited automatically by all overriding methods so we only have to look at the immediate base method
if (DeclaringMethod != null && DeclaringMethod.IsVirtual && !DeclaringMethod.IsAbstract && !DeclaringMethod.IsFinal
&& (DeclaringMethod.Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot) {
// Find nearest ancestor base method which has us as a generic type parameter
var sig = DeclaringMethod.GetSignatureString();
var method = DeclaringMethod.DeclaringType.BaseType.GetAllMethods()
.FirstOrDefault(m => m.IsHideBySig && m.IsVirtual && m.GetSignatureString() == sig && m.GetGenericArguments().Any(p => p.Name == Name));
// Stop if we are inherited from a base method
if (method != null)
return string.Empty;
}
var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.GetScopedCSharpName(scope)).ToList();
// struct or class must be the first constraint specified
if ((GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint)
constraintList.Insert(0, "struct");
if ((GenericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint)
constraintList.Insert(0, "class");
if ((GenericParameterAttributes & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint
&& !constraintList.Contains("struct"))
// new() must be the last constraint specified
constraintList.Add("new()");
// Covariance/contravariance constraints can lead to an empty constraint list
if (!constraintList.Any())
return string.Empty;
return "where " + Name + " : " + string.Join(", ", constraintList);
}
public override string ToString() => Name;
}
}