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>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ using System.Linq;
|
|||||||
using dnlib.DotNet;
|
using dnlib.DotNet;
|
||||||
using dnlib.DotNet.Emit;
|
using dnlib.DotNet.Emit;
|
||||||
using Il2CppInspector.Reflection;
|
using Il2CppInspector.Reflection;
|
||||||
using BindingFlags = System.Reflection.BindingFlags;
|
|
||||||
|
|
||||||
namespace Il2CppInspector.Outputs
|
namespace Il2CppInspector.Outputs
|
||||||
{
|
{
|
||||||
@@ -77,7 +76,8 @@ namespace Il2CppInspector.Outputs
|
|||||||
private const string rootNamespace = "Il2CppInspector.DLL";
|
private const string rootNamespace = "Il2CppInspector.DLL";
|
||||||
|
|
||||||
// All modules (single-module assemblies)
|
// All modules (single-module assemblies)
|
||||||
private Dictionary<Assembly, ModuleDef> modules = new Dictionary<Assembly, ModuleDef>();
|
private Dictionary<Assembly, ModuleDef> modules = [];
|
||||||
|
private Dictionary<ModuleDef, Dictionary<TypeInfo, TypeDefUser>> types = [];
|
||||||
|
|
||||||
// Custom attributes we will apply directly instead of with a custom attribute function pointer
|
// Custom attributes we will apply directly instead of with a custom attribute function pointer
|
||||||
private Dictionary<TypeInfo, TypeDef> directApplyAttributes;
|
private Dictionary<TypeInfo, TypeDef> directApplyAttributes;
|
||||||
@@ -158,13 +158,34 @@ namespace Il2CppInspector.Outputs
|
|||||||
return module;
|
return module;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate type recursively with all nested types
|
// Create a shallow type definition that only populates the type itself and its nested types.
|
||||||
private TypeDefUser CreateType(ModuleDef module, TypeInfo type) {
|
// Used for custom attributes.
|
||||||
|
private TypeDefUser CreateTypeShallow(ModuleDef module, TypeInfo type)
|
||||||
|
{
|
||||||
// Initialize with base class
|
// Initialize with base class
|
||||||
var mType = new TypeDefUser(type.Namespace, type.BaseName, GetTypeRef(module, type.BaseType)) {
|
var mType = new TypeDefUser(type.Namespace, type.BaseName, GetTypeRef(module, type.BaseType))
|
||||||
Attributes = (TypeAttributes) type.Attributes
|
{
|
||||||
|
Attributes = (TypeAttributes)type.Attributes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Add nested types
|
||||||
|
foreach (var nestedType in type.DeclaredNestedTypes)
|
||||||
|
mType.NestedTypes.Add(CreateTypeShallow(module, nestedType));
|
||||||
|
|
||||||
|
if (!types.TryAdd(module, new Dictionary<TypeInfo, TypeDefUser> {[type] = mType}))
|
||||||
|
types[module][type] = mType;
|
||||||
|
|
||||||
|
// Add to attribute apply list if we're looking for it
|
||||||
|
if (directApplyAttributes.ContainsKey(type))
|
||||||
|
directApplyAttributes[type] = mType;
|
||||||
|
|
||||||
|
return mType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate shallow type definition with all members, events, etc.
|
||||||
|
// Type definition is done in a two-stage process so that attributes can reference the type beforehand
|
||||||
|
private TypeDefUser PopulateType(ModuleDef module, TypeDefUser mType, TypeInfo type) {
|
||||||
|
|
||||||
// Generic parameters
|
// Generic parameters
|
||||||
foreach (var gp in type.GenericTypeParameters) {
|
foreach (var gp in type.GenericTypeParameters) {
|
||||||
var p = new GenericParamUser((ushort) gp.GenericParameterPosition, (GenericParamAttributes) gp.GenericParameterAttributes, gp.Name);
|
var p = new GenericParamUser((ushort) gp.GenericParameterPosition, (GenericParamAttributes) gp.GenericParameterAttributes, gp.Name);
|
||||||
@@ -180,10 +201,6 @@ namespace Il2CppInspector.Outputs
|
|||||||
foreach (var @interface in type.ImplementedInterfaces)
|
foreach (var @interface in type.ImplementedInterfaces)
|
||||||
mType.Interfaces.Add(new InterfaceImplUser(GetTypeRef(module, @interface)));
|
mType.Interfaces.Add(new InterfaceImplUser(GetTypeRef(module, @interface)));
|
||||||
|
|
||||||
// Add nested types
|
|
||||||
foreach (var nestedType in type.DeclaredNestedTypes)
|
|
||||||
mType.NestedTypes.Add(CreateType(module, nestedType));
|
|
||||||
|
|
||||||
// Add fields
|
// Add fields
|
||||||
foreach (var field in type.DeclaredFields)
|
foreach (var field in type.DeclaredFields)
|
||||||
AddField(module, mType, field);
|
AddField(module, mType, field);
|
||||||
@@ -309,19 +326,7 @@ namespace Il2CppInspector.Outputs
|
|||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Return type and parameter signature
|
// Return type and parameter signature
|
||||||
MethodSig s;
|
var s = GetMethodSig(module, method);
|
||||||
|
|
||||||
// Static or instance
|
|
||||||
if (method.IsStatic)
|
|
||||||
s = MethodSig.CreateStatic(
|
|
||||||
method is MethodInfo mi ? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void,
|
|
||||||
method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType))
|
|
||||||
.ToArray());
|
|
||||||
else
|
|
||||||
s = MethodSig.CreateInstance(
|
|
||||||
method is MethodInfo mi? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void,
|
|
||||||
method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType))
|
|
||||||
.ToArray());
|
|
||||||
|
|
||||||
// Definition
|
// Definition
|
||||||
var mMethod = new MethodDefUser(method.Name, s, (MethodImplAttributes) method.MethodImplementationFlags, (MethodAttributes) method.Attributes);
|
var mMethod = new MethodDefUser(method.Name, s, (MethodImplAttributes) method.MethodImplementationFlags, (MethodAttributes) method.Attributes);
|
||||||
@@ -410,10 +415,24 @@ namespace Il2CppInspector.Outputs
|
|||||||
return mMethod;
|
return mMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MethodSig GetMethodSig(ModuleDef module, MethodBase method)
|
||||||
|
{
|
||||||
|
if (method.IsStatic)
|
||||||
|
return MethodSig.CreateStatic(
|
||||||
|
method is MethodInfo mi ? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void,
|
||||||
|
method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType))
|
||||||
|
.ToArray());
|
||||||
|
else
|
||||||
|
return MethodSig.CreateInstance(
|
||||||
|
method is MethodInfo mi ? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void,
|
||||||
|
method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType))
|
||||||
|
.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
// Add a custom attributes attribute to an item, or the attribute itself if it is in our direct apply list
|
// Add a custom attributes attribute to an item, or the attribute itself if it is in our direct apply list
|
||||||
private CustomAttribute AddCustomAttribute(ModuleDef module, IHasCustomAttribute def, CustomAttributeData ca) {
|
private CustomAttribute AddCustomAttribute(ModuleDef module, IHasCustomAttribute def, CustomAttributeData ca) {
|
||||||
if (directApplyAttributes.TryGetValue(ca.AttributeType, out var attrDef) && attrDef != null)
|
if (directApplyAttributes.TryGetValue(ca.AttributeType, out var attrDef) && attrDef != null)
|
||||||
return def.AddAttribute(module, attrDef);
|
return AddAttribute(def, module, attrDef, ca);
|
||||||
|
|
||||||
return def.AddAttribute(module, attributeAttribute,
|
return def.AddAttribute(module, attributeAttribute,
|
||||||
("Name", ca.AttributeType.Name),
|
("Name", ca.AttributeType.Name),
|
||||||
@@ -422,13 +441,60 @@ namespace Il2CppInspector.Outputs
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CustomAttribute AddAttribute(IHasCustomAttribute def, ModuleDef module, TypeDef attrTypeDef, CustomAttributeData cad)
|
||||||
|
{
|
||||||
|
if (cad.CtorInfo == null)
|
||||||
|
return def.AddAttribute(module, attrTypeDef);
|
||||||
|
|
||||||
|
var ctorInfo = cad.CtorInfo;
|
||||||
|
|
||||||
|
var attRef = module.Import(attrTypeDef);
|
||||||
|
var attCtor = GetMethodSig(module, ctorInfo.Ctor);
|
||||||
|
var attCtorRef = new MemberRefUser(attrTypeDef.Module, ".ctor", attCtor, attRef);
|
||||||
|
|
||||||
|
var attr = new CustomAttribute(attCtorRef);
|
||||||
|
|
||||||
|
foreach (var argument in ctorInfo.Arguments)
|
||||||
|
attr.ConstructorArguments.Add(GetArgument(argument));
|
||||||
|
|
||||||
|
foreach (var field in ctorInfo.Fields)
|
||||||
|
attr.NamedArguments.Add(new CANamedArgument(true, GetTypeSig(module, field.Field.FieldType), field.Field.CSharpName, GetArgument(field)));
|
||||||
|
|
||||||
|
foreach (var property in ctorInfo.Properties)
|
||||||
|
attr.NamedArguments.Add(new CANamedArgument(false, GetTypeSig(module, property.Property.PropertyType), property.Property.CSharpName, GetArgument(property)));
|
||||||
|
|
||||||
|
def.CustomAttributes.Add(attr);
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
|
||||||
|
CAArgument GetArgument(CustomAttributeArgument argument)
|
||||||
|
{
|
||||||
|
var typeSig = GetTypeSig(module, argument.Type);
|
||||||
|
|
||||||
|
switch (argument.Value)
|
||||||
|
{
|
||||||
|
case TypeInfo info:
|
||||||
|
var sig = GetTypeSig(module, info);
|
||||||
|
return new CAArgument(typeSig, sig);
|
||||||
|
case CustomAttributeArgument[] argumentArray:
|
||||||
|
return new CAArgument(new SZArraySig(typeSig),
|
||||||
|
argumentArray.Select(GetArgument).ToList());
|
||||||
|
case object[] caArray:
|
||||||
|
return new CAArgument(new SZArraySig(typeSig),
|
||||||
|
caArray.Select(x => new CustomAttributeArgument
|
||||||
|
{
|
||||||
|
Type = argument.Type,
|
||||||
|
Value = x
|
||||||
|
}).Select(GetArgument).ToList());
|
||||||
|
default:
|
||||||
|
return new CAArgument(typeSig, argument.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate type recursively with all nested types and add to module
|
// Generate type recursively with all nested types and add to module
|
||||||
private TypeDefUser AddType(ModuleDef module, TypeInfo type) {
|
private TypeDefUser AddType(ModuleDef module, TypeInfo type) {
|
||||||
var mType = CreateType(module, type);
|
var mType = CreateTypeShallow(module, type);
|
||||||
|
|
||||||
// Add to attribute apply list if we're looking for it
|
|
||||||
if (directApplyAttributes.ContainsKey(type))
|
|
||||||
directApplyAttributes[type] = mType;
|
|
||||||
|
|
||||||
// Add type to module
|
// Add type to module
|
||||||
module.Types.Add(mType);
|
module.Types.Add(mType);
|
||||||
@@ -494,17 +560,29 @@ namespace Il2CppInspector.Outputs
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate and save all DLLs
|
// Generate and save all DLLs
|
||||||
public void Write(string outputPath, EventHandler<string> statusCallback = null) {
|
public void Write(string outputPath, EventHandler<string> statusCallback = null)
|
||||||
|
{
|
||||||
|
|
||||||
// Create folder for DLLs
|
// Create folder for DLLs
|
||||||
Directory.CreateDirectory(outputPath);
|
Directory.CreateDirectory(outputPath);
|
||||||
|
|
||||||
|
if (model.Package.Version >= 29)
|
||||||
|
{
|
||||||
|
// We can now apply all attributes directly.
|
||||||
|
directApplyAttributes = model.TypesByDefinitionIndex
|
||||||
|
.Where(IsAttributeType)
|
||||||
|
.ToDictionary(x => x, _ => (TypeDef) null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Get all custom attributes with no parameters
|
// Get all custom attributes with no parameters
|
||||||
// We'll add these directly to objects instead of the attribute generator function pointer
|
// We'll add these directly to objects instead of the attribute generator function pointer
|
||||||
directApplyAttributes = model.TypesByDefinitionIndex
|
directApplyAttributes = model.TypesByDefinitionIndex
|
||||||
.Where(t => t.BaseType?.FullName == "System.Attribute"
|
.Where(t => IsAttributeType(t)
|
||||||
&& !t.DeclaredFields.Any() && !t.DeclaredProperties.Any())
|
&& t.DeclaredFields.Count == 0
|
||||||
.ToDictionary(t => t, t => (TypeDef) null);
|
&& t.DeclaredProperties.Count == 0)
|
||||||
|
.ToDictionary(t => t, t => (TypeDef)null);
|
||||||
|
}
|
||||||
|
|
||||||
// Generate blank assemblies
|
// Generate blank assemblies
|
||||||
// We have to do this before adding anything else so we can reference every module
|
// We have to do this before adding anything else so we can reference every module
|
||||||
@@ -524,18 +602,6 @@ namespace Il2CppInspector.Outputs
|
|||||||
baseDll.Write(Path.Combine(outputPath, baseDll.Name));
|
baseDll.Write(Path.Combine(outputPath, baseDll.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add assembly custom attribute attributes (must do this after all assemblies are created due to type referencing)
|
|
||||||
foreach (var asm in model.Assemblies) {
|
|
||||||
var module = modules[asm];
|
|
||||||
|
|
||||||
foreach (var ca in asm.CustomAttributes)
|
|
||||||
AddCustomAttribute(module, module.Assembly, ca);
|
|
||||||
|
|
||||||
// Add token attributes
|
|
||||||
module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.token:X8}"));
|
|
||||||
module.Assembly.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.MetadataToken:X8}"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all types
|
// Add all types
|
||||||
foreach (var asm in model.Assemblies) {
|
foreach (var asm in model.Assemblies) {
|
||||||
statusCallback?.Invoke(this, "Preparing " + asm.ShortName);
|
statusCallback?.Invoke(this, "Preparing " + asm.ShortName);
|
||||||
@@ -543,11 +609,34 @@ namespace Il2CppInspector.Outputs
|
|||||||
AddType(modules[asm], type);
|
AddType(modules[asm], type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var asm in model.Assemblies)
|
||||||
|
{
|
||||||
|
statusCallback?.Invoke(this, "Populating " + asm.ShortName);
|
||||||
|
var module = modules[asm];
|
||||||
|
|
||||||
|
// Add assembly custom attribute attributes (must do this after all assemblies and types are created due to type referencing)
|
||||||
|
foreach (var ca in asm.CustomAttributes)
|
||||||
|
AddCustomAttribute(module, module.Assembly, ca);
|
||||||
|
|
||||||
|
// Add token attributes
|
||||||
|
module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.token:X8}"));
|
||||||
|
module.Assembly.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.MetadataToken:X8}"));
|
||||||
|
|
||||||
|
if (types.TryGetValue(module, out var shallowTypes))
|
||||||
|
foreach (var (typeInfo, typeDef) in shallowTypes)
|
||||||
|
PopulateType(module, typeDef, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
// Write all assemblies to disk
|
// Write all assemblies to disk
|
||||||
foreach (var asm in modules.Values) {
|
foreach (var asm in modules.Values) {
|
||||||
statusCallback?.Invoke(this, "Generating " + asm.Name);
|
statusCallback?.Invoke(this, "Generating " + asm.Name);
|
||||||
asm.Write(Path.Combine(outputPath, asm.Name));
|
asm.Write(Path.Combine(outputPath, asm.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
static bool IsAttributeType(TypeInfo type) =>
|
||||||
|
type.FullName == "System.Attribute" || (type.BaseType != null && IsAttributeType(type.BaseType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ namespace Il2CppInspector.Outputs
|
|||||||
|
|
||||||
// Add assembly attribute namespaces to reference list
|
// Add assembly attribute namespaces to reference list
|
||||||
if (outputAssemblyAttributes)
|
if (outputAssemblyAttributes)
|
||||||
nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace));
|
nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).SelectMany(a => a.GetAllTypeReferences()).Select(x => x.Namespace));
|
||||||
|
|
||||||
var results = new ConcurrentBag<Dictionary<TypeInfo, StringBuilder>>();
|
var results = new ConcurrentBag<Dictionary<TypeInfo, StringBuilder>>();
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ namespace Il2CppInspector.Outputs
|
|||||||
var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace);
|
var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace);
|
||||||
nsRefs.UnionWith(ns);
|
nsRefs.UnionWith(ns);
|
||||||
}
|
}
|
||||||
nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace));
|
nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).SelectMany(a => a.GetAllTypeReferences()).Select(x => x.Namespace));
|
||||||
|
|
||||||
var usings = nsRefs.OrderBy(n => (n.StartsWith("System.") || n == "System") ? "0" + n : "1" + n);
|
var usings = nsRefs.OrderBy(n => (n.StartsWith("System.") || n == "System") ? "0" + n : "1" + n);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ namespace Il2CppInspector.Reflection
|
|||||||
// 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
|
||||||
|
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
|
// 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]]);
|
(Model.Package.CustomAttributeGenerators[Index], Model.Package.FunctionAddresses[Model.Package.CustomAttributeGenerators[Index]]);
|
||||||
|
|
||||||
@@ -37,6 +42,48 @@ namespace Il2CppInspector.Reflection
|
|||||||
// Get the machine code of the C++ function
|
// Get the machine code of the C++ function
|
||||||
public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start));
|
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
|
// Get all the custom attributes for a given assembly, type, member or parameter
|
||||||
private static IEnumerable<CustomAttributeData> getCustomAttributes(Assembly asm, int customAttributeIndex) {
|
private static IEnumerable<CustomAttributeData> getCustomAttributes(Assembly asm, int customAttributeIndex) {
|
||||||
if (customAttributeIndex < 0)
|
if (customAttributeIndex < 0)
|
||||||
@@ -69,8 +116,30 @@ namespace Il2CppInspector.Reflection
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Console.WriteLine("Skipping custom attributes for 29+");
|
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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,16 +21,76 @@ namespace Il2CppInspector.Reflection
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
foreach (var cad in attributes) {
|
foreach (var cad in attributes) {
|
||||||
|
if (cad.CtorInfo != null)
|
||||||
|
{
|
||||||
|
// v29+ attribute handling
|
||||||
|
// We now have much more information, so we can reconstruct the actual attribute
|
||||||
|
var ctor = cad.CtorInfo;
|
||||||
|
|
||||||
|
var name = ctor.Ctor.DeclaringType.GetScopedCSharpName(scope);
|
||||||
|
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||||
|
if (suffix != -1)
|
||||||
|
name = name[..suffix];
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
// Find a constructor that either has no parameters, or all optional parameters
|
// 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));
|
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
|
// 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 commentStart = mustCompile && !parameterlessConstructor ? inline ? "/* " : "// " : "";
|
||||||
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
|
var commentEnd = commentStart.Length > 0 && inline ? " */" : "";
|
||||||
var arguments = "";
|
var arguments = "";
|
||||||
|
|
||||||
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
||||||
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
|
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute")
|
||||||
|
{
|
||||||
commentStart = "";
|
commentStart = "";
|
||||||
commentEnd = "";
|
commentEnd = "";
|
||||||
arguments = "(AttributeTargets.All)";
|
arguments = "(AttributeTargets.All)";
|
||||||
@@ -43,8 +102,9 @@ namespace Il2CppInspector.Reflection
|
|||||||
name = name[..suffix];
|
name = name[..suffix];
|
||||||
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
||||||
if (emitPointer)
|
if (emitPointer)
|
||||||
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
|
sb.Append($" {(inline ? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline ? " */" : "")}");
|
||||||
sb.Append(inline? " ":"\n");
|
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 b ? "true" : "false";
|
||||||
|
case float f:
|
||||||
return value switch {
|
return value switch {
|
||||||
float.PositiveInfinity => "1F / 0F",
|
float.PositiveInfinity => "1F / 0F",
|
||||||
float.NegativeInfinity => "-1F / 0F",
|
float.NegativeInfinity => "-1F / 0F",
|
||||||
float.NaN => "0F / 0F",
|
float.NaN => "0F / 0F",
|
||||||
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
||||||
};
|
};
|
||||||
if (value is double d)
|
case double d:
|
||||||
return value switch {
|
return value switch {
|
||||||
double.PositiveInfinity => "1D / 0D",
|
double.PositiveInfinity => "1D / 0D",
|
||||||
double.NegativeInfinity => "-1D / 0D",
|
double.NegativeInfinity => "-1D / 0D",
|
||||||
double.NaN => "0D / 0D",
|
double.NaN => "0D / 0D",
|
||||||
_ => d.ToString(CultureInfo.InvariantCulture)
|
_ => d.ToString(CultureInfo.InvariantCulture)
|
||||||
};
|
};
|
||||||
if (value is string str) {
|
case string str:
|
||||||
return $"\"{str.ToEscapedString()}\"";
|
return $"\"{str.ToEscapedString()}\"";
|
||||||
}
|
case char c:
|
||||||
if (value is char) {
|
{
|
||||||
var cValue = (int) (char) value;
|
var cValue = (int) c;
|
||||||
if (cValue < 32 || cValue > 126)
|
if (cValue < 32 || cValue > 126)
|
||||||
return $"'\\x{cValue:x4}'";
|
return $"'\\x{cValue:x4}'";
|
||||||
return $"'{value}'";
|
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);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ 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; }
|
||||||
|
|||||||
@@ -1010,7 +1010,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
|
|
||||||
// Constructor, event, field, method, nested type, property attributes
|
// Constructor, event, field, method, nested type, property attributes
|
||||||
var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes);
|
var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes);
|
||||||
refs.UnionWith(attrs.Select(a => a.AttributeType));
|
refs.UnionWith(attrs.SelectMany(a => a.GetAllTypeReferences()));
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType));
|
refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType));
|
||||||
@@ -1038,7 +1038,7 @@ namespace Il2CppInspector.Reflection
|
|||||||
.SelectMany(p => p.GetGenericParameterConstraints()));
|
.SelectMany(p => p.GetGenericParameterConstraints()));
|
||||||
|
|
||||||
// Type declaration attributes
|
// Type declaration attributes
|
||||||
refs.UnionWith(CustomAttributes.Select(a => a.AttributeType));
|
refs.UnionWith(CustomAttributes.SelectMany(a => a.GetAllTypeReferences()));
|
||||||
|
|
||||||
// Parent type
|
// Parent type
|
||||||
if (BaseType != null)
|
if (BaseType != null)
|
||||||
|
|||||||
@@ -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