use harmony instead of torch patcher

This commit is contained in:
zznty
2023-02-08 21:00:21 +07:00
parent 8cdd992350
commit 83d8eea9ef
9 changed files with 197 additions and 438 deletions

View File

@@ -162,12 +162,10 @@
},
"HarmonyX": {
"type": "Transitive",
"resolved": "2.10.2-prerelease.1",
"contentHash": "5hbH0ENhQ+JV7tk63fQ2ab7MtplRHKJYH1JfIjG39rlltHNXxAcGJh05an+SQbVRV4EoP/+Qm9w9BrLc3RwRMA==",
"resolved": "2.10.2-prerelease.2",
"contentHash": "JCoFKWQx90PqF3iztNWCLLYaPnNjMyet4/pJZaSpX8zQGIhk8pWLomUD3JFLyqP+NpUzFxLbOYMxiQZYjDVpFw==",
"dependencies": {
"MonoModReorg.RuntimeDetour": "22.11.21-prerelease.2",
"System.Reflection.Emit": "4.7.0",
"System.Reflection.Emit.Lightweight": "4.7.0"
"MonoModReorg.RuntimeDetour": "23.1.2-prerelease.1"
}
},
"JorgeSerrano.Json.JsonSnakeCaseNamingPolicy": {
@@ -320,48 +318,48 @@
},
"MonoModReorg.Backports": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "69T6jjA5nx29jLkdqtfXKlJ8sMqIlc6czNDTomy0rbM68W0xo2JRJBgsu2mroBuqx7nvUdX+zIU6k1edS/pPbw==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "m1wlCgVjZTFJs3mUxmC1aE/O0RIvsNbSFBI/g93Bqzz1tHa+LhXFyrHzL60PeZMQBIPVy3CeDX4um/UrqLOn/g==",
"dependencies": {
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2"
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1"
}
},
"MonoModReorg.Core": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "gDoxu4aAF6TeOo8rsrj5prq2X36i12ch6NeRHu/Ct0H3qoPDHuEQ6JMJN/Eiy45YrLNEN7C5+Ku4BrNX4nwVQg==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "t1Y89M0rbwUx2VjDMCJOWgtSdsi1F5KNu0O6JAMOtwo2EWJ0HfYj9nS8UWWPwrgRpsquGjqbmYA8jhb59F2a/A==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "22.11.21-prerelease.2",
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2",
"MonoModReorg.Utils": "22.11.21-prerelease.2"
"MonoModReorg.Backports": "23.1.2-prerelease.1",
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1",
"MonoModReorg.Utils": "23.1.2-prerelease.1"
}
},
"MonoModReorg.ILHelpers": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "JtOKHJR4DEyq3HxmdEVXIxhqNQnu1KmjGFXuEQrNHoPbzi8Yr9465VKVXdsoAF0Lm8StdyJHQ03efjv3+OlonA=="
"resolved": "23.1.2-prerelease.1",
"contentHash": "GVh1cmrTCAK0zHr3t8aHnKsyKIlDFiDERn++lCZomHcYc8dgcOAhpkZ7KmaKgZCTJuBIrc44RjpKFr/4ScQnGA=="
},
"MonoModReorg.RuntimeDetour": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "Qv1h4rW03LrHwxwVuw5R6hbL8X78l8Lfnxe5tMlyVAe+AK0HnwsRzjsTwzFF57wxWUwq12NbLflkzV6T+hIhJw==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "UZyJ7OIbLCIBg+dzLejWq2paL1s11koUrq1noSLGCP9uNmFjwDPK+lRmGs0X4qg+Alfq6VsOpI45pGqmaAvP+Q==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "22.11.21-prerelease.2",
"MonoModReorg.Core": "22.11.21-prerelease.2",
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2",
"MonoModReorg.Utils": "22.11.21-prerelease.2"
"MonoModReorg.Backports": "23.1.2-prerelease.1",
"MonoModReorg.Core": "23.1.2-prerelease.1",
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1",
"MonoModReorg.Utils": "23.1.2-prerelease.1"
}
},
"MonoModReorg.Utils": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "TX+vlgg2/x8rzEOqwiAy2qv61FjlJsr4u10WGTekCkulZVmmC+xxDmK+4Do9noXF/4RlgFN6sR3m9/W8KvJq3g==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "6N4LNG+x4RVPLOc8QWL7dc5sqWdl0gxR+4ASRd1CvvappsK84ISgD9qgeYHgQQtTgE+h6Cuqr3Om4Ly0roLfoA==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "22.11.21-prerelease.2",
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2"
"MonoModReorg.Backports": "23.1.2-prerelease.1",
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1"
}
},
"Newtonsoft.Json": {
@@ -517,16 +515,6 @@
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reflection.Emit": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
},
"System.Reflection.Emit.Lightweight": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "5.0.0",
@@ -597,11 +585,11 @@
"type": "Project",
"dependencies": {
"ControlzEx": "[5.0.2, )",
"HarmonyX": "[2.10.2-prerelease.1, )",
"HarmonyX": "[2.10.2-prerelease.2, )",
"MahApps.Metro": "[2.4.9, )",
"Microsoft.CodeAnalysis.CSharp": "[4.4.0, )",
"Microsoft.CodeAnalysis.Common": "[4.4.0, )",
"MonoModReorg.RuntimeDetour": "[22.11.21-prerelease.2, )",
"MonoModReorg.RuntimeDetour": "[23.1.2-prerelease.1, )",
"NLog": "[5.1.0, )",
"System.ComponentModel.Annotations": "[5.0.0, )",
"Torch.API": "[1.0.0, )",

View File

@@ -1,40 +1,34 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MonoMod.Cil;
using MonoMod.RuntimeDetour;
using HarmonyLib;
using MonoMod.Utils;
using MonoMod.Utils.Cil;
using NLog;
using Torch.Managers.PatchManager.MSIL;
using Torch.Managers.PatchManager.Transpile;
using Torch.Utils;
namespace Torch.Managers.PatchManager
{
internal class DecoratedMethod : MethodRewritePattern
{
[ReflectedMethodInfo(typeof(MethodBase), nameof(MethodBase.GetMethodFromHandle), Parameters = new[] {typeof(RuntimeMethodHandle)})]
private static MethodInfo _getMethodFromHandle = null!;
[ReflectedMethodInfo(typeof(MethodBase), nameof(MethodBase.GetMethodFromHandle), Parameters = new[] {typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle)})]
private static MethodInfo _getMethodFromHandleGeneric = null!;
private static readonly ConcurrentDictionary<MethodBase, DecoratedMethod> Methods = new();
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
private readonly MethodBase _method;
private readonly Harmony _harmony;
private ILHook _hook;
private readonly PatchProcessor _processor;
private bool _hasRan;
internal DecoratedMethod(MethodBase method) : base(null)
internal DecoratedMethod(MethodBase method, Harmony harmony) : base(null)
{
_method = method;
_harmony = harmony;
_processor = harmony.CreateProcessor(method);
Methods[method] = this;
}
internal bool HasChanged()
@@ -56,29 +50,25 @@ namespace Torch.Managers.PatchManager
_log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug,
$"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
_hook ??= new ILHook(_method, Manipulator, false);
try
foreach (var prefix in Prefixes)
{
_hook.Apply();
_processor.AddPrefix(prefix);
}
catch (InvalidProgramException e)
foreach (var suffix in Suffixes)
{
_hook.Undo();
PrintMode = PrintModeEnum.Emitted | PrintModeEnum.Original;
try
{
_hook.Apply();
}
catch
{
// Ignore, we are already know there is an error in IL
}
_processor.AddPostfix(suffix);
}
throw;
}
if (Transpilers.Any() || PostTranspilers.Any())
_processor.AddTranspiler(SymbolExtensions.GetMethodInfo(() => TranspilerProxy(null, null, null)));
_processor.Patch();
_log.Log(PrintMode != 0 ? LogLevel.Info : LogLevel.Debug,
$"Done patching {_method.GetID()})");
_hasRan = true;
}
catch (Exception exception)
{
@@ -87,332 +77,40 @@ namespace Torch.Managers.PatchManager
}
}
private static IEnumerable<CodeInstruction> TranspilerProxy(IEnumerable<CodeInstruction> instructions,
MethodBase __originalMethod,
ILGenerator generator)
{
if (!Methods.TryGetValue(__originalMethod, out var decoratedMethod))
throw new Exception($"Unknown method {__originalMethod.GetID()}");
var loggingGenerator = new LoggingIlGenerator(generator, decoratedMethod.PrintMode != 0 ? LogLevel.Info : LogLevel.Debug);
MsilLocal LocalFactory(Type type) => new(loggingGenerator.DeclareLocal(type));
foreach (var transpiler in decoratedMethod.Transpilers.Concat(decoratedMethod.PostTranspilers))
{
var ins = (IEnumerable<MsilInstruction>) transpiler.Invoke(null, transpiler.GetParameters().Select<ParameterInfo, object>(b => b switch
{
_ when b.ParameterType.IsAssignableTo(typeof(MethodBase)) => __originalMethod,
_ when b.ParameterType.IsAssignableTo(typeof(IEnumerable<MsilInstruction>)) => instructions.Select(c => new MsilInstruction(c)),
_ when b.ParameterType.IsAssignableTo(typeof(Func<Type, MsilLocal>)) => new Func<Type, MsilLocal>(LocalFactory),
_ => null
}).ToArray());
instructions = ins!.Select(b => b.ToCodeIns(loggingGenerator)).ToList();
}
return instructions;
}
internal void Revert()
{
if (_hook == null)
if (!_hasRan)
return;
_log.Debug($"Revert {_method.GetID()}");
_hook.Dispose();
_hook = null;
_processor.Unpatch(HarmonyPatchType.All, _harmony.Id);
}
#region Create
public const string INSTANCE_PARAMETER = "__instance";
public const string RESULT_PARAMETER = "__result";
public const string PREFIX_SKIPPED_PARAMETER = "__prefixSkipped";
public const string ORIGINAL_PARAMETER = "__original";
public const string LOCAL_PARAMETER = "__local";
private void SavePatchedMethod(string target)
{
throw new NotSupportedException();
// var asmBuilder =
// AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("SomeName"), AssemblyBuilderAccess.RunAndSave, Path.GetDirectoryName(target));
// var moduleBuilder = asmBuilder.DefineDynamicModule(Path.GetFileNameWithoutExtension(target), Path.GetFileName(target));
// var typeBuilder = moduleBuilder.DefineType("Test", TypeAttributes.Public);
//
//
// 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[] {_method.DeclaringType})
// .Concat(parameters.Select(x => x.ParameterType)).ToArray();
//
// var patchMethod = typeBuilder.DefineMethod(methodName, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
// returnType, parameterTypes);
// 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);
//
// var generator = new LoggingIlGenerator(patchMethod.GetILGenerator(), LogLevel.Trace);
// List<MsilInstruction> il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList();
//
// MethodTranspiler.EmitMethod(il, generator);
//
// Type res = typeBuilder.CreateType();
// asmBuilder.Save(Path.GetFileName(target));
// foreach (var method in res.GetMethods(BindingFlags.Public | BindingFlags.Static))
// _log.Info($"Information " + method);
}
public void Manipulator(ILContext context)
{
context.IL.Clear();
var generator = new LoggingIlGenerator(new CecilILGenerator(context.IL),
PrintMode.HasFlag(PrintModeEnum.EmittedReflection) ? LogLevel.Info : LogLevel.Trace);
List<MsilInstruction> il = EmitPatched((type, pinned) => new MsilLocal(generator.DeclareLocal(type, pinned))).ToList();
var dumpTarget = DumpTarget != null ? File.CreateText(DumpTarget) : null;
try
{
const string gap = "\n\n\n\n\n";
void LogTarget(PrintModeEnum mode, bool err, string msg)
{
if (DumpMode.HasFlag(mode))
dumpTarget?.WriteLine((err ? "ERROR " : "") + msg);
if (!PrintMode.HasFlag(mode)) return;
if (err)
_log.Error(msg);
else
_log.Info(msg);
}
#pragma warning disable CS0612
if (PrintMsil || DumpTarget != null)
#pragma warning restore CS0612
{
lock (_log)
{
var ctx = new MethodContext(_method);
ctx.Read();
LogTarget(PrintModeEnum.Original, false, "========== Original method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Original, a, b), ctx.Instructions, true);
LogTarget(PrintModeEnum.Original, false, gap);
LogTarget(PrintModeEnum.Emitted, false, "========== Desired method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Emitted, a, b), il);
LogTarget(PrintModeEnum.Emitted, false, gap);
// If the method is invalid the program is likely to hard crash in EmitMethod or Compile, so flush the log
LogManager.Flush();
}
}
MethodTranspiler.EmitMethod(il, generator);
#pragma warning disable CS0612
if (PrintMsil || DumpTarget != null)
#pragma warning restore CS0612
{
lock (_log)
{
var instructions = context.Body.Instructions
.Select(b => b.ToMsilInstruction()).ToList();
LogTarget(PrintModeEnum.Patched, false, "========== Patched method ==========");
MethodTranspiler.IntegrityAnalysis((a, b) => LogTarget(PrintModeEnum.Patched, a, b), instructions, true);
LogTarget(PrintModeEnum.Patched, false, gap);
}
}
}
finally
{
dumpTarget?.Close();
}
}
#endregion
#region Emit
private IEnumerable<MsilInstruction> EmitPatched(Func<Type, bool, MsilLocal> declareLocal)
{
var methodBody = _method.GetMethodBody();
Debug.Assert(methodBody != null, "Method body is null");
foreach (var localVar in methodBody.LocalVariables)
{
Debug.Assert(localVar.LocalType != null);
declareLocal(localVar.LocalType, localVar.IsPinned);
}
var instructions = new List<MsilInstruction>();
var specialVariables = new Dictionary<string, MsilLocal>();
var labelAfterOriginalContent = new MsilLabel();
var labelSkipMethodContent = new MsilLabel();
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
MsilLocal resultVariable = null;
if (returnType != typeof(void))
{
if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER)
|| Prefixes.Any(x => x.ReturnType == typeof(bool)))
resultVariable = declareLocal(returnType, false);
}
if (resultVariable != null)
instructions.AddRange(resultVariable.SetToDefault());
MsilLocal prefixSkippedVariable = null;
if (Prefixes.Count > 0 && Suffixes.Any(x => x.GetParameters()
.Any(y => y.Name.Equals(PREFIX_SKIPPED_PARAMETER))))
{
prefixSkippedVariable = declareLocal(typeof(bool), false);
specialVariables.Add(PREFIX_SKIPPED_PARAMETER, prefixSkippedVariable);
}
if (resultVariable != null)
specialVariables.Add(RESULT_PARAMETER, resultVariable);
// Create special variables
foreach (var m in Prefixes.Concat(Suffixes))
foreach (var param in m.GetParameters())
if (param.Name.StartsWith(LOCAL_PARAMETER))
{
var requiredType = param.ParameterType.IsByRef ? param.ParameterType.GetElementType() : param.ParameterType;
if (specialVariables.TryGetValue(param.Name, out var existingParam))
{
if (existingParam.Type != requiredType)
throw new ArgumentException(
$"Trying to use injected local {param.Name} for {m.DeclaringType?.FullName}#{m.ToString()} with type {requiredType} but a local with the same name already exists with type {existingParam.Type}",
param.Name);
}
else
specialVariables.Add(param.Name, declareLocal(requiredType, false));
}
foreach (MethodInfo prefix in Prefixes)
{
instructions.AddRange(EmitMonkeyCall(prefix, specialVariables));
if (prefix.ReturnType == typeof(bool))
instructions.Add(new MsilInstruction(OpCodes.Brfalse).InlineTarget(labelSkipMethodContent));
else if (prefix.ReturnType != typeof(void))
throw new PatchException(
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}", prefix);
}
instructions.AddRange(MethodTranspiler.Transpile(_method, (x) => declareLocal(x, false), Transpilers, labelAfterOriginalContent));
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(labelAfterOriginalContent));
if (resultVariable != null)
instructions.Add(new MsilInstruction(OpCodes.Stloc).InlineValue(resultVariable));
var notSkip = new MsilLabel();
instructions.Add(new MsilInstruction(OpCodes.Br).InlineTarget(notSkip));
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(labelSkipMethodContent));
if (prefixSkippedVariable != null)
{
instructions.Add(new MsilInstruction(OpCodes.Ldc_I4_1));
instructions.Add(new MsilInstruction(OpCodes.Stloc).InlineValue(prefixSkippedVariable));
}
instructions.Add(new MsilInstruction(OpCodes.Nop).LabelWith(notSkip));
foreach (MethodInfo suffix in Suffixes)
{
instructions.AddRange(EmitMonkeyCall(suffix, specialVariables));
if (suffix.ReturnType != typeof(void))
throw new PatchException($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}", suffix);
}
if (resultVariable != null)
instructions.Add(new MsilInstruction(OpCodes.Ldloc).InlineValue(resultVariable));
instructions.Add(new MsilInstruction(OpCodes.Ret));
var result = MethodTranspiler.Transpile(_method, instructions, (x) => declareLocal(x, false), PostTranspilers, null).ToList();
if (result.Last().OpCode != OpCodes.Ret)
result.Add(new MsilInstruction(OpCodes.Ret));
return result;
}
private IEnumerable<MsilInstruction> EmitMonkeyCall(MethodInfo patch,
IReadOnlyDictionary<string, MsilLocal> specialVariables)
{
foreach (var param in patch.GetParameters())
{
switch (param.Name)
{
case INSTANCE_PARAMETER:
{
if (_method.IsStatic)
throw new PatchException("Can't use an instance parameter for a static method", _method);
yield return new MsilInstruction(OpCodes.Ldarg_0);
break;
}
case ORIGINAL_PARAMETER:
{
if (!typeof(MethodBase).IsAssignableFrom(param.ParameterType))
throw new PatchException($"Original parameter should be assignable to {nameof(MethodBase)}",
_method);
yield return new MsilInstruction(OpCodes.Ldtoken).InlineValue(_method);
if (_method.DeclaringType!.ContainsGenericParameters)
{
yield return new MsilInstruction(OpCodes.Ldtoken).InlineValue(_method.DeclaringType);
yield return new MsilInstruction(OpCodes.Call).InlineValue(_getMethodFromHandleGeneric);
}
else
yield return new MsilInstruction(OpCodes.Call).InlineValue(_getMethodFromHandle);
if (param.ParameterType != typeof(MethodBase))
yield return new MsilInstruction(OpCodes.Castclass).InlineValue(param.ParameterType);
break;
}
case PREFIX_SKIPPED_PARAMETER:
{
if (param.ParameterType != typeof(bool))
throw new PatchException($"Prefix skipped parameter {param.ParameterType} must be of type bool", _method);
if (param.ParameterType.IsByRef || param.IsOut)
throw new PatchException($"Prefix skipped parameter {param.ParameterType} can't be a reference type", _method);
if (specialVariables.TryGetValue(PREFIX_SKIPPED_PARAMETER, out MsilLocal prefixSkip))
yield return new MsilInstruction(OpCodes.Ldloc).InlineValue(prefixSkip);
else
yield return new MsilInstruction(OpCodes.Ldc_I4_0);
break;
}
case RESULT_PARAMETER:
{
var retType = param.ParameterType.IsByRef
? param.ParameterType.GetElementType()
: param.ParameterType;
if (retType == null || !retType.IsAssignableFrom(specialVariables[RESULT_PARAMETER].Type))
throw new PatchException(
$"Return type {specialVariables[RESULT_PARAMETER].Type} can't be assigned to result parameter type {retType}", _method);
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc)
.InlineValue(specialVariables[RESULT_PARAMETER]);
break;
}
default:
{
if (specialVariables.TryGetValue(param.Name, out var specialVar))
{
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc)
.InlineValue(specialVar);
break;
}
if (param.Name.StartsWith("__field_"))
{
var fieldName = param.Name.Substring(8);
var fieldDef = _method.DeclaringType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static).FirstOrDefault(x => x.Name == fieldName);
if (fieldDef == null) throw new PatchException($"Could not find field {fieldName}", _method);
if (fieldDef.IsStatic)
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldsflda : OpCodes.Ldsfld)
.InlineValue(fieldDef);
else
{
yield return new MsilInstruction(OpCodes.Ldarg_0);
yield return new MsilInstruction(param.ParameterType.IsByRef ? OpCodes.Ldflda : OpCodes.Ldfld)
.InlineValue(fieldDef);
}
break;
}
ParameterInfo declParam = _method.GetParameters().FirstOrDefault(x => x.Name == param.Name);
if (declParam == null)
throw new PatchException($"Parameter name {param.Name} not found", _method);
int paramIdx = (_method.IsStatic ? 0 : 1) + declParam.Position;
bool patchByRef = param.IsOut || param.ParameterType.IsByRef;
bool declByRef = declParam.IsOut || declParam.ParameterType.IsByRef;
if (patchByRef == declByRef)
yield return new MsilInstruction(OpCodes.Ldarg).InlineValue(new MsilArgument(paramIdx));
else if (patchByRef)
yield return new MsilInstruction(OpCodes.Ldarga).InlineValue(new MsilArgument(paramIdx));
else
{
yield return new MsilInstruction(OpCodes.Ldarg).InlineValue(new MsilArgument(paramIdx));
yield return EmitExtensions.EmitDereference(declParam.ParameterType);
}
break;
}
}
}
yield return new MsilInstruction(OpCodes.Call).InlineValue(patch);
}
#endregion
}
}

View File

@@ -7,6 +7,7 @@ using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Windows.Documents;
using HarmonyLib;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
@@ -88,6 +89,85 @@ namespace Torch.Managers.PatchManager.MSIL
throw new ArgumentOutOfRangeException();
}
}
public MsilInstruction(CodeInstruction instruction) : this(instruction.opcode)
{
switch (instruction.operand)
{
case LocalBuilder builder when Operand is MsilOperandInline.MsilOperandLocal operandLocal:
operandLocal.Value = new(builder);
break;
case MethodBase methodBase when Operand is MsilOperandInline.MsilOperandReflected<MethodBase> operandMethod:
operandMethod.Value = methodBase;
break;
case Type type when Operand is MsilOperandInline.MsilOperandReflected<Type> operandType:
operandType.Value = type;
break;
case MemberInfo info when Operand is MsilOperandInline.MsilOperandReflected<MemberInfo> operandMember:
operandMember.Value = info;
break;
case IEnumerable<Label> labels when Operand is MsilOperandSwitch operandSwitch:
operandSwitch.Labels = labels.Select(b => new MsilLabel(b)).ToArray();
break;
case float single when Operand is MsilOperandInline.MsilOperandSingle operandSingle:
operandSingle.Value = single;
break;
case FieldInfo fieldInfo when Operand is MsilOperandInline.MsilOperandReflected<FieldInfo> operandField:
operandField.Value = fieldInfo;
break;
case string str when Operand is MsilOperandInline.MsilOperandString operandString:
operandString.Value = str;
break;
case SignatureHelper signatureHelper when Operand is MsilOperandInline.MsilOperandSignature operandSignature:
operandSignature.Value = signatureHelper;
break;
case int int32 when Operand is MsilOperandInline.MsilOperandInt32 operandInt32:
operandInt32.Value = int32;
break;
case long int64 when Operand is MsilOperandInline.MsilOperandInt64 operandInt64:
operandInt64.Value = int64;
break;
case Label label when Operand is MsilOperandBrTarget operandBrTarget:
operandBrTarget.Target = new(label);
break;
case double @double when Operand is MsilOperandInline.MsilOperandDouble operandDouble:
operandDouble.Value = @double;
break;
}
Labels = instruction.labels.Select(b => new MsilLabel(b)).ToHashSet();
TryCatchOperations =
instruction.blocks.Select(
b => new MsilTryCatchOperation((MsilTryCatchOperationType)b.blockType, b.catchType == typeof(object) ? null : b.catchType)).ToList();
}
internal CodeInstruction ToCodeIns(LoggingIlGenerator generator)
{
var ins = new CodeInstruction(OpCode, Operand switch
{
MsilOperandBrTarget msilOperandBrTarget => msilOperandBrTarget.Target.LabelFor(generator),
MsilOperandInline.MsilOperandReflected<MethodBase> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandReflected<MemberInfo> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandReflected<FieldInfo> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandReflected<Type> msilOperandReflected => msilOperandReflected.Value,
MsilOperandInline.MsilOperandDouble msilOperandDouble => msilOperandDouble.Value,
MsilOperandInline.MsilOperandInt32 msilOperandInt32 => msilOperandInt32.Value,
MsilOperandInline.MsilOperandInt64 msilOperandInt64 => msilOperandInt64.Value,
MsilOperandInline.MsilOperandLocal msilOperandLocal => msilOperandLocal.Value.Local,
MsilOperandInline.MsilOperandSignature msilOperandSignature => msilOperandSignature.Value,
MsilOperandInline.MsilOperandSingle msilOperandSingle => msilOperandSingle.Value,
MsilOperandInline.MsilOperandString msilOperandString => msilOperandString.Value,
MsilOperandSwitch msilOperandSwitch => msilOperandSwitch.Labels.Select(b => b.LabelFor(generator)).ToArray(),
_ => null
})
{
labels = Labels.Select(b => b.LabelFor(generator)).ToList(),
blocks = TryCatchOperations.Select(b => new ExceptionBlock((ExceptionBlockType)b.Type, b.CatchType))
.ToList()
};
return ins;
}
/// <summary>
/// Opcode of this instruction

View File

@@ -13,14 +13,14 @@ namespace Torch.Managers.PatchManager.MSIL
private readonly List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>> _labelInstances =
new List<KeyValuePair<WeakReference<LoggingIlGenerator>, Label>>();
private readonly Label? _overrideLabel;
internal readonly Label? OverrideLabel;
/// <summary>
/// Creates an empty label the allocates a new <see cref="Label" /> when requested.
/// </summary>
public MsilLabel()
{
_overrideLabel = null;
OverrideLabel = null;
}
/// <summary>
@@ -28,7 +28,7 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public MsilLabel(Label overrideLabel)
{
_overrideLabel = overrideLabel;
OverrideLabel = overrideLabel;
}
/// <summary>
@@ -46,8 +46,8 @@ namespace Torch.Managers.PatchManager.MSIL
internal Label LabelFor(LoggingIlGenerator gen)
{
if (_overrideLabel.HasValue)
return _overrideLabel.Value;
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;

View File

@@ -14,6 +14,8 @@ namespace Torch.Managers.PatchManager.MSIL
/// </summary>
public class MsilLocal
{
internal LocalBuilder Local { get; }
/// <summary>
/// The index of this local.
/// </summary>
@@ -31,6 +33,7 @@ namespace Torch.Managers.PatchManager.MSIL
internal MsilLocal(LocalBuilder local)
{
Local = local;
Index = local.LocalIndex;
Type = local.LocalType;
Name = null;

View File

@@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using HarmonyLib;
using NLog;
using Torch.API;
using Torch.Managers.PatchManager.Transpile;
@@ -65,6 +66,7 @@ namespace Torch.Managers.PatchManager
private static readonly Dictionary<Assembly, List<PatchContext>> _contexts = new Dictionary<Assembly, List<PatchContext>>();
// ReSharper disable once CollectionNeverQueried.Local because we may want this in the future.
private static readonly List<PatchContext> _coreContexts = new List<PatchContext>();
private static readonly Harmony HarmonyInstance = new("PatchManager");
/// <inheritdoc cref="GetPattern"/>
internal static MethodRewritePattern GetPatternInternal(MethodBase method)
@@ -73,7 +75,7 @@ namespace Torch.Managers.PatchManager
{
if (_rewritePatterns.TryGetValue(method, out DecoratedMethod pattern))
return pattern;
var res = new DecoratedMethod(method);
var res = new DecoratedMethod(method, HarmonyInstance);
_rewritePatterns.Add(method, res);
return res;
}

View File

@@ -25,7 +25,7 @@ namespace Torch.Managers.PatchManager.Transpile
/// <summary>
/// Backing generator
/// </summary>
public ILGeneratorShim Backing { get; }
public ILGenerator Backing { get; }
private readonly LogLevel _level;
@@ -33,7 +33,7 @@ namespace Torch.Managers.PatchManager.Transpile
/// Creates a new logging IL generator backed by the given generator.
/// </summary>
/// <param name="backing">Backing generator</param>
public LoggingIlGenerator(ILGeneratorShim backing, LogLevel level)
public LoggingIlGenerator(ILGenerator backing, LogLevel level)
{
Backing = backing;
_level = level;

View File

@@ -24,12 +24,12 @@
<ItemGroup>
<PackageReference Include="ControlzEx" Version="5.0.2" />
<PackageReference Include="HarmonyX" Version="2.10.2-prerelease.1" />
<PackageReference Include="HarmonyX" Version="2.10.2-prerelease.2" />
<PackageReference Include="InfoOf.Fody" Version="2.1.1" PrivateAssets="all" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
<PackageReference Include="MonoModReorg.RuntimeDetour" Version="22.11.21-prerelease.2" />
<PackageReference Include="MonoModReorg.RuntimeDetour" Version="23.1.2-prerelease.1" />
<PackageReference Include="NLog" Version="5.1.0" />
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="all" />
<PackageReference Include="protobuf-net" Version="3.1.26" />

View File

@@ -14,13 +14,11 @@
},
"HarmonyX": {
"type": "Direct",
"requested": "[2.10.2-prerelease.1, )",
"resolved": "2.10.2-prerelease.1",
"contentHash": "5hbH0ENhQ+JV7tk63fQ2ab7MtplRHKJYH1JfIjG39rlltHNXxAcGJh05an+SQbVRV4EoP/+Qm9w9BrLc3RwRMA==",
"requested": "[2.10.2-prerelease.2, )",
"resolved": "2.10.2-prerelease.2",
"contentHash": "JCoFKWQx90PqF3iztNWCLLYaPnNjMyet4/pJZaSpX8zQGIhk8pWLomUD3JFLyqP+NpUzFxLbOYMxiQZYjDVpFw==",
"dependencies": {
"MonoModReorg.RuntimeDetour": "22.11.21-prerelease.2",
"System.Reflection.Emit": "4.7.0",
"System.Reflection.Emit.Lightweight": "4.7.0"
"MonoModReorg.RuntimeDetour": "23.1.2-prerelease.1"
}
},
"InfoOf.Fody": {
@@ -67,15 +65,15 @@
},
"MonoModReorg.RuntimeDetour": {
"type": "Direct",
"requested": "[22.11.21-prerelease.2, )",
"resolved": "22.11.21-prerelease.2",
"contentHash": "Qv1h4rW03LrHwxwVuw5R6hbL8X78l8Lfnxe5tMlyVAe+AK0HnwsRzjsTwzFF57wxWUwq12NbLflkzV6T+hIhJw==",
"requested": "[23.1.2-prerelease.1, )",
"resolved": "23.1.2-prerelease.1",
"contentHash": "UZyJ7OIbLCIBg+dzLejWq2paL1s11koUrq1noSLGCP9uNmFjwDPK+lRmGs0X4qg+Alfq6VsOpI45pGqmaAvP+Q==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "22.11.21-prerelease.2",
"MonoModReorg.Core": "22.11.21-prerelease.2",
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2",
"MonoModReorg.Utils": "22.11.21-prerelease.2"
"MonoModReorg.Backports": "23.1.2-prerelease.1",
"MonoModReorg.Core": "23.1.2-prerelease.1",
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1",
"MonoModReorg.Utils": "23.1.2-prerelease.1"
}
},
"NLog": {
@@ -159,36 +157,36 @@
},
"MonoModReorg.Backports": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "69T6jjA5nx29jLkdqtfXKlJ8sMqIlc6czNDTomy0rbM68W0xo2JRJBgsu2mroBuqx7nvUdX+zIU6k1edS/pPbw==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "m1wlCgVjZTFJs3mUxmC1aE/O0RIvsNbSFBI/g93Bqzz1tHa+LhXFyrHzL60PeZMQBIPVy3CeDX4um/UrqLOn/g==",
"dependencies": {
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2"
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1"
}
},
"MonoModReorg.Core": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "gDoxu4aAF6TeOo8rsrj5prq2X36i12ch6NeRHu/Ct0H3qoPDHuEQ6JMJN/Eiy45YrLNEN7C5+Ku4BrNX4nwVQg==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "t1Y89M0rbwUx2VjDMCJOWgtSdsi1F5KNu0O6JAMOtwo2EWJ0HfYj9nS8UWWPwrgRpsquGjqbmYA8jhb59F2a/A==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "22.11.21-prerelease.2",
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2",
"MonoModReorg.Utils": "22.11.21-prerelease.2"
"MonoModReorg.Backports": "23.1.2-prerelease.1",
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1",
"MonoModReorg.Utils": "23.1.2-prerelease.1"
}
},
"MonoModReorg.ILHelpers": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "JtOKHJR4DEyq3HxmdEVXIxhqNQnu1KmjGFXuEQrNHoPbzi8Yr9465VKVXdsoAF0Lm8StdyJHQ03efjv3+OlonA=="
"resolved": "23.1.2-prerelease.1",
"contentHash": "GVh1cmrTCAK0zHr3t8aHnKsyKIlDFiDERn++lCZomHcYc8dgcOAhpkZ7KmaKgZCTJuBIrc44RjpKFr/4ScQnGA=="
},
"MonoModReorg.Utils": {
"type": "Transitive",
"resolved": "22.11.21-prerelease.2",
"contentHash": "TX+vlgg2/x8rzEOqwiAy2qv61FjlJsr4u10WGTekCkulZVmmC+xxDmK+4Do9noXF/4RlgFN6sR3m9/W8KvJq3g==",
"resolved": "23.1.2-prerelease.1",
"contentHash": "6N4LNG+x4RVPLOc8QWL7dc5sqWdl0gxR+4ASRd1CvvappsK84ISgD9qgeYHgQQtTgE+h6Cuqr3Om4Ly0roLfoA==",
"dependencies": {
"Mono.Cecil": "0.11.4",
"MonoModReorg.Backports": "22.11.21-prerelease.2",
"MonoModReorg.ILHelpers": "22.11.21-prerelease.2"
"MonoModReorg.Backports": "23.1.2-prerelease.1",
"MonoModReorg.ILHelpers": "23.1.2-prerelease.1"
}
},
"Newtonsoft.Json": {
@@ -331,16 +329,6 @@
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
},
"System.Reflection.Emit": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ=="
},
"System.Reflection.Emit.Lightweight": {
"type": "Transitive",
"resolved": "4.7.0",
"contentHash": "a4OLB4IITxAXJeV74MDx49Oq2+PsF6Sml54XAFv+2RyWwtDBcabzoxiiJRhdhx+gaohLh4hEGCLQyBozXoQPqA=="
},
"System.Reflection.Metadata": {
"type": "Transitive",
"resolved": "5.0.0",