mirror of
https://github.com/AssetRipper/Cpp2IL.git
synced 2026-11-13 04:26:33 -10:00
220 lines
11 KiB
C#
220 lines
11 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using Cpp2IL.Core.Api;
|
|
using Cpp2IL.Core.Model.Contexts;
|
|
using Cpp2IL.Core.Model.CustomAttributes;
|
|
using Cpp2IL.Core.Utils;
|
|
|
|
namespace Cpp2IL.Core.ProcessingLayers;
|
|
|
|
public class AttributeInjectorProcessingLayer : Cpp2IlProcessingLayer
|
|
{
|
|
public override string Name => "Attribute Injector";
|
|
public override string Id => "attributeinjector";
|
|
|
|
private static bool _useEzDiffMode;
|
|
|
|
public override void Process(ApplicationAnalysisContext appContext, Action<int, int>? progressCallback = null)
|
|
{
|
|
//EZ diff mode removes token attributes and method address/rva fields to make diffing using e.g. git easier
|
|
_useEzDiffMode = appContext.GetExtraData<string>("attr-injector-use-ez-diff") != null;
|
|
|
|
InjectAttributeAttribute(appContext);
|
|
|
|
if (!_useEzDiffMode)
|
|
InjectTokenAttribute(appContext);
|
|
|
|
InjectAddressAttribute(appContext);
|
|
InjectFieldOffsetAttribute(appContext);
|
|
}
|
|
|
|
private static void InjectFieldOffsetAttribute(ApplicationAnalysisContext appContext)
|
|
{
|
|
var fieldOffsetAttributes = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "FieldOffsetAttribute", appContext.SystemTypes.SystemAttributeType);
|
|
|
|
var offsetFields = fieldOffsetAttributes.InjectFieldToAllAssemblies("Offset", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
|
|
var fieldOffsetConstructors = fieldOffsetAttributes.InjectConstructor(false);
|
|
|
|
foreach (var assemblyAnalysisContext in appContext.Assemblies)
|
|
{
|
|
var offsetField = offsetFields[assemblyAnalysisContext];
|
|
|
|
var fieldOffsetConstructor = fieldOffsetConstructors[assemblyAnalysisContext];
|
|
|
|
foreach (var f in assemblyAnalysisContext.Types.SelectMany(t => t.Fields))
|
|
{
|
|
if (f.CustomAttributes == null || f.BackingData == null || f.IsStatic)
|
|
continue;
|
|
|
|
var newAttribute = new AnalyzedCustomAttribute(fieldOffsetConstructor);
|
|
|
|
//This loop is not done parallel because f.Offset has heavy lock contention
|
|
newAttribute.Fields.Add(new(offsetField, new CustomAttributePrimitiveParameter($"0x{f.Offset:X}", newAttribute, CustomAttributeParameterKind.Field, 0)));
|
|
f.CustomAttributes.Add(newAttribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InjectAddressAttribute(ApplicationAnalysisContext appContext)
|
|
{
|
|
var addressAttributes = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "AddressAttribute", appContext.SystemTypes.SystemAttributeType);
|
|
|
|
var rvaFields = addressAttributes.InjectFieldToAllAssemblies("RVA", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
var offsetFields = addressAttributes.InjectFieldToAllAssemblies("Offset", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
var lengthFields = addressAttributes.InjectFieldToAllAssemblies("Length", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
|
|
var addressConstructors = addressAttributes.InjectConstructor(false);
|
|
|
|
foreach (var assemblyAnalysisContext in appContext.Assemblies)
|
|
{
|
|
var rvaField = rvaFields[assemblyAnalysisContext];
|
|
var offsetField = offsetFields[assemblyAnalysisContext];
|
|
var lengthField = lengthFields[assemblyAnalysisContext];
|
|
|
|
var addressConstructor = addressConstructors[assemblyAnalysisContext];
|
|
|
|
foreach (var m in assemblyAnalysisContext.Types.SelectMany(t => t.Methods))
|
|
{
|
|
if (m.CustomAttributes == null || m.UnderlyingPointer == 0)
|
|
continue;
|
|
|
|
var newAttribute = new AnalyzedCustomAttribute(addressConstructor);
|
|
|
|
if (!_useEzDiffMode)
|
|
{
|
|
newAttribute.Fields.Add(new(rvaField, new CustomAttributePrimitiveParameter($"0x{m.Rva:X}", newAttribute, CustomAttributeParameterKind.Field, 0)));
|
|
if (appContext.Binary.TryMapVirtualAddressToRaw(m.UnderlyingPointer, out var offset))
|
|
newAttribute.Fields.Add(new(offsetField, new CustomAttributePrimitiveParameter($"0x{offset:X}", newAttribute, CustomAttributeParameterKind.Field, 1)));
|
|
}
|
|
|
|
newAttribute.Fields.Add(new(lengthField, new CustomAttributePrimitiveParameter($"0x{m.RawBytes.Length:X}", newAttribute, CustomAttributeParameterKind.Field, newAttribute.Fields.Count)));
|
|
m.CustomAttributes.Add(newAttribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void InjectTokenAttribute(ApplicationAnalysisContext appContext)
|
|
{
|
|
var tokenAttributes = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "TokenAttribute", appContext.SystemTypes.SystemAttributeType);
|
|
|
|
var tokenFields = tokenAttributes.InjectFieldToAllAssemblies("Token", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
|
|
var tokenConstructors = tokenAttributes.InjectConstructor(false);
|
|
|
|
foreach (var assemblyAnalysisContext in appContext.Assemblies)
|
|
{
|
|
var tokenField = tokenFields[assemblyAnalysisContext];
|
|
|
|
var tokenConstructor = tokenConstructors[assemblyAnalysisContext];
|
|
|
|
var toProcess = assemblyAnalysisContext.Types.SelectMany(ctx => ctx.Methods.Cast<HasCustomAttributes>()
|
|
.Concat(ctx.Fields)
|
|
.Concat(ctx.Events)
|
|
.Concat(ctx.Properties)
|
|
.Append(ctx))
|
|
.Append(assemblyAnalysisContext);
|
|
|
|
Parallel.ForEach(toProcess, context =>
|
|
{
|
|
if (context.CustomAttributes == null || context.Token == 0)
|
|
return;
|
|
|
|
var newAttribute = new AnalyzedCustomAttribute(tokenConstructor);
|
|
newAttribute.Fields.Add(new(tokenField, new CustomAttributePrimitiveParameter($"0x{context.Token:X}", newAttribute, CustomAttributeParameterKind.Field, 0)));
|
|
context.CustomAttributes.Add(newAttribute);
|
|
});
|
|
}
|
|
}
|
|
|
|
private static void InjectAttributeAttribute(ApplicationAnalysisContext appContext)
|
|
{
|
|
if (appContext.MetadataVersion >= 29f)
|
|
{
|
|
//All attributes should be fully serializable anyway, as they're stored in metadata
|
|
//However, we still need to read them all
|
|
var toAnalyze = appContext.Assemblies.SelectMany(ctx => ctx.Types)
|
|
.SelectMany(ctx => ctx.Methods
|
|
.SelectMany(m => m.Parameters.Cast<HasCustomAttributes>().Append(m))
|
|
.Concat(ctx.Fields)
|
|
.Concat(ctx.Events)
|
|
.Concat(ctx.Properties)
|
|
.Append(ctx));
|
|
|
|
foreach (var hasCustomAttributes in toAnalyze)
|
|
hasCustomAttributes.AnalyzeCustomAttributeData();
|
|
|
|
// MiscUtils.ExecuteParallel(toAnalyze, ctx => ctx.AnalyzeCustomAttributeData());
|
|
return;
|
|
}
|
|
|
|
var attributeAttributes = appContext.InjectTypeIntoAllAssemblies("Cpp2ILInjected", "AttributeAttribute", appContext.SystemTypes.SystemAttributeType);
|
|
|
|
var attributeTypeFields = attributeAttributes.InjectFieldToAllAssemblies("Type", appContext.SystemTypes.SystemTypeType, FieldAttributes.Public);
|
|
var attributeRvaFields = attributeAttributes.InjectFieldToAllAssemblies("RVA", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
var attributeOffsetFields = attributeAttributes.InjectFieldToAllAssemblies("Offset", appContext.SystemTypes.SystemStringType, FieldAttributes.Public);
|
|
|
|
var attributeConstructors = attributeAttributes.InjectConstructor(false);
|
|
|
|
foreach (var assemblyAnalysisContext in appContext.Assemblies)
|
|
{
|
|
var typeField = attributeTypeFields[assemblyAnalysisContext];
|
|
var rvaField = attributeRvaFields[assemblyAnalysisContext];
|
|
var offsetField = attributeOffsetFields[assemblyAnalysisContext];
|
|
|
|
var attributeConstructor = attributeConstructors[assemblyAnalysisContext];
|
|
|
|
var toProcess = assemblyAnalysisContext.Types
|
|
.SelectMany(ctx => ctx.Methods.SelectMany(m => m.Parameters.Cast<HasCustomAttributes>().Append(m))
|
|
.Concat(ctx.Fields)
|
|
.Concat(ctx.Events)
|
|
.Concat(ctx.Properties)
|
|
.Append(ctx))
|
|
.Append(assemblyAnalysisContext);
|
|
|
|
MiscUtils.ExecuteParallel(toProcess, c => ProcessCustomAttributesForContext(c, typeField, rvaField, offsetField, attributeConstructor));
|
|
}
|
|
}
|
|
|
|
private static void ProcessCustomAttributesForContext(HasCustomAttributes context, FieldAnalysisContext typeField, FieldAnalysisContext rvaField, FieldAnalysisContext offsetField, MethodAnalysisContext ctor)
|
|
{
|
|
if (_useEzDiffMode)
|
|
context.CustomAttributes = [];
|
|
else
|
|
{
|
|
context.AnalyzeCustomAttributeData(false);
|
|
|
|
if (context.CustomAttributes == null)
|
|
return;
|
|
}
|
|
|
|
for (var index = 0; index < context.CustomAttributes.Count; index++)
|
|
{
|
|
var attribute = context.CustomAttributes[index];
|
|
|
|
if (attribute.IsSuitableForEmission)
|
|
//Attribute has all required parameters, so we don't need an injected one
|
|
continue;
|
|
|
|
//Create replacement attribute
|
|
var replacementAttribute = new AnalyzedCustomAttribute(ctor);
|
|
|
|
//Get ptr, and from it, rva and offset
|
|
var generatorPtr = context.CaCacheGeneratorAnalysis!.UnderlyingPointer;
|
|
var generatorRva = context.AppContext.Binary.GetRva(generatorPtr);
|
|
if (!context.AppContext.Binary.TryMapVirtualAddressToRaw(generatorPtr, out var offsetInBinary))
|
|
offsetInBinary = 0;
|
|
|
|
//Add the 3 fields to the replacement attribute
|
|
replacementAttribute.Fields.Add(new(typeField, new CustomAttributeTypeParameter(attribute.Constructor.DeclaringType, replacementAttribute, CustomAttributeParameterKind.Field, 0)));
|
|
replacementAttribute.Fields.Add(new(rvaField, new CustomAttributePrimitiveParameter($"0x{generatorRva:X}", replacementAttribute, CustomAttributeParameterKind.Field, 1)));
|
|
replacementAttribute.Fields.Add(new(offsetField, new CustomAttributePrimitiveParameter($"0x{offsetInBinary:X}", replacementAttribute, CustomAttributeParameterKind.Field, 2)));
|
|
|
|
//Replace the original attribute with the replacement attribute
|
|
context.CustomAttributes[index] = replacementAttribute;
|
|
}
|
|
}
|
|
}
|