From a61b646295e24fda48499c9995b7640718d4208e Mon Sep 17 00:00:00 2001 From: Westin Miller Date: Mon, 11 Sep 2017 22:32:37 -0700 Subject: [PATCH] ReflectedMethodInfo allows non-public type names. MsilInstructionExtensions to make life easier --- NLog.config | 2 +- .../Managers/PatchManager/DecoratedMethod.cs | 3 +- .../PatchManager/MSIL/MsilArgument.cs | 49 +++++ .../PatchManager/MSIL/MsilInstruction.cs | 62 +----- .../MSIL/MsilInstructionExtensions.cs | 202 ++++++++++++++++++ Torch/Managers/PatchManager/MSIL/MsilLocal.cs | 56 +++++ .../PatchManager/MSIL/MsilOperandInline.cs | 19 +- .../Transpile/MethodTranspiler.cs | 25 ++- Torch/Torch.csproj | 3 + Torch/Utils/ReflectedManager.cs | 10 + 10 files changed, 353 insertions(+), 78 deletions(-) create mode 100644 Torch/Managers/PatchManager/MSIL/MsilArgument.cs create mode 100644 Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs create mode 100644 Torch/Managers/PatchManager/MSIL/MsilLocal.cs diff --git a/NLog.config b/NLog.config index 3ac0285..270fe34 100644 --- a/NLog.config +++ b/NLog.config @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/Torch/Managers/PatchManager/DecoratedMethod.cs b/Torch/Managers/PatchManager/DecoratedMethod.cs index 5892de8..8ca6a47 100644 --- a/Torch/Managers/PatchManager/DecoratedMethod.cs +++ b/Torch/Managers/PatchManager/DecoratedMethod.cs @@ -7,6 +7,7 @@ using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using NLog; +using Torch.Managers.PatchManager.MSIL; using Torch.Managers.PatchManager.Transpile; using Torch.Utils; @@ -147,7 +148,7 @@ namespace Torch.Managers.PatchManager target.EmitComment("Prefixes End"); target.EmitComment("Original Begin"); - MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent); + MethodTranspiler.Transpile(_method, (type) => new MsilLocal(target.DeclareLocal(type)), Transpilers, target, labelAfterOriginalContent); target.EmitComment("Original End"); target.MarkLabel(labelAfterOriginalContent); diff --git a/Torch/Managers/PatchManager/MSIL/MsilArgument.cs b/Torch/Managers/PatchManager/MSIL/MsilArgument.cs new file mode 100644 index 0000000..2e10b14 --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilArgument.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Represents metadata about a method's parameter + /// + public class MsilArgument + { + /// + /// The positon of this argument. Note, if the method is static, index 0 is the instance. + /// + public int Position { get; } + + /// + /// The type of this parameter, or null if unknown. + /// + public Type Type { get; } + + /// + /// The name of this parameter, or null if unknown. + /// + public string Name { get; } + + internal MsilArgument(ParameterInfo local) + { + Position = (((MethodBase)local.Member).IsStatic ? 0 : 1) + local.Position; + Type = local.ParameterType; + Name = local.Name; + } + + /// + /// Creates an empty argument reference with the given position. + /// + /// The argument's position + public MsilArgument(int position) + { + Position = position; + Type = null; + Name = null; + } + } +} diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs index 4614098..54ad06e 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilInstruction.cs @@ -67,7 +67,7 @@ namespace Torch.Managers.PatchManager.MSIL if (OpCode.Name.IndexOf("loc", StringComparison.OrdinalIgnoreCase) != -1) Operand = new MsilOperandInline.MsilOperandLocal(this); else - Operand = new MsilOperandInline.MsilOperandParameter(this); + Operand = new MsilOperandInline.MsilOperandArgument(this); break; case OperandType.ShortInlineI: Operand = OpCode == OpCodes.Ldc_I4_S @@ -193,66 +193,6 @@ namespace Torch.Managers.PatchManager.MSIL private static Func _stackChange; #pragma warning restore 169 - - /// - /// Gets an instruction that represents the inverse of this load or store instruction. - /// - /// - /// - /// new MsilInstruction(OpCodes.Ldloc_0).StoreLoadInverse().OpCode == OpCodes.Stloc_0 - /// - /// - /// Inverse - public MsilInstruction StoreLoadInverse() - { - if (OpCode == OpCodes.Ldloc) - return new MsilInstruction(OpCodes.Stloc).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Ldloc_S) - return new MsilInstruction(OpCodes.Stloc_S).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Ldloc_0) - return new MsilInstruction(OpCodes.Stloc_0); - if (OpCode == OpCodes.Ldloc_1) - return new MsilInstruction(OpCodes.Stloc_1); - if (OpCode == OpCodes.Ldloc_2) - return new MsilInstruction(OpCodes.Stloc_2); - if (OpCode == OpCodes.Ldloc_3) - return new MsilInstruction(OpCodes.Stloc_3); - - if (OpCode == OpCodes.Stloc) - return new MsilInstruction(OpCodes.Ldloc).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Stloc_S) - return new MsilInstruction(OpCodes.Ldloc_S).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Stloc_0) - return new MsilInstruction(OpCodes.Ldloc_0); - if (OpCode == OpCodes.Stloc_1) - return new MsilInstruction(OpCodes.Ldloc_1); - if (OpCode == OpCodes.Stloc_2) - return new MsilInstruction(OpCodes.Ldloc_2); - if (OpCode == OpCodes.Stloc_3) - return new MsilInstruction(OpCodes.Ldloc_3); - - if (OpCode == OpCodes.Ldarg) - return new MsilInstruction(OpCodes.Starg).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Ldarg_S) - return new MsilInstruction(OpCodes.Starg_S).InlineValue( - ((MsilOperandInline)Operand).Value); - // TODO Ldarg_0 etc - - if (OpCode == OpCodes.Starg) - return new MsilInstruction(OpCodes.Ldarg).InlineValue( - ((MsilOperandInline)Operand).Value); - if (OpCode == OpCodes.Starg_S) - return new MsilInstruction(OpCodes.Ldarg_S).InlineValue( - ((MsilOperandInline)Operand).Value); - - throw new ArgumentException($"Can't invert the instruction {this}"); - } - /// /// Estimates the stack delta for this instruction. /// diff --git a/Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs b/Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs new file mode 100644 index 0000000..4629f7f --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Various methods to make composing MSIL easier + /// + public static class MsilInstructionExtensions + { + #region Local Utils + /// + /// Is this instruction a local load-by-value instruction. + /// + public static bool IsLocalLoad(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldloc || me.OpCode == OpCodes.Ldloc_S || me.OpCode == OpCodes.Ldloc_0 || + me.OpCode == OpCodes.Ldloc_1 || me.OpCode == OpCodes.Ldloc_2 || me.OpCode == OpCodes.Ldloc_3; + } + + /// + /// Is this instruction a local load-by-reference instruction. + /// + public static bool IsLocalLoadByRef(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldloca || me.OpCode == OpCodes.Ldloca_S; + } + + /// + /// Is this instruction a local store instruction. + /// + public static bool IsLocalStore(this MsilInstruction me) + { + return me.OpCode == OpCodes.Stloc || me.OpCode == OpCodes.Stloc_S || me.OpCode == OpCodes.Stloc_0 || + me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Stloc_3; + } + + /// + /// For a local referencing opcode, get the local it is referencing. + /// + public static MsilLocal GetReferencedLocal(this MsilInstruction me) + { + if (me.Operand is MsilOperandInline.MsilOperandLocal mol) + return mol.Value; + if (me.OpCode == OpCodes.Stloc_0 || me.OpCode == OpCodes.Ldloc_0) + return new MsilLocal(0); + if (me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Ldloc_1) + return new MsilLocal(1); + if (me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Ldloc_2) + return new MsilLocal(2); + if (me.OpCode == OpCodes.Stloc_3 || me.OpCode == OpCodes.Ldloc_3) + return new MsilLocal(3); + throw new ArgumentException($"Can't get referenced local in instruction {me}"); + } + /// + /// Gets an instruction representing a load-by-value from the given local. + /// + /// Local to load + /// Loading instruction + public static MsilInstruction AsValueLoad(this MsilLocal local) + { + switch (local.Index) + { + case 0: + return new MsilInstruction(OpCodes.Ldloc_0); + case 1: + return new MsilInstruction(OpCodes.Ldloc_1); + case 2: + return new MsilInstruction(OpCodes.Ldloc_2); + case 3: + return new MsilInstruction(OpCodes.Ldloc_3); + default: + return new MsilInstruction(local.Index < 0xFF ? OpCodes.Ldloc_S : OpCodes.Ldloc).InlineValue(local); + } + } + + /// + /// Gets an instruction representing a store-by-value to the given local. + /// + /// Local to write to + /// Loading instruction + public static MsilInstruction AsValueStore(this MsilLocal local) + { + switch (local.Index) + { + case 0: + return new MsilInstruction(OpCodes.Stloc_0); + case 1: + return new MsilInstruction(OpCodes.Stloc_1); + case 2: + return new MsilInstruction(OpCodes.Stloc_2); + case 3: + return new MsilInstruction(OpCodes.Stloc_3); + default: + return new MsilInstruction(local.Index < 0xFF ? OpCodes.Stloc_S : OpCodes.Stloc).InlineValue(local); + } + } + + /// + /// Gets an instruction representing a load-by-reference from the given local. + /// + /// Local to load + /// Loading instruction + public static MsilInstruction AsReferenceLoad(this MsilLocal local) + { + return new MsilInstruction(local.Index < 0xFF ? OpCodes.Ldloca_S : OpCodes.Ldloca).InlineValue(local); + } + #endregion + + #region Argument Utils + /// + /// Is this instruction an argument load-by-value instruction. + /// + public static bool IsArgumentLoad(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldarg || me.OpCode == OpCodes.Ldarg_S || me.OpCode == OpCodes.Ldarg_0 || + me.OpCode == OpCodes.Ldarg_1 || me.OpCode == OpCodes.Ldarg_2 || me.OpCode == OpCodes.Ldarg_3; + } + + /// + /// Is this instruction an argument load-by-reference instruction. + /// + public static bool IsArgumentLoadByRef(this MsilInstruction me) + { + return me.OpCode == OpCodes.Ldarga || me.OpCode == OpCodes.Ldarga_S; + } + + /// + /// Is this instruction an argument store instruction. + /// + public static bool IsArgumentStore(this MsilInstruction me) + { + return me.OpCode == OpCodes.Starg || me.OpCode == OpCodes.Starg_S; + } + + /// + /// For an argument referencing opcode, get the index of the local it is referencing. + /// + public static MsilArgument GetReferencedArgument(this MsilInstruction me) + { + if (me.Operand is MsilOperandInline.MsilOperandArgument mol) + return mol.Value; + if (me.OpCode == OpCodes.Ldarg_0) + return new MsilArgument(0); + if (me.OpCode == OpCodes.Ldarg_1) + return new MsilArgument(1); + if (me.OpCode == OpCodes.Ldarg_2) + return new MsilArgument(2); + if (me.OpCode == OpCodes.Ldarg_3) + return new MsilArgument(3); + throw new ArgumentException($"Can't get referenced argument in instruction {me}"); + } + + /// + /// Gets an instruction representing a load-by-value from the given argument. + /// + /// argument to load + /// Load instruction + public static MsilInstruction AsValueLoad(this MsilArgument argument) + { + switch (argument.Position) + { + case 0: + return new MsilInstruction(OpCodes.Ldarg_0); + case 1: + return new MsilInstruction(OpCodes.Ldarg_1); + case 2: + return new MsilInstruction(OpCodes.Ldarg_2); + case 3: + return new MsilInstruction(OpCodes.Ldarg_3); + default: + return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Ldarg_S : OpCodes.Ldarg).InlineValue(argument); + } + } + + /// + /// Gets an instruction representing a store-by-value to the given argument. + /// + /// argument to write to + /// Store instruction + public static MsilInstruction AsValueStore(this MsilArgument argument) + { + return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Starg_S : OpCodes.Starg).InlineValue(argument); + } + + /// + /// Gets an instruction representing a load-by-reference from the given argument. + /// + /// argument to load + /// Reference load instruction + public static MsilInstruction AsReferenceLoad(this MsilArgument argument) + { + return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Ldarga_S : OpCodes.Ldarga).InlineValue(argument); + } + #endregion + } +} diff --git a/Torch/Managers/PatchManager/MSIL/MsilLocal.cs b/Torch/Managers/PatchManager/MSIL/MsilLocal.cs new file mode 100644 index 0000000..8268aee --- /dev/null +++ b/Torch/Managers/PatchManager/MSIL/MsilLocal.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace Torch.Managers.PatchManager.MSIL +{ + /// + /// Represents metadata about a method's local + /// + public class MsilLocal + { + /// + /// The index of this local. + /// + public int Index { get; } + + /// + /// The type of this local, or null if unknown. + /// + public Type Type { get; } + + /// + /// The name of this local, or null if unknown. + /// + public string Name { get; } + + internal MsilLocal(LocalBuilder local) + { + Index = local.LocalIndex; + Type = local.LocalType; + Name = null; + } + + internal MsilLocal(LocalVariableInfo local) + { + Index = local.LocalIndex; + Type = local.LocalType; + Name = null; + } + + /// + /// Creates an empty local reference with the given index. + /// + /// The local's index + public MsilLocal(int index) + { + Index = index; + Type = null; + Name = null; + } + } +} diff --git a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs index b247a54..2442fd9 100644 --- a/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs +++ b/Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs @@ -209,11 +209,11 @@ namespace Torch.Managers.PatchManager.MSIL } /// - /// Inline parameter reference + /// Inline argument reference /// - public class MsilOperandParameter : MsilOperandInline + public class MsilOperandArgument : MsilOperandInline { - internal MsilOperandParameter(MsilInstruction instruction) : base(instruction) + internal MsilOperandArgument(MsilInstruction instruction) : base(instruction) { } @@ -225,20 +225,19 @@ namespace Torch.Managers.PatchManager.MSIL : reader.ReadUInt16(); if (paramID == 0 && !context.Method.IsStatic) throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument"); - Value = context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]; + Value = new MsilArgument(context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]); } internal override void Emit(LoggingIlGenerator generator) { - var methodInfo = Value.Member as MethodBase; - generator.Emit(Instruction.OpCode, Value.Position + (methodInfo != null && methodInfo.IsStatic ? 0 : 1)); + generator.Emit(Instruction.OpCode, Value.Position); } } /// /// Inline local variable reference /// - public class MsilOperandLocal : MsilOperandInline + public class MsilOperandLocal : MsilOperandInline { internal MsilOperandLocal(MsilInstruction instruction) : base(instruction) { @@ -247,15 +246,15 @@ namespace Torch.Managers.PatchManager.MSIL internal override void Read(MethodContext context, BinaryReader reader) { Value = - context.Method.GetMethodBody().LocalVariables[ + new MsilLocal(context.Method.GetMethodBody().LocalVariables[ Instruction.OpCode.OperandType == OperandType.ShortInlineVar ? reader.ReadByte() - : reader.ReadUInt16()]; + : reader.ReadUInt16()]); } internal override void Emit(LoggingIlGenerator generator) { - generator.Emit(Instruction.OpCode, Value.LocalIndex); + generator.Emit(Instruction.OpCode, Value.Index); } } diff --git a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs index 33faedb..e2db491 100644 --- a/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs +++ b/Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs @@ -11,17 +11,32 @@ namespace Torch.Managers.PatchManager.Transpile { public static readonly Logger _log = LogManager.GetCurrentClassLogger(); - internal static void Transpile(MethodBase baseMethod, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel) + internal static void Transpile(MethodBase baseMethod, Func localCreator, IEnumerable transpilers, LoggingIlGenerator output, Label? retLabel) { var context = new MethodContext(baseMethod); context.Read(); context.CheckIntegrity(); -// _log.Trace("Input Method:"); -// _log.Trace(context.ToHumanMsil); + // _log.Trace("Input Method:"); + // _log.Trace(context.ToHumanMsil); var methodContent = (IEnumerable)context.Instructions; - foreach (var transpiler in transpilers) - methodContent = (IEnumerable)transpiler.Invoke(null, new object[] { methodContent }); + foreach (MethodInfo transpiler in transpilers) + { + var paramList = new List(); + foreach (var parameter in transpiler.GetParameters()) + { + if (parameter.Name.Equals("__methodBody")) + paramList.Add(baseMethod.GetMethodBody()); + else if (parameter.Name.Equals("__localCreator")) + paramList.Add(localCreator); + else if (parameter.ParameterType == typeof(IEnumerable)) + paramList.Add(methodContent); + else + throw new ArgumentException( + $"Bad transpiler parameter type {parameter.ParameterType.FullName} {parameter.Name}"); + } + methodContent = (IEnumerable)transpiler.Invoke(null, paramList.ToArray()); + } methodContent = FixBranchAndReturn(methodContent, retLabel); foreach (var k in methodContent) k.Emit(output); diff --git a/Torch/Torch.csproj b/Torch/Torch.csproj index db958da..5a33e03 100644 --- a/Torch/Torch.csproj +++ b/Torch/Torch.csproj @@ -165,7 +165,10 @@ + + + diff --git a/Torch/Utils/ReflectedManager.cs b/Torch/Utils/ReflectedManager.cs index 5976912..9687cf1 100644 --- a/Torch/Utils/ReflectedManager.cs +++ b/Torch/Utils/ReflectedManager.cs @@ -74,6 +74,16 @@ namespace Torch.Utils /// Expected parameters of this method, or null if any parameters are accepted. /// public Type[] Parameters { get; set; } = null; + + /// + /// Assembly qualified names of + /// + public string[] ParameterNames + { + get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray(); + set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray(); + } + /// /// Expected return type of this method, or null if any return type is accepted. ///