ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Harmony 入门指南:基于 CIL 的 .NET/Mono 运行时方法修补原理与 Hello World 实战

Harmony 入门指南:基于 CIL 的 .NET/Mono 运行时方法修补原理与 Hello World 实战 开发工具【免费下载链接】HarmonyA library for patching, replacing and decorating .NET and Mono methods during runtime项目地址https://gitcode.com/gh_mirrors/ha/Harmony点击查看免费下载本篇技术指南围绕开源仓库 Harmony 的官方入门文档 intro.md 展开系统讲解 Harmony 面向 .NET Framework、Mono 与 Unity 的运行时方法修补Patching方案从前置运行环境、启动注入Bootstrapping、依赖约束到Hooking vs 改 DLL两种修改既有应用功能的技术路线以及 Harmony 内部保留原方法、前后缀执行、IL 加工、多补丁共存的工作原理与运行时修补边界。读完本文你将掌握 Harmony 的适用场景判断方法、两种标准补丁编写方式注解驱动PatchAll()与手动反射Patch()并能结合仓库源码理解其底层调用链为阅读后续的 patching.md、basics.md 等进阶文档打下基础。前置条件Harmony 能修补哪些运行时上的代码Harmony 是一个在运行时对 .NET 方法进行修补patching、替换replacing和装饰decorating的库。它的工作对象并不是某个具体的语言而是所有能够编译为CILCommon Intermediate Language公共中间语言的产物——也就是微软的中间字节码语言。这意味着.NET Framework天然支持是最主要的目标平台Mono由游戏引擎 Unity 使用同样在支持范围内其他能产出 CIL 的 .NET 语言C#、VB.NET、F# 等也在理论上可用。需要特别指出的一个例外是Unity .NET Standard Profile该配置没有提供在运行时完全动态创建方法的能力因此 Harmony 在这种目标配置下无法正常工作。如果你面向 Unity 打补丁务必确认工程使用的是完整 .NET/Mono 配置而非 .NET Standard Profile。从源码看Harmony 的核心修补工作位于 Internal 目录如MethodPatcher.cs、Emitter.cs、MethodCopier.cs它们通过System.Reflection.Emit级别的 IL 操作实现动态方法构造这正解释了为什么运行时动态创建方法能力是硬性前提。启动注入让补丁代码进入目标进程Harmony 本身不提供在未设计为执行外来代码的应用程序中运行你自己代码的能力。它假设已经有一小段代码在目标应用或游戏内部启动并执行了 Harmony 的补丁流程。因此你需要一种方式完成注入Injection至少注入那几行启动 Harmony 修补的代码——这项工作通常由**加载器Loader**完成。常见加载器示例文档列举并非全部Unity DoorstopBepInExUnityAssemblyInjectorMonoJunkieMInjectornet-core-injector另一条更省事的路是选择原生支持用户 DLL 加载通常称为 Mods的游戏例如 RimWorld这类游戏由官方机制加载 Mod 程序集你只需在 Mod 入口处调用 Harmony 的启动代码即可。关于注入那几行启动代码可进一步阅读仓库中的 patching-injections.md其中详细讨论了各类注入器的工作方式与取舍。依赖与运行环境Harmony 在依赖上非常轻量无其他第三方依赖只需一个0Harmony.dll文档说明已在PC、Mac、Linux上测试同时支持32 位与 64 位并很可能在其他环境中也能工作对于典型的 Unity 目标工程只需将工程设为.NET 3.5 或 Mono 2.x并把 Harmony 的 dll 包含进工程即可。在实际工程接入层面详见 basics.md通常的接入步骤是找到在目标应用内执行代码的途径注入或 Mod 支持→ 磁盘上具备0Harmony.dll→ 在工程中引用它 → 编写补丁代码 → 在代码早期创建 Harmony 实例 → 用该实例应用补丁 → 编译并确保运行时能拿到0Harmony.dll随发布包携带。值得一提的是即使运行环境中已由加载器或其他 Mod 提供了 Harmony官方也推荐每个 Mod 自带一份0Harmony.dll——Harmony 允许多版本共存这可以避免编译时版本与运行时版本不一致导致缺失依赖的问题。修改既有应用功能的两种思路改 DLL 与 Hook当你没有某个 C# 应用如游戏的源码却想改变它的行为时本质上只有两种思路修改磁盘上的 DLL 文件重新指向方法实现Hooking挂钩修改 DLL 文件虽然直接但在很多场景下并不可取存在法律风险可能被反作弊系统拦截与多个并发变更难以协调多个 Mod 各改各的容易冲突必须在原应用启动之前、之外完成无法动态调整。Harmony 采用 Hooking 的变体专注于不影响磁盘文件的纯运行时修改由此获得一系列优点多个 Mod 之间冲突更少兼容已有的 Mod 加载器生态修改可以动态、有条件地进行补丁执行顺序灵活可控可以对其他 Mod 的方法进行再修补法律风险更低。这一纯内存、不落盘的设计取向是理解 Harmony 一切 API 形态的前提。Harmony 的工作原理许多补丁库只允许你整体替换原方法Harmony 则更进一步提供了四类能力保留原方法完好无损——原始 IL 代码仍被保留后续补丁重新应用时还能恢复在原方法之前和/或之后执行你的代码——即 Prefix前置与 Postfix后置补丁用 IL 代码处理器修改原方法——即 TranspilerIL 转译补丁在构建新方法阶段直接加工指令序列多个 Harmony 补丁共存且互不冲突——同一原方法可被多个 Mod 的多个补丁同时修改。下图展示了 Harmony 的修补逻辑原方法Original的 IL 被取出后经过各补丁加工最终生成一个替换方法Replacement供运行时调用原方法本身并未被改动从源码层面印证这一架构入口实例Harmony类Harmony.cs是主入口构造时要求传入唯一标识 Id——构造函数 在 Id 为 null 或空字符串时会直接抛出ArgumentException同时会读取HARMONY_DEBUG环境变量来启用全局调试Harmony.cs。所有补丁都以该 Id 为所有者登记这正是补丁排序、before/after 协调能够跨 Mod 工作的基础。手动补丁链路Harmony.Patch(...)Harmony.cs内部创建PatchProcessor依次AddPrefix/AddPostfix/AddTranspiler/AddFinalizer最后调用Patch()生成替换方法——这与一个补丁处理器负责一个原方法的设计一一对应PatchProcessor.cs。注解补丁链路PatchAll()/PatchAll(Assembly)遍历程序集中所有类型为每个带注解的类型创建PatchClassProcessor并执行Patch()Harmony.cs。PatchClassProcessor要求类型至少带一个[HarmonyPatch]属性PatchClassProcessor.cs并将类级注解合并到每个补丁方法上同时识别HarmonyPrepare、HarmonyCleanup、HarmonyTargetMethod、HarmonyTargetMethods等辅助方法PatchClassProcessor.cs。运行时修补的边界Harmony 并不是万能的官方文档明确提示以下几点使用前务必理解Harmony 只能操纵方法methods包括构造函数和 getter/setter只能处理拥有真实 IL 方法体的方法——即在反汇编器如 dnSpy中能显示 IL 指令的方法过小的方法可能被内联inlined一旦内联发生你的补丁将不会被执行不能为类添加字段不能扩展枚举枚举会被编译为 int修补泛型方法或泛型类中的方法很棘手可能无法按预期工作。此外在配套的 patching.md 中还有两条重要的边界补充Harmony 只作用于当前 AppDomain跨 AppDomain 需要 xpc 与序列化不被支持补丁方法必须是静态的因为 Harmony 面向多程序集、多用户协同的场景需要通过可序列化的方法指针在补丁重放时重新应用它们。Hello World打第一个补丁下面完整走一遍官方入门示例对应示例文件 intro_somegame.cs、intro_annotations.cs 与 intro_manual.cs。目标方法原始游戏代码假设某游戏类SomeGameClass有一个私有方法DoSomething()游戏运行时它递增计数器并返回计数的十倍public class SomeGameClass { public bool isRunning; public int counter; private int DoSomething() { if (isRunning) { counter; } return counter * 10; } }我们没有这个类的源码只有它的程序集。下面用 Harmony 对它做两处修改让DoSomething()总是运行中地递增同时在计数器超过 100 时跳过原方法把结果翻倍。方式一注解驱动补丁PatchAll()在你自己的 DLL中编写如下代码通过[HarmonyPatch]注解声明目标类与方法并按约定命名Prefix/Postfix静态方法using HarmonyLib; using Intro_SomeGame; public class MyPatcher { // 确保 DoPatching() 在启动时被 mod loader 或注入器调用 public static void DoPatching() { var harmony new Harmony(com.example.patch); harmony.PatchAll(); } } [HarmonyPatch(typeof(SomeGameClass))] [HarmonyPatch(DoSomething)] // 如果可能这里应使用 nameof() class Patch01 { static AccessTools.FieldRefSomeGameClass, bool isRunningRef AccessTools.FieldRefAccessSomeGameClass, 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; }这里已经涉及 Harmony 的核心魔法参数约定__instance当前实例方法的实例对象___counter带三个下划线的参数名映射到实例的private 字段counterHarmony 会按名字自动绑定字段__result原方法的返回值前缀/后缀都可以读写它AccessTools.FieldRefAccessSomeGameClass, bool(isRunning)通过 AccessTools 工具类拿到字段的强类型引用绕过私有访问限制。Prefix的bool返回值语义很关键返回false会跳过原方法以及后续有副作用的前缀返回true则让原方法继续执行。上面的例子中当计数器超过 100 时跳过原方法否则把计数器清零后再执行原方法随后的Postfix再把结果翻倍。方式二手动反射补丁Patch()如果你更喜欢显式控制可以完全用反射手动完成同样的工作using HarmonyLib; using Intro_SomeGame; public class MyPatcher { // 确保 DoPatching() 在启动时被 mod loader 或注入器调用 public static void DoPatching() { var harmony new Harmony(com.example.patch); var mOriginal AccessTools.Method(typeof(SomeGameClass), DoSomething); // 如果可能这里应使用 nameof() var mPrefix SymbolExtensions.GetMethodInfo(() MyPrefix()); var mPostfix SymbolExtensions.GetMethodInfo(() MyPostfix()); // 一般来说这里应该加 null 检查new HarmonyMethod() 也会为你做 harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix)); } public static void MyPrefix() { // ... } public static void MyPostfix() { // ... } }两种方式最终殊途同归注解方式由PatchAll()自动扫描并交给PatchClassProcessor处理见上文源码链路手动方式则直接调用harmony.Patch(original, prefix, postfix, transpiler, finalizer)。注意HarmonyMethod是两种方式共用的统一载体——在手动方式中它负责携带方法指针以及 priority、before/after 等扩展属性详见 basics.md 的 manual patching 小节。一个常见的坑是获取 original 或补丁方法的引用失败导致null传入HarmonyMethod时会抛异常——务必备好空值检查并优先使用nameof()或SymbolExtensions.GetMethodInfo(() ...)这类编译期安全的方式取MethodInfo。验证一下Debug 日志如果补丁没有按预期工作可以开启 Harmony 的全局调试设置Harmony.DEBUG true或启动目标进程前设置环境变量HARMONY_DEBUG1Harmony 会通过FileLog把详细的 IL 修补过程写入桌面上的harmony.log.txt文件详见 basics.md 的 Debug Log 小节与 Harmony.cs 的说明这是排查补丁顺序、指令生成问题的最直接手段。继续深入阅读入门之后仓库文档体系围绕本主题提供了完整的进阶路径patching.mdPrefix、Postfix、Transpiler、Finalizer、Reverse Patch 五种补丁类型的完整概念与补丁类组织方式patching-prefix.md、patching-postfix.md前后缀的详细参数与用法patching-transpiler.mdIL 转译器与指令级加工basics.md0Harmony.dll引用、Harmony实例、PatchAll()/Patch()/Unpatch()/ 补丁查询等完整 API 基础patching-injections.md各类启动注入方案的深入对比。源码层面Public 目录是全部公开 API 的权威实现Internal 目录则展示了 IL 层面修补的引擎细节HarmonyTests 中的测试用例可作为各种补丁场景的行为参考。赞分享开发工具【免费下载链接】HarmonyA library for patching, replacing and decorating .NET and Mono methods during runtime项目地址https://gitcode.com/gh_mirrors/ha/Harmony点击查看免费下载相关推荐终极Harmony实战指南轻松掌握.NET和Mono运行时方法修补技巧终极Harmony实战指南轻松掌握.NET和Mono运行时方法修补技巧 Harmony是一款强大的.NET和Mono运行时方法修补库它允许开发者在不修改原始开发工具Harmony终极指南如何在运行时优雅修补.NET和Mono方法Harmony终极指南如何在运行时优雅修补.NET和Mono方法 Harmony是一个强大的.NET和Mono运行时方法修补库让开发者能够在运行时优雅地修改开发工具res-downloader 资源下载器 3 分钟快速上手res downloader 资源下载器 3 分钟快速上手 想收藏一条视频号里的视频App 里只允许观看长按录屏画质又差。res downloader爱享桌面应用网络音视频上一篇Hermes Agent容器网络配置CNI插件与网络策略全解析下一篇Vanna智能SQL助手3步打造你的数据库对话专家创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表