Method patching framework
This commit is contained in:
99
Torch/Managers/PatchManager/AssemblyMemory.cs
Normal file
99
Torch/Managers/PatchManager/AssemblyMemory.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
internal class AssemblyMemory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address, in RAM, where the body of a method starts.
|
||||
/// </summary>
|
||||
/// <param name="method">Method to find the start of</param>
|
||||
/// <returns>Address of the method's start</returns>
|
||||
public static long GetMethodBodyStart(MethodBase method)
|
||||
{
|
||||
RuntimeMethodHandle handle;
|
||||
if (method is DynamicMethod)
|
||||
handle = (RuntimeMethodHandle)typeof(DynamicMethod).GetMethod("GetMethodDescriptor", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.Invoke(method, new object[0]);
|
||||
else
|
||||
handle = method.MethodHandle;
|
||||
RuntimeHelpers.PrepareMethod(handle);
|
||||
return handle.GetFunctionPointer().ToInt64();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// x64 ISA format:
|
||||
// [prefixes] [opcode] [mod-r/m]
|
||||
// [mod-r/m] is bitfield:
|
||||
// [7-6] = "mod" adressing mode
|
||||
// [5-3] = register or opcode extension
|
||||
// [2-0] = "r/m" extra addressing mode
|
||||
|
||||
|
||||
// http://ref.x86asm.net/coder64.html
|
||||
/// Direct register addressing mode. (Jump directly to register)
|
||||
private const byte MODRM_MOD_DIRECT = 0b11;
|
||||
|
||||
/// Long-mode prefix (64-bit operand)
|
||||
private const byte REX_W = 0x48;
|
||||
|
||||
/// Moves a 16/32/64 operand into register i when opcode is (MOV_R0+i)
|
||||
private const byte MOV_R0 = 0xB8;
|
||||
|
||||
// Extra opcodes. Used with opcode extension.
|
||||
private const byte EXT = 0xFF;
|
||||
|
||||
/// Opcode extension used with <see cref="EXT"/> for the JMP opcode.
|
||||
private const byte OPCODE_EXTENSION_JMP = 4;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a byte array from a memory location
|
||||
/// </summary>
|
||||
/// <param name="memory">Address to read from</param>
|
||||
/// <param name="bytes">Number of bytes to read</param>
|
||||
/// <returns>The bytes that were read</returns>
|
||||
public static byte[] ReadMemory(long memory, int bytes)
|
||||
{
|
||||
var data = new byte[bytes];
|
||||
Marshal.Copy(new IntPtr(memory), data,0, bytes);
|
||||
return data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a byte array to a memory location.
|
||||
/// </summary>
|
||||
/// <param name="memory">Address to write to</param>
|
||||
/// <param name="bytes">Data to write</param>
|
||||
public static void WriteMemory(long memory, byte[] bytes)
|
||||
{
|
||||
Marshal.Copy(bytes,0, new IntPtr(memory), bytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an x64 assembly jump instruction at the given address.
|
||||
/// </summary>
|
||||
/// <param name="memory">Address to write the instruction at</param>
|
||||
/// <param name="jumpTarget">Target address of the jump</param>
|
||||
/// <returns>The bytes that were overwritten</returns>
|
||||
public static byte[] WriteJump(long memory, long jumpTarget)
|
||||
{
|
||||
byte[] result = ReadMemory(memory, 12);
|
||||
unsafe
|
||||
{
|
||||
var ptr = (byte*)memory;
|
||||
*ptr = REX_W;
|
||||
*(ptr + 1) = MOV_R0;
|
||||
*((long*)(ptr + 2)) = jumpTarget;
|
||||
*(ptr + 10) = EXT;
|
||||
*(ptr + 11) = (MODRM_MOD_DIRECT << 6) | (OPCODE_EXTENSION_JMP << 3) | 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
193
Torch/Managers/PatchManager/DecoratedMethod.cs
Normal file
193
Torch/Managers/PatchManager/DecoratedMethod.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using NLog;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
internal class DecoratedMethod : MethodRewritePattern
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
private readonly MethodBase _method;
|
||||
|
||||
internal DecoratedMethod(MethodBase method) : base(null)
|
||||
{
|
||||
_method = method;
|
||||
}
|
||||
|
||||
private long _revertAddress;
|
||||
private byte[] _revertData = null;
|
||||
private GCHandle? _pinnedPatch;
|
||||
|
||||
internal void Commit()
|
||||
{
|
||||
if (!Prefixes.HasChanges() && !Suffixes.HasChanges() && !Transpilers.HasChanges())
|
||||
return;
|
||||
Revert();
|
||||
|
||||
if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0)
|
||||
return;
|
||||
var patch = ComposePatchedMethod();
|
||||
|
||||
_revertAddress = AssemblyMemory.GetMethodBodyStart(_method);
|
||||
var newAddress = AssemblyMemory.GetMethodBodyStart(patch);
|
||||
_revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress);
|
||||
_pinnedPatch = GCHandle.Alloc(patch);
|
||||
}
|
||||
|
||||
internal void Revert()
|
||||
{
|
||||
if (_pinnedPatch.HasValue)
|
||||
{
|
||||
AssemblyMemory.WriteMemory(_revertAddress, _revertData);
|
||||
_revertData = null;
|
||||
_pinnedPatch.Value.Free();
|
||||
}
|
||||
}
|
||||
|
||||
#region Create
|
||||
private int _patchSalt = 0;
|
||||
private DynamicMethod AllocatePatchMethod()
|
||||
{
|
||||
Debug.Assert(_method.DeclaringType != null);
|
||||
var methodName = _method.Name + $"_{_patchSalt++}";
|
||||
var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
||||
var parameters = _method.GetParameters();
|
||||
var parameterTypes = (_method.IsStatic ? Enumerable.Empty<Type>() : new[] { typeof(object) })
|
||||
.Concat(parameters.Select(x => x.ParameterType)).ToArray();
|
||||
|
||||
var patchMethod = new DynamicMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
|
||||
returnType, parameterTypes, _method.DeclaringType, true);
|
||||
if (!_method.IsStatic)
|
||||
patchMethod.DefineParameter(0, ParameterAttributes.None, INSTANCE_PARAMETER);
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
patchMethod.DefineParameter((patchMethod.IsStatic ? 0 : 1) + i, parameters[i].Attributes, parameters[i].Name);
|
||||
|
||||
return patchMethod;
|
||||
}
|
||||
|
||||
|
||||
private const string INSTANCE_PARAMETER = "__instance";
|
||||
private const string RESULT_PARAMETER = "__result";
|
||||
|
||||
public DynamicMethod ComposePatchedMethod()
|
||||
{
|
||||
var method = AllocatePatchMethod();
|
||||
var generator = new LoggingIlGenerator(method.GetILGenerator());
|
||||
EmitPatched(generator);
|
||||
|
||||
// Force it to compile
|
||||
const BindingFlags nonPublicInstance = BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
const BindingFlags nonPublicStatic = BindingFlags.NonPublic | BindingFlags.Static;
|
||||
var compileMethod = typeof(RuntimeHelpers).GetMethod("_CompileMethod", nonPublicStatic);
|
||||
var getMethodDescriptor = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", nonPublicInstance);
|
||||
var handle = (RuntimeMethodHandle)getMethodDescriptor.Invoke(method, new object[0]);
|
||||
var getMethodInfo = typeof(RuntimeMethodHandle).GetMethod("GetMethodInfo", nonPublicInstance);
|
||||
var runtimeMethodInfo = getMethodInfo.Invoke(handle, new object[0]);
|
||||
compileMethod.Invoke(null, new[] { runtimeMethodInfo });
|
||||
return method;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Emit
|
||||
private void EmitPatched(LoggingIlGenerator target)
|
||||
{
|
||||
var originalLocalVariables = _method.GetMethodBody().LocalVariables
|
||||
.Select(x =>
|
||||
{
|
||||
Debug.Assert(x.LocalType != null);
|
||||
return target.DeclareLocal(x.LocalType, x.IsPinned);
|
||||
}).ToArray();
|
||||
|
||||
var specialVariables = new Dictionary<string, LocalBuilder>();
|
||||
|
||||
var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
||||
var resultVariable = returnType != typeof(void) && Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)
|
||||
? target.DeclareLocal(returnType)
|
||||
: null;
|
||||
resultVariable?.SetToDefault(target);
|
||||
|
||||
if (resultVariable != null)
|
||||
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
||||
|
||||
var labelAfterOriginalContent = target.DefineLabel();
|
||||
var labelAfterOriginalReturn = target.DefineLabel();
|
||||
|
||||
foreach (var prefix in Prefixes)
|
||||
{
|
||||
EmitMonkeyCall(target, prefix, specialVariables);
|
||||
if (prefix.ReturnType == typeof(bool))
|
||||
target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn);
|
||||
else if (prefix.ReturnType != typeof(void))
|
||||
throw new Exception($"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
|
||||
}
|
||||
|
||||
MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent);
|
||||
target.MarkLabel(labelAfterOriginalContent);
|
||||
if (resultVariable != null)
|
||||
target.Emit(OpCodes.Stloc, resultVariable);
|
||||
target.MarkLabel(labelAfterOriginalReturn);
|
||||
|
||||
foreach (var suffix in Suffixes)
|
||||
{
|
||||
EmitMonkeyCall(target, suffix, specialVariables);
|
||||
if (suffix.ReturnType != typeof(void))
|
||||
throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}");
|
||||
}
|
||||
|
||||
if (resultVariable != null)
|
||||
target.Emit(OpCodes.Ldloc, resultVariable);
|
||||
target.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch,
|
||||
IReadOnlyDictionary<string, LocalBuilder> specialVariables)
|
||||
{
|
||||
foreach (var param in patch.GetParameters())
|
||||
{
|
||||
switch (param.Name)
|
||||
{
|
||||
case INSTANCE_PARAMETER:
|
||||
if (_method.IsStatic)
|
||||
throw new Exception("Can't use an instance parameter for a static method");
|
||||
target.Emit(OpCodes.Ldarg_0);
|
||||
break;
|
||||
case RESULT_PARAMETER:
|
||||
var retType = param.ParameterType.IsByRef
|
||||
? param.ParameterType.GetElementType()
|
||||
: param.ParameterType;
|
||||
if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].LocalType))
|
||||
throw new Exception($"Return type {specialVariables[RESULT_PARAMETER].LocalType} can't be assigned to result parameter type {retType}");
|
||||
target.Emit(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc, specialVariables[RESULT_PARAMETER]);
|
||||
break;
|
||||
default:
|
||||
var declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
|
||||
if (declParam == null)
|
||||
throw new Exception($"Parameter name {param.Name} not found");
|
||||
var paramIdx = (_method.IsStatic ? 0 : 1) + declParam.Position;
|
||||
|
||||
var patchByRef = param.IsOut || param.ParameterType.IsByRef;
|
||||
var declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
|
||||
if (patchByRef == declByRef)
|
||||
target.Emit(OpCodes.Ldarg, paramIdx);
|
||||
else if (patchByRef)
|
||||
target.Emit(OpCodes.Ldarga, paramIdx);
|
||||
else
|
||||
{
|
||||
target.Emit(OpCodes.Ldarg, paramIdx);
|
||||
target.EmitDereference(declParam.ParameterType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
target.Emit(OpCodes.Call, patch);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
75
Torch/Managers/PatchManager/EmitExtensions.cs
Normal file
75
Torch/Managers/PatchManager/EmitExtensions.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
internal static class EmitExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the given local to its default value in the given IL generator.
|
||||
/// </summary>
|
||||
/// <param name="local">Local to set to default</param>
|
||||
/// <param name="target">The IL generator</param>
|
||||
public static void SetToDefault(this LocalBuilder local, LoggingIlGenerator target)
|
||||
{
|
||||
Debug.Assert(local.LocalType != null);
|
||||
if (local.LocalType.IsEnum || local.LocalType.IsPrimitive)
|
||||
{
|
||||
if (local.LocalType == typeof(float))
|
||||
target.Emit(OpCodes.Ldc_R4, 0f);
|
||||
else if (local.LocalType == typeof(double))
|
||||
target.Emit(OpCodes.Ldc_R8, 0d);
|
||||
else if (local.LocalType == typeof(long) || local.LocalType == typeof(ulong))
|
||||
target.Emit(OpCodes.Ldc_I8, 0L);
|
||||
else
|
||||
target.Emit(OpCodes.Ldc_I4, 0);
|
||||
target.Emit(OpCodes.Stloc, local);
|
||||
}
|
||||
else if (local.LocalType.IsValueType) // struct
|
||||
{
|
||||
target.Emit(OpCodes.Ldloca, local);
|
||||
target.Emit(OpCodes.Initobj, local.LocalType);
|
||||
}
|
||||
else // class
|
||||
{
|
||||
target.Emit(OpCodes.Ldnull);
|
||||
target.Emit(OpCodes.Stloc, local);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits a dereference for the given type.
|
||||
/// </summary>
|
||||
/// <param name="target">IL Generator to emit on</param>
|
||||
/// <param name="type">Type to dereference</param>
|
||||
public static void EmitDereference(this LoggingIlGenerator target, Type type)
|
||||
{
|
||||
if (type.IsByRef)
|
||||
type = type.GetElementType();
|
||||
Debug.Assert(type != null);
|
||||
|
||||
if (type == typeof(float))
|
||||
target.Emit(OpCodes.Ldind_R4);
|
||||
else if (type == typeof(double))
|
||||
target.Emit(OpCodes.Ldind_R8);
|
||||
else if (type == typeof(byte))
|
||||
target.Emit(OpCodes.Ldind_U1);
|
||||
else if (type == typeof(ushort) || type == typeof(char))
|
||||
target.Emit(OpCodes.Ldind_U2);
|
||||
else if (type == typeof(uint))
|
||||
target.Emit(OpCodes.Ldind_U4);
|
||||
else if (type == typeof(sbyte))
|
||||
target.Emit(OpCodes.Ldind_I1);
|
||||
else if (type == typeof(short))
|
||||
target.Emit(OpCodes.Ldind_I2);
|
||||
else if (type == typeof(int) || type.IsEnum)
|
||||
target.Emit(OpCodes.Ldind_I4);
|
||||
else if (type == typeof(long) || type == typeof(ulong))
|
||||
target.Emit(OpCodes.Ldind_I8);
|
||||
else
|
||||
target.Emit(OpCodes.Ldind_Ref);
|
||||
}
|
||||
}
|
||||
}
|
226
Torch/Managers/PatchManager/MSIL/ITokenResolver.cs
Normal file
226
Torch/Managers/PatchManager/MSIL/ITokenResolver.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
//https://stackoverflow.com/questions/4148297/resolving-the-tokens-found-in-the-il-from-a-dynamic-method/35711376#35711376
|
||||
internal interface ITokenResolver
|
||||
{
|
||||
MemberInfo ResolveMember(int token);
|
||||
Type ResolveType(int token);
|
||||
FieldInfo ResolveField(int token);
|
||||
MethodBase ResolveMethod(int token);
|
||||
byte[] ResolveSignature(int token);
|
||||
string ResolveString(int token);
|
||||
}
|
||||
|
||||
internal sealed class NormalTokenResolver : ITokenResolver
|
||||
{
|
||||
private readonly Type[] _genericTypeArgs, _genericMethArgs;
|
||||
private readonly Module _module;
|
||||
|
||||
internal NormalTokenResolver(MethodBase method)
|
||||
{
|
||||
_module = method.Module;
|
||||
_genericTypeArgs = method.DeclaringType?.GenericTypeArguments ?? new Type[0];
|
||||
_genericMethArgs = method.GetGenericArguments();
|
||||
}
|
||||
|
||||
public MemberInfo ResolveMember(int token)
|
||||
{
|
||||
return _module.ResolveMember(token, _genericTypeArgs, _genericMethArgs);
|
||||
}
|
||||
|
||||
public Type ResolveType(int token)
|
||||
{
|
||||
return _module.ResolveType(token, _genericTypeArgs, _genericMethArgs);
|
||||
}
|
||||
|
||||
public FieldInfo ResolveField(int token)
|
||||
{
|
||||
return _module.ResolveField(token, _genericTypeArgs, _genericMethArgs);
|
||||
}
|
||||
|
||||
public MethodBase ResolveMethod(int token)
|
||||
{
|
||||
return _module.ResolveMethod(token, _genericTypeArgs, _genericMethArgs);
|
||||
}
|
||||
|
||||
public byte[] ResolveSignature(int token)
|
||||
{
|
||||
return _module.ResolveSignature(token);
|
||||
}
|
||||
|
||||
public string ResolveString(int token)
|
||||
{
|
||||
return _module.ResolveString(token);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NullTokenResolver : ITokenResolver
|
||||
{
|
||||
internal static readonly NullTokenResolver Instance = new NullTokenResolver();
|
||||
|
||||
private NullTokenResolver()
|
||||
{
|
||||
}
|
||||
|
||||
public MemberInfo ResolveMember(int token)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public Type ResolveType(int token)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public FieldInfo ResolveField(int token)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public MethodBase ResolveMethod(int token)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] ResolveSignature(int token)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public string ResolveString(int token)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DynamicMethodTokenResolver : ITokenResolver
|
||||
{
|
||||
private readonly MethodInfo _getFieldInfo;
|
||||
private readonly MethodInfo _getMethodBase;
|
||||
private readonly GetTypeFromHandleUnsafe _getTypeFromHandleUnsafe;
|
||||
private readonly ConstructorInfo _runtimeFieldHandleStubCtor;
|
||||
private readonly ConstructorInfo _runtimeMethodHandleInternalCtor;
|
||||
private readonly SignatureResolver _signatureResolver;
|
||||
private readonly StringResolver _stringResolver;
|
||||
|
||||
private readonly TokenResolver _tokenResolver;
|
||||
|
||||
public DynamicMethodTokenResolver(DynamicMethod dynamicMethod)
|
||||
{
|
||||
object resolver = typeof(DynamicMethod)
|
||||
.GetField("m_resolver", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(dynamicMethod);
|
||||
if (resolver == null) throw new ArgumentException("The dynamic method's IL has not been finalized.");
|
||||
|
||||
_tokenResolver = (TokenResolver) resolver.GetType()
|
||||
.GetMethod("ResolveToken", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.CreateDelegate(typeof(TokenResolver), resolver);
|
||||
_stringResolver = (StringResolver) resolver.GetType()
|
||||
.GetMethod("GetStringLiteral", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.CreateDelegate(typeof(StringResolver), resolver);
|
||||
_signatureResolver = (SignatureResolver) resolver.GetType()
|
||||
.GetMethod("ResolveSignature", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.CreateDelegate(typeof(SignatureResolver), resolver);
|
||||
|
||||
_getTypeFromHandleUnsafe = (GetTypeFromHandleUnsafe) typeof(Type)
|
||||
.GetMethod("GetTypeFromHandleUnsafe", BindingFlags.Static | BindingFlags.NonPublic, null,
|
||||
new[] {typeof(IntPtr)}, null).CreateDelegate(typeof(GetTypeFromHandleUnsafe), null);
|
||||
Type runtimeType = typeof(RuntimeTypeHandle).Assembly.GetType("System.RuntimeType");
|
||||
|
||||
Type runtimeMethodHandleInternal =
|
||||
typeof(RuntimeTypeHandle).Assembly.GetType("System.RuntimeMethodHandleInternal");
|
||||
_getMethodBase = runtimeType.GetMethod("GetMethodBase", BindingFlags.Static | BindingFlags.NonPublic, null,
|
||||
new[] {runtimeType, runtimeMethodHandleInternal}, null);
|
||||
_runtimeMethodHandleInternalCtor =
|
||||
runtimeMethodHandleInternal.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null,
|
||||
new[] {typeof(IntPtr)}, null);
|
||||
|
||||
Type runtimeFieldInfoStub = typeof(RuntimeTypeHandle).Assembly.GetType("System.RuntimeFieldInfoStub");
|
||||
_runtimeFieldHandleStubCtor =
|
||||
runtimeFieldInfoStub.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null,
|
||||
new[] {typeof(IntPtr), typeof(object)}, null);
|
||||
_getFieldInfo = runtimeType.GetMethod("GetFieldInfo", BindingFlags.Static | BindingFlags.NonPublic, null,
|
||||
new[] {runtimeType, typeof(RuntimeTypeHandle).Assembly.GetType("System.IRuntimeFieldInfo")}, null);
|
||||
}
|
||||
|
||||
public Type ResolveType(int token)
|
||||
{
|
||||
IntPtr typeHandle, methodHandle, fieldHandle;
|
||||
_tokenResolver.Invoke(token, out typeHandle, out methodHandle, out fieldHandle);
|
||||
|
||||
return _getTypeFromHandleUnsafe.Invoke(typeHandle);
|
||||
}
|
||||
|
||||
public MethodBase ResolveMethod(int token)
|
||||
{
|
||||
IntPtr typeHandle, methodHandle, fieldHandle;
|
||||
_tokenResolver.Invoke(token, out typeHandle, out methodHandle, out fieldHandle);
|
||||
|
||||
return (MethodBase) _getMethodBase.Invoke(null, new[]
|
||||
{
|
||||
typeHandle == IntPtr.Zero ? null : _getTypeFromHandleUnsafe.Invoke(typeHandle),
|
||||
_runtimeMethodHandleInternalCtor.Invoke(new object[] {methodHandle})
|
||||
});
|
||||
}
|
||||
|
||||
public FieldInfo ResolveField(int token)
|
||||
{
|
||||
IntPtr typeHandle, methodHandle, fieldHandle;
|
||||
_tokenResolver.Invoke(token, out typeHandle, out methodHandle, out fieldHandle);
|
||||
|
||||
return (FieldInfo) _getFieldInfo.Invoke(null, new[]
|
||||
{
|
||||
typeHandle == IntPtr.Zero ? null : _getTypeFromHandleUnsafe.Invoke(typeHandle),
|
||||
_runtimeFieldHandleStubCtor.Invoke(new object[] {fieldHandle, null})
|
||||
});
|
||||
}
|
||||
|
||||
public MemberInfo ResolveMember(int token)
|
||||
{
|
||||
IntPtr typeHandle, methodHandle, fieldHandle;
|
||||
_tokenResolver.Invoke(token, out typeHandle, out methodHandle, out fieldHandle);
|
||||
|
||||
if (methodHandle != IntPtr.Zero)
|
||||
return (MethodBase) _getMethodBase.Invoke(null, new[]
|
||||
{
|
||||
typeHandle == IntPtr.Zero ? null : _getTypeFromHandleUnsafe.Invoke(typeHandle),
|
||||
_runtimeMethodHandleInternalCtor.Invoke(new object[] {methodHandle})
|
||||
});
|
||||
|
||||
if (fieldHandle != IntPtr.Zero)
|
||||
return (FieldInfo) _getFieldInfo.Invoke(null, new[]
|
||||
{
|
||||
typeHandle == IntPtr.Zero ? null : _getTypeFromHandleUnsafe.Invoke(typeHandle),
|
||||
_runtimeFieldHandleStubCtor.Invoke(new object[] {fieldHandle, null})
|
||||
});
|
||||
|
||||
if (typeHandle != IntPtr.Zero)
|
||||
return _getTypeFromHandleUnsafe.Invoke(typeHandle);
|
||||
|
||||
throw new NotImplementedException(
|
||||
"DynamicMethods are not able to reference members by token other than types, methods and fields.");
|
||||
}
|
||||
|
||||
public byte[] ResolveSignature(int token)
|
||||
{
|
||||
return _signatureResolver.Invoke(token, 0);
|
||||
}
|
||||
|
||||
public string ResolveString(int token)
|
||||
{
|
||||
return _stringResolver.Invoke(token);
|
||||
}
|
||||
|
||||
private delegate void TokenResolver(int token, out IntPtr typeHandle, out IntPtr methodHandle,
|
||||
out IntPtr fieldHandle);
|
||||
|
||||
private delegate string StringResolver(int token);
|
||||
|
||||
private delegate byte[] SignatureResolver(int token, int fromMethod);
|
||||
|
||||
private delegate Type GetTypeFromHandleUnsafe(IntPtr handle);
|
||||
}
|
||||
}
|
162
Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
Normal file
162
Torch/Managers/PatchManager/MSIL/MsilInstruction.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single MSIL instruction, and its operand
|
||||
/// </summary>
|
||||
public class MsilInstruction
|
||||
{
|
||||
private MsilOperand _operandBacking;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instruction with the given opcode.
|
||||
/// </summary>
|
||||
/// <param name="opcode">Opcode</param>
|
||||
public MsilInstruction(OpCode opcode)
|
||||
{
|
||||
OpCode = opcode;
|
||||
switch (opcode.OperandType)
|
||||
{
|
||||
case OperandType.InlineNone:
|
||||
Operand = null;
|
||||
break;
|
||||
case OperandType.ShortInlineBrTarget:
|
||||
case OperandType.InlineBrTarget:
|
||||
Operand = new MsilOperandBrTarget(this);
|
||||
break;
|
||||
case OperandType.InlineField:
|
||||
Operand = new MsilOperandInline.MsilOperandReflected<FieldInfo>(this);
|
||||
break;
|
||||
case OperandType.InlineI:
|
||||
Operand = new MsilOperandInline.MsilOperandInt32(this);
|
||||
break;
|
||||
case OperandType.InlineI8:
|
||||
Operand = new MsilOperandInline.MsilOperandInt64(this);
|
||||
break;
|
||||
case OperandType.InlineMethod:
|
||||
Operand = new MsilOperandInline.MsilOperandReflected<MethodInfo>(this);
|
||||
break;
|
||||
case OperandType.InlineR:
|
||||
Operand = new MsilOperandInline.MsilOperandDouble(this);
|
||||
break;
|
||||
case OperandType.InlineSig:
|
||||
Operand = new MsilOperandInline.MsilOperandSignature(this);
|
||||
break;
|
||||
case OperandType.InlineString:
|
||||
Operand = new MsilOperandInline.MsilOperandString(this);
|
||||
break;
|
||||
case OperandType.InlineSwitch:
|
||||
Operand = new MsilOperandSwitch(this);
|
||||
break;
|
||||
case OperandType.InlineTok:
|
||||
Operand = new MsilOperandInline.MsilOperandReflected<MemberInfo>(this);
|
||||
break;
|
||||
case OperandType.InlineType:
|
||||
Operand = new MsilOperandInline.MsilOperandReflected<Type>(this);
|
||||
break;
|
||||
case OperandType.ShortInlineVar:
|
||||
case OperandType.InlineVar:
|
||||
if (OpCode.Name.IndexOf("loc", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
Operand = new MsilOperandInline.MsilOperandLocal(this);
|
||||
else
|
||||
Operand = new MsilOperandInline.MsilOperandParameter(this);
|
||||
break;
|
||||
case OperandType.ShortInlineI:
|
||||
Operand = OpCode == OpCodes.Ldc_I4_S
|
||||
? (MsilOperand) new MsilOperandInline.MsilOperandInt8(this)
|
||||
: new MsilOperandInline.MsilOperandUInt8(this);
|
||||
break;
|
||||
case OperandType.ShortInlineR:
|
||||
Operand = new MsilOperandInline.MsilOperandSingle(this);
|
||||
break;
|
||||
#pragma warning disable 618
|
||||
case OperandType.InlinePhi:
|
||||
#pragma warning restore 618
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opcode of this instruction
|
||||
/// </summary>
|
||||
public OpCode OpCode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raw memory offset of this instruction; optional.
|
||||
/// </summary>
|
||||
public int Offset { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The operand for this instruction, or null.
|
||||
/// </summary>
|
||||
public MsilOperand Operand
|
||||
{
|
||||
get => _operandBacking;
|
||||
set
|
||||
{
|
||||
if (_operandBacking != null && value.GetType() != _operandBacking.GetType())
|
||||
throw new ArgumentException($"Operand for {OpCode.Name} must be {_operandBacking.GetType().Name}");
|
||||
_operandBacking = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Labels pointing to this instruction.
|
||||
/// </summary>
|
||||
public HashSet<MsilLabel> Labels { get; } = new HashSet<MsilLabel>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the inline value for this instruction.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the inline constraint</typeparam>
|
||||
/// <param name="o">Value</param>
|
||||
/// <returns>This instruction</returns>
|
||||
public MsilInstruction InlineValue<T>(T o)
|
||||
{
|
||||
((MsilOperandInline<T>) Operand).Value = o;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the inline branch target for this instruction.
|
||||
/// </summary>
|
||||
/// <param name="label">Target to jump to</param>
|
||||
/// <returns>This instruction</returns>
|
||||
public MsilInstruction InlineTarget(MsilLabel label)
|
||||
{
|
||||
((MsilOperandBrTarget) Operand).Target = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits this instruction to the given generator
|
||||
/// </summary>
|
||||
/// <param name="target">Emit target</param>
|
||||
public void Emit(LoggingIlGenerator target)
|
||||
{
|
||||
foreach (MsilLabel label in Labels)
|
||||
target.MarkLabel(label.LabelFor(target));
|
||||
if (Operand != null)
|
||||
Operand.Emit(target);
|
||||
else
|
||||
target.Emit(OpCode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (MsilLabel label in Labels)
|
||||
sb.Append(label).Append(": ");
|
||||
sb.Append(OpCode.Name).Append("\t").Append(Operand);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
67
Torch/Managers/PatchManager/MSIL/MsilLabel.cs
Normal file
67
Torch/Managers/PatchManager/MSIL/MsilLabel.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an abstract label, identified by its reference.
|
||||
/// </summary>
|
||||
public class MsilLabel
|
||||
{
|
||||
private readonly List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>> _labelInstances =
|
||||
new List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>>();
|
||||
|
||||
private readonly Label? _overrideLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty label the allocates a new <see cref="Label" /> when requested.
|
||||
/// </summary>
|
||||
public MsilLabel()
|
||||
{
|
||||
_overrideLabel = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a label the always supplies the given <see cref="Label" />
|
||||
/// </summary>
|
||||
public MsilLabel(Label overrideLabel)
|
||||
{
|
||||
_overrideLabel = overrideLabel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a label that supplies the given <see cref="Label" /> when a label for the given generator is requested,
|
||||
/// otherwise it creates a new label.
|
||||
/// </summary>
|
||||
/// <param name="generator">Generator to register the label on</param>
|
||||
/// <param name="label">Label to register</param>
|
||||
public MsilLabel(LoggingIlGenerator generator, Label label)
|
||||
{
|
||||
_labelInstances.Add(
|
||||
new KeyValuePair<WeakReference<LoggingIlGenerator>, Label>(
|
||||
new WeakReference<LoggingIlGenerator>(generator), label));
|
||||
}
|
||||
|
||||
internal Label LabelFor(LoggingIlGenerator gen)
|
||||
{
|
||||
if (_overrideLabel.HasValue)
|
||||
return _overrideLabel.Value;
|
||||
foreach (KeyValuePair<WeakReference<LoggingIlGenerator>, Label> kv in _labelInstances)
|
||||
if (kv.Key.TryGetTarget(out LoggingIlGenerator gen2) && gen2 == gen)
|
||||
return kv.Value;
|
||||
Label label = gen.DefineLabel();
|
||||
_labelInstances.Add(
|
||||
new KeyValuePair<WeakReference<LoggingIlGenerator>, Label>(new WeakReference<LoggingIlGenerator>(gen),
|
||||
label));
|
||||
return label;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"L{GetHashCode() & 0xFFFF:X4}";
|
||||
}
|
||||
}
|
||||
}
|
25
Torch/Managers/PatchManager/MSIL/MsilOperand.cs
Normal file
25
Torch/Managers/PatchManager/MSIL/MsilOperand.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.IO;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an operand for a MSIL instruction
|
||||
/// </summary>
|
||||
public abstract class MsilOperand
|
||||
{
|
||||
protected MsilOperand(MsilInstruction instruction)
|
||||
{
|
||||
Instruction = instruction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instruction this operand is associated with
|
||||
/// </summary>
|
||||
public MsilInstruction Instruction { get; }
|
||||
|
||||
internal abstract void Read(MethodContext context, BinaryReader reader);
|
||||
|
||||
internal abstract void Emit(LoggingIlGenerator generator);
|
||||
}
|
||||
}
|
40
Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs
Normal file
40
Torch/Managers/PatchManager/MSIL/MsilOperandBrTarget.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.IO;
|
||||
using System.Reflection.Emit;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a branch target operand.
|
||||
/// </summary>
|
||||
public class MsilOperandBrTarget : MsilOperand
|
||||
{
|
||||
internal MsilOperandBrTarget(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Branch target
|
||||
/// </summary>
|
||||
public MsilLabel Target { get; set; }
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
|
||||
? reader.ReadInt32()
|
||||
: reader.ReadByte();
|
||||
Target = context.LabelAt((int) reader.BaseStream.Position + val);
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Target?.ToString() ?? "null";
|
||||
}
|
||||
}
|
||||
}
|
292
Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
Normal file
292
Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
Normal file
@@ -0,0 +1,292 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an inline value
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the inline value</typeparam>
|
||||
public abstract class MsilOperandInline<T> : MsilOperand
|
||||
{
|
||||
internal MsilOperandInline(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline value
|
||||
/// </summary>
|
||||
public T Value { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Value?.ToString() ?? "null";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registry of different inline operand types
|
||||
/// </summary>
|
||||
public static class MsilOperandInline
|
||||
{
|
||||
/// <summary>
|
||||
/// Inline unsigned byte
|
||||
/// </summary>
|
||||
public class MsilOperandUInt8 : MsilOperandInline<byte>
|
||||
{
|
||||
internal MsilOperandUInt8(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value = reader.ReadByte();
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline signed byte
|
||||
/// </summary>
|
||||
public class MsilOperandInt8 : MsilOperandInline<sbyte>
|
||||
{
|
||||
internal MsilOperandInt8(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value =
|
||||
(sbyte) reader.ReadByte();
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline integer
|
||||
/// </summary>
|
||||
public class MsilOperandInt32 : MsilOperandInline<int>
|
||||
{
|
||||
internal MsilOperandInt32(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value = reader.ReadInt32();
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline single
|
||||
/// </summary>
|
||||
public class MsilOperandSingle : MsilOperandInline<float>
|
||||
{
|
||||
internal MsilOperandSingle(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value = reader.ReadSingle();
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline double
|
||||
/// </summary>
|
||||
public class MsilOperandDouble : MsilOperandInline<double>
|
||||
{
|
||||
internal MsilOperandDouble(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value = reader.ReadDouble();
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline long
|
||||
/// </summary>
|
||||
public class MsilOperandInt64 : MsilOperandInline<long>
|
||||
{
|
||||
internal MsilOperandInt64(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value = reader.ReadInt64();
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline string
|
||||
/// </summary>
|
||||
public class MsilOperandString : MsilOperandInline<string>
|
||||
{
|
||||
internal MsilOperandString(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value =
|
||||
context.TokenResolver.ResolveString(reader.ReadInt32());
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline CLR signature
|
||||
/// </summary>
|
||||
public class MsilOperandSignature : MsilOperandInline<SignatureHelper>
|
||||
{
|
||||
internal MsilOperandSignature(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
byte[] sig = context.TokenResolver
|
||||
.ResolveSignature(reader.ReadInt32());
|
||||
throw new ArgumentException("Can't figure out how to convert this.");
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline parameter reference
|
||||
/// </summary>
|
||||
public class MsilOperandParameter : MsilOperandInline<ParameterInfo>
|
||||
{
|
||||
internal MsilOperandParameter(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value =
|
||||
context.Method.GetParameters()[
|
||||
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
||||
? reader.ReadByte()
|
||||
: reader.ReadUInt16()];
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value.Position);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline local variable reference
|
||||
/// </summary>
|
||||
public class MsilOperandLocal : MsilOperandInline<LocalVariableInfo>
|
||||
{
|
||||
internal MsilOperandLocal(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
Value =
|
||||
context.Method.GetMethodBody().LocalVariables[
|
||||
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
||||
? reader.ReadByte()
|
||||
: reader.ReadUInt16()];
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Value.LocalIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inline <see cref="Type" /> or <see cref="MemberInfo" />
|
||||
/// </summary>
|
||||
/// <typeparam name="TY">Actual member type</typeparam>
|
||||
public class MsilOperandReflected<TY> : MsilOperandInline<TY> where TY : class
|
||||
{
|
||||
internal MsilOperandReflected(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
switch (Instruction.OpCode.OperandType)
|
||||
{
|
||||
case OperandType.InlineTok:
|
||||
Value = context.TokenResolver.ResolveMember(reader.ReadInt32()) as TY;
|
||||
break;
|
||||
case OperandType.InlineType:
|
||||
Value = context.TokenResolver.ResolveType(reader.ReadInt32()) as TY;
|
||||
break;
|
||||
case OperandType.InlineMethod:
|
||||
Value = context.TokenResolver.ResolveMethod(reader.ReadInt32()) as TY;
|
||||
break;
|
||||
case OperandType.InlineField:
|
||||
Value = context.TokenResolver.ResolveField(reader.ReadInt32()) as TY;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException("Reflected operand only applies to inline reflected types");
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
if (Value is ConstructorInfo)
|
||||
generator.Emit(Instruction.OpCode, Value as ConstructorInfo);
|
||||
else if (Value is FieldInfo)
|
||||
generator.Emit(Instruction.OpCode, Value as FieldInfo);
|
||||
else if (Value is Type)
|
||||
generator.Emit(Instruction.OpCode, Value as Type);
|
||||
else if (Value is MethodInfo)
|
||||
generator.Emit(Instruction.OpCode, Value as MethodInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
36
Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs
Normal file
36
Torch/Managers/PatchManager/MSIL/MsilOperandSwitch.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using Torch.Managers.PatchManager.Transpile;
|
||||
|
||||
namespace Torch.Managers.PatchManager.MSIL
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the operand for an inline switch statement
|
||||
/// </summary>
|
||||
public class MsilOperandSwitch : MsilOperand
|
||||
{
|
||||
internal MsilOperandSwitch(MsilInstruction instruction) : base(instruction)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target labels for this switch
|
||||
/// </summary>
|
||||
public MsilLabel[] Labels { get; set; }
|
||||
|
||||
internal override void Read(MethodContext context, BinaryReader reader)
|
||||
{
|
||||
int length = reader.ReadInt32();
|
||||
int offset = (int) reader.BaseStream.Position + 4 * length;
|
||||
Labels = new MsilLabel[length];
|
||||
for (var i = 0; i < Labels.Length; i++)
|
||||
Labels[i] = context.LabelAt(offset + reader.ReadInt32());
|
||||
}
|
||||
|
||||
internal override void Emit(LoggingIlGenerator generator)
|
||||
{
|
||||
generator.Emit(Instruction.OpCode, Labels?.Select(x => x.LabelFor(generator))?.ToArray() ?? new Label[0]);
|
||||
}
|
||||
}
|
||||
}
|
172
Torch/Managers/PatchManager/MethodRewritePattern.cs
Normal file
172
Torch/Managers/PatchManager/MethodRewritePattern.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the different components used to rewrite a method.
|
||||
/// </summary>
|
||||
public class MethodRewritePattern
|
||||
{
|
||||
/// <summary>
|
||||
/// Sorts methods so that their <see cref="PatchPriorityAttribute"/> priority is in descending order. Assumes priority zero if no attribute exists.
|
||||
/// </summary>
|
||||
private class MethodPriorityCompare : Comparer<MethodInfo>
|
||||
{
|
||||
internal static readonly MethodPriorityCompare Instance = new MethodPriorityCompare();
|
||||
|
||||
public override int Compare(MethodInfo x, MethodInfo y)
|
||||
{
|
||||
return -(x?.GetCustomAttribute<PatchPriorityAttribute>()?.Priority ?? 0).CompareTo(
|
||||
y?.GetCustomAttribute<PatchPriorityAttribute>()?.Priority ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores an set of methods according to a certain order.
|
||||
/// </summary>
|
||||
public class MethodRewriteSet : IEnumerable<MethodInfo>
|
||||
{
|
||||
private readonly MethodRewriteSet _backingSet;
|
||||
private bool _sortDirty = false;
|
||||
private readonly List<MethodInfo> _backingList = new List<MethodInfo>();
|
||||
|
||||
private int _hasChanges = 0;
|
||||
|
||||
internal bool HasChanges()
|
||||
{
|
||||
return Interlocked.Exchange(ref _hasChanges, 0) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// <param name="backingSet">The set to track changes on</param>
|
||||
internal MethodRewriteSet(MethodRewriteSet backingSet)
|
||||
{
|
||||
_backingSet = backingSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the given method to this set if it doesn't already exist in the tracked set and this set.
|
||||
/// </summary>
|
||||
/// <param name="m">Method to add</param>
|
||||
/// <returns>true if added</returns>
|
||||
public bool Add(MethodInfo m)
|
||||
{
|
||||
if (!m.IsStatic)
|
||||
throw new ArgumentException("Patch methods must be static");
|
||||
if (_backingSet != null && !_backingSet.Add(m))
|
||||
return false;
|
||||
if (_backingList.Contains(m))
|
||||
return false;
|
||||
_sortDirty = true;
|
||||
Interlocked.Exchange(ref _hasChanges, 1);
|
||||
_backingList.Add(m);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given method from this set, and from the tracked set if it existed in this set.
|
||||
/// </summary>
|
||||
/// <param name="m">Method to remove</param>
|
||||
/// <returns>true if removed</returns>
|
||||
public bool Remove(MethodInfo m)
|
||||
{
|
||||
if (_backingList.Remove(m))
|
||||
{
|
||||
_sortDirty = true;
|
||||
Interlocked.Exchange(ref _hasChanges, 1);
|
||||
return _backingSet == null || _backingSet.Remove(m);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all methods from this set, and their matches in the tracked set.
|
||||
/// </summary>
|
||||
public void RemoveAll()
|
||||
{
|
||||
foreach (var k in _backingList)
|
||||
_backingSet.Remove(k);
|
||||
_backingList.Clear();
|
||||
_sortDirty = true;
|
||||
Interlocked.Exchange(ref _hasChanges, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of methods stored in this set.
|
||||
/// </summary>
|
||||
public int Count => _backingList.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ordered enumerator over this set
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<MethodInfo> GetEnumerator()
|
||||
{
|
||||
CheckSort();
|
||||
return _backingList.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an ordered enumerator over this set
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
CheckSort();
|
||||
return _backingList.GetEnumerator();
|
||||
}
|
||||
|
||||
private void CheckSort()
|
||||
{
|
||||
if (!_sortDirty)
|
||||
return;
|
||||
var tmp = _backingList.ToArray();
|
||||
MergeSort(tmp, _backingList, MethodPriorityCompare.Instance, 0, _backingList.Count);
|
||||
_sortDirty = false;
|
||||
}
|
||||
|
||||
private static void MergeSort<T>(IList<T> src, IList<T> dst, Comparer<T> comparer, int left, int right)
|
||||
{
|
||||
if (left + 1 >= right)
|
||||
return;
|
||||
var mid = (left + right) / 2;
|
||||
MergeSort<T>(dst, src, comparer, left, mid);
|
||||
MergeSort<T>(dst, src, comparer, mid, right);
|
||||
for (int i = left, j = left, k = mid; i < right; i++)
|
||||
if ((k >= right || j < mid) && comparer.Compare(src[j], src[k]) <= 0)
|
||||
dst[i] = src[j++];
|
||||
else
|
||||
dst[i] = src[k++];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Methods run before the original method is run. If they return false the original method is skipped.
|
||||
/// </summary>
|
||||
public MethodRewriteSet Prefixes { get; }
|
||||
/// <summary>
|
||||
/// Methods capable of accepting one <see cref="IEnumerable{MsilInstruction}"/> and returing another, modified.
|
||||
/// </summary>
|
||||
public MethodRewriteSet Transpilers { get; }
|
||||
/// <summary>
|
||||
/// Methods run after the original method has run.
|
||||
/// </summary>
|
||||
public MethodRewriteSet Suffixes { get; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="parentPattern">The pattern to track changes on, or null</param>
|
||||
public MethodRewritePattern(MethodRewritePattern parentPattern)
|
||||
{
|
||||
Prefixes = new MethodRewriteSet(parentPattern?.Prefixes);
|
||||
Transpilers = new MethodRewriteSet(parentPattern?.Transpilers);
|
||||
Suffixes = new MethodRewriteSet(parentPattern?.Suffixes);
|
||||
}
|
||||
}
|
||||
}
|
44
Torch/Managers/PatchManager/PatchContext.cs
Normal file
44
Torch/Managers/PatchManager/PatchContext.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a set of common patches that can all be reversed in a single step.
|
||||
/// </summary>
|
||||
public class PatchContext
|
||||
{
|
||||
private readonly PatchManager _replacer;
|
||||
private readonly Dictionary<MethodBase, MethodRewritePattern> _rewritePatterns = new Dictionary<MethodBase, MethodRewritePattern>();
|
||||
|
||||
internal PatchContext(PatchManager replacer)
|
||||
{
|
||||
_replacer = replacer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rewrite pattern used to tracking changes in this context, creating one if it doesn't exist.
|
||||
/// </summary>
|
||||
/// <param name="method">Method to get the pattern for</param>
|
||||
/// <returns></returns>
|
||||
public MethodRewritePattern GetPattern(MethodBase method)
|
||||
{
|
||||
if (_rewritePatterns.TryGetValue(method, out MethodRewritePattern pattern))
|
||||
return pattern;
|
||||
MethodRewritePattern parent = _replacer.GetPattern(method);
|
||||
var res = new MethodRewritePattern(parent);
|
||||
_rewritePatterns.Add(method, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
internal void RemoveAll()
|
||||
{
|
||||
foreach (MethodRewritePattern pattern in _rewritePatterns.Values)
|
||||
{
|
||||
pattern.Prefixes.RemoveAll();
|
||||
pattern.Transpilers.RemoveAll();
|
||||
pattern.Suffixes.RemoveAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
86
Torch/Managers/PatchManager/PatchManager.cs
Normal file
86
Torch/Managers/PatchManager/PatchManager.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Torch.API;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies and removes patches from the IL of methods.
|
||||
/// </summary>
|
||||
public class PatchManager : Manager
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new patch manager. Only have one active at a time.
|
||||
/// </summary>
|
||||
/// <param name="torchInstance"></param>
|
||||
public PatchManager(ITorchBase torchInstance) : base(torchInstance)
|
||||
{
|
||||
}
|
||||
|
||||
private readonly Dictionary<MethodBase, DecoratedMethod> _rewritePatterns = new Dictionary<MethodBase, DecoratedMethod>();
|
||||
private readonly HashSet<PatchContext> _contexts = new HashSet<PatchContext>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rewrite pattern for the given method, creating one if it doesn't exist.
|
||||
/// </summary>
|
||||
/// <param name="method">Method to get the pattern for</param>
|
||||
/// <returns></returns>
|
||||
public MethodRewritePattern GetPattern(MethodBase method)
|
||||
{
|
||||
if (_rewritePatterns.TryGetValue(method, out DecoratedMethod pattern))
|
||||
return pattern;
|
||||
var res = new DecoratedMethod(method);
|
||||
_rewritePatterns.Add(method, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PatchContext"/> used for tracking changes. A call to <see cref="Commit"/> will apply the patches.
|
||||
/// </summary>
|
||||
public PatchContext AcquireContext()
|
||||
{
|
||||
var context = new PatchContext(this);
|
||||
_contexts.Add(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees the given context, and unregister all patches from it. A call to <see cref="Commit"/> will apply the unpatching operation.
|
||||
/// </summary>
|
||||
/// <param name="context">Context to remove</param>
|
||||
public void FreeContext(PatchContext context)
|
||||
{
|
||||
context.RemoveAll();
|
||||
_contexts.Remove(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits all method decorations into IL.
|
||||
/// </summary>
|
||||
public void Commit()
|
||||
{
|
||||
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
||||
m.Commit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Commits any existing patches.
|
||||
/// </summary>
|
||||
public override void Attach()
|
||||
{
|
||||
Commit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters and removes all patches, then applies the unpatching operation.
|
||||
/// </summary>
|
||||
public override void Detach()
|
||||
{
|
||||
foreach (DecoratedMethod m in _rewritePatterns.Values)
|
||||
m.Revert();
|
||||
_rewritePatterns.Clear();
|
||||
_contexts.Clear();
|
||||
}
|
||||
}
|
||||
}
|
24
Torch/Managers/PatchManager/PatchPriorityAttribute.cs
Normal file
24
Torch/Managers/PatchManager/PatchPriorityAttribute.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Torch.Managers.PatchManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Attribute used to decorate methods used for replacement.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class PatchPriorityAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Priority"/>
|
||||
/// </summary>
|
||||
public PatchPriorityAttribute(int priority)
|
||||
{
|
||||
Priority = priority;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The priority of this replacement. A high priority prefix occurs first, and a high priority suffix or transpiler occurs last.
|
||||
/// </summary>
|
||||
public int Priority { get; set; } = 0;
|
||||
}
|
||||
}
|
173
Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs
Normal file
173
Torch/Managers/PatchManager/Transpile/LoggingILGenerator.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using NLog;
|
||||
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalse
|
||||
#pragma warning disable 162 // unreachable code
|
||||
namespace Torch.Managers.PatchManager.Transpile
|
||||
{
|
||||
/// <summary>
|
||||
/// An ILGenerator that can log emit calls when <see cref="LoggingIlGenerator._logging"/> is enabled.
|
||||
/// </summary>
|
||||
public class LoggingIlGenerator
|
||||
{
|
||||
private const bool _logging = false;
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
/// <summary>
|
||||
/// Backing generator
|
||||
/// </summary>
|
||||
public ILGenerator Backing { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new logging IL generator backed by the given generator.
|
||||
/// </summary>
|
||||
/// <param name="backing">Backing generator</param>
|
||||
public LoggingIlGenerator(ILGenerator backing)
|
||||
{
|
||||
Backing = backing;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.DeclareLocal(Type, bool)"/>
|
||||
public LocalBuilder DeclareLocal(Type localType, bool isPinned = false)
|
||||
{
|
||||
LocalBuilder res = Backing.DeclareLocal(localType, isPinned);
|
||||
if (_logging)
|
||||
_log.Trace($"DeclareLocal {res.LocalType} {res.IsPinned} => {res.LocalIndex}");
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode)"/>
|
||||
public void Emit(OpCode op)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op}");
|
||||
Backing.Emit(op);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
|
||||
public void Emit(OpCode op, LocalBuilder arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} L:{arg.LocalIndex} {arg.LocalType}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/>
|
||||
public void Emit(OpCode op, int arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, long)"/>
|
||||
public void Emit(OpCode op, long arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, float)"/>
|
||||
public void Emit(OpCode op, float arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, double)"/>
|
||||
public void Emit(OpCode op, double arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, string)"/>
|
||||
public void Emit(OpCode op, string arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Type)"/>
|
||||
public void Emit(OpCode op, Type arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, FieldInfo)"/>
|
||||
public void Emit(OpCode op, FieldInfo arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, MethodInfo)"/>
|
||||
public void Emit(OpCode op, MethodInfo arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
private static FieldInfo _labelID =
|
||||
typeof(Label).GetField("m_label", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label)"/>
|
||||
public void Emit(OpCode op, Label arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} L:{_labelID.GetValue(arg)}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label[])"/>
|
||||
public void Emit(OpCode op, Label[] arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {string.Join(", ", arg.Select(x => "L:" + _labelID.GetValue(x)))}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, SignatureHelper)"/>
|
||||
public void Emit(OpCode op, SignatureHelper arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, ConstructorInfo)"/>
|
||||
public void Emit(OpCode op, ConstructorInfo arg)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"Emit {op} {arg}");
|
||||
Backing.Emit(op, arg);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/>
|
||||
public void MarkLabel(Label label)
|
||||
{
|
||||
if (_logging)
|
||||
_log.Trace($"MarkLabel L:{_labelID.GetValue(label)}");
|
||||
Backing.MarkLabel(label);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ILGenerator.DefineLabel()"/>
|
||||
public Label DefineLabel()
|
||||
{
|
||||
return Backing.DefineLabel();
|
||||
}
|
||||
}
|
||||
#pragma warning restore 162
|
||||
}
|
110
Torch/Managers/PatchManager/Transpile/MethodContext.cs
Normal file
110
Torch/Managers/PatchManager/Transpile/MethodContext.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using NLog;
|
||||
using Torch.Managers.PatchManager.MSIL;
|
||||
|
||||
namespace Torch.Managers.PatchManager.Transpile
|
||||
{
|
||||
internal class MethodContext
|
||||
{
|
||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public readonly MethodBase Method;
|
||||
private readonly byte[] _msilBytes;
|
||||
|
||||
internal Dictionary<int, MsilLabel> Labels { get; } = new Dictionary<int, MsilLabel>();
|
||||
private readonly List<MsilInstruction> _instructions = new List<MsilInstruction>();
|
||||
public IReadOnlyList<MsilInstruction> Instructions => _instructions;
|
||||
|
||||
internal ITokenResolver TokenResolver { get; }
|
||||
|
||||
internal MsilLabel LabelAt(int i)
|
||||
{
|
||||
if (Labels.TryGetValue(i, out MsilLabel label))
|
||||
return label;
|
||||
Labels.Add(i, label = new MsilLabel());
|
||||
return label;
|
||||
}
|
||||
|
||||
public MethodContext(MethodBase method)
|
||||
{
|
||||
Method = method;
|
||||
_msilBytes = Method.GetMethodBody().GetILAsByteArray();
|
||||
TokenResolver = new NormalTokenResolver(method);
|
||||
}
|
||||
|
||||
public void Read()
|
||||
{
|
||||
ReadInstructions();
|
||||
ResolveLabels();
|
||||
}
|
||||
|
||||
private void ReadInstructions()
|
||||
{
|
||||
Labels.Clear();
|
||||
_instructions.Clear();
|
||||
using (var memory = new MemoryStream(_msilBytes))
|
||||
using (var reader = new BinaryReader(memory))
|
||||
while (memory.Length > memory.Position)
|
||||
{
|
||||
var instructionValue = (short)memory.ReadByte();
|
||||
if (Prefixes.Contains(instructionValue))
|
||||
instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
|
||||
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
|
||||
throw new Exception($"Unknown opcode {instructionValue:X}");
|
||||
var instruction = new MsilInstruction(opcode)
|
||||
{
|
||||
Offset = (int) memory.Position
|
||||
};
|
||||
_instructions.Add(instruction);
|
||||
instruction.Operand?.Read(this, reader);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveLabels()
|
||||
{
|
||||
foreach (var label in Labels)
|
||||
{
|
||||
int min = 0, max = _instructions.Count - 1;
|
||||
while (min <= max)
|
||||
{
|
||||
var mid = min + ((max - min) / 2);
|
||||
if (label.Key < _instructions[mid].Offset)
|
||||
max = mid - 1;
|
||||
else
|
||||
min = mid + 1;
|
||||
}
|
||||
_instructions[min]?.Labels?.Add(label.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public string ToHumanMsil()
|
||||
{
|
||||
return string.Join("\n", _instructions.Select(x => x.Offset + "\t" + x));
|
||||
}
|
||||
|
||||
private static readonly Dictionary<short, OpCode> OpCodeLookup;
|
||||
private static readonly HashSet<short> Prefixes;
|
||||
|
||||
static MethodContext()
|
||||
{
|
||||
OpCodeLookup = new Dictionary<short, OpCode>();
|
||||
Prefixes = new HashSet<short>();
|
||||
foreach (FieldInfo field in typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public))
|
||||
{
|
||||
var opcode = (OpCode)field.GetValue(null);
|
||||
if (opcode.OpCodeType != OpCodeType.Nternal)
|
||||
OpCodeLookup.Add(opcode.Value, opcode);
|
||||
if ((ushort) opcode.Value > 0xFF)
|
||||
{
|
||||
Prefixes.Add((short) ((ushort) opcode.Value >> 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
61
Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
Normal file
61
Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using Torch.Managers.PatchManager.MSIL;
|
||||
|
||||
namespace Torch.Managers.PatchManager.Transpile
|
||||
{
|
||||
internal class MethodTranspiler
|
||||
{
|
||||
internal static void Transpile(MethodBase baseMethod, IEnumerable<MethodInfo> transpilers, LoggingIlGenerator output, Label? retLabel)
|
||||
{
|
||||
var context = new MethodContext(baseMethod);
|
||||
context.Read();
|
||||
var methodContent = (IEnumerable<MsilInstruction>) context.Instructions;
|
||||
foreach (var transpiler in transpilers)
|
||||
methodContent = (IEnumerable<MsilInstruction>)transpiler.Invoke(null, new object[] { methodContent });
|
||||
methodContent = FixBranchAndReturn(methodContent, retLabel);
|
||||
foreach (var k in methodContent)
|
||||
k.Emit(output);
|
||||
}
|
||||
|
||||
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, Label? retTarget)
|
||||
{
|
||||
foreach (var i in insn)
|
||||
{
|
||||
if (retTarget.HasValue && i.OpCode == OpCodes.Ret)
|
||||
{
|
||||
var j = new MsilInstruction(OpCodes.Br);
|
||||
((MsilOperandBrTarget)j.Operand).Target = new MsilLabel(retTarget.Value);
|
||||
yield return j;
|
||||
continue;
|
||||
}
|
||||
if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
|
||||
{
|
||||
yield return new MsilInstruction(replaceOpcode) { Operand = i.Operand };
|
||||
continue;
|
||||
}
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<OpCode, OpCode> _opcodeReplaceRule;
|
||||
static MethodTranspiler()
|
||||
{
|
||||
_opcodeReplaceRule = new Dictionary<OpCode, OpCode>();
|
||||
foreach (var field in typeof(OpCodes).GetFields(BindingFlags.Static | BindingFlags.Public))
|
||||
{
|
||||
var opcode = (OpCode)field.GetValue(null);
|
||||
if (opcode.OperandType == OperandType.ShortInlineBrTarget &&
|
||||
opcode.Name.EndsWith("_S", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var other = (OpCode?) typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2),
|
||||
BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
|
||||
if (other.HasValue && other.Value.OperandType == OperandType.InlineBrTarget)
|
||||
_opcodeReplaceRule.Add(opcode, other.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,6 +21,7 @@
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||
@@ -31,6 +32,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.xml</DocumentationFile>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||
@@ -156,6 +158,23 @@
|
||||
<Compile Include="Collections\ObservableList.cs" />
|
||||
<Compile Include="DispatcherExtensions.cs" />
|
||||
<Compile Include="Managers\DependencyManager.cs" />
|
||||
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
||||
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
||||
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\ITokenResolver.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\MsilInstruction.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\MsilLabel.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperand.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandBrTarget.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandSwitch.cs" />
|
||||
<Compile Include="Managers\PatchManager\MethodRewritePattern.cs" />
|
||||
<Compile Include="Managers\PatchManager\PatchContext.cs" />
|
||||
<Compile Include="Managers\PatchManager\PatchManager.cs" />
|
||||
<Compile Include="Managers\PatchManager\PatchPriorityAttribute.cs" />
|
||||
<Compile Include="Managers\PatchManager\Transpile\LoggingILGenerator.cs" />
|
||||
<Compile Include="Managers\PatchManager\Transpile\MethodContext.cs" />
|
||||
<Compile Include="Managers\PatchManager\Transpile\MethodTranspiler.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SaveGameStatus.cs" />
|
||||
<Compile Include="Collections\KeyTree.cs" />
|
||||
|
Reference in New Issue
Block a user