Support reading and writing custom arguments for v29
This commit is contained in:
192
Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs
Normal file
192
Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs
Normal file
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using dnlib.DotNet;
|
||||
using Il2CppInspector.Reflection;
|
||||
using Il2CppInspector.Utils;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
public class CustomAttributeDataReader
|
||||
{
|
||||
private readonly Il2CppInspector _inspector;
|
||||
private readonly Assembly _assembly;
|
||||
private readonly BinaryObjectStream _data;
|
||||
|
||||
private readonly uint _start;
|
||||
private readonly uint _end;
|
||||
|
||||
private readonly long _ctorBufferStart;
|
||||
private readonly long _dataBufferStart;
|
||||
|
||||
public uint Count { get; }
|
||||
|
||||
public CustomAttributeDataReader(Il2CppInspector inspector, Assembly assembly, BinaryObjectStream data, uint startOffset, uint endOffset)
|
||||
{
|
||||
_inspector = inspector;
|
||||
_assembly = assembly;
|
||||
_data = data;
|
||||
|
||||
_start = startOffset;
|
||||
_end = endOffset;
|
||||
|
||||
data.Position = _start;
|
||||
Count = data.ReadCompressedUInt32();
|
||||
|
||||
_ctorBufferStart = data.Position;
|
||||
_dataBufferStart = _ctorBufferStart + Count * sizeof(int);
|
||||
}
|
||||
|
||||
public IEnumerable<CustomAttributeCtor> Read()
|
||||
{
|
||||
_data.Position = _ctorBufferStart;
|
||||
|
||||
var ctors = new CustomAttributeCtor[Count];
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
ctors[i] = new CustomAttributeCtor();
|
||||
|
||||
var ctorIndex = _data.ReadUInt32();
|
||||
ctors[i].Ctor = _assembly.Model.MethodsByDefinitionIndex[ctorIndex];
|
||||
}
|
||||
|
||||
_data.Position = _dataBufferStart;
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
var ctor = ctors[i];
|
||||
var attrClass = ctor.Ctor.DeclaringType;
|
||||
|
||||
var argumentCount = _data.ReadCompressedUInt32();
|
||||
var fieldCount = _data.ReadCompressedUInt32();
|
||||
var propertyCount = _data.ReadCompressedUInt32();
|
||||
|
||||
ctor.Arguments = new CustomAttributeArgument[argumentCount];
|
||||
for (int j = 0; j < argumentCount; j++)
|
||||
{
|
||||
ctor.Arguments[j] = new CustomAttributeArgument();
|
||||
|
||||
ReadAttributeDataValue(ctor.Arguments[j]);
|
||||
}
|
||||
|
||||
ctor.Fields = new CustomAttributeFieldArgument[fieldCount];
|
||||
for (int j = 0; j < fieldCount; j++)
|
||||
{
|
||||
ctor.Fields[j] = new CustomAttributeFieldArgument();
|
||||
ReadAttributeDataValue(ctor.Fields[j]);
|
||||
|
||||
var (fieldClass, fieldIndex) = ReadCustomAttributeNamedArgumentClassAndIndex(attrClass);
|
||||
ctor.Fields[j].Field = fieldClass.DeclaredFields[fieldIndex];
|
||||
}
|
||||
|
||||
ctor.Properties = new CustomAttributePropertyArgument[propertyCount];
|
||||
for (int j = 0; j < propertyCount; j++)
|
||||
{
|
||||
ctor.Properties[j] = new CustomAttributePropertyArgument();
|
||||
ReadAttributeDataValue(ctor.Properties[j]);
|
||||
|
||||
var (propertyClass, propertyIndex) = ReadCustomAttributeNamedArgumentClassAndIndex(attrClass);
|
||||
ctor.Properties[j].Property = propertyClass.DeclaredProperties[propertyIndex];
|
||||
}
|
||||
|
||||
yield return ctor;
|
||||
}
|
||||
|
||||
if (_data.Position != _end)
|
||||
Debugger.Break();
|
||||
}
|
||||
|
||||
private void ReadAttributeDataValue(CustomAttributeArgument arg)
|
||||
{
|
||||
var type = BlobReader.ReadEncodedTypeEnum(_inspector, _data, out var typeDef);
|
||||
var value = BlobReader.GetConstantValueFromBlob(_inspector, type, _data);
|
||||
|
||||
if (value is BlobReader.ConstantBlobArray blobArray)
|
||||
{
|
||||
arg.Type = ConvertTypeDef(blobArray.ArrayTypeDef, blobArray.ArrayTypeEnum);
|
||||
arg.Value = blobArray.Elements.Select(ConvertAttributeValue).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
arg.Type = ConvertTypeDef(typeDef, type);
|
||||
arg.Value = ConvertAttributeValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private object ConvertAttributeValue(object value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case Il2CppType type:
|
||||
return _assembly.Model.TypesByReferenceIndex[_inspector.TypeReferences.IndexOf(type)];
|
||||
case BlobReader.ConstantBlobArray blobArray:
|
||||
{
|
||||
var arrValue = new CustomAttributeArgument
|
||||
{
|
||||
Type = ConvertTypeDef(blobArray.ArrayTypeDef, blobArray.ArrayTypeEnum),
|
||||
Value = blobArray.Elements.Select(ConvertAttributeValue).ToArray()
|
||||
};
|
||||
|
||||
return arrValue;
|
||||
}
|
||||
case BlobReader.ConstantBlobArrayElement blobElem:
|
||||
{
|
||||
var subArgument = new CustomAttributeArgument
|
||||
{
|
||||
Type = ConvertTypeDef(blobElem.TypeDef, blobElem.TypeEnum),
|
||||
Value = blobElem.Value
|
||||
};
|
||||
|
||||
return subArgument;
|
||||
}
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private TypeInfo ConvertTypeDef(Il2CppTypeDefinition typeDef, Il2CppTypeEnum type)
|
||||
=> typeDef == null
|
||||
? _assembly.Model.GetTypeDefinitionFromTypeEnum(type)
|
||||
: _assembly.Model.TypesByDefinitionIndex[Array.IndexOf(_inspector.TypeDefinitions, typeDef)];
|
||||
|
||||
private (TypeInfo, int) ReadCustomAttributeNamedArgumentClassAndIndex(TypeInfo attrInfo)
|
||||
{
|
||||
var memberIndex = _data.ReadCompressedInt32();
|
||||
if (memberIndex >= 0) // Negative indices mean that it's a member of a base class
|
||||
return (attrInfo, memberIndex);
|
||||
|
||||
memberIndex = -(memberIndex + 1);
|
||||
|
||||
var typeDefIndex = _data.ReadCompressedUInt32();
|
||||
var typeInfo = _assembly.Model.TypesByDefinitionIndex[typeDefIndex];
|
||||
|
||||
return (typeInfo, memberIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomAttributeCtor
|
||||
{
|
||||
public MethodBase Ctor { get; set; }
|
||||
public CustomAttributeArgument[] Arguments { get; set; }
|
||||
public CustomAttributeFieldArgument[] Fields { get; set; }
|
||||
public CustomAttributePropertyArgument[] Properties { get; set; }
|
||||
}
|
||||
|
||||
public class CustomAttributeArgument
|
||||
{
|
||||
public TypeInfo Type { get; set; }
|
||||
public object Value { get; set; }
|
||||
}
|
||||
|
||||
public class CustomAttributeFieldArgument : CustomAttributeArgument
|
||||
{
|
||||
public FieldInfo Field { get; set; }
|
||||
}
|
||||
|
||||
public class CustomAttributePropertyArgument : CustomAttributeArgument
|
||||
{
|
||||
public PropertyInfo Property { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dnlib" Version="3.3.2" />
|
||||
<PackageReference Include="dnlib" Version="4.3.0" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.1" />
|
||||
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
|
||||
<NoWarn>NU1605</NoWarn>
|
||||
|
||||
@@ -12,7 +12,6 @@ using System.Linq;
|
||||
using dnlib.DotNet;
|
||||
using dnlib.DotNet.Emit;
|
||||
using Il2CppInspector.Reflection;
|
||||
using BindingFlags = System.Reflection.BindingFlags;
|
||||
|
||||
namespace Il2CppInspector.Outputs
|
||||
{
|
||||
@@ -77,7 +76,8 @@ namespace Il2CppInspector.Outputs
|
||||
private const string rootNamespace = "Il2CppInspector.DLL";
|
||||
|
||||
// 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
|
||||
private Dictionary<TypeInfo, TypeDef> directApplyAttributes;
|
||||
@@ -158,13 +158,34 @@ namespace Il2CppInspector.Outputs
|
||||
return module;
|
||||
}
|
||||
|
||||
// Generate type recursively with all nested types
|
||||
private TypeDefUser CreateType(ModuleDef module, TypeInfo type) {
|
||||
// Create a shallow type definition that only populates the type itself and its nested types.
|
||||
// Used for custom attributes.
|
||||
private TypeDefUser CreateTypeShallow(ModuleDef module, TypeInfo type)
|
||||
{
|
||||
// Initialize with base class
|
||||
var mType = new TypeDefUser(type.Namespace, type.BaseName, GetTypeRef(module, type.BaseType)) {
|
||||
Attributes = (TypeAttributes) type.Attributes
|
||||
var mType = new TypeDefUser(type.Namespace, type.BaseName, GetTypeRef(module, type.BaseType))
|
||||
{
|
||||
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
|
||||
foreach (var gp in type.GenericTypeParameters) {
|
||||
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)
|
||||
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
|
||||
foreach (var field in type.DeclaredFields)
|
||||
AddField(module, mType, field);
|
||||
@@ -247,7 +264,7 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
// Add custom attribute attributes
|
||||
foreach (var ca in field.CustomAttributes)
|
||||
AddCustomAttribute(module, mField, ca);
|
||||
AddCustomAttribute(module, mField, ca);
|
||||
|
||||
mType.Fields.Add(mField);
|
||||
return mField;
|
||||
@@ -309,19 +326,7 @@ namespace Il2CppInspector.Outputs
|
||||
return null;
|
||||
|
||||
// Return type and parameter signature
|
||||
MethodSig s;
|
||||
|
||||
// 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());
|
||||
var s = GetMethodSig(module, method);
|
||||
|
||||
// Definition
|
||||
var mMethod = new MethodDefUser(method.Name, s, (MethodImplAttributes) method.MethodImplementationFlags, (MethodAttributes) method.Attributes);
|
||||
@@ -410,10 +415,24 @@ namespace Il2CppInspector.Outputs
|
||||
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
|
||||
private CustomAttribute AddCustomAttribute(ModuleDef module, IHasCustomAttribute def, CustomAttributeData ca) {
|
||||
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,
|
||||
("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
|
||||
private TypeDefUser AddType(ModuleDef module, TypeInfo type) {
|
||||
var mType = CreateType(module, type);
|
||||
|
||||
// Add to attribute apply list if we're looking for it
|
||||
if (directApplyAttributes.ContainsKey(type))
|
||||
directApplyAttributes[type] = mType;
|
||||
var mType = CreateTypeShallow(module, type);
|
||||
|
||||
// Add type to module
|
||||
module.Types.Add(mType);
|
||||
@@ -494,17 +560,29 @@ namespace Il2CppInspector.Outputs
|
||||
}
|
||||
|
||||
// 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
|
||||
Directory.CreateDirectory(outputPath);
|
||||
|
||||
// Get all custom attributes with no parameters
|
||||
// We'll add these directly to objects instead of the attribute generator function pointer
|
||||
directApplyAttributes = model.TypesByDefinitionIndex
|
||||
.Where(t => t.BaseType?.FullName == "System.Attribute"
|
||||
&& !t.DeclaredFields.Any() && !t.DeclaredProperties.Any())
|
||||
.ToDictionary(t => t, t => (TypeDef) null);
|
||||
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
|
||||
// We'll add these directly to objects instead of the attribute generator function pointer
|
||||
directApplyAttributes = model.TypesByDefinitionIndex
|
||||
.Where(t => IsAttributeType(t)
|
||||
&& t.DeclaredFields.Count == 0
|
||||
&& t.DeclaredProperties.Count == 0)
|
||||
.ToDictionary(t => t, t => (TypeDef)null);
|
||||
}
|
||||
|
||||
// Generate blank assemblies
|
||||
// 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));
|
||||
}
|
||||
|
||||
// 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
|
||||
foreach (var asm in model.Assemblies) {
|
||||
statusCallback?.Invoke(this, "Preparing " + asm.ShortName);
|
||||
@@ -543,11 +609,34 @@ namespace Il2CppInspector.Outputs
|
||||
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
|
||||
foreach (var asm in modules.Values) {
|
||||
statusCallback?.Invoke(this, "Generating " + 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
|
||||
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>>();
|
||||
|
||||
@@ -287,7 +287,7 @@ namespace Il2CppInspector.Outputs
|
||||
var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace);
|
||||
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);
|
||||
|
||||
|
||||
@@ -21,7 +21,12 @@ namespace Il2CppInspector.Reflection
|
||||
// The type of the attribute
|
||||
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
|
||||
(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
|
||||
public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start));
|
||||
|
||||
public IEnumerable<TypeInfo> GetAllTypeReferences()
|
||||
{
|
||||
yield return AttributeType;
|
||||
|
||||
if (CtorInfo != null)
|
||||
{
|
||||
foreach (var typeRef in GetTypeReferences(CtorInfo.Arguments))
|
||||
yield return typeRef;
|
||||
|
||||
foreach (var typeRef in GetTypeReferences(CtorInfo.Fields))
|
||||
yield return typeRef;
|
||||
|
||||
foreach (var typeRef in GetTypeReferences(CtorInfo.Properties))
|
||||
yield return typeRef;
|
||||
}
|
||||
|
||||
yield break;
|
||||
|
||||
static IEnumerable<TypeInfo> GetTypeReferences(IEnumerable<CustomAttributeArgument> arguments)
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
{
|
||||
yield return arg.Type;
|
||||
|
||||
switch (arg.Value)
|
||||
{
|
||||
case TypeInfo info:
|
||||
yield return info;
|
||||
break;
|
||||
case CustomAttributeArgument[] array:
|
||||
foreach (var info in GetTypeReferences(array))
|
||||
yield return info;
|
||||
break;
|
||||
case TypeInfo[] infos:
|
||||
foreach (var info in infos)
|
||||
yield return info;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all the custom attributes for a given assembly, type, member or parameter
|
||||
private static IEnumerable<CustomAttributeData> getCustomAttributes(Assembly asm, int customAttributeIndex) {
|
||||
if (customAttributeIndex < 0)
|
||||
@@ -50,27 +97,49 @@ namespace Il2CppInspector.Reflection
|
||||
|
||||
if (pkg.Version < 29)
|
||||
{
|
||||
var range = pkg.AttributeTypeRanges[customAttributeIndex];
|
||||
var range = pkg.AttributeTypeRanges[customAttributeIndex];
|
||||
for (var i = range.start; i < range.start + range.count; i++)
|
||||
{
|
||||
var typeIndex = pkg.AttributeTypeIndices[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;
|
||||
continue;
|
||||
}
|
||||
|
||||
attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] };
|
||||
|
||||
asm.Model.AttributesByIndices.TryAdd(i, attribute);
|
||||
yield return attribute;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Skipping custom attributes for 29+");
|
||||
yield break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -22,29 +21,90 @@ namespace Il2CppInspector.Reflection
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var cad in attributes) {
|
||||
// Find a constructor that either has no parameters, or all optional parameters
|
||||
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
|
||||
if (cad.CtorInfo != null)
|
||||
{
|
||||
// v29+ attribute handling
|
||||
// We now have much more information, so we can reconstruct the actual attribute
|
||||
var ctor = cad.CtorInfo;
|
||||
|
||||
// IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile
|
||||
var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
|
||||
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
|
||||
var arguments = "";
|
||||
var name = ctor.Ctor.DeclaringType.GetScopedCSharpName(scope);
|
||||
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||
if (suffix != -1)
|
||||
name = name[..suffix];
|
||||
|
||||
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
||||
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
|
||||
commentStart = "";
|
||||
commentEnd = "";
|
||||
arguments = "(AttributeTargets.All)";
|
||||
sb.Append(linePrefix);
|
||||
sb.Append('[');
|
||||
sb.Append(attributePrefix);
|
||||
sb.Append(name);
|
||||
|
||||
var totalCount = ctor.Arguments.Length + ctor.Fields.Length + ctor.Properties.Length;
|
||||
|
||||
if (totalCount > 0)
|
||||
{
|
||||
// We have parameters, need to use brackets
|
||||
sb.Append('(');
|
||||
|
||||
var totalIndex = 0;
|
||||
foreach (var argument in ctor.Arguments)
|
||||
{
|
||||
sb.Append(argument.Value.ToCSharpValue(argument.Type, scope));
|
||||
if (++totalIndex != totalCount)
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
foreach (var field in ctor.Fields)
|
||||
{
|
||||
sb.Append(field.Field.CSharpName);
|
||||
sb.Append(" = ");
|
||||
sb.Append(field.Value.ToCSharpValue(field.Type, scope));
|
||||
if (++totalIndex != totalCount)
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
foreach (var property in ctor.Properties)
|
||||
{
|
||||
sb.Append(property.Property.CSharpName);
|
||||
sb.Append(" = ");
|
||||
sb.Append(property.Value.ToCSharpValue(property.Type, scope));
|
||||
if (++totalIndex != totalCount)
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.Append(')');
|
||||
}
|
||||
|
||||
sb.Append(']');
|
||||
sb.Append(inline ? " " : "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pre-v29 attribute handling
|
||||
|
||||
var name = cad.AttributeType.GetScopedCSharpName(scope);
|
||||
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||
if (suffix != -1)
|
||||
name = name[..suffix];
|
||||
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
||||
if (emitPointer)
|
||||
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
|
||||
sb.Append(inline? " ":"\n");
|
||||
// Find a constructor that either has no parameters, or all optional parameters
|
||||
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
|
||||
|
||||
// IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile
|
||||
var commentStart = mustCompile && !parameterlessConstructor ? inline ? "/* " : "// " : "";
|
||||
var commentEnd = commentStart.Length > 0 && inline ? " */" : "";
|
||||
var arguments = "";
|
||||
|
||||
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
|
||||
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute")
|
||||
{
|
||||
commentStart = "";
|
||||
commentEnd = "";
|
||||
arguments = "(AttributeTargets.All)";
|
||||
}
|
||||
|
||||
var name = cad.AttributeType.GetScopedCSharpName(scope);
|
||||
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
|
||||
if (suffix != -1)
|
||||
name = name[..suffix];
|
||||
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
|
||||
if (emitPointer)
|
||||
sb.Append($" {(inline ? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline ? " */" : "")}");
|
||||
sb.Append(inline ? " " : "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
@@ -120,31 +180,60 @@ namespace Il2CppInspector.Reflection
|
||||
|
||||
// Output a value in C#-friendly syntax
|
||||
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
|
||||
if (value is bool)
|
||||
return (bool) value ? "true" : "false";
|
||||
if (value is float f)
|
||||
return value switch {
|
||||
float.PositiveInfinity => "1F / 0F",
|
||||
float.NegativeInfinity => "-1F / 0F",
|
||||
float.NaN => "0F / 0F",
|
||||
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
||||
};
|
||||
if (value is double d)
|
||||
return value switch {
|
||||
double.PositiveInfinity => "1D / 0D",
|
||||
double.NegativeInfinity => "-1D / 0D",
|
||||
double.NaN => "0D / 0D",
|
||||
_ => d.ToString(CultureInfo.InvariantCulture)
|
||||
};
|
||||
if (value is string str) {
|
||||
return $"\"{str.ToEscapedString()}\"";
|
||||
}
|
||||
if (value is char) {
|
||||
var cValue = (int) (char) value;
|
||||
if (cValue < 32 || cValue > 126)
|
||||
return $"'\\x{cValue:x4}'";
|
||||
return $"'{value}'";
|
||||
switch (value)
|
||||
{
|
||||
case bool b:
|
||||
return b ? "true" : "false";
|
||||
case float f:
|
||||
return value switch {
|
||||
float.PositiveInfinity => "1F / 0F",
|
||||
float.NegativeInfinity => "-1F / 0F",
|
||||
float.NaN => "0F / 0F",
|
||||
_ => f.ToString(CultureInfo.InvariantCulture) + "f"
|
||||
};
|
||||
case double d:
|
||||
return value switch {
|
||||
double.PositiveInfinity => "1D / 0D",
|
||||
double.NegativeInfinity => "-1D / 0D",
|
||||
double.NaN => "0D / 0D",
|
||||
_ => d.ToString(CultureInfo.InvariantCulture)
|
||||
};
|
||||
case string str:
|
||||
return $"\"{str.ToEscapedString()}\"";
|
||||
case char c:
|
||||
{
|
||||
var cValue = (int) c;
|
||||
if (cValue < 32 || cValue > 126)
|
||||
return $"'\\x{cValue:x4}'";
|
||||
return $"'{value}'";
|
||||
}
|
||||
case TypeInfo typeInfo:
|
||||
return $"typeof({typeInfo.GetScopedCSharpName(usingScope)})";
|
||||
case object[] array:
|
||||
var arraySb = new StringBuilder();
|
||||
arraySb.Append("new ");
|
||||
arraySb.Append(type.GetScopedCSharpName(usingScope));
|
||||
arraySb.Append('[');
|
||||
arraySb.Append(array.Length);
|
||||
arraySb.Append(']');
|
||||
|
||||
if (array.Length > 0)
|
||||
{
|
||||
arraySb.Append(" {");
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
if (array[i] is CustomAttributeArgument arrayArgument) // Used for array with different entries, see BlobReader for more info
|
||||
arraySb.Append(arrayArgument.Value.ToCSharpValue(arrayArgument.Type, usingScope));
|
||||
|
||||
if (i + 1 != array.Length)
|
||||
arraySb.Append(", ");
|
||||
}
|
||||
arraySb.Append(" }");
|
||||
}
|
||||
|
||||
return arraySb.ToString();
|
||||
}
|
||||
|
||||
if (type.IsEnum) {
|
||||
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
|
||||
var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v);
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Il2CppInspector.Reflection {
|
||||
public class FieldInfo : MemberInfo
|
||||
public class FieldInfo : MemberInfo // L-TODO: Add support for [ThreadLocal] fields
|
||||
{
|
||||
// IL2CPP-specific data
|
||||
public Il2CppFieldDefinition Definition { get; }
|
||||
|
||||
@@ -1010,7 +1010,7 @@ namespace Il2CppInspector.Reflection
|
||||
|
||||
// Constructor, event, field, method, nested type, property attributes
|
||||
var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes);
|
||||
refs.UnionWith(attrs.Select(a => a.AttributeType));
|
||||
refs.UnionWith(attrs.SelectMany(a => a.GetAllTypeReferences()));
|
||||
|
||||
// Events
|
||||
refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType));
|
||||
@@ -1038,7 +1038,7 @@ namespace Il2CppInspector.Reflection
|
||||
.SelectMany(p => p.GetGenericParameterConstraints()));
|
||||
|
||||
// Type declaration attributes
|
||||
refs.UnionWith(CustomAttributes.Select(a => a.AttributeType));
|
||||
refs.UnionWith(CustomAttributes.SelectMany(a => a.GetAllTypeReferences()));
|
||||
|
||||
// Parent type
|
||||
if (BaseType != null)
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace Il2CppInspector.Reflection
|
||||
|
||||
// List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices
|
||||
public ConcurrentDictionary<int, CustomAttributeData> AttributesByIndices { get; } = new ConcurrentDictionary<int, CustomAttributeData>();
|
||||
public ConcurrentDictionary<int, List<CustomAttributeData>> AttributesByDataIndices { get; } = [];
|
||||
|
||||
// List of unique custom attributes generators indexed by type (multiple indices above may refer to a single generator function)
|
||||
public Dictionary<TypeInfo, List<CustomAttributeData>> CustomAttributeGenerators { get; }
|
||||
@@ -254,7 +255,7 @@ namespace Il2CppInspector.Reflection
|
||||
|
||||
// Primitive types
|
||||
default:
|
||||
underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type);
|
||||
underlyingType = GetTypeDefinitionFromTypeEnum(typeRef.type);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -263,12 +264,20 @@ namespace Il2CppInspector.Reflection
|
||||
}
|
||||
|
||||
// Basic primitive types are specified via a flag value
|
||||
private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) {
|
||||
if ((int)t >= Il2CppConstants.FullNameTypeString.Count)
|
||||
return null;
|
||||
public TypeInfo GetTypeDefinitionFromTypeEnum(Il2CppTypeEnum t)
|
||||
{
|
||||
// IL2CPP_TYPE_IL2CPP_TYPE_INDEX is handled seperately because it has enum value 0xff
|
||||
var fqn = t switch
|
||||
{
|
||||
Il2CppTypeEnum.IL2CPP_TYPE_IL2CPP_TYPE_INDEX => "System.Type",
|
||||
_ => (int) t >= Il2CppConstants.FullNameTypeString.Count
|
||||
? null
|
||||
: Il2CppConstants.FullNameTypeString[(int) t]
|
||||
};
|
||||
|
||||
var fqn = Il2CppConstants.FullNameTypeString[(int)t];
|
||||
return TypesByFullName[fqn];
|
||||
return fqn == null
|
||||
? null
|
||||
: TypesByFullName[fqn];
|
||||
}
|
||||
|
||||
// Get a TypeRef by its virtual address
|
||||
@@ -311,12 +320,11 @@ namespace Il2CppInspector.Reflection
|
||||
if (Package.Version <= 24.0)
|
||||
return customAttributeIndex;
|
||||
|
||||
if (Package.Version >= 29)
|
||||
// From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image
|
||||
// v29 uses this same system but with CustomAttributeDataRanges instead
|
||||
if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint)token, out var index))
|
||||
return -1;
|
||||
|
||||
// From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image
|
||||
if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint) token, out var index))
|
||||
return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Il2CppInspector.Utils;
|
||||
|
||||
@@ -70,6 +71,10 @@ public static class BlobReader
|
||||
if (length == -1)
|
||||
break;
|
||||
|
||||
// This is only used in custom arguments.
|
||||
// We actually want the reflection TypeInfo here, but as we do not have it yet
|
||||
// we store everything in a custom array type to be changed out later in the TypeModel.
|
||||
|
||||
var arrayElementType = ReadEncodedTypeEnum(inspector, blob, out var arrayElementDef);
|
||||
var arrayElementsAreDifferent = blob.ReadByte();
|
||||
|
||||
@@ -79,10 +84,10 @@ public static class BlobReader
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
var elementType = ReadEncodedTypeEnum(inspector, blob, out var elementTypeDef);
|
||||
array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob));
|
||||
array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob), elementType);
|
||||
}
|
||||
|
||||
value = new ConstantBlobArray(arrayElementDef, array);
|
||||
value = new ConstantBlobArray(arrayElementDef, array, true, arrayElementType);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -92,7 +97,7 @@ public static class BlobReader
|
||||
array[i] = GetConstantValueFromBlob(inspector, arrayElementType, blob);
|
||||
}
|
||||
|
||||
value = new ConstantBlobArray(arrayElementDef, array);
|
||||
value = new ConstantBlobArray(arrayElementDef, array, false, arrayElementType);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -107,8 +112,11 @@ public static class BlobReader
|
||||
value = inspector.TypeReferences[index];
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE:
|
||||
break;
|
||||
default:
|
||||
Debugger.Break();
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
@@ -123,15 +131,19 @@ public static class BlobReader
|
||||
if (typeEnum == Il2CppTypeEnum.IL2CPP_TYPE_ENUM)
|
||||
{
|
||||
var typeIndex = blob.ReadCompressedInt32();
|
||||
enumType = inspector.TypeDefinitions[typeIndex];
|
||||
typeEnum = inspector.TypeReferences[enumType.byvalTypeIndex].type;
|
||||
var typeHandle = (uint)inspector.TypeReferences[typeIndex].datapoint;
|
||||
enumType = inspector.TypeDefinitions[typeHandle];
|
||||
var elementTypeHandle = inspector.TypeReferences[enumType.elementTypeIndex].datapoint;
|
||||
var elementType = inspector.TypeDefinitions[elementTypeHandle];
|
||||
|
||||
typeEnum = inspector.TypeReferences[elementType.byvalTypeIndex].type;
|
||||
}
|
||||
// This technically also handles SZARRAY (System.Array) and all others by just returning their system type
|
||||
|
||||
return typeEnum;
|
||||
}
|
||||
|
||||
public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements);
|
||||
public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements, bool DifferentElements, Il2CppTypeEnum ArrayTypeEnum);
|
||||
|
||||
public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object value);
|
||||
public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object Value, Il2CppTypeEnum TypeEnum);
|
||||
}
|
||||
Reference in New Issue
Block a user