Harmony是一个开放源代码库,旨在在运行时替换、修饰或修改任何现有C#方法。它的主要用在用Mono语言编写的游戏和插件,但是该技术可以与任何.NET版本一起使用。它还照顾对同一方法的多次更改(它们累积而不是覆盖)。
它为每个原始方法创建DynamicMethod方法,并向其织入代码,该代码在开始和结束时调用自定义方法。它还允许您编写过滤器来处理原始的IL代码,从而可以对原始方法进行更详细的操作。
文档可以在这里找到。
最新2.0版本终于支持.net core.
Harmony支持手动(Patch)和自动(PatchAll)织入
织入位置可以是执行前(Prefix)、执行后(Postfix)和终结嚣(Finalizer),也可以更详细的手动修改IL(Transpiler)
支持构造函数、Getter/Setter、虚/非虚方法、静态方法
手动模式
class NoneGenericClass
{private readonly bool _isRunning = true;private int _counter = 1;public int DoSomething(){Console.WriteLine(nameof(DoSomething));if (_isRunning){_counter++;}return _counter * 10;}public static int DoSomething2(){Console.WriteLine(nameof(DoSomething2));return 3333;}public IEnumerable<int> GetNumbers(){Console.WriteLine(nameof(GetNumbers));yield return 1;yield return 2;yield return 3;}
}static class NoneGenericClassPatcher
{public static void Patch(){var harmony = new Harmony(nameof(NoneGenericClassPatcher));harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.DoSomething)),new HarmonyMethod(GetMethod(nameof(MyPrefix))),new HarmonyMethod(GetMethod(nameof(MyPostfix))),new HarmonyMethod(GetMethod(nameof(MyTranspiler))),new HarmonyMethod(GetMethod(nameof(MyFinalizer))));Console.WriteLine(new NoneGenericClass().DoSomething());Console.WriteLine();harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.GetNumbers)),new HarmonyMethod(GetMethod(nameof(MyPrefix))),new HarmonyMethod(GetMethod(nameof(PassthroughPostfix))),new HarmonyMethod(GetMethod(nameof(MyTranspiler))),new HarmonyMethod(GetMethod(nameof(MyFinalizer))));Console.WriteLine(string.Join(", ", new NoneGenericClass().GetNumbers())); //BUG:有Finalizer方法时PassthroughPostfix不生效Console.WriteLine();harmony.Patch(typeof(NoneGenericClass).GetMethod(nameof(NoneGenericClass.DoSomething2)),new HarmonyMethod(GetMethod(nameof(StaticPrefix))),new HarmonyMethod(GetMethod(nameof(MyPostfix))),new HarmonyMethod(GetMethod(nameof(MyTranspiler))),new HarmonyMethod(GetMethod(nameof(MyFinalizer))));Console.WriteLine(NoneGenericClass.DoSomething2());}static MethodInfo GetMethod(string name) => typeof(NoneGenericClassPatcher).GetMethod(name, BindingFlags.Static | BindingFlags.Public);public static bool MyPrefix(out Stopwatch __state, ref bool ____isRunning){__state = Stopwatch.StartNew();Console.WriteLine($"{nameof(MyPrefix)} {____isRunning}");return true;}public static bool StaticPrefix(out Stopwatch __state){__state = Stopwatch.StartNew();Console.WriteLine($"{nameof(StaticPrefix)}");return true;}public static void MyPostfix(Stopwatch __state, ref int __result, MethodBase __originalMethod){Console.WriteLine($"{__state.ElapsedMilliseconds} {__result++}");Console.WriteLine(nameof(MyPostfix));}public static IEnumerable<int> PassthroughPostfix(IEnumerable<int> values){yield return 0;foreach (var value in values)if (value > 1)yield return value * 10;yield return 99;Console.WriteLine(nameof(PassthroughPostfix));}// looks for STDFLD someField and inserts CALL MyExtraMethod before itpublic static IEnumerable<CodeInstruction> MyTranspiler(IEnumerable<CodeInstruction> instructions){Console.WriteLine(nameof(MyTranspiler));//var found = false;foreach (var instruction in instructions){//if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField)//{// yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod);// found = true;//}yield return instruction;}//if (found == false)// ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod");}public static void MyFinalizer(Exception __exception){Console.WriteLine($"{nameof(MyFinalizer)} {__exception}");}
}
自动模式
public class Annotations
{private readonly bool _isRunning;public IEnumerable<int> GetNumbers(){Console.WriteLine(nameof(GetNumbers));yield return 1;yield return 2;yield return 3;}
}[HarmonyPatch(typeof(Annotations))]
[HarmonyPatch(nameof(Annotations.GetNumbers))]
public class AnnotationsPatcher
{static AccessTools.FieldRef<Annotations, bool> isRunningRef =AccessTools.FieldRefAccess<Annotations, bool>("_isRunning");public static void Patch(){var harmony = new Harmony(nameof(AnnotationsPatcher));harmony.PatchAll();Console.WriteLine(string.Join(", ", new Annotations().GetNumbers()));}static bool Prefix(Annotations __instance){Console.WriteLine("Prefix");return true;}/// <summary>Not working</summary>static IEnumerable<int> Postfix(IEnumerable<int> values){yield return 0;foreach (var value in values)if (value > 1)yield return value * 10;yield return 99;Console.WriteLine(nameof(Postfix));}// looks for STDFLD someField and inserts CALL MyExtraMethod before itpublic static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions){Console.WriteLine(nameof(Transpiler));//var found = false;foreach (var instruction in instructions){//if (instruction.opcode == OpCodes.Stfld && instruction.operand == f_someField)//{// yield return new CodeInstruction(OpCodes.Call, m_MyExtraMethod);// found = true;//}yield return instruction;}//if (found == false)// ReportError("Cannot find <Stdfld someField> in OriginalType.OriginalMethod");}
}
运行代码
static void Main(string[] args)
{NoneGenericClassPatcher.Patch();Console.WriteLine();AnnotationsPatcher.Patch();
}
输出结果
MyTranspiler
MyPrefix True
DoSomething
20
MyPostfix
MyFinalizerMyTranspiler
MyPrefix True
MyFinalizer
GetNumbers
1, 2, 3MyTranspiler
StaticPrefix
DoSomething2
3333
MyPostfix
MyFinalizerTranspiler
Prefix
GetNumbers
Postfix
0, 20, 30, 99