Minecraft Modding 编年史

by fifth_light

蛮荒年代

曾经,没有什么 Forge、更不存在什么 Fabric。你有的就是一个混淆后的 minecraft.jar。对于混淆,我们有一个伟大的项目叫 MCP,它提供了每个版本的 Minecraft 混淆名到可以实际开发使用的 MCP 名的映射。为了方便多版本移植,MCP 项目还有 SRG 名,基本上就是根据代码相似度匹配,跨版本相似的代码就会使用同一个名称,这样不同版本只要使用 SRG 名,如果逻辑不大,就不需要进行重构。

那个年代还没有模组加载器。那怎么打 mod 呢?我们采用的方法叫 JAR Modding。实际做法非常简单,用 MCP 工具链对 Minecraft 反编译,反混淆,得到 MCP 名的 Minecraft,然后修改反混淆的代码,最后重新编译回 JAR,并进行重混淆。这么做之后,你就得到了混淆名的,有着你的修改的 minecraft.jar。

发布你修改的 minecraft.jar 自然是违法的。因此开发者想出了个办法,因为 minecraft.jar 里最终包含的代码是若干个 .class 文件,采用 ZIP 压缩,因此我们只发布包含修改后的 class 文件就好了。聪明的人肯定会想到,这样还不是非法分发了 Minecraft?确实是的。不同的模组修改一个 class 文件怎么办?会冲突。JAR Modding 有着这么多的缺点,因此人们开发了更好的解决方案。

Forge 模组加载器

修改 Minecraft 代码兼容性是如此的差,还有侵权风险,于是 Forge 出现了。Forge 本身还是基于 MCP 开发的,但是它包揽了所有的事情:

由于这些优点带来的庞大 mod 兼容性,Forge 瞬间在 modding 社区流行了起来,成为打 mod 的标准方式。

不过想一想,Forge 真的有那么好吗?也不见得。OptiFine 等模组需要深度修改 Minecraft 底层,因此它仍然还是采用修改 Minecraft 的方式来实现。不是所有的模组都是内容模组,总是有一些模组需要想办法修改游戏代码的,Forge 也提供了一定的方式,叫 CoreMod。不过 CoreMod 不太符合 Forge 的想法,毕竟要是人人都用 CoreMod,那 Forge 是干啥的?由于这个原因,CoreMod 没有详细的文档,一切全靠开发者摸索出来。

虽然 Forge 出现使得开发者不用再直接修改 Minecraft 代码了,不过 Forge 仍然基于 MCP。MCP 不仅提供中间名和反混淆名,还提供了一系列代码补丁,用于修复反编译得到的代码,使其可以重新编译。不过这些过程终究还是手动的,每个版本都需要对 MCP 补丁进行调整,某种程度上也降低了 MCP 跟进新游戏版本的速度。

还有一个不好的地方,由于 Forge 修改了很多 Minecraft 代码,因此 Minecraft 升级时 Forge 本身也得做很大升级。在 Minecraft 1.13 的时候,整体代码进行了大幅改动,恰好 Forge 觉得自己也得整理一下自己的烂摊子了,于是进行了长时间的大规模重构,导致连续几个月都没有可用的 Forge 版本。

这两个因素导致人们不断地想,模组加载和修改游戏这两者是否能分开?CoreMod 真有那么邪恶吗?早在 1.6 左右的时代,就有 LiteLoader 做了这个事,但是没有成为主流。到了 1.13,这个想法被 Fabric 发扬光大了。

Fabric 的横空出世

Fabric 通过把模组加载器和修改游戏代码进行分离,成功解决了更新慢的问题(因为 Minecraft 代码修改,Forge 就需要修改去适配,而 Fabric 根本不修改代码),并且从一开始就采用了 Sponge 项目的 Mixin 技术,鼓励 modder 去写自己的 mixin 修改游戏代码。

至于 Sponge 呢,我的了解不多,这里就简单介绍一下。曾经有一个项目叫 Bukkit,它致力于提供一个跨版本稳定的服务器插件 API。不过不幸的是,Bukkit 因为版权问题被 Mojang 拿下。Sponge 继承了 Bukkit 的思想,但是他们面临了和 Forge 同样的问题:怎么修改 Minecraft 代码好?Sponge 选择了和 Forge 的源代码补丁不一样的思路:自己开发了一个叫 Mixin 的系统,允许结构化修改 Java 字节码。

说起 Mixin,就不得不说:从一开始想,为什么要修改反编译后的代码呢?能不能直接修改编译后的字节码呢?事实上这样完全可行(其实这就是 CoreMod 的工作方法),而且不用再打代码补丁,不过这样非常繁琐,即使使用 ASM 等库辅助,也是非常麻烦。

Mixin 解决了字节码修改繁琐的问题。有了 Mixin,你可以规定怎么修改一个类及其方法,并且多个 Mixin 可以同时应用在同一个 class 文件(类)上,彻底解决了 JAR Modding 带来的冲突问题。不过缺点也比较明显:内容模组不太需要修改游戏,但是也需要一些原版没有的功能。即使是工具模组,有时也需要修改同一个地方。Fabric 给出了解决方案:提供一个 Fabric API 项目,它提供一些常用的 Minecraft 修改,从而部分解决了冲突问题。

回到混淆映射表上面来。我们之前提到了 MCP 映射表,它授权上有一些不清楚的地方,别的项目不太方便用。Fabric 一开始挪用了 MCP 的映射表,毕竟重复做映射表工作量大,而且也是没必要的行为。不过 MCP 这边直接说 Fabric 侵权,于是 Fabric 不得已,开发了自己的 Intermediary 中间名(约等于 MCP 的 SRG 名)和 Yarn 反混淆表。Yarn 映射表采用 CC0 协议授权,确保以后其他模组加载器也可以使用 Yarn,而不用再做一次重复且复杂的反混淆过程。

Mojang 映射表和 NeoForge

Mojang 有点看不下去了。怎么模组开发社区为这个吵架了?后来 Mojang 想了下,反正自己都采取这种很开放的策略了,没必要让每个版本玩家都去猜测混淆的名字,于是 Mojang 放出了官方的反混淆表,里面包含了官方开发时使用的名字和混淆名字的映射。

Forge 项目是最先采用 Mojang 映射表的。在 Mojang 映射表确保没有使用问题后,Forge 就停止了 MCP 的开发,采用 Mojang 名作为开发时使用的名字,并在 1.20.1 版本正式放弃使用 SRG 名作为运行时的名字。到此为止,Forge 不再维护映射表相关的工作。

Fabric 觉得 Mojang 映射表还是有一定限制,因为它限制了只能用于“开发用途”使用。“开发用途”是什么?在 Git 仓库发布代码时算吗?发布最终 JAR 时能用吗?这些都没有明确说明。于是 Yarn 项目还是一直维护了下去。

前两三年,Forge 社区也发生了分裂。由于 LexManos 实在是太嘴臭、太偏执,于是 Forge 开发人员决定分家,建立自己的 NeoForge 项目。从第一个版本开始(1.20.1),NeoForge 就跟随 Forge,采用 Mojang 名作为全部过程的名称。

至于 CoreMod 怎么样了?不知道 Forge 那边的人怎么就想开了(注:听说是在一个大模组的开发人员的强烈要求下),在大概 1.12~1.16 中间的版本包含了 Mixin,从此 Forge 开发也可以有方便且可靠的方式修改 Minecraft 代码了。不过他们好像还是不太支持 Mixin,一直没更新使用的 Mixin。

NeoForge 由于是新兴团队,大胆拥抱各种技术,重构 Forge 的烂摊子,于是 NeoForge 直接包含了 Fabric 版本的 Mixin(因为 Fabric 的核心工具是 Mixin,自然他们加强了 Mixin)

Mojang 放弃混淆

就在几天前前,Mojang 决定不再混淆 Minecraft JAR。对于 Forge/NeoForge 来说,这个影响微乎其微:不论是游玩还是开发时,他们都已经在使用 Mojang 的映射表。影响最大的是 Fabric:Fabric 在 Mojang 发布官方映射表后,为了保证 Yarn 是合法的,因此禁止其开发人员查看 Mojang 的映射表。

现在 Mojang 放弃混淆,Yarn 开发人员就必然会看到 Mojang 的官方名,因此 Yarn 也在某种程度上失去了其原本的意义。Fabric 支持 Yarn,也支持官方映射表,方案和早期的 Forge 是一样的:MCP 和 Yarn 都会可能发生变更,因此他们在开发时采用 MCP/Yarn 名,发布时重映射到 SRG/Intermediary 中间名上。由于中间名不变,因此同一个模组在 Mojang 不修改相应代码的情况下,可以支持多个游戏版本。

Mojang 不混淆的场景下,中间名也失去了其存在意义:如果 Mojang 修改了他们内部使用的名字,那么一般也就是代码发生了重构。

这么来说,Fabric 不需要,也无法继续维护 Yarn 和 Intermediary 名称了,因为混淆名不再存在,Yarn 开发人员很容易就可以看到官方名,因此无法保证 Yarn 的版权纯净性(也就是无法保证不参考官方命名)。至于 Forge/NeoForge,则基本没有影响,因为他们早就开始用官方名了。