Support reading and writing custom arguments for v29

This commit is contained in:
LukeFZ
2023-12-01 04:43:27 +01:00
parent 40201d6e84
commit 6aa96b431d
10 changed files with 3230 additions and 2771 deletions

View File

@@ -1,89 +1,158 @@
/*
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
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 TypeModel 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]]);
// C++ method names
// TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness
public string Name => $"{AttributeType.Name.ToCIdentifier()}_CustomAttributesCacheGenerator";
// C++ method signature
public string Signature => $"void {Name}(CustomAttributesCache *)";
public override string ToString() => "[" + AttributeType.FullName + "]";
// Get the machine code of the C++ function
public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start));
// 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;
if (pkg.Version < 29)
{
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;
}
}
else
{
Console.WriteLine("Skipping custom attributes for 29+");
yield break;
}
}
private static IList<CustomAttributeData> getCustomAttributes(Assembly asm, int token, int customAttributeIndex) =>
getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList();
public static IList<CustomAttributeData> GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.MetadataToken, asm.AssemblyDefinition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.MetadataToken, evt.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.MetadataToken, field.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.MetadataToken, method.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.MetadataToken, param.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(PropertyInfo prop)
=> prop.Definition != null ? getCustomAttributes(prop.Assembly, prop.MetadataToken, prop.Definition.customAttributeIndex) : new List<CustomAttributeData>();
public static IList<CustomAttributeData> GetCustomAttributes(TypeInfo type) => type.Definition != null? getCustomAttributes(type.Assembly, type.MetadataToken, type.Definition.customAttributeIndex) : new List<CustomAttributeData>();
}
}
/*
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
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 TypeModel Model => AttributeType.Assembly.Model;
public int Index { get; set; }
// The type of the attribute
public TypeInfo AttributeType { get; set; }
// v29 custom attribute info
public CustomAttributeCtor CtorInfo { get; set; }
// Pre-v29 Properties used for stub Attributes
public (ulong Start, ulong End) VirtualAddress => CtorInfo != null ? (0, 0) :
// 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]]);
// C++ method names
// TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness
public string Name => $"{AttributeType.Name.ToCIdentifier()}_CustomAttributesCacheGenerator";
// C++ method signature
public string Signature => $"void {Name}(CustomAttributesCache *)";
public override string ToString() => "[" + AttributeType.FullName + "]";
// Get the machine code of the C++ function
public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start));
public IEnumerable<TypeInfo> GetAllTypeReferences()
{
yield return AttributeType;
if (CtorInfo != null)
{
foreach (var typeRef in GetTypeReferences(CtorInfo.Arguments))
yield return typeRef;
foreach (var typeRef in GetTypeReferences(CtorInfo.Fields))
yield return typeRef;
foreach (var typeRef in GetTypeReferences(CtorInfo.Properties))
yield return typeRef;
}
yield break;
static IEnumerable<TypeInfo> GetTypeReferences(IEnumerable<CustomAttributeArgument> arguments)
{
foreach (var arg in arguments)
{
yield return arg.Type;
switch (arg.Value)
{
case TypeInfo info:
yield return info;
break;
case CustomAttributeArgument[] array:
foreach (var info in GetTypeReferences(array))
yield return info;
break;
case TypeInfo[] infos:
foreach (var info in infos)
yield return info;
break;
}
}
}
}
// 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;
if (pkg.Version < 29)
{
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;
}
}
else
{
if (!asm.Model.AttributesByDataIndices.TryGetValue(customAttributeIndex, out var attributes))
{
var range = pkg.Metadata.AttributeDataRanges[customAttributeIndex];
var next = pkg.Metadata.AttributeDataRanges[customAttributeIndex + 1];
var startOffset = pkg.Metadata.Header.attributeDataOffset + range.startOffset;
var endOffset = pkg.Metadata.Header.attributeDataOffset + next.startOffset;
var reader = new CustomAttributeDataReader(pkg, asm, pkg.Metadata, startOffset, endOffset);
if (reader.Count == 0)
yield break;
attributes = reader.Read().Select((x, i) => new CustomAttributeData
{
AttributeType = x.Ctor.DeclaringType,
CtorInfo = x,
Index = i,
}).ToList();
asm.Model.AttributesByDataIndices[customAttributeIndex] = attributes;
}
foreach (var attribute in attributes)
yield return attribute;
}
}
private static IList<CustomAttributeData> getCustomAttributes(Assembly asm, int token, int customAttributeIndex) =>
getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList();
public static IList<CustomAttributeData> GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.MetadataToken, asm.AssemblyDefinition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.MetadataToken, evt.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.MetadataToken, field.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.MetadataToken, method.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.MetadataToken, param.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(PropertyInfo prop)
=> prop.Definition != null ? getCustomAttributes(prop.Assembly, prop.MetadataToken, prop.Definition.customAttributeIndex) : new List<CustomAttributeData>();
public static IList<CustomAttributeData> GetCustomAttributes(TypeInfo type) => type.Definition != null? getCustomAttributes(type.Assembly, type.MetadataToken, type.Definition.customAttributeIndex) : new List<CustomAttributeData>();
}
}

View File

@@ -6,7 +6,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -22,29 +21,90 @@ namespace Il2CppInspector.Reflection
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));
if (cad.CtorInfo != null)
{
// v29+ attribute handling
// We now have much more information, so we can reconstruct the actual attribute
var ctor = cad.CtorInfo;
// 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 = "";
var name = ctor.Ctor.DeclaringType.GetScopedCSharpName(scope);
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
if (suffix != -1)
name = name[..suffix];
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
commentStart = "";
commentEnd = "";
arguments = "(AttributeTargets.All)";
sb.Append(linePrefix);
sb.Append('[');
sb.Append(attributePrefix);
sb.Append(name);
var totalCount = ctor.Arguments.Length + ctor.Fields.Length + ctor.Properties.Length;
if (totalCount > 0)
{
// We have parameters, need to use brackets
sb.Append('(');
var totalIndex = 0;
foreach (var argument in ctor.Arguments)
{
sb.Append(argument.Value.ToCSharpValue(argument.Type, scope));
if (++totalIndex != totalCount)
sb.Append(", ");
}
foreach (var field in ctor.Fields)
{
sb.Append(field.Field.CSharpName);
sb.Append(" = ");
sb.Append(field.Value.ToCSharpValue(field.Type, scope));
if (++totalIndex != totalCount)
sb.Append(", ");
}
foreach (var property in ctor.Properties)
{
sb.Append(property.Property.CSharpName);
sb.Append(" = ");
sb.Append(property.Value.ToCSharpValue(property.Type, scope));
if (++totalIndex != totalCount)
sb.Append(", ");
}
sb.Append(')');
}
sb.Append(']');
sb.Append(inline ? " " : "\n");
}
else
{
// Pre-v29 attribute handling
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");
// 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();
@@ -120,31 +180,60 @@ namespace Il2CppInspector.Reflection
// Output a value in C#-friendly syntax
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
if (value is bool)
return (bool) value ? "true" : "false";
if (value is float f)
return value switch {
float.PositiveInfinity => "1F / 0F",
float.NegativeInfinity => "-1F / 0F",
float.NaN => "0F / 0F",
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
};
if (value is double d)
return value switch {
double.PositiveInfinity => "1D / 0D",
double.NegativeInfinity => "-1D / 0D",
double.NaN => "0D / 0D",
_ => d.ToString(CultureInfo.InvariantCulture)
};
if (value is string str) {
return $"\"{str.ToEscapedString()}\"";
}
if (value is char) {
var cValue = (int) (char) value;
if (cValue < 32 || cValue > 126)
return $"'\\x{cValue:x4}'";
return $"'{value}'";
switch (value)
{
case bool b:
return b ? "true" : "false";
case float f:
return value switch {
float.PositiveInfinity => "1F / 0F",
float.NegativeInfinity => "-1F / 0F",
float.NaN => "0F / 0F",
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
};
case double d:
return value switch {
double.PositiveInfinity => "1D / 0D",
double.NegativeInfinity => "-1D / 0D",
double.NaN => "0D / 0D",
_ => d.ToString(CultureInfo.InvariantCulture)
};
case string str:
return $"\"{str.ToEscapedString()}\"";
case char c:
{
var cValue = (int) c;
if (cValue < 32 || cValue > 126)
return $"'\\x{cValue:x4}'";
return $"'{value}'";
}
case TypeInfo typeInfo:
return $"typeof({typeInfo.GetScopedCSharpName(usingScope)})";
case object[] array:
var arraySb = new StringBuilder();
arraySb.Append("new ");
arraySb.Append(type.GetScopedCSharpName(usingScope));
arraySb.Append('[');
arraySb.Append(array.Length);
arraySb.Append(']');
if (array.Length > 0)
{
arraySb.Append(" {");
for (int i = 0; i < array.Length; i++)
{
if (array[i] is CustomAttributeArgument arrayArgument) // Used for array with different entries, see BlobReader for more info
arraySb.Append(arrayArgument.Value.ToCSharpValue(arrayArgument.Type, usingScope));
if (i + 1 != array.Length)
arraySb.Append(", ");
}
arraySb.Append(" }");
}
return arraySb.ToString();
}
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);

View File

@@ -1,158 +1,158 @@
/*
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
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; }
// Root definition: the field with Definition != null
protected readonly FieldInfo rootDefinition;
// Offsets for reference types start at 0x8 or 0x10 due to Il2CppObject "header" containing 2 pointers
// Value types don't have this header but the offsets are still stored as starting at 0x8 or 0x10, so we have to subtract this
// Open generic types have offsets that aren't known until runtime
private readonly long rawOffset;
public long Offset => DeclaringType.ContainsGenericParameters? 0 :
rawOffset - (DeclaringType.IsValueType && !IsStatic? (Assembly.Model.Package.BinaryImage.Bits / 8) * 2 : 0);
public bool HasFieldRVA => (Attributes & FieldAttributes.HasFieldRVA) != 0;
public ulong DefaultValueMetadataAddress { get; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition);
public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0;
public object DefaultValue { get; }
public string GetDefaultValueString(Scope usingScope = null) => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType, usingScope) : "";
// Information/flags about the field
public FieldAttributes Attributes { get; }
// Type of field
private readonly TypeRef fieldTypeReference;
public TypeInfo FieldType => fieldTypeReference.Value;
// 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;
// Returns true if using this field requires that the using method is declared as unsafe
public bool RequiresUnsafeContext => FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any();
public override MemberTypes MemberType => MemberTypes.Field;
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Fields[fieldIndex];
MetadataToken = (int) Definition.token;
Index = fieldIndex;
Name = pkg.Strings[Definition.nameIndex];
rawOffset = pkg.FieldOffsets[fieldIndex];
rootDefinition = this;
fieldTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.typeIndex);
var fieldType = pkg.TypeReferences[Definition.typeIndex];
// Copy attributes
Attributes = (FieldAttributes) fieldType.attrs;
// Default initialization value if present
if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) {
DefaultValue = value.variant;
DefaultValueMetadataAddress = value.address;
}
}
public FieldInfo(FieldInfo fieldDef, TypeInfo declaringType) : base(declaringType) {
if (fieldDef.Definition == null)
throw new ArgumentException("Argument must be a bare field definition");
rootDefinition = fieldDef;
Name = fieldDef.Name;
Attributes = fieldDef.Attributes;
fieldTypeReference = TypeRef.FromTypeInfo(fieldDef.FieldType.SubstituteGenericArguments(declaringType.GetGenericArguments()));
DefaultValue = fieldDef.DefaultValue;
DefaultValueMetadataAddress = fieldDef.DefaultValueMetadataAddress;
}
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 (IsLiteral)
modifiers.Append("const ");
// All const fields are also static by implication
else if (IsStatic)
modifiers.Append("static ");
if (IsInitOnly)
modifiers.Append("readonly ");
if (RequiresUnsafeContext)
modifiers.Append("unsafe ");
if (IsPinvokeImpl)
modifiers.Append("extern ");
if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
modifiers.Append("fixed ");
return modifiers.ToString();
}
}
/*
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection {
public class FieldInfo : MemberInfo // L-TODO: Add support for [ThreadLocal] fields
{
// IL2CPP-specific data
public Il2CppFieldDefinition Definition { get; }
public int Index { get; }
// Root definition: the field with Definition != null
protected readonly FieldInfo rootDefinition;
// Offsets for reference types start at 0x8 or 0x10 due to Il2CppObject "header" containing 2 pointers
// Value types don't have this header but the offsets are still stored as starting at 0x8 or 0x10, so we have to subtract this
// Open generic types have offsets that aren't known until runtime
private readonly long rawOffset;
public long Offset => DeclaringType.ContainsGenericParameters? 0 :
rawOffset - (DeclaringType.IsValueType && !IsStatic? (Assembly.Model.Package.BinaryImage.Bits / 8) * 2 : 0);
public bool HasFieldRVA => (Attributes & FieldAttributes.HasFieldRVA) != 0;
public ulong DefaultValueMetadataAddress { get; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition);
public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0;
public object DefaultValue { get; }
public string GetDefaultValueString(Scope usingScope = null) => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType, usingScope) : "";
// Information/flags about the field
public FieldAttributes Attributes { get; }
// Type of field
private readonly TypeRef fieldTypeReference;
public TypeInfo FieldType => fieldTypeReference.Value;
// 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;
// Returns true if using this field requires that the using method is declared as unsafe
public bool RequiresUnsafeContext => FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any();
public override MemberTypes MemberType => MemberTypes.Field;
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Fields[fieldIndex];
MetadataToken = (int) Definition.token;
Index = fieldIndex;
Name = pkg.Strings[Definition.nameIndex];
rawOffset = pkg.FieldOffsets[fieldIndex];
rootDefinition = this;
fieldTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.typeIndex);
var fieldType = pkg.TypeReferences[Definition.typeIndex];
// Copy attributes
Attributes = (FieldAttributes) fieldType.attrs;
// Default initialization value if present
if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) {
DefaultValue = value.variant;
DefaultValueMetadataAddress = value.address;
}
}
public FieldInfo(FieldInfo fieldDef, TypeInfo declaringType) : base(declaringType) {
if (fieldDef.Definition == null)
throw new ArgumentException("Argument must be a bare field definition");
rootDefinition = fieldDef;
Name = fieldDef.Name;
Attributes = fieldDef.Attributes;
fieldTypeReference = TypeRef.FromTypeInfo(fieldDef.FieldType.SubstituteGenericArguments(declaringType.GetGenericArguments()));
DefaultValue = fieldDef.DefaultValue;
DefaultValueMetadataAddress = fieldDef.DefaultValueMetadataAddress;
}
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 (IsLiteral)
modifiers.Append("const ");
// All const fields are also static by implication
else if (IsStatic)
modifiers.Append("static ");
if (IsInitOnly)
modifiers.Append("readonly ");
if (RequiresUnsafeContext)
modifiers.Append("unsafe ");
if (IsPinvokeImpl)
modifiers.Append("extern ");
if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
modifiers.Append("fixed ");
return modifiers.ToString();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -57,6 +57,7 @@ namespace Il2CppInspector.Reflection
// List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices
public ConcurrentDictionary<int, CustomAttributeData> AttributesByIndices { get; } = new ConcurrentDictionary<int, CustomAttributeData>();
public ConcurrentDictionary<int, List<CustomAttributeData>> AttributesByDataIndices { get; } = [];
// List of unique custom attributes generators indexed by type (multiple indices above may refer to a single generator function)
public Dictionary<TypeInfo, List<CustomAttributeData>> CustomAttributeGenerators { get; }
@@ -254,7 +255,7 @@ namespace Il2CppInspector.Reflection
// Primitive types
default:
underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type);
underlyingType = GetTypeDefinitionFromTypeEnum(typeRef.type);
break;
}
@@ -263,12 +264,20 @@ namespace Il2CppInspector.Reflection
}
// Basic primitive types are specified via a flag value
private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) {
if ((int)t >= Il2CppConstants.FullNameTypeString.Count)
return null;
public TypeInfo GetTypeDefinitionFromTypeEnum(Il2CppTypeEnum t)
{
// IL2CPP_TYPE_IL2CPP_TYPE_INDEX is handled seperately because it has enum value 0xff
var fqn = t switch
{
Il2CppTypeEnum.IL2CPP_TYPE_IL2CPP_TYPE_INDEX => "System.Type",
_ => (int) t >= Il2CppConstants.FullNameTypeString.Count
? null
: Il2CppConstants.FullNameTypeString[(int) t]
};
var fqn = Il2CppConstants.FullNameTypeString[(int)t];
return TypesByFullName[fqn];
return fqn == null
? null
: TypesByFullName[fqn];
}
// Get a TypeRef by its virtual address
@@ -311,12 +320,11 @@ namespace Il2CppInspector.Reflection
if (Package.Version <= 24.0)
return customAttributeIndex;
if (Package.Version >= 29)
// From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image
// v29 uses this same system but with CustomAttributeDataRanges instead
if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint)token, out var index))
return -1;
// 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((uint) token, out var index))
return -1;
return index;
}