模组:制作指南/APIs/Harmony
跳到导航
跳到搜索
← 模组:目录
“ | “小心驶得万年船。” |
Harmony 使您能够给方法打补丁或替换方法,从而高效地重写游戏代码。SMAPI 自带一个 Harmony 的副本,以供模组使用。
Harmony的使用场景
Harmony 是一种终极手段。 它固然强大到让您能做到其他方式难以做到之事,但也有显著的缺点:
- 非常容易导致崩溃、错误或微妙的漏洞,例如难以诊断的内存崩溃错误。
- SMAPI 往往不能检测不兼容的 Harmony 代码。
- SMAPI 往往不能重写 Harmony 补丁以求兼容性,因此模组很容易在其他平台(例如 Android)或在未来的版本上崩溃。
- 补丁可能会与其他的 Harmony 模组冲突,而且有时很难排查此故障。
- 补丁可能会对其他不使用 Harmony 的模组产生非预期的影响。如果玩家因为您的补丁的缘故经常报告其他模组的错误,可能会惹恼其他的模组开发者。
- 补丁可能会使您无法在测试时附加调试器(甚至您在测试无关代码时也是如此)。
- 如果其他模组调用了您打补丁的代码而崩溃,则错误会被归到那个模组上(除非您主动处理错误)。这可能导致玩家向其他模组作者报告漏洞,如果后者知道这时因为您的模组,他们可能会很生气。
- SMAPI 在加载您的模组时会警告玩家您的模组可能影响游戏稳定性。
您仅应该在不得不用 Harmony 的情况下使用它。例如,如果您希望检测和抑制玩家交互,则无需使用 Harmony 修改游戏代码,而可以使用 SMAPI 的输入接口配合您自己的代码来处理它。
如何使用
Harmony 是一种终极手段。(参见前一节)。
- 编辑您模组的 .csproj 项目文件,并将下述代码添加到第一个 <PropertyGroup> 中添加以下内容:
<EnableHarmony>true</EnableHarmony>
- 在您模组的 Entry 方法中,调用 Harmony 代码接口以注册补丁:
var harmony = new Harmony(this.ModManifest.UniqueID); // example patch, you'll need to edit this for your patch harmony.Patch( original: AccessTools.Method(typeof(StardewValley.Object), nameof(StardewValley.Object.canBePlacedHere)), prefix: new HarmonyMethod(typeof(ObjectPatches), nameof(ObjectPatches.CanBePlacedHere_Prefix)) );
- 参见 Harmony 教程和文档。此外,您可以在 Modding wiki 上查看一些针对星露谷的例子。
最佳使用方式
- 参见 Harmony的使用场景。
- 补丁中未被处理的错误可能难于排查,且往往会导致玩家在 SMAPI 页面或其他人的模组页面下寻求帮助。请务必处理错误、记录日志、并默认使用原版游戏的代码。例如:
internal class ObjectPatches { private static IMonitor Monitor; // call this method from your Entry class internal static void Initialize(IMonitor monitor) { Monitor = monitor; } // patches need to be static! internal static bool CanBePlacedHere_Prefix(StardewValley.Object __instance, GameLocation location, Vector2 tile, ref bool __result) { try { ...; // your patch logic here return false; // don't run original logic } catch (Exception ex) { Monitor.Log($"Failed in {nameof(CanBePlacedHere_Prefix)}:\n{ex}", LogLevel.Error); return true; // run original logic } } }
- 如果可能,使用后缀以获得以获得最佳兼容性和稳定性。
- 不要使用转译的代码补丁,除非您别无他选,且清楚自己在做什么。这会有更高概率引发问题、更不稳定、更容易在游戏更新时损坏且使您更难寻求其他模组开发者的帮助。
- 使用代码接口,而不是像 [HarmonyPatch] 这样的注解(参见为什么应当避免使用 Harmony 注解和 PatchAll)。
问答
我应当使用哪个 Harmony 版本
Harmony 版本由 SMAPI 管理(当前为 2.2.x)。如果您使用 EnableHarmony 选项,您将使用 SMAPI 捆绑的 Harmony 版本。
为什么应当避免使用Harmony注解和PatchAll
有两种方法添加补丁:
- 使用代码接口(推荐)。
- 在静态类中使用 [HarmonyPatch] 这样的注解,然后调用 Harmony 的 PatchAll 以动态扫描相应注解的程序集。这并不稳定,不推荐使用。SMAPI 会根据上下文来自动重写模组以确保兼容性。这用于在游戏/SMAPI/Harmony 发生较大变更时保持模组的正常运行,或者处理跨平台差异(例如,在 Android 上使用 PC 模组)。例如,Stardew Valley 1.5.5 更新损坏了大多数 C# 模组,但 SMAPI 的重写功能几乎自动修复了所有这些模组.
然而,注解在编译后的代码中工作方式不同,因此 SMAPI 无法重写它们。这意味着使用注解的模组更加不稳定;它们可能会在未来的游戏/SMAPI/Harmony 更新中无声无息地出错,并且可能无法在某些平台上运行。
何为内联以及它为什么重要
内联 是指 JIT 将对方法的调用替换为方法体中的实际代码、而忽略方法调用的过程。若发生内联,则应用于被调用方法的 Harmony 补丁将不会生效。
JIT 用于决定是否内联的启发式规则没有文档记录,并且可能会发生变化,但 不抛出异常的小而简单的方法 最有可能被内联。此外,启发式规则似乎在不同平台之间也有所不同,例如 macOS 比 Windows 更激进。