Support reading and writing custom arguments for v29

This commit is contained in:
LukeFZ
2023-12-01 04:43:27 +01:00
parent 40201d6e84
commit 6aa96b431d
10 changed files with 3230 additions and 2771 deletions

View File

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

View File

@@ -38,7 +38,7 @@
</ItemGroup> </ItemGroup>
<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>

View File

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

View File

@@ -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);

View File

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

View File

@@ -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);

View File

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

View File

@@ -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)

View File

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

View File

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