332.C# Harmony - 動態代碼補丁的強大工具

2024年2月6日 21点热度 0人点赞

摘要


Harmony 是一個用於 .NET 的庫,它允許開發者在運行時創建、應用或修改程序集的補丁,即所謂的 "熱補丁" (hot patching)。這使得開發者能夠動態地改變已編譯代碼的行為,而無需修改原始的源代碼。Harmony 在遊戲Mod開發、軟件插件系統和復雜的應用程序中非常流行,因為它可以在不改變原始程序集的情況下,註入或改變代碼的功能。

正文


應用場景

Harmony 的常見應用場景包括:

  1. 遊戲Mod開發:在不修改遊戲原始代碼的情況下添加新功能或修改現有功能。
  2. 軟件擴展:在現有應用程序中註入代碼以增加新功能或修改現有邏輯。
  3. 行為修正:修復第三方庫中的錯誤或不希望的行為,而不需要等待官方更新。
  4. 測試和Mocking:動態替換方法以進行單元測試或集成測試。

Harmony 的基本概念

在深入例子之前,讓我們快速了解一下 Harmony 的一些基本概念:

  • 原始方法 (Original Method):需要被修改或補丁的方法。
  • 前置補丁 (Prefix):在原始方法執行之前運行的代碼。
  • 後置補丁 (Postfix):在原始方法執行之後運行的代碼。
  • 替換方法 (Transpiler):修改原始方法的IL代碼的方法。
  • 反射補丁 (Reverse Patch):允許調用私有方法作為公共的靜態方法。

例子 1: 在遊戲中添加Mod功能

假設我們正在為一個遊戲添加Mod,我們想要修改玩傢的移動速度。原始的遊戲代碼可能是這樣的:

public class Player
{
    public float MoveSpeed { get; set; } = 10f;
    public void Move(Vector3 direction)
    {
        // 移動玩傢的邏輯
        Console.WriteLine("MOVE:"   direction.X * MoveSpeed);
        Console.WriteLine("MOVE:"   direction.Y * MoveSpeed);
        Console.WriteLine("MOVE:"   direction.Z * MoveSpeed);
    }
}

我們想要在不修改原始遊戲代碼的情況下增加玩傢的移動速度。我們可以使用 Harmony 創建一個前置補丁:

using C20240114C;
using HarmonyLib;
[HarmonyPatch(typeof(Player))]
[HarmonyPatch("Move")]
public static class PlayerMoveSpeedPatch
{
    static void Prefix(Player __instance)
    {
        __instance.MoveSpeed *= 2; // 將移動速度翻倍
    }
}
class Program
{
    static void Main()
    {
        var harmony = new Harmony("com.mod.yourmod");
        harmony.PatchAll();
        Player player= new Player();
        player.Move(new System.Numerics.Vector3(1, 2, 3));
    }
}

在這個例子中,我們創建了一個名為 PlayerMoveSpeedPatch 的類,並使用 HarmonyPatch 屬性指定要補丁的類和方法。我們定義了一個 Prefix 方法,它會在玩傢移動之前執行,並將移動速度翻倍。

例子 2: 修改第三方庫的行為

假設我們使用了一個第三方庫,該庫有一個方法 Calculate,但它的行為不是我們想要的。我們想要修改它的返回值。

public class ThirdPartyCalculator
{
    public int Calculate(int a, int b)
    {
        return a   b; // 原始行為是相加
    }
}
using C20240114C;
using HarmonyLib;
[HarmonyPatch(typeof(ThirdPartyCalculator))]
[HarmonyPatch("Calculate")]
public static class CalculatorPatch
{
    static void Postfix(ref int __result, int a, int b)
    {
        __result = a * b; // 修改返回值為乘積
    }
}
class Program
{
    static void Main()
    {
        var harmony = new Harmony("com.mod.yourmod");
        harmony.PatchAll();
        ThirdPartyCalculator calculator=new ThirdPartyCalculator();
        var ret= calculator.Calculate(10,20);
        Console.WriteLine(ret);
    }
}

在這個例子中,我們使用了 Postfix 方法來修改 Calculate 方法的返回值。我們引用了 __result 參數來改變原始方法的輸出。

例子 3: 動態日志記錄

假設你有一個生產環境中的應用程序,你想要為特定的方法添加額外的日志記錄,但你不能重新編譯和部署整個應用程序。使用Harmony,你可以創建一個前置補丁來動態添加日志記錄。

// 假設這是你想要添加日志的方法
public class PaymentProcessor
{
    public bool ProcessPayment(decimal amount, string accountNumber)
    {
        // 支付處理邏輯
        return true;
    }
}
using C20240114C;
using HarmonyLib;
// Harmony補丁類
[HarmonyPatch(typeof(PaymentProcessor))]
[HarmonyPatch("ProcessPayment")]
public static class LogPaymentPatch
{
    static void Prefix(decimal amount, string accountNumber)
    {
        Console.WriteLine($"Processing payment of {amount:C} for account {accountNumber}.");
    }
}
class Program
{
    static void Main()
    {
        var harmony = new Harmony("com.mod.yourmod");
        harmony.PatchAll();
        PaymentProcessor payment=new PaymentProcessor();
        payment.ProcessPayment(200, "10001");
    }
}

在這個例子中,LogPaymentPatch 類在 PaymentProcessor 的 ProcessPayment 方法之前添加了日志記錄,記錄了每次支付處理的詳細信息。

結論

Harmony 是一個功能強大的庫,它為C#開發者提供了前所未有的靈活性,使他們能夠動態地修改和擴展代碼的行為。無論是在遊戲開發、軟件插件還是單元測試中,Harmony 都能提供強大的支持。通過合理使用 Harmony,開發者可以跨越原有代碼的限制,實現更加豐富和定制化的功能。