Merge branch 'staging' into plugins
This commit is contained in:
386
Torch.Tests/PatchTest.cs
Normal file
386
Torch.Tests/PatchTest.cs
Normal 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
|
||||||
|
}
|
@@ -63,6 +63,7 @@
|
|||||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="PatchTest.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="ReflectionTestManager.cs" />
|
<Compile Include="ReflectionTestManager.cs" />
|
||||||
<Compile Include="ReflectionSystemTest.cs" />
|
<Compile Include="ReflectionSystemTest.cs" />
|
||||||
|
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
@@ -10,28 +12,148 @@ using System.Windows.Threading;
|
|||||||
namespace Torch.Collections
|
namespace Torch.Collections
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ObservableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
|
public class ObservableDictionary<TKey, TValue> : ViewModel, IDictionary<TKey, TValue>, INotifyCollectionChanged
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
private IDictionary<TKey, TValue> _internalDict;
|
||||||
public new void Add(TKey key, TValue value)
|
|
||||||
|
public ObservableDictionary()
|
||||||
{
|
{
|
||||||
base.Add(key, value);
|
_internalDict = new Dictionary<TKey, TValue>();
|
||||||
var kv = new KeyValuePair<TKey, TValue>(key, value);
|
}
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, kv));
|
|
||||||
|
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 />
|
/// <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;
|
return false;
|
||||||
|
|
||||||
var kv = new KeyValuePair<TKey, TValue>(key, this[key]);
|
var kv = new KeyValuePair<TKey, TValue>(key, this[key]);
|
||||||
base.Remove(key);
|
if (!_internalDict.Remove(key))
|
||||||
|
return false;
|
||||||
|
|
||||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, kv));
|
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, kv));
|
||||||
|
OnPropertyChanged(nameof(Count));
|
||||||
return true;
|
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)
|
private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
|
||||||
@@ -52,12 +174,5 @@ namespace Torch.Collections
|
|||||||
nh.Invoke(this, e);
|
nh.Invoke(this, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event NotifyCollectionChangedEventHandler CollectionChanged;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event PropertyChangedEventHandler PropertyChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
104
Torch/Managers/PatchManager/AssemblyMemory.cs
Normal file
104
Torch/Managers/PatchManager/AssemblyMemory.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
219
Torch/Managers/PatchManager/DecoratedMethod.cs
Normal file
219
Torch/Managers/PatchManager/DecoratedMethod.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
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<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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
298
Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
Normal file
298
Torch/Managers/PatchManager/MSIL/MsilOperandInline.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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.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
|
||||||
|
}
|
116
Torch/Managers/PatchManager/Transpile/MethodContext.cs
Normal file
116
Torch/Managers/PatchManager/Transpile/MethodContext.cs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
67
Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
Normal file
67
Torch/Managers/PatchManager/Transpile/MethodTranspiler.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@
|
|||||||
<PlatformTarget>x64</PlatformTarget>
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||||
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
<OutputPath>$(SolutionDir)\bin\x64\Release\</OutputPath>
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||||
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.xml</DocumentationFile>
|
<DocumentationFile>$(SolutionDir)\bin\x64\Release\Torch.xml</DocumentationFile>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="HavokWrapper, Version=1.0.6278.22649, Culture=neutral, processorArchitecture=AMD64">
|
||||||
@@ -156,6 +158,23 @@
|
|||||||
<Compile Include="Collections\ObservableList.cs" />
|
<Compile Include="Collections\ObservableList.cs" />
|
||||||
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
||||||
<Compile Include="Managers\DependencyManager.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="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="SaveGameStatus.cs" />
|
<Compile Include="SaveGameStatus.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -20,6 +21,16 @@ namespace Torch
|
|||||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
|
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>
|
/// <summary>
|
||||||
/// Fires PropertyChanged for all properties.
|
/// Fires PropertyChanged for all properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Reference in New Issue
Block a user