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

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using dnlib.DotNet;
using Il2CppInspector.Reflection;
using Il2CppInspector.Utils;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
public class CustomAttributeDataReader
{
private readonly Il2CppInspector _inspector;
private readonly Assembly _assembly;
private readonly BinaryObjectStream _data;
private readonly uint _start;
private readonly uint _end;
private readonly long _ctorBufferStart;
private readonly long _dataBufferStart;
public uint Count { get; }
public CustomAttributeDataReader(Il2CppInspector inspector, Assembly assembly, BinaryObjectStream data, uint startOffset, uint endOffset)
{
_inspector = inspector;
_assembly = assembly;
_data = data;
_start = startOffset;
_end = endOffset;
data.Position = _start;
Count = data.ReadCompressedUInt32();
_ctorBufferStart = data.Position;
_dataBufferStart = _ctorBufferStart + Count * sizeof(int);
}
public IEnumerable<CustomAttributeCtor> Read()
{
_data.Position = _ctorBufferStart;
var ctors = new CustomAttributeCtor[Count];
for (int i = 0; i < Count; i++)
{
ctors[i] = new CustomAttributeCtor();
var ctorIndex = _data.ReadUInt32();
ctors[i].Ctor = _assembly.Model.MethodsByDefinitionIndex[ctorIndex];
}
_data.Position = _dataBufferStart;
for (int i = 0; i < Count; i++)
{
var ctor = ctors[i];
var attrClass = ctor.Ctor.DeclaringType;
var argumentCount = _data.ReadCompressedUInt32();
var fieldCount = _data.ReadCompressedUInt32();
var propertyCount = _data.ReadCompressedUInt32();
ctor.Arguments = new CustomAttributeArgument[argumentCount];
for (int j = 0; j < argumentCount; j++)
{
ctor.Arguments[j] = new CustomAttributeArgument();
ReadAttributeDataValue(ctor.Arguments[j]);
}
ctor.Fields = new CustomAttributeFieldArgument[fieldCount];
for (int j = 0; j < fieldCount; j++)
{
ctor.Fields[j] = new CustomAttributeFieldArgument();
ReadAttributeDataValue(ctor.Fields[j]);
var (fieldClass, fieldIndex) = ReadCustomAttributeNamedArgumentClassAndIndex(attrClass);
ctor.Fields[j].Field = fieldClass.DeclaredFields[fieldIndex];
}
ctor.Properties = new CustomAttributePropertyArgument[propertyCount];
for (int j = 0; j < propertyCount; j++)
{
ctor.Properties[j] = new CustomAttributePropertyArgument();
ReadAttributeDataValue(ctor.Properties[j]);
var (propertyClass, propertyIndex) = ReadCustomAttributeNamedArgumentClassAndIndex(attrClass);
ctor.Properties[j].Property = propertyClass.DeclaredProperties[propertyIndex];
}
yield return ctor;
}
if (_data.Position != _end)
Debugger.Break();
}
private void ReadAttributeDataValue(CustomAttributeArgument arg)
{
var type = BlobReader.ReadEncodedTypeEnum(_inspector, _data, out var typeDef);
var value = BlobReader.GetConstantValueFromBlob(_inspector, type, _data);
if (value is BlobReader.ConstantBlobArray blobArray)
{
arg.Type = ConvertTypeDef(blobArray.ArrayTypeDef, blobArray.ArrayTypeEnum);
arg.Value = blobArray.Elements.Select(ConvertAttributeValue).ToArray();
}
else
{
arg.Type = ConvertTypeDef(typeDef, type);
arg.Value = ConvertAttributeValue(value);
}
}
private object ConvertAttributeValue(object value)
{
switch (value)
{
case Il2CppType type:
return _assembly.Model.TypesByReferenceIndex[_inspector.TypeReferences.IndexOf(type)];
case BlobReader.ConstantBlobArray blobArray:
{
var arrValue = new CustomAttributeArgument
{
Type = ConvertTypeDef(blobArray.ArrayTypeDef, blobArray.ArrayTypeEnum),
Value = blobArray.Elements.Select(ConvertAttributeValue).ToArray()
};
return arrValue;
}
case BlobReader.ConstantBlobArrayElement blobElem:
{
var subArgument = new CustomAttributeArgument
{
Type = ConvertTypeDef(blobElem.TypeDef, blobElem.TypeEnum),
Value = blobElem.Value
};
return subArgument;
}
default:
return value;
}
}
private TypeInfo ConvertTypeDef(Il2CppTypeDefinition typeDef, Il2CppTypeEnum type)
=> typeDef == null
? _assembly.Model.GetTypeDefinitionFromTypeEnum(type)
: _assembly.Model.TypesByDefinitionIndex[Array.IndexOf(_inspector.TypeDefinitions, typeDef)];
private (TypeInfo, int) ReadCustomAttributeNamedArgumentClassAndIndex(TypeInfo attrInfo)
{
var memberIndex = _data.ReadCompressedInt32();
if (memberIndex >= 0) // Negative indices mean that it's a member of a base class
return (attrInfo, memberIndex);
memberIndex = -(memberIndex + 1);
var typeDefIndex = _data.ReadCompressedUInt32();
var typeInfo = _assembly.Model.TypesByDefinitionIndex[typeDefIndex];
return (typeInfo, memberIndex);
}
}
public class CustomAttributeCtor
{
public MethodBase Ctor { get; set; }
public CustomAttributeArgument[] Arguments { get; set; }
public CustomAttributeFieldArgument[] Fields { get; set; }
public CustomAttributePropertyArgument[] Properties { get; set; }
}
public class CustomAttributeArgument
{
public TypeInfo Type { get; set; }
public object Value { get; set; }
}
public class CustomAttributeFieldArgument : CustomAttributeArgument
{
public FieldInfo Field { get; set; }
}
public class CustomAttributePropertyArgument : CustomAttributeArgument
{
public PropertyInfo Property { get; set; }
}
}

View File

@@ -38,7 +38,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="dnlib" Version="3.3.2" />
<PackageReference Include="dnlib" Version="4.3.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.1" />
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
<NoWarn>NU1605</NoWarn>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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;
}

View File

@@ -1,6 +1,7 @@
using NoisyCowStudios.Bin2Object;
using System.Text;
using System;
using System.Diagnostics;
namespace Il2CppInspector.Utils;
@@ -70,6 +71,10 @@ public static class BlobReader
if (length == -1)
break;
// This is only used in custom arguments.
// We actually want the reflection TypeInfo here, but as we do not have it yet
// we store everything in a custom array type to be changed out later in the TypeModel.
var arrayElementType = ReadEncodedTypeEnum(inspector, blob, out var arrayElementDef);
var arrayElementsAreDifferent = blob.ReadByte();
@@ -79,10 +84,10 @@ public static class BlobReader
for (int i = 0; i < length; i++)
{
var elementType = ReadEncodedTypeEnum(inspector, blob, out var elementTypeDef);
array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob));
array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob), elementType);
}
value = new ConstantBlobArray(arrayElementDef, array);
value = new ConstantBlobArray(arrayElementDef, array, true, arrayElementType);
}
else
{
@@ -92,7 +97,7 @@ public static class BlobReader
array[i] = GetConstantValueFromBlob(inspector, arrayElementType, blob);
}
value = new ConstantBlobArray(arrayElementDef, array);
value = new ConstantBlobArray(arrayElementDef, array, false, arrayElementType);
}
break;
@@ -107,8 +112,11 @@ public static class BlobReader
value = inspector.TypeReferences[index];
break;
case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE:
break;
default:
Debugger.Break();
break;
}
return value;
@@ -123,15 +131,19 @@ public static class BlobReader
if (typeEnum == Il2CppTypeEnum.IL2CPP_TYPE_ENUM)
{
var typeIndex = blob.ReadCompressedInt32();
enumType = inspector.TypeDefinitions[typeIndex];
typeEnum = inspector.TypeReferences[enumType.byvalTypeIndex].type;
var typeHandle = (uint)inspector.TypeReferences[typeIndex].datapoint;
enumType = inspector.TypeDefinitions[typeHandle];
var elementTypeHandle = inspector.TypeReferences[enumType.elementTypeIndex].datapoint;
var elementType = inspector.TypeDefinitions[elementTypeHandle];
typeEnum = inspector.TypeReferences[elementType.byvalTypeIndex].type;
}
// This technically also handles SZARRAY (System.Array) and all others by just returning their system type
return typeEnum;
}
public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements);
public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements, bool DifferentElements, Il2CppTypeEnum ArrayTypeEnum);
public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object value);
public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object Value, Il2CppTypeEnum TypeEnum);
}