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())); 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 it public 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 it public 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 1 20 MyPostfix MyFinalizer 21 MyTranspiler MyPrefix True MyFinalizer GetNumbers 1, 2, 3 MyTranspiler StaticPrefix DoSomething2 0 3333 MyPostfix MyFinalizer 3334 Transpiler Prefix GetNumbers Postfix 0, 20, 30, 99