Merge branch 'staging' into plugins

This commit is contained in:
John Gross
2017-09-21 20:26:32 -07:00
committed by GitHub
22 changed files with 2482 additions and 16 deletions

386
Torch.Tests/PatchTest.cs Normal file
View File

@@ -0,0 +1,386 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Text;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
using Torch.Utils;
using Xunit;
// ReSharper disable UnusedMember.Local
namespace Torch.Tests
{
#pragma warning disable 414
public class PatchTest
{
#region TestRunner
private static readonly PatchManager _patchContext = new PatchManager(null);
[Theory]
[MemberData(nameof(Prefixes))]
public void TestPrefix(TestBootstrap runner)
{
runner.TestPrefix();
}
[Theory]
[MemberData(nameof(Transpilers))]
public void TestTranspile(TestBootstrap runner)
{
runner.TestTranspile();
}
[Theory]
[MemberData(nameof(Suffixes))]
public void TestSuffix(TestBootstrap runner)
{
runner.TestSuffix();
}
[Theory]
[MemberData(nameof(Combo))]
public void TestCombo(TestBootstrap runner)
{
runner.TestCombo();
}
public class TestBootstrap
{
public bool HasPrefix => _prefixMethod != null;
public bool HasTranspile => _transpileMethod != null;
public bool HasSuffix => _suffixMethod != null;
private readonly MethodInfo _prefixMethod, _prefixAssert;
private readonly MethodInfo _suffixMethod, _suffixAssert;
private readonly MethodInfo _transpileMethod, _transpileAssert;
private readonly MethodInfo _targetMethod, _targetAssert;
private readonly MethodInfo _resetMethod;
private readonly object _instance;
private readonly object[] _targetParams;
private readonly Type _type;
public TestBootstrap(Type t)
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance;
_type = t;
_prefixMethod = t.GetMethod("Prefix", flags);
_prefixAssert = t.GetMethod("AssertPrefix", flags);
_suffixMethod = t.GetMethod("Suffix", flags);
_suffixAssert = t.GetMethod("AssertSuffix", flags);
_transpileMethod = t.GetMethod("Transpile", flags);
_transpileAssert = t.GetMethod("AssertTranspile", flags);
_targetMethod = t.GetMethod("Target", flags);
_targetAssert = t.GetMethod("AssertNormal", flags);
_resetMethod = t.GetMethod("Reset", flags);
if (_targetMethod == null)
throw new Exception($"{t.FullName} must have a method named Target");
if (_targetAssert == null)
throw new Exception($"{t.FullName} must have a method named AssertNormal");
_instance = !_targetMethod.IsStatic ? Activator.CreateInstance(t) : null;
_targetParams = (object[])t.GetField("_targetParams", flags)?.GetValue(null) ?? new object[0];
}
private void Invoke(MethodBase i, params object[] args)
{
if (i == null) return;
i.Invoke(i.IsStatic ? null : _instance, args);
}
private void Invoke()
{
_targetMethod.Invoke(_instance, _targetParams);
Invoke(_targetAssert);
}
public void TestPrefix()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
_patchContext.Commit();
Invoke();
Invoke(_prefixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestSuffix()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
_patchContext.Commit();
Invoke();
Invoke(_suffixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestTranspile()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
_patchContext.Commit();
Invoke();
Invoke(_transpileAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public void TestCombo()
{
Invoke(_resetMethod);
PatchContext context = _patchContext.AcquireContext();
if (_prefixMethod != null)
context.GetPattern(_targetMethod).Prefixes.Add(_prefixMethod);
if (_transpileMethod != null)
context.GetPattern(_targetMethod).Transpilers.Add(_transpileMethod);
if (_suffixMethod != null)
context.GetPattern(_targetMethod).Suffixes.Add(_suffixMethod);
_patchContext.Commit();
Invoke();
Invoke(_prefixAssert);
Invoke(_transpileAssert);
Invoke(_suffixAssert);
_patchContext.FreeContext(context);
_patchContext.Commit();
}
public override string ToString()
{
return _type.Name;
}
}
private class PatchTestAttribute : Attribute
{
}
private static readonly List<TestBootstrap> _patchTest;
static PatchTest()
{
TestUtils.Init();
foreach (Type type in typeof(PatchManager).Assembly.GetTypes())
if (type.Namespace?.StartsWith(typeof(PatchManager).Namespace ?? "") ?? false)
ReflectedManager.Process(type);
_patchTest = new List<TestBootstrap>();
foreach (Type type in typeof(PatchTest).GetNestedTypes(BindingFlags.NonPublic))
if (type.GetCustomAttribute(typeof(PatchTestAttribute)) != null)
_patchTest.Add(new TestBootstrap(type));
}
public static IEnumerable<object[]> Prefixes => _patchTest.Where(x => x.HasPrefix).Select(x => new object[] { x });
public static IEnumerable<object[]> Transpilers => _patchTest.Where(x => x.HasTranspile).Select(x => new object[] { x });
public static IEnumerable<object[]> Suffixes => _patchTest.Where(x => x.HasSuffix).Select(x => new object[] { x });
public static IEnumerable<object[]> Combo => _patchTest.Where(x => x.HasPrefix || x.HasTranspile || x.HasSuffix).Select(x => new object[] { x });
#endregion
#region PatchTests
[PatchTest]
private class StaticNoRetNoParm
{
private static bool _prefixHit, _normalHit, _suffixHit, _transpileHit;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix()
{
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target()
{
_normalHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Suffix()
{
_suffixHit = true;
}
public static IEnumerable<MsilInstruction> Transpile(IEnumerable<MsilInstruction> instructions)
{
yield return new MsilInstruction(OpCodes.Ldnull);
yield return new MsilInstruction(OpCodes.Ldc_I4_1);
yield return new MsilInstruction(OpCodes.Stfld).InlineValue(typeof(StaticNoRetNoParm).GetField("_transpileHit", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public));
foreach (MsilInstruction i in instructions)
yield return i;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = _transpileHit = false;
}
public static void AssertTranspile()
{
Assert.True(_transpileHit, "Failed to transpile");
}
public static void AssertSuffix()
{
Assert.True(_suffixHit, "Failed to suffix");
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
[PatchTest]
private class StaticNoRetParam
{
private static bool _prefixHit, _normalHit, _suffixHit;
private static readonly object[] _targetParams = { "test", 1, new StringBuilder("test1") };
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix(string str, int i, StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(string str, int i, StringBuilder o)
{
_normalHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Suffix(string str, int i, StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
_suffixHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertSuffix()
{
Assert.True(_suffixHit, "Failed to suffix");
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
[PatchTest]
private class StaticNoRetParamReplace
{
private static bool _prefixHit, _normalHit, _suffixHit;
private static readonly object[] _targetParams = { "test", 1, new StringBuilder("stest1") };
private static readonly object[] _replacedParams = { "test2", 2, new StringBuilder("stest2") };
private static object[] _calledParams;
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Prefix(ref string str, ref int i, ref StringBuilder o)
{
Assert.Equal(_targetParams[0], str);
Assert.Equal(_targetParams[1], i);
Assert.Equal(_targetParams[2], o);
str = (string)_replacedParams[0];
i = (int)_replacedParams[1];
o = (StringBuilder)_replacedParams[2];
_prefixHit = true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target(string str, int i, StringBuilder o)
{
_calledParams = new object[] { str, i, o };
_normalHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertNormal()
{
Assert.True(_normalHit, "Failed to execute normally");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
for (var i = 0; i < 3; i++)
Assert.Equal(_replacedParams[i], _calledParams[i]);
}
}
[PatchTest]
private class StaticCancelExec
{
private static bool _prefixHit, _normalHit, _suffixHit;
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool Prefix()
{
_prefixHit = true;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Target()
{
_normalHit = true;
}
public static void Reset()
{
_prefixHit = _normalHit = _suffixHit = false;
}
public static void AssertNormal()
{
Assert.False(_normalHit, "Executed normally when canceled");
}
public static void AssertPrefix()
{
Assert.True(_prefixHit, "Failed to prefix");
}
}
#endregion
}
#pragma warning restore 414
}

View File

@@ -63,6 +63,7 @@
<Compile Include="..\Versioning\AssemblyVersion.cs">
<Link>Properties\AssemblyVersion.cs</Link>
</Compile>
<Compile Include="PatchTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ReflectionTestManager.cs" />
<Compile Include="ReflectionSystemTest.cs" />

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Threading;
@@ -10,28 +12,148 @@ using System.Windows.Threading;
namespace Torch.Collections
{
[Serializable]
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
public class ObservableDictionary<TKey, TValue> : ViewModel, IDictionary<TKey, TValue>, INotifyCollectionChanged
{
/// <inheritdoc />
public new void Add(TKey key, TValue value)
private IDictionary<TKey, TValue> _internalDict;
public ObservableDictionary()
{
base.Add(key, value);
var kv = new KeyValuePair<TKey, TValue>(key, value);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, kv));
_internalDict = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_internalDict = new Dictionary<TKey, TValue>(dictionary);
}
/// <summary>
/// Create a <see cref="ObservableDictionary{TKey,TValue}"/> using the given dictionary by reference. The original dictionary should not be used after calling this.
/// </summary>
public static ObservableDictionary<TKey, TValue> ByReference(IDictionary<TKey, TValue> dictionary)
{
return new ObservableDictionary<TKey, TValue>
{
_internalDict = dictionary
};
}
/// <inheritdoc />
public new bool Remove(TKey key)
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
/// <inheritdoc />
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
if (!ContainsKey(key))
return _internalDict.GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)_internalDict).GetEnumerator();
}
/// <inheritdoc />
public void Add(KeyValuePair<TKey, TValue> item)
{
Add(item.Key, item.Value);
}
/// <inheritdoc />
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
/// <inheritdoc />
public void Clear()
{
_internalDict.Clear();
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
OnPropertyChanged(nameof(Count));
}
/// <inheritdoc />
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _internalDict.Contains(item);
}
/// <inheritdoc />
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
foreach (var kv in _internalDict)
{
array[arrayIndex] = kv;
arrayIndex++;
}
}
/// <inheritdoc />
public int Count => _internalDict.Count;
/// <inheritdoc />
public bool IsReadOnly => false;
/// <inheritdoc />
public bool ContainsKey(TKey key)
{
return _internalDict.ContainsKey(key);
}
/// <inheritdoc />
public void Add(TKey key, TValue value)
{
_internalDict.Add(key, value);
var kv = new KeyValuePair<TKey, TValue>(key, value);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, kv));
OnPropertyChanged(nameof(Count));
}
/// <inheritdoc />
public bool Remove(TKey key)
{
if (!_internalDict.ContainsKey(key))
return false;
var kv = new KeyValuePair<TKey, TValue>(key, this[key]);
base.Remove(key);
if (!_internalDict.Remove(key))
return false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, kv));
OnPropertyChanged(nameof(Count));
return true;
}
/// <inheritdoc />
public bool TryGetValue(TKey key, out TValue value)
{
return _internalDict.TryGetValue(key, out value);
}
/// <inheritdoc />
public TValue this[TKey key]
{
get => _internalDict[key];
set
{
var oldKv = new KeyValuePair<TKey, TValue>(key, _internalDict[key]);
var newKv = new KeyValuePair<TKey, TValue>(key, value);
_internalDict[key] = value;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newKv, oldKv));
}
}
/// <inheritdoc />
public ICollection<TKey> Keys => _internalDict.Keys;
/// <inheritdoc />
public ICollection<TValue> Values => _internalDict.Values;
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
@@ -52,12 +174,5 @@ namespace Torch.Collections
nh.Invoke(this, e);
}
}
/// <inheritdoc />
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <inheritdoc />
public event PropertyChangedEventHandler PropertyChanged;
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Torch.Utils;
namespace Torch.Managers.PatchManager
{
internal class AssemblyMemory
{
#pragma warning disable 649
[ReflectedMethod(Name = "GetMethodDescriptor")]
private static Func<DynamicMethod, RuntimeMethodHandle> _getMethodHandle;
#pragma warning restore 649
/// <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 dyn)
handle = _getMethodHandle.Invoke(dyn);
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;
}
}
}

View File

@@ -0,0 +1,219 @@
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;
using Torch.Utils;
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();
_pinnedPatch = null;
}
}
#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";
#pragma warning disable 649
[ReflectedStaticMethod(Type = typeof(RuntimeHelpers), Name = "_CompileMethod", OverrideTypeNames = new[] { "System.IRuntimeMethodInfo" })]
private static Action<object> _compileDynamicMethod;
[ReflectedMethod(Name = "GetMethodInfo")]
private static Func<RuntimeMethodHandle, object> _getMethodInfo;
[ReflectedMethod(Name = "GetMethodDescriptor")]
private static Func<DynamicMethod, RuntimeMethodHandle> _getMethodHandle;
#pragma warning restore 649
public DynamicMethod ComposePatchedMethod()
{
DynamicMethod method = AllocatePatchMethod();
var generator = new LoggingIlGenerator(method.GetILGenerator());
EmitPatched(generator);
// Force it to compile
RuntimeMethodHandle handle = _getMethodHandle.Invoke(method);
object runtimeMethodInfo = _getMethodInfo.Invoke(handle);
_compileDynamicMethod.Invoke(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>();
Label? labelAfterOriginalContent = Suffixes.Count > 0 ? target.DefineLabel() : (Label?)null;
Label? labelAfterOriginalReturn = Prefixes.Any(x => x.ReturnType == typeof(bool)) ? target.DefineLabel() : (Label?)null;
var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
var resultVariable = returnType != typeof(void) && (labelAfterOriginalReturn.HasValue || // If we jump past main content we need local to store return val
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);
target.EmitComment("Prefixes Begin");
foreach (var prefix in Prefixes)
{
EmitMonkeyCall(target, prefix, specialVariables);
if (prefix.ReturnType == typeof(bool))
{
Debug.Assert(labelAfterOriginalReturn.HasValue);
target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn.Value);
}
else if (prefix.ReturnType != typeof(void))
throw new Exception(
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
}
target.EmitComment("Prefixes End");
target.EmitComment("Original Begin");
MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent);
target.EmitComment("Original End");
if (labelAfterOriginalContent.HasValue)
{
target.MarkLabel(labelAfterOriginalContent.Value);
if (resultVariable != null)
target.Emit(OpCodes.Stloc, resultVariable);
}
if (labelAfterOriginalReturn.HasValue)
target.MarkLabel(labelAfterOriginalReturn.Value);
target.EmitComment("Suffixes Begin");
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}");
}
target.EmitComment("Suffixes End");
if (labelAfterOriginalContent.HasValue || labelAfterOriginalReturn.HasValue)
{
if (resultVariable != null)
target.Emit(OpCodes.Ldloc, resultVariable);
target.Emit(OpCodes.Ret);
}
}
private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch,
IReadOnlyDictionary<string, LocalBuilder> specialVariables)
{
target.EmitComment($"Call {patch.DeclaringType?.FullName}#{patch.Name}");
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
}
}

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

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

View 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<MethodBase>(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();
}
}
}

View 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}";
}
}
}

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

View 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";
}
}
}

View File

@@ -0,0 +1,298 @@
using System;
using System.Diagnostics;
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)
{
object value = null;
switch (Instruction.OpCode.OperandType)
{
case OperandType.InlineTok:
value = context.TokenResolver.ResolveMember(reader.ReadInt32());
break;
case OperandType.InlineType:
value = context.TokenResolver.ResolveType(reader.ReadInt32());
break;
case OperandType.InlineMethod:
value = context.TokenResolver.ResolveMethod(reader.ReadInt32());
break;
case OperandType.InlineField:
value = context.TokenResolver.ResolveField(reader.ReadInt32());
break;
default:
throw new ArgumentException("Reflected operand only applies to inline reflected types");
}
if (value is TY vty)
Value = vty;
else
throw new Exception($"Expected type {typeof(TY).Name} from operand {Instruction.OpCode.OperandType}, got {value.GetType()?.Name ?? "null"}");
}
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);
}
}
}
}

View 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]);
}
}
}

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

View 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();
}
}
}
}

View 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();
}
}
}

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

View File

@@ -0,0 +1,173 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using NLog;
using Torch.Utils;
// ReSharper disable ConditionIsAlwaysTrueOrFalse
#pragma warning disable 162 // unreachable code
namespace Torch.Managers.PatchManager.Transpile
{
/// <summary>
/// An ILGenerator that can log emit calls when the TRACE level is enabled.
/// </summary>
public class LoggingIlGenerator
{
private const int _opcodePadding = -10;
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);
_log?.Trace($"DclLoc\t{res.LocalIndex}\t=> {res.LocalType} {res.IsPinned}");
return res;
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode)"/>
public void Emit(OpCode op)
{
_log?.Trace($"Emit\t{op,_opcodePadding}");
Backing.Emit(op);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
public void Emit(OpCode op, LocalBuilder arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, int)"/>
public void Emit(OpCode op, int arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, long)"/>
public void Emit(OpCode op, long arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, float)"/>
public void Emit(OpCode op, float arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, double)"/>
public void Emit(OpCode op, double arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, string)"/>
public void Emit(OpCode op, string arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Type)"/>
public void Emit(OpCode op, Type arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, FieldInfo)"/>
public void Emit(OpCode op, FieldInfo arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, MethodInfo)"/>
public void Emit(OpCode op, MethodInfo arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
#pragma warning disable 649
[ReflectedGetter(Name="m_label")]
private static Func<Label, int> _labelID;
#pragma warning restore 649
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label)"/>
public void Emit(OpCode op, Label arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding}\tL:{_labelID.Invoke(arg)}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, Label[])"/>
public void Emit(OpCode op, Label[] arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding}\t{string.Join(", ", arg.Select(x => "L:" + _labelID.Invoke(x)))}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, SignatureHelper)"/>
public void Emit(OpCode op, SignatureHelper arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.Emit(OpCode, ConstructorInfo)"/>
public void Emit(OpCode op, ConstructorInfo arg)
{
_log?.Trace($"Emit\t{op,_opcodePadding} {arg}");
Backing.Emit(op, arg);
}
/// <inheritdoc cref="ILGenerator.MarkLabel(Label)"/>
public void MarkLabel(Label label)
{
_log?.Trace($"MkLbl\tL:{_labelID.Invoke(label)}");
Backing.MarkLabel(label);
}
/// <inheritdoc cref="ILGenerator.DefineLabel()"/>
public Label DefineLabel()
{
return Backing.DefineLabel();
}
/// <summary>
/// Emits a comment to the log.
/// </summary>
/// <param name="comment">Comment</param>
[Conditional("DEBUG")]
public void EmitComment(string comment)
{
_log?.Trace($"// {comment}");
}
}
#pragma warning restore 162
}

View File

@@ -0,0 +1,116 @@
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 count = 1;
var instructionValue = (short)memory.ReadByte();
if (Prefixes.Contains(instructionValue))
{
instructionValue = (short) ((instructionValue << 8) | memory.ReadByte());
count++;
}
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
throw new Exception($"Unknown opcode {instructionValue:X}");
if (opcode.Size != count)
throw new Exception($"Opcode said it was {opcode.Size} but we read {count}");
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 => $"IL_{x.Offset:X4}: {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));
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using NLog;
using Torch.Managers.PatchManager.MSIL;
namespace Torch.Managers.PatchManager.Transpile
{
internal class MethodTranspiler
{
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
internal static void Transpile(MethodBase baseMethod, IEnumerable<MethodInfo> transpilers, LoggingIlGenerator output, Label? retLabel)
{
var context = new MethodContext(baseMethod);
context.Read();
_log.Trace("Input Method:");
_log.Trace(context.ToHumanMsil);
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);
}
}
}
}
}

View File

@@ -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="Extensions\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" />

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
@@ -20,6 +21,16 @@ namespace Torch
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
protected virtual void SetValue<T>(ref T backingField, T value, [CallerMemberName] string propName = "")
{
if (backingField.Equals(value))
return;
backingField = value;
// ReSharper disable once ExplicitCallerInfoArgument
OnPropertyChanged(propName);
}
/// <summary>
/// Fires PropertyChanged for all properties.
/// </summary>