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>
|
||||||
|
|
||||||
<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="McMaster.NETCore.Plugins" Version="1.3.1" />
|
||||||
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
|
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
|
||||||
<NoWarn>NU1605</NoWarn>
|
<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
|
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Il2CppInspector.Reflection
|
namespace Il2CppInspector.Reflection
|
||||||
{
|
{
|
||||||
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?view=netframework-4.8
|
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?view=netframework-4.8
|
||||||
public class CustomAttributeData
|
public class CustomAttributeData
|
||||||
{
|
{
|
||||||
// IL2CPP-specific data
|
// IL2CPP-specific data
|
||||||
public TypeModel Model => AttributeType.Assembly.Model;
|
public TypeModel Model => AttributeType.Assembly.Model;
|
||||||
public int Index { get; set; }
|
public int Index { get; set; }
|
||||||
|
|
||||||
// The type of the attribute
|
// The type of the attribute
|
||||||
public TypeInfo AttributeType { get; set; }
|
public TypeInfo AttributeType { get; set; }
|
||||||
|
|
||||||
public (ulong Start, ulong End) VirtualAddress =>
|
// v29 custom attribute info
|
||||||
// The last one will be wrong but there is no way to calculate it
|
public CustomAttributeCtor CtorInfo { get; set; }
|
||||||
(Model.Package.CustomAttributeGenerators[Index], Model.Package.FunctionAddresses[Model.Package.CustomAttributeGenerators[Index]]);
|
|
||||||
|
// Pre-v29 Properties used for stub Attributes
|
||||||
// C++ method names
|
|
||||||
// TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness
|
public (ulong Start, ulong End) VirtualAddress => CtorInfo != null ? (0, 0) :
|
||||||
public string Name => $"{AttributeType.Name.ToCIdentifier()}_CustomAttributesCacheGenerator";
|
// 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 signature
|
|
||||||
public string Signature => $"void {Name}(CustomAttributesCache *)";
|
// C++ method names
|
||||||
|
// TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness
|
||||||
public override string ToString() => "[" + AttributeType.FullName + "]";
|
public string Name => $"{AttributeType.Name.ToCIdentifier()}_CustomAttributesCacheGenerator";
|
||||||
|
|
||||||
// Get the machine code of the C++ function
|
// C++ method signature
|
||||||
public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start));
|
public string Signature => $"void {Name}(CustomAttributesCache *)";
|
||||||
|
|
||||||
// Get all the custom attributes for a given assembly, type, member or parameter
|
public override string ToString() => "[" + AttributeType.FullName + "]";
|
||||||
private static IEnumerable<CustomAttributeData> getCustomAttributes(Assembly asm, int customAttributeIndex) {
|
|
||||||
if (customAttributeIndex < 0)
|
// Get the machine code of the C++ function
|
||||||
yield break;
|
public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start));
|
||||||
|
|
||||||
var pkg = asm.Model.Package;
|
public IEnumerable<TypeInfo> GetAllTypeReferences()
|
||||||
|
{
|
||||||
// Attribute type ranges weren't included before v21 (customASttributeGenerators was though)
|
yield return AttributeType;
|
||||||
if (pkg.Version < 21)
|
|
||||||
yield break;
|
if (CtorInfo != null)
|
||||||
|
{
|
||||||
if (pkg.Version < 29)
|
foreach (var typeRef in GetTypeReferences(CtorInfo.Arguments))
|
||||||
{
|
yield return typeRef;
|
||||||
var range = pkg.AttributeTypeRanges[customAttributeIndex];
|
|
||||||
for (var i = range.start; i < range.start + range.count; i++)
|
foreach (var typeRef in GetTypeReferences(CtorInfo.Fields))
|
||||||
{
|
yield return typeRef;
|
||||||
var typeIndex = pkg.AttributeTypeIndices[i];
|
|
||||||
|
foreach (var typeRef in GetTypeReferences(CtorInfo.Properties))
|
||||||
if (asm.Model.AttributesByIndices.TryGetValue(i, out var attribute))
|
yield return typeRef;
|
||||||
{
|
}
|
||||||
yield return attribute;
|
|
||||||
continue;
|
yield break;
|
||||||
}
|
|
||||||
|
static IEnumerable<TypeInfo> GetTypeReferences(IEnumerable<CustomAttributeArgument> arguments)
|
||||||
attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] };
|
{
|
||||||
|
foreach (var arg in arguments)
|
||||||
asm.Model.AttributesByIndices.TryAdd(i, attribute);
|
{
|
||||||
yield return attribute;
|
yield return arg.Type;
|
||||||
}
|
|
||||||
}
|
switch (arg.Value)
|
||||||
else
|
{
|
||||||
{
|
case TypeInfo info:
|
||||||
Console.WriteLine("Skipping custom attributes for 29+");
|
yield return info;
|
||||||
yield break;
|
break;
|
||||||
}
|
case CustomAttributeArgument[] array:
|
||||||
}
|
foreach (var info in GetTypeReferences(array))
|
||||||
|
yield return info;
|
||||||
private static IList<CustomAttributeData> getCustomAttributes(Assembly asm, int token, int customAttributeIndex) =>
|
break;
|
||||||
getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList();
|
case TypeInfo[] infos:
|
||||||
|
foreach (var info in infos)
|
||||||
public static IList<CustomAttributeData> GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.MetadataToken, asm.AssemblyDefinition.customAttributeIndex);
|
yield return info;
|
||||||
public static IList<CustomAttributeData> GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.MetadataToken, evt.Definition.customAttributeIndex);
|
break;
|
||||||
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>();
|
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -22,29 +21,90 @@ namespace Il2CppInspector.Reflection
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
foreach (var cad in attributes) {
|
foreach (var cad in attributes) {
|
||||||
// Find a constructor that either has no parameters, or all optional parameters
|
if (cad.CtorInfo != null)
|
||||||
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
|
{
|
||||||
|
// 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 name = ctor.Ctor.DeclaringType.GetScopedCSharpName(scope);
|
||||||
var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
|
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||||
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
|
if (suffix != -1)
|
||||||
var arguments = "";
|
name = name[..suffix];
|
||||||
|
|
||||||
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
sb.Append(linePrefix);
|
||||||
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
|
sb.Append('[');
|
||||||
commentStart = "";
|
sb.Append(attributePrefix);
|
||||||
commentEnd = "";
|
sb.Append(name);
|
||||||
arguments = "(AttributeTargets.All)";
|
|
||||||
|
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);
|
// Find a constructor that either has no parameters, or all optional parameters
|
||||||
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
|
||||||
if (suffix != -1)
|
|
||||||
name = name[..suffix];
|
// IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile
|
||||||
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
var commentStart = mustCompile && !parameterlessConstructor ? inline ? "/* " : "// " : "";
|
||||||
if (emitPointer)
|
var commentEnd = commentStart.Length > 0 && inline ? " */" : "";
|
||||||
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
|
var arguments = "";
|
||||||
sb.Append(inline? " ":"\n");
|
|
||||||
|
// 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();
|
return sb.ToString();
|
||||||
@@ -120,31 +180,60 @@ namespace Il2CppInspector.Reflection
|
|||||||
|
|
||||||
// Output a value in C#-friendly syntax
|
// Output a value in C#-friendly syntax
|
||||||
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
|
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
|
||||||
if (value is bool)
|
switch (value)
|
||||||
return (bool) value ? "true" : "false";
|
{
|
||||||
if (value is float f)
|
case bool b:
|
||||||
return value switch {
|
return b ? "true" : "false";
|
||||||
float.PositiveInfinity => "1F / 0F",
|
case float f:
|
||||||
float.NegativeInfinity => "-1F / 0F",
|
return value switch {
|
||||||
float.NaN => "0F / 0F",
|
float.PositiveInfinity => "1F / 0F",
|
||||||
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
float.NegativeInfinity => "-1F / 0F",
|
||||||
};
|
float.NaN => "0F / 0F",
|
||||||
if (value is double d)
|
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
||||||
return value switch {
|
};
|
||||||
double.PositiveInfinity => "1D / 0D",
|
case double d:
|
||||||
double.NegativeInfinity => "-1D / 0D",
|
return value switch {
|
||||||
double.NaN => "0D / 0D",
|
double.PositiveInfinity => "1D / 0D",
|
||||||
_ => d.ToString(CultureInfo.InvariantCulture)
|
double.NegativeInfinity => "-1D / 0D",
|
||||||
};
|
double.NaN => "0D / 0D",
|
||||||
if (value is string str) {
|
_ => d.ToString(CultureInfo.InvariantCulture)
|
||||||
return $"\"{str.ToEscapedString()}\"";
|
};
|
||||||
}
|
case string str:
|
||||||
if (value is char) {
|
return $"\"{str.ToEscapedString()}\"";
|
||||||
var cValue = (int) (char) value;
|
case char c:
|
||||||
if (cValue < 32 || cValue > 126)
|
{
|
||||||
return $"'\\x{cValue:x4}'";
|
var cValue = (int) c;
|
||||||
return $"'{value}'";
|
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) {
|
if (type.IsEnum) {
|
||||||
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
|
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
|
||||||
var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v);
|
var 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
|
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
|
||||||
|
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace Il2CppInspector.Reflection {
|
namespace Il2CppInspector.Reflection {
|
||||||
public class FieldInfo : MemberInfo
|
public class FieldInfo : MemberInfo // L-TODO: Add support for [ThreadLocal] fields
|
||||||
{
|
{
|
||||||
// IL2CPP-specific data
|
// IL2CPP-specific data
|
||||||
public Il2CppFieldDefinition Definition { get; }
|
public Il2CppFieldDefinition Definition { get; }
|
||||||
public int Index { get; }
|
public int Index { get; }
|
||||||
// Root definition: the field with Definition != null
|
// Root definition: the field with Definition != null
|
||||||
protected readonly FieldInfo rootDefinition;
|
protected readonly FieldInfo rootDefinition;
|
||||||
|
|
||||||
// Offsets for reference types start at 0x8 or 0x10 due to Il2CppObject "header" containing 2 pointers
|
// 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
|
// 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
|
// Open generic types have offsets that aren't known until runtime
|
||||||
private readonly long rawOffset;
|
private readonly long rawOffset;
|
||||||
public long Offset => DeclaringType.ContainsGenericParameters? 0 :
|
public long Offset => DeclaringType.ContainsGenericParameters? 0 :
|
||||||
rawOffset - (DeclaringType.IsValueType && !IsStatic? (Assembly.Model.Package.BinaryImage.Bits / 8) * 2 : 0);
|
rawOffset - (DeclaringType.IsValueType && !IsStatic? (Assembly.Model.Package.BinaryImage.Bits / 8) * 2 : 0);
|
||||||
|
|
||||||
public bool HasFieldRVA => (Attributes & FieldAttributes.HasFieldRVA) != 0;
|
public bool HasFieldRVA => (Attributes & FieldAttributes.HasFieldRVA) != 0;
|
||||||
public ulong DefaultValueMetadataAddress { get; }
|
public ulong DefaultValueMetadataAddress { get; }
|
||||||
|
|
||||||
// Custom attributes for this member
|
// Custom attributes for this member
|
||||||
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition);
|
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition);
|
||||||
|
|
||||||
public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0;
|
public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0;
|
||||||
public object DefaultValue { get; }
|
public object DefaultValue { get; }
|
||||||
|
|
||||||
public string GetDefaultValueString(Scope usingScope = null) => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType, usingScope) : "";
|
public string GetDefaultValueString(Scope usingScope = null) => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType, usingScope) : "";
|
||||||
|
|
||||||
// Information/flags about the field
|
// Information/flags about the field
|
||||||
public FieldAttributes Attributes { get; }
|
public FieldAttributes Attributes { get; }
|
||||||
|
|
||||||
// Type of field
|
// Type of field
|
||||||
private readonly TypeRef fieldTypeReference;
|
private readonly TypeRef fieldTypeReference;
|
||||||
public TypeInfo FieldType => fieldTypeReference.Value;
|
public TypeInfo FieldType => fieldTypeReference.Value;
|
||||||
|
|
||||||
// For the Is* definitions below, see:
|
// 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
|
// 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
|
// True if the field is declared as internal
|
||||||
public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly;
|
public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly;
|
||||||
|
|
||||||
// True if the field is declared as protected
|
// True if the field is declared as protected
|
||||||
public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family;
|
public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family;
|
||||||
|
|
||||||
// True if the field is declared as 'protected private' (always false)
|
// True if the field is declared as 'protected private' (always false)
|
||||||
public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem;
|
public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem;
|
||||||
|
|
||||||
// True if the field is declared as protected public
|
// True if the field is declared as protected public
|
||||||
public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem;
|
public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem;
|
||||||
|
|
||||||
// True if the field is declared as readonly
|
// True if the field is declared as readonly
|
||||||
public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly;
|
public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly;
|
||||||
|
|
||||||
// True if the field is const
|
// True if the field is const
|
||||||
public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal;
|
public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal;
|
||||||
|
|
||||||
// True if the field has the NonSerialized attribute
|
// True if the field has the NonSerialized attribute
|
||||||
public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized;
|
public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized;
|
||||||
|
|
||||||
// True if the field is extern
|
// True if the field is extern
|
||||||
public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl;
|
public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl;
|
||||||
|
|
||||||
// True if the field is declared a private
|
// True if the field is declared a private
|
||||||
public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private;
|
public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private;
|
||||||
|
|
||||||
// True if the field is declared as public
|
// True if the field is declared as public
|
||||||
public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public;
|
public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public;
|
||||||
|
|
||||||
// True if the field has a special name
|
// True if the field has a special name
|
||||||
public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) == FieldAttributes.SpecialName;
|
public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) == FieldAttributes.SpecialName;
|
||||||
|
|
||||||
// True if the field is declared as static
|
// True if the field is declared as static
|
||||||
public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static;
|
public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static;
|
||||||
|
|
||||||
// Returns true if using this field requires that the using method is declared as unsafe
|
// 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 bool RequiresUnsafeContext => FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any();
|
||||||
|
|
||||||
public override MemberTypes MemberType => MemberTypes.Field;
|
public override MemberTypes MemberType => MemberTypes.Field;
|
||||||
|
|
||||||
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
|
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
|
||||||
base(declaringType) {
|
base(declaringType) {
|
||||||
Definition = pkg.Fields[fieldIndex];
|
Definition = pkg.Fields[fieldIndex];
|
||||||
MetadataToken = (int) Definition.token;
|
MetadataToken = (int) Definition.token;
|
||||||
Index = fieldIndex;
|
Index = fieldIndex;
|
||||||
Name = pkg.Strings[Definition.nameIndex];
|
Name = pkg.Strings[Definition.nameIndex];
|
||||||
|
|
||||||
rawOffset = pkg.FieldOffsets[fieldIndex];
|
rawOffset = pkg.FieldOffsets[fieldIndex];
|
||||||
|
|
||||||
rootDefinition = this;
|
rootDefinition = this;
|
||||||
|
|
||||||
fieldTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.typeIndex);
|
fieldTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.typeIndex);
|
||||||
var fieldType = pkg.TypeReferences[Definition.typeIndex];
|
var fieldType = pkg.TypeReferences[Definition.typeIndex];
|
||||||
|
|
||||||
// Copy attributes
|
// Copy attributes
|
||||||
Attributes = (FieldAttributes) fieldType.attrs;
|
Attributes = (FieldAttributes) fieldType.attrs;
|
||||||
|
|
||||||
// Default initialization value if present
|
// Default initialization value if present
|
||||||
if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) {
|
if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) {
|
||||||
DefaultValue = value.variant;
|
DefaultValue = value.variant;
|
||||||
DefaultValueMetadataAddress = value.address;
|
DefaultValueMetadataAddress = value.address;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public FieldInfo(FieldInfo fieldDef, TypeInfo declaringType) : base(declaringType) {
|
public FieldInfo(FieldInfo fieldDef, TypeInfo declaringType) : base(declaringType) {
|
||||||
if (fieldDef.Definition == null)
|
if (fieldDef.Definition == null)
|
||||||
throw new ArgumentException("Argument must be a bare field definition");
|
throw new ArgumentException("Argument must be a bare field definition");
|
||||||
|
|
||||||
rootDefinition = fieldDef;
|
rootDefinition = fieldDef;
|
||||||
|
|
||||||
Name = fieldDef.Name;
|
Name = fieldDef.Name;
|
||||||
Attributes = fieldDef.Attributes;
|
Attributes = fieldDef.Attributes;
|
||||||
fieldTypeReference = TypeRef.FromTypeInfo(fieldDef.FieldType.SubstituteGenericArguments(declaringType.GetGenericArguments()));
|
fieldTypeReference = TypeRef.FromTypeInfo(fieldDef.FieldType.SubstituteGenericArguments(declaringType.GetGenericArguments()));
|
||||||
|
|
||||||
DefaultValue = fieldDef.DefaultValue;
|
DefaultValue = fieldDef.DefaultValue;
|
||||||
DefaultValueMetadataAddress = fieldDef.DefaultValueMetadataAddress;
|
DefaultValueMetadataAddress = fieldDef.DefaultValueMetadataAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetAccessModifierString() => this switch {
|
public string GetAccessModifierString() => this switch {
|
||||||
{ IsPrivate: true } => "private ",
|
{ IsPrivate: true } => "private ",
|
||||||
{ IsPublic: true } => "public ",
|
{ IsPublic: true } => "public ",
|
||||||
{ IsFamily: true } => "protected ",
|
{ IsFamily: true } => "protected ",
|
||||||
{ IsAssembly: true } => "internal ",
|
{ IsAssembly: true } => "internal ",
|
||||||
{ IsFamilyOrAssembly: true } => "protected internal ",
|
{ IsFamilyOrAssembly: true } => "protected internal ",
|
||||||
{ IsFamilyAndAssembly: true } => "private protected ",
|
{ IsFamilyAndAssembly: true } => "private protected ",
|
||||||
_ => ""
|
_ => ""
|
||||||
};
|
};
|
||||||
|
|
||||||
public string GetModifierString() {
|
public string GetModifierString() {
|
||||||
var modifiers = new StringBuilder(GetAccessModifierString());
|
var modifiers = new StringBuilder(GetAccessModifierString());
|
||||||
|
|
||||||
if (IsLiteral)
|
if (IsLiteral)
|
||||||
modifiers.Append("const ");
|
modifiers.Append("const ");
|
||||||
// All const fields are also static by implication
|
// All const fields are also static by implication
|
||||||
else if (IsStatic)
|
else if (IsStatic)
|
||||||
modifiers.Append("static ");
|
modifiers.Append("static ");
|
||||||
if (IsInitOnly)
|
if (IsInitOnly)
|
||||||
modifiers.Append("readonly ");
|
modifiers.Append("readonly ");
|
||||||
if (RequiresUnsafeContext)
|
if (RequiresUnsafeContext)
|
||||||
modifiers.Append("unsafe ");
|
modifiers.Append("unsafe ");
|
||||||
if (IsPinvokeImpl)
|
if (IsPinvokeImpl)
|
||||||
modifiers.Append("extern ");
|
modifiers.Append("extern ");
|
||||||
if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
|
if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
|
||||||
modifiers.Append("fixed ");
|
modifiers.Append("fixed ");
|
||||||
return modifiers.ToString();
|
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
|
// List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices
|
||||||
public ConcurrentDictionary<int, CustomAttributeData> AttributesByIndices { get; } = new ConcurrentDictionary<int, CustomAttributeData>();
|
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)
|
// 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; }
|
public Dictionary<TypeInfo, List<CustomAttributeData>> CustomAttributeGenerators { get; }
|
||||||
@@ -254,7 +255,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
|
|
||||||
// Primitive types
|
// Primitive types
|
||||||
default:
|
default:
|
||||||
underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type);
|
underlyingType = GetTypeDefinitionFromTypeEnum(typeRef.type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,12 +264,20 @@ namespace Il2CppInspector.Reflection
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Basic primitive types are specified via a flag value
|
// Basic primitive types are specified via a flag value
|
||||||
private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) {
|
public TypeInfo GetTypeDefinitionFromTypeEnum(Il2CppTypeEnum t)
|
||||||
if ((int)t >= Il2CppConstants.FullNameTypeString.Count)
|
{
|
||||||
return null;
|
// 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 fqn == null
|
||||||
return TypesByFullName[fqn];
|
? null
|
||||||
|
: TypesByFullName[fqn];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a TypeRef by its virtual address
|
// Get a TypeRef by its virtual address
|
||||||
@@ -311,12 +320,11 @@ namespace Il2CppInspector.Reflection
|
|||||||
if (Package.Version <= 24.0)
|
if (Package.Version <= 24.0)
|
||||||
return customAttributeIndex;
|
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;
|
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;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using NoisyCowStudios.Bin2Object;
|
using NoisyCowStudios.Bin2Object;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Il2CppInspector.Utils;
|
namespace Il2CppInspector.Utils;
|
||||||
|
|
||||||
@@ -70,6 +71,10 @@ public static class BlobReader
|
|||||||
if (length == -1)
|
if (length == -1)
|
||||||
break;
|
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 arrayElementType = ReadEncodedTypeEnum(inspector, blob, out var arrayElementDef);
|
||||||
var arrayElementsAreDifferent = blob.ReadByte();
|
var arrayElementsAreDifferent = blob.ReadByte();
|
||||||
|
|
||||||
@@ -79,10 +84,10 @@ public static class BlobReader
|
|||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
var elementType = ReadEncodedTypeEnum(inspector, blob, out var elementTypeDef);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -92,7 +97,7 @@ public static class BlobReader
|
|||||||
array[i] = GetConstantValueFromBlob(inspector, arrayElementType, blob);
|
array[i] = GetConstantValueFromBlob(inspector, arrayElementType, blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = new ConstantBlobArray(arrayElementDef, array);
|
value = new ConstantBlobArray(arrayElementDef, array, false, arrayElementType);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -107,8 +112,11 @@ public static class BlobReader
|
|||||||
value = inspector.TypeReferences[index];
|
value = inspector.TypeReferences[index];
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Debugger.Break();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
@@ -123,15 +131,19 @@ public static class BlobReader
|
|||||||
if (typeEnum == Il2CppTypeEnum.IL2CPP_TYPE_ENUM)
|
if (typeEnum == Il2CppTypeEnum.IL2CPP_TYPE_ENUM)
|
||||||
{
|
{
|
||||||
var typeIndex = blob.ReadCompressedInt32();
|
var typeIndex = blob.ReadCompressedInt32();
|
||||||
enumType = inspector.TypeDefinitions[typeIndex];
|
var typeHandle = (uint)inspector.TypeReferences[typeIndex].datapoint;
|
||||||
typeEnum = inspector.TypeReferences[enumType.byvalTypeIndex].type;
|
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
|
// This technically also handles SZARRAY (System.Array) and all others by just returning their system type
|
||||||
|
|
||||||
return typeEnum;
|
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