首页 > 快讯 > >正文

在Forge端是Forge模组,在Fabric端是Fabric模组


(资料图)

本教程适用于至少使用过Forge或Fabric开发环境(之一)的人。同时需要涉及到一些gradle知识。

(教程见置顶评论)

TL;DR

见置顶评论

Forge和Fabric的区别,以及为什么他们可以兼容

任何一个有一定时长的mod玩家都知道一个常识,那就是Forge和Fabric是不能兼容的,他们不能装在同一个客户端上。这是因为Forge和Fabric对游戏代码的修改都非常复杂,同时应用二者的修改难以避免冲突。但是对于mod作者而言,想要制作一个同时兼容Forge和Fabric端的模组则有完全不同的挑战。这是因为Forge和Fabric端的模组加载方式有根本上的区别。

Forge模组是如何加载的

Forge在启动时会扫描所有class文件,并且从中选取拥有 @Mod 注解的类作为模组的主类,以主类作为入口点启动mod程序。 Forge还提供一个通过 META-INF/coremods.json 文件加载js mixin的功能。

Fabric模组是如何加载的

Fabric在启动时会读取所有mod的 fabric.mod.json 文件,并根据其内容加载mod。其中包含Fabric模组的主类和mixin类,还支持根据可选依赖mod加载对应的接口类。总而言之,所有Fabric模组加载的东西都能直接或间接地从 fabric.mod.mixin 文件中找到。

如何让两个mod兼容

我们知道Java是在需要使用一个类的时候才会加载这个类的,因此只要我们在Forge端不加载Fabric相关的类、在Fabric端不加载Forge相关的类就能兼容了。具体来说,就是 @Mod 注解的类(和其他涉及Forge API的类)不要在 fabric.mod.json 直接或间接引用,反过来也一样。

一般来说,我们可以把所有代码分为三部分:涉及Forge的部分、涉及Fabric的部分、通用的部分。Forge部分和Fabric部分可以调用通用部分,而通用部分不能调用另外两部分。我们尽量压缩Forge和Fabric的部分,只把直接和他们相关的代码放在这里。如果确实有通用部分调用Forge/Fabric功能的需求,我们可以在通用部分添加一个接口,让Forge/Fabric部分主动提供对应的实现(比如放到通用部分里的一个静态字段里),间接调用通用部分。

如何编译一个兼容的mod

我们现在已经知道一个兼容mod长什么样了,但是要怎么编译出一个这样的mod呢?这就要求我们知道mod编译的流程了。Fabric和Forge两个框架的mod编译都是使用gradle的:

Forge编译流程

Forge编译总的来说是先编译Java类,再将编译结果和资源文件放到一起打包,最后将打包结果反混淆。

Fabric编译流程

Fabric编译总的来说是先编译Java类,再将编译结果和资源文件放到一起打包,最后将打包结果反混淆。

还真不一样

Forge和Fabric使用的混淆方式不同,Forge在开发时会使用MCP名(似乎现在改成官方反混淆名了),而运行时会使用Srg名;Fabric在开发时会使用yarn名,在运行时会使用intermediary名。如果你的代码当中有涉及到minecraft的类、字段或方法,那么Forge和Fabric在运行时的叫法是不同的。

那怎么办呢

我们可以先编译出来,让Fabric和Forge分别反混淆一次。。但是这样做有一定的风险,MCP名和yarn名都使用自然语言取名,难免有冲突的可能。如果有一个类的MCP名和yarn名一样,那么它在Fabric和Forge上都会被反混淆,最终得到的结果就是不确定的了。这种方式还有一个问题,就是Forge官方的编译插件和Fabric官方的编译插件不兼容,修补起来很麻烦。

我最终采取的做法是让三个模块(forge、fabric、common)分别编译打包反混淆,都打包出最终结果后再将其拆包合并到一个最终产物上。具体做法:

allprojects {

tasks.register('feedbackClass', Copy) {

dependsOn "jar"

dependsOn ":forge:reobfJar"

dependsOn ":fabric:remapJar"

from (zipTree(new File(buildDir, "libs/${project.name}.jar"))) {

include '**/*.class'

}

into new File(rootProject.buildDir, 'classes/java/main')

}

tasks.register('feedbackResource', Copy) {

dependsOn "jar"

dependsOn ":forge:reobfJar"

dependsOn ":fabric:remapJar"

from (zipTree(new File(buildDir, "libs/${project.name}.jar"))) {

exclude '**/*.class'

}

into new File(rootProject.buildDir, 'resources/main')

}

tasks.register('feedback') {

dependsOn("feedbackClass")

dependsOn("feedbackResource")

}}

project.jar {

dependsOn ':common:feedback'

dependsOn ':forge:feedback'

dependsOn ':fabric:feedback'

}

这里给每个子模块都定义了一个feedback任务,其中包含处理java类的feedbackClass和处理资源文件的feedbackResource。feedback任务将打包好的文件解包复制到主模块的构建目录下。每个子任务都在forge和fabric反混淆完成之后执行,主模块只要在三个feedback任务之后再打包jar就可以了。

最后

以上给出了编译一个forge/fabric兼容mod的原理和核心代码,完整实现还需要进一步的工作,包括为forge和fabric模块进行进一步的配置和优化。这部分可以参考我的mod,或者下载TLDR中的示例代码。注意我的mod和示例代码都没有使用forge推荐的反混淆表,因为Mojang官方提供的反混淆表存在法律问题 。如果你已知晓风险并仍然希望使用官方反混淆表,可以将

mapping_channel=snapshot

mapping_version=20210309-1.16.5

改成

mapping_channel=official

mapping_version=1.20

上一篇 下一篇
x
相关阅读