Refactor solution layout
This commit is contained in:
83
Il2CppInspector.Common/Reflection/Assembly.cs
Normal file
83
Il2CppInspector.Common/Reflection/Assembly.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
40
Il2CppInspector.Common/Reflection/Constants.cs
Normal file
40
Il2CppInspector.Common/Reflection/Constants.cs
Normal 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"
|
||||
};
|
||||
}
|
||||
}
|
||||
31
Il2CppInspector.Common/Reflection/ConstructorInfo.cs
Normal file
31
Il2CppInspector.Common/Reflection/ConstructorInfo.cs
Normal 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())) + ")";
|
||||
}
|
||||
}
|
||||
75
Il2CppInspector.Common/Reflection/CustomAttributeData.cs
Normal file
75
Il2CppInspector.Common/Reflection/CustomAttributeData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
61
Il2CppInspector.Common/Reflection/EventInfo.cs
Normal file
61
Il2CppInspector.Common/Reflection/EventInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Il2CppInspector.Common/Reflection/Extensions.cs
Normal file
125
Il2CppInspector.Common/Reflection/Extensions.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Il2CppInspector.Common/Reflection/FieldInfo.cs
Normal file
150
Il2CppInspector.Common/Reflection/FieldInfo.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
260
Il2CppInspector.Common/Reflection/Il2CppModel.cs
Normal file
260
Il2CppInspector.Common/Reflection/Il2CppModel.cs
Normal 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")
|
||||
};
|
||||
}
|
||||
}
|
||||
48
Il2CppInspector.Common/Reflection/MemberInfo.cs
Normal file
48
Il2CppInspector.Common/Reflection/MemberInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
280
Il2CppInspector.Common/Reflection/MethodBase.cs
Normal file
280
Il2CppInspector.Common/Reflection/MethodBase.cs
Normal 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"] = "~"
|
||||
};
|
||||
}
|
||||
}
|
||||
47
Il2CppInspector.Common/Reflection/MethodInfo.cs
Normal file
47
Il2CppInspector.Common/Reflection/MethodInfo.cs
Normal 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())) + ")";
|
||||
}
|
||||
}
|
||||
64
Il2CppInspector.Common/Reflection/MethodInvoker.cs
Normal file
64
Il2CppInspector.Common/Reflection/MethodInvoker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
133
Il2CppInspector.Common/Reflection/ParameterInfo.cs
Normal file
133
Il2CppInspector.Common/Reflection/ParameterInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
72
Il2CppInspector.Common/Reflection/PropertyInfo.cs
Normal file
72
Il2CppInspector.Common/Reflection/PropertyInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Il2CppInspector.Common/Reflection/Scope.cs
Normal file
20
Il2CppInspector.Common/Reflection/Scope.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
960
Il2CppInspector.Common/Reflection/TypeInfo.cs
Normal file
960
Il2CppInspector.Common/Reflection/TypeInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user