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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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