一、Harmony的核心定位与设计哲学
Harmony是一个运行时动态方法修补库,专为修改已编译的.NET/Mono应用程序而设计,尤其适用于游戏Mod开发。其核心创新在于:
- 非破坏性修改:保留原始方法完整性,避免直接替换或覆盖。
- 多维度干预:支持在原始方法执行前(Prefix)、后(Postfix)插入逻辑,或通过IL转译器(Transpiler)直接修改方法体。
- 协同工作能力:允许多个独立补丁共存于同一方法,通过优先级机制协调执行顺序。
- 零磁盘修改:所有操作在内存中完成,规避法律风险与反作弊系统拦截。
二、技术架构与工作原理
(一)运行时修补流程
-
引导注入
Harmony本身不提供入口注入,需依赖加载器(如Unity门挡、BepInEx)在宿主程序启动时执行初始化代码。典型启动逻辑:
public static void DoPatching() { var harmony = new Harmony("com.example.patch"); // 唯一标识符 harmony.PatchAll(); // 自动扫描程序集内补丁 }
-
补丁类型与作用域
补丁类型 执行时机 核心能力 限制条件 Prefix 原方法执行前 修改参数、跳过原方法执行(返回 false
)、状态传递(__state
)必须为静态方法 Postfix 原方法执行后 修改返回值(通过 __result
)、错误处理无法阻止原方法执行 Transpiler JIT编译阶段 直接操作IL指令(增删改查),实现深度逻辑重构 需熟练掌握IL语法 Finalizer 异常发生时 全局异常捕获,避免崩溃 独立于其他补丁执行 -
IL转译机制
Transpiler通过操作
CodeInstruction
序列实现IL重写,典型流程:static IEnumerable<CodeInstruction> Transpiler( IEnumerable<CodeInstruction> instructions) { var matcher = new CodeMatcher(instructions) .MatchForward(false, // 定位目标指令 new CodeMatch(OpCodes.Ldarg_0), new CodeMatch(OpCodes.Call, typeof(SomeClass).GetMethod("TargetMethod")) ) .SetOperandAndAdvance(typeof(NewClass).GetMethod("Replacement")); // 替换方法 return matcher.InstructionEnumeration(); }
(二)与传统Hooking的本质区别
特性 | 传统Hook | Harmony |
---|---|---|
原始方法保留 | 被替换丢失 | 完整保留,可随时调用 |
多补丁兼容性 | 仅支持单一Hook | 多补丁协同执行 |
修改粒度 | 方法级替换 | 指令级修改(IL) |
磁盘影响 | 常需修改DLL | 纯内存操作 |
法律风险 | 较高 | 显著降低 |
三、平台支持与依赖管理
-
运行时环境
- 支持框架:.NET Framework 2.0+、Mono、.NET Core 3.1+、.NET 5/6/7/8
- 系统兼容:Windows/macOS/Linux (x86/x64/ARM)
- Unity特殊限制 :
.NET Standard配置因缺乏动态方法支持无法使用,需切至Mono 2.x或.NET 3.5。
-
依赖方案
方案 适用场景 实现方式 Lib.Harmony 单文件部署 NuGet包合并所有依赖 Lib.Harmony.Thin 自定义依赖管理 仅核心库,需手动保障引用完整
四、关键限制与规避策略
-
不可修补场景
- 内联方法(JIT优化导致无法拦截)
- 动态生成的方法(无稳定IL结构)
- 泛型方法/泛型类方法(需特化版本)
- 枚举扩展(编译为常量值)
-
冲突解决机制
- 优先级标记 :通过
[HarmonyPriority(Priority.High)]
控制补丁顺序。 - 依赖声明 :使用
[HarmonyBefore("modA")]
/[HarmonyAfter("modB")]
显式定义执行顺序。 - 补丁查询 :
Harmony.GetPatchInfo()
获取已应用补丁列表,动态调整行为。
- 优先级标记 :通过
五、Unity集成实践详解
(一)典型工作流对比
工作流 | 优势 | 适用场景 |
---|---|---|
Prefab Hierarchy | 保持Harmony时间线结构,支持运行时动态附加 | 角色动画、程序化道具生成 |
XML/Harmony Renderer | 跨引擎兼容性,C++高性能渲染 | 多引擎复用项目 |
2D Animation | 无缝转换骨骼系统,支持IK程序控制 | 2D角色动画 |
![]() |
(二)动画控制示例
[HarmonyPatch(typeof(CharacterAnimator))]
[HarmonyPatch("UpdateAnimation")]
class AnimationPatch
{
static bool Prefix(ref bool __runOriginal, CharacterAnimator __instance)
{
if (__instance.IsStunned) // 自定义条件
{
__instance.Play("StunAnimation"); // 覆盖原逻辑
__runOriginal = false; // 阻止原方法执行
return false;
}
return true;
}
}
通过__runOriginal
控制原方法执行,实现动画状态机覆盖。
六、Hello World示例全解析
[HarmonyPatch(typeof(SomeGameClass))]
[HarmonyPatch(nameof(SomeGameClass.DoSomething))] // 推荐使用nameof
class Patch01
{
// 字段反射:高效访问私有字段
static AccessTools.FieldRef<SomeGameClass, bool> isRunningRef =
AccessTools.FieldRefAccess<SomeGameClass, bool>("isRunning");
static bool Prefix(SomeGameClass __instance, ref int ___counter)
{
isRunningRef(__instance) = true; // 强制开启运行状态
if (___counter > 100)
return false; // 跳过原方法执行
___counter = 0; // 修改原类私有字段
return true; // 继续执行原方法
}
static void Postfix(ref int __result)
{
__result *= 2; // 修改返回值
}
}
关键设计解读:
- 字段访问优化
FieldRef
比传统反射快10倍以上,通过委托直接操作内存。 - 命名规范
__instance
:原类实例(非静态方法)___counter
:三下划线访问原私有字段__result
:修改返回值
- 执行控制
Prefix返回false
时完全跳过原方法,结合___counter > 100
实现条件阻断。
七、生产环境最佳实践
-
调试与日志
Harmony.DEBUG = true; // 启用诊断模式 FileLog.Log($"Patching {MethodBase.GetCurrentMethod().Name}");
日志输出到
Harmony.log.txt
。 -
补丁卸载
harmony.UnpatchAll(); // 移除所有补丁 harmony.Unpatch(method, HarmonyPatchType.All, "patchID"); // 定向移除
动态管理补丁生命周期。
-
跨版本兼容
- 使用
AccessTools.Method()
柔性匹配方法 - 为不同游戏版本创建条件补丁
- 使用
终极忠告:Harmony应作为最后手段,优先考虑子类化(Subclassing)或组件系统(如Unity的ThingComp),过度使用将增加模组冲突风险。