mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 01:57:07 +02:00
257 lines
9.7 KiB
C#
257 lines
9.7 KiB
C#
using System;
|
|
using System.Linq;
|
|
using Mono.Cecil;
|
|
using Mono.Cecil.Cil;
|
|
using Mono.Cecil.Rocks;
|
|
using Mono.Collections.Generic;
|
|
using XamlX.TypeSystem;
|
|
|
|
namespace Robust.Xaml;
|
|
|
|
/// <summary>
|
|
/// Class that performs find/replace operations on IL in assemblies that contain
|
|
/// SS14 content.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This code used to live in Robust.Client.Injectors.
|
|
///
|
|
/// Paul Ritter wrote a lot of code that does low-level Cecil based patching
|
|
/// of AoT-compiled XamlX code.
|
|
///
|
|
/// That's "fine" (it's not actually fine) -- this class just moves that all
|
|
/// to one place, and removes the extremely verbose Cecil-based type lookups
|
|
/// to a separate shared location.
|
|
/// </remarks>
|
|
internal sealed class LowLevelCustomizations
|
|
{
|
|
public const string TrampolineName = "!XamlIlPopulateTrampoline";
|
|
public const int ExpectedNMetadataArgs = 3;
|
|
|
|
private readonly CecilTypeSystem _typeSystem;
|
|
private readonly AssemblyDefinition _asm;
|
|
|
|
private readonly TypeDefinition _iocManager;
|
|
private readonly TypeDefinition _iXamlProxyHelper;
|
|
private readonly TypeDefinition _systemType;
|
|
private readonly TypeDefinition _stringType;
|
|
private readonly TypeDefinition _xamlMetadataAttributeType;
|
|
|
|
private readonly MethodReference _resolveXamlProxyHelperMethod;
|
|
private readonly MethodReference _populateMethod;
|
|
private readonly MethodReference _getTypeFromHandleMethod;
|
|
private readonly MethodReference _xamlMetadataAttributeConstructor;
|
|
|
|
/// <summary>
|
|
/// Create a <see cref="LowLevelCustomizations"/> object.
|
|
/// </summary>
|
|
/// <param name="typeSystem">the <see cref="CecilTypeSystem" /></param>
|
|
/// <exception cref="NullReferenceException">if some needed types were undefined</exception>
|
|
public LowLevelCustomizations(CecilTypeSystem typeSystem)
|
|
{
|
|
// resolve every type that we look for or substitute in when doing surgery
|
|
// what a mess!
|
|
_typeSystem = typeSystem;
|
|
if (typeSystem.TargetAssemblyDefinition == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(typeSystem),
|
|
"Provided type system should have a non-null target assembly definition");
|
|
}
|
|
|
|
_asm = typeSystem.TargetAssemblyDefinition;
|
|
|
|
TypeDefinition ResolveType(string name) =>
|
|
typeSystem.GetTypeReference(_typeSystem.FindType(name) ??
|
|
throw new NullReferenceException($"type must exist: {name}"))
|
|
.Resolve();
|
|
|
|
_iocManager = ResolveType("Robust.Shared.IoC.IoCManager");
|
|
_iXamlProxyHelper = ResolveType(
|
|
"Robust.Client.UserInterface.XAML.Proxy.IXamlProxyHelper"
|
|
);
|
|
_resolveXamlProxyHelperMethod = _asm.MainModule.ImportReference(
|
|
_iocManager.Methods
|
|
.First(m => m.Name == "Resolve")
|
|
.MakeGenericMethod(_iXamlProxyHelper)
|
|
);
|
|
|
|
_populateMethod = _asm.MainModule.ImportReference(
|
|
_iXamlProxyHelper.Methods
|
|
.First(m => m.Name == "Populate")
|
|
);
|
|
|
|
_systemType = ResolveType("System.Type");
|
|
|
|
_getTypeFromHandleMethod = _asm.MainModule.ImportReference(
|
|
_systemType.Resolve()
|
|
.Methods
|
|
.First(m => m.Name == "GetTypeFromHandle")
|
|
);
|
|
|
|
_stringType = ResolveType("System.String");
|
|
|
|
_xamlMetadataAttributeType = ResolveType(
|
|
"Robust.Client.UserInterface.XAML.Proxy.XamlMetadataAttribute"
|
|
);
|
|
|
|
_xamlMetadataAttributeConstructor = _asm.MainModule.ImportReference(
|
|
_xamlMetadataAttributeType
|
|
.GetConstructors()
|
|
.First(
|
|
c => c.Parameters.Count == ExpectedNMetadataArgs &&
|
|
c.Parameters.All(p => p.ParameterType.FullName == "System.String")
|
|
)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a "trampoline" -- this is a method on the given subject which has the following general logic:
|
|
///
|
|
/// <code>
|
|
/// void TrampolineName(Subject subject) {
|
|
/// if (IoCManager.Resolve{XamlProxyHelper}().Populate(typeof(Subject), subject)) {
|
|
/// return;
|
|
/// }
|
|
/// aotPopulateMethod(null, subject)
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// </summary>
|
|
/// <param name="subject">the type to create a trampoline on</param>
|
|
/// <param name="aotPopulateMethod">the populate method to call if XamlProxyHelper's Populate method returns false</param>
|
|
/// <returns>the new trampoline method</returns>
|
|
private MethodDefinition CreateTrampoline(TypeDefinition subject, MethodDefinition aotPopulateMethod)
|
|
{
|
|
var trampoline = new MethodDefinition(
|
|
TrampolineName,
|
|
MethodAttributes.Static | MethodAttributes.Private,
|
|
_asm.MainModule.TypeSystem.Void
|
|
);
|
|
trampoline.Parameters.Add(new ParameterDefinition(subject));
|
|
subject.Methods.Add(trampoline);
|
|
|
|
void Emit(Instruction i) => trampoline.Body.Instructions.Add(i);
|
|
|
|
Emit(Instruction.Create(OpCodes.Call, _resolveXamlProxyHelperMethod));
|
|
Emit(Instruction.Create(OpCodes.Ldtoken, subject));
|
|
Emit(Instruction.Create(OpCodes.Call, _getTypeFromHandleMethod));
|
|
Emit(Instruction.Create(OpCodes.Ldarg_0));
|
|
Emit(Instruction.Create(OpCodes.Callvirt, _populateMethod));
|
|
|
|
var ret = Instruction.Create(OpCodes.Ret);
|
|
Emit(Instruction.Create(OpCodes.Brtrue_S, ret));
|
|
Emit(Instruction.Create(OpCodes.Ldnull));
|
|
Emit(Instruction.Create(OpCodes.Ldarg_0));
|
|
Emit(Instruction.Create(OpCodes.Call, aotPopulateMethod));
|
|
Emit(ret);
|
|
|
|
return trampoline;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a trampoline on <paramref name="subject" />, then replaces
|
|
/// calls to RobustXamlLoader.Load with calls to the generated trampoline.
|
|
/// Returns true if the patching succeeded.
|
|
/// </summary>
|
|
/// <param name="subject">the subject</param>
|
|
/// <param name="aotPopulateMethod">the populate method</param>
|
|
/// <returns>true</returns>
|
|
public bool TrampolineCallsToXamlLoader(TypeDefinition subject, MethodDefinition aotPopulateMethod)
|
|
{
|
|
// PYREX NOTE: This logic is brittle and has a lot of cases
|
|
// I do not understand all of them, but I have faithfully ported them
|
|
// Paul Ritter wrote most of this
|
|
var trampoline = CreateTrampoline(subject, aotPopulateMethod);
|
|
|
|
var foundXamlLoader = false;
|
|
// Find RobustXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this)
|
|
foreach (var method in subject.Methods
|
|
.Where(m => !m.Attributes.HasFlag(MethodAttributes.Static)))
|
|
{
|
|
var i = method.Body.Instructions;
|
|
for (var c = 1; c < i.Count; c++)
|
|
{
|
|
if (i[c].OpCode == OpCodes.Call)
|
|
{
|
|
var op = i[c].Operand as MethodReference;
|
|
|
|
if (op != null
|
|
&& op.Name == TrampolineName)
|
|
{
|
|
foundXamlLoader = true;
|
|
break;
|
|
}
|
|
|
|
if (op != null
|
|
&& op.Name == "Load"
|
|
&& op.Parameters.Count == 1
|
|
&& op.Parameters[0].ParameterType.FullName == "System.Object"
|
|
&& op.DeclaringType.FullName == "Robust.Client.UserInterface.XAML.RobustXamlLoader")
|
|
{
|
|
if (MatchThisCall(i, c - 1))
|
|
{
|
|
i[c].Operand = trampoline;
|
|
foundXamlLoader = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundXamlLoader)
|
|
{
|
|
var ctors = subject.GetConstructors()
|
|
.Where(c => !c.IsStatic)
|
|
.ToList();
|
|
// We can inject xaml loader into default constructor
|
|
if (ctors.Count == 1 && ctors[0].Body.Instructions.Count(o => o.OpCode != OpCodes.Nop) == 3)
|
|
{
|
|
var i = ctors[0].Body.Instructions;
|
|
var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret));
|
|
i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline));
|
|
i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0));
|
|
foundXamlLoader = true;
|
|
}
|
|
}
|
|
|
|
return foundXamlLoader;
|
|
}
|
|
|
|
private static bool MatchThisCall(Collection<Instruction> instructions, int idx)
|
|
{
|
|
var i = instructions[idx];
|
|
// A "normal" way of passing `this` to a static method:
|
|
|
|
// ldarg.0
|
|
// call void [uAvalonia.Markup.Xaml]Avalonia.Markup.Xaml.AvaloniaXamlLoader::Load(object)
|
|
|
|
return i.OpCode == OpCodes.Ldarg_0 || (i.OpCode == OpCodes.Ldarg && i.Operand?.Equals(0) == true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a XamlMetadataAttribute to a given type, containing all the compiler
|
|
/// parameters for its Populate method.
|
|
/// </summary>
|
|
/// <param name="subject">the subject type</param>
|
|
/// <param name="uri">the URI we generated</param>
|
|
/// <param name="filename">the filename</param>
|
|
/// <param name="content">the new content</param>
|
|
public void AddXamlMetadata(TypeDefinition subject, Uri uri, string filename, string content)
|
|
{
|
|
var attribute = new CustomAttribute(_xamlMetadataAttributeConstructor);
|
|
var args = new string[ExpectedNMetadataArgs] // reference this so that changing the number is a compile error
|
|
{
|
|
uri.ToString(), filename, content
|
|
};
|
|
|
|
foreach (var arg in args)
|
|
{
|
|
attribute.ConstructorArguments.Add(
|
|
new CustomAttributeArgument(_stringType, arg)
|
|
);
|
|
}
|
|
|
|
subject.CustomAttributes.Add(attribute);
|
|
}
|
|
|
|
}
|