Support reading and writing custom arguments for v29
This commit is contained in:
192
Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs
Normal file
192
Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user