Forge/Fabric通用模組開發(fā)教程
在Forge端是Forge模組,在Fabric端是Fabric模組
本教程適用于至少使用過Forge或Fabric開發(fā)環(huán)境(之一)的人。同時(shí)需要涉及到一些gradle知識(shí)。
(教程見置頂評(píng)論)
TL;DR
見置頂評(píng)論
Forge和Fabric的區(qū)別,以及為什么他們可以兼容
任何一個(gè)有一定時(shí)長(zhǎng)的mod玩家都知道一個(gè)常識(shí),那就是Forge和Fabric是不能兼容的,他們不能裝在同一個(gè)客戶端上。這是因?yàn)镕orge和Fabric對(duì)游戲代碼的修改都非常復(fù)雜,同時(shí)應(yīng)用二者的修改難以避免沖突。但是對(duì)于mod作者而言,想要制作一個(gè)同時(shí)兼容Forge和Fabric端的模組則有完全不同的挑戰(zhàn)。這是因?yàn)镕orge和Fabric端的模組加載方式有根本上的區(qū)別。
Forge模組是如何加載的
Forge在啟動(dòng)時(shí)會(huì)掃描所有class文件,并且從中選取擁有 @Mod 注解的類作為模組的主類,以主類作為入口點(diǎn)啟動(dòng)mod程序。 Forge還提供一個(gè)通過 META-INF/coremods.json 文件加載js mixin的功能。
Fabric模組是如何加載的
Fabric在啟動(dòng)時(shí)會(huì)讀取所有mod的 fabric.mod.json 文件,并根據(jù)其內(nèi)容加載mod。其中包含F(xiàn)abric模組的主類和mixin類,還支持根據(jù)可選依賴mod加載對(duì)應(yīng)的接口類??偠灾蠪abric模組加載的東西都能直接或間接地從 fabric.mod.mixin 文件中找到。
如何讓兩個(gè)mod兼容
我們知道Java是在需要使用一個(gè)類的時(shí)候才會(huì)加載這個(gè)類的,因此只要我們?cè)贔orge端不加載Fabric相關(guān)的類、在Fabric端不加載Forge相關(guān)的類就能兼容了。具體來說,就是 @Mod 注解的類(和其他涉及Forge API的類)不要在 fabric.mod.json 直接或間接引用,反過來也一樣。
一般來說,我們可以把所有代碼分為三部分:涉及Forge的部分、涉及Fabric的部分、通用的部分。Forge部分和Fabric部分可以調(diào)用通用部分,而通用部分不能調(diào)用另外兩部分。我們盡量壓縮Forge和Fabric的部分,只把直接和他們相關(guān)的代碼放在這里。如果確實(shí)有通用部分調(diào)用Forge/Fabric功能的需求,我們可以在通用部分添加一個(gè)接口,讓Forge/Fabric部分主動(dòng)提供對(duì)應(yīng)的實(shí)現(xiàn)(比如放到通用部分里的一個(gè)靜態(tài)字段里),間接調(diào)用通用部分。
如何編譯一個(gè)兼容的mod
我們現(xiàn)在已經(jīng)知道一個(gè)兼容mod長(zhǎng)什么樣了,但是要怎么編譯出一個(gè)這樣的mod呢?這就要求我們知道m(xù)od編譯的流程了。Fabric和Forge兩個(gè)框架的mod編譯都是使用gradle的:
Forge編譯流程
Forge編譯總的來說是先編譯Java類,再將編譯結(jié)果和資源文件放到一起打包,最后將打包結(jié)果反混淆。
Fabric編譯流程
Fabric編譯總的來說是先編譯Java類,再將編譯結(jié)果和資源文件放到一起打包,最后將打包結(jié)果反混淆。

還真不一樣
Forge和Fabric使用的混淆方式不同,F(xiàn)orge在開發(fā)時(shí)會(huì)使用MCP名(似乎現(xiàn)在改成官方反混淆名了),而運(yùn)行時(shí)會(huì)使用Srg名;Fabric在開發(fā)時(shí)會(huì)使用yarn名,在運(yùn)行時(shí)會(huì)使用intermediary名。如果你的代碼當(dāng)中有涉及到minecraft的類、字段或方法,那么Forge和Fabric在運(yùn)行時(shí)的叫法是不同的。
那怎么辦呢
我們可以先編譯出來,讓Fabric和Forge分別反混淆一次。。但是這樣做有一定的風(fēng)險(xiǎn),MCP名和yarn名都使用自然語言取名,難免有沖突的可能。如果有一個(gè)類的MCP名和yarn名一樣,那么它在Fabric和Forge上都會(huì)被反混淆,最終得到的結(jié)果就是不確定的了。這種方式還有一個(gè)問題,就是Forge官方的編譯插件和Fabric官方的編譯插件不兼容,修補(bǔ)起來很麻煩。
我最終采取的做法是讓三個(gè)模塊(forge、fabric、common)分別編譯打包反混淆,都打包出最終結(jié)果后再將其拆包合并到一個(gè)最終產(chǎn)物上。具體做法:
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'
}
這里給每個(gè)子模塊都定義了一個(gè)feedback任務(wù),其中包含處理java類的feedbackClass和處理資源文件的feedbackResource。feedback任務(wù)將打包好的文件解包復(fù)制到主模塊的構(gòu)建目錄下。每個(gè)子任務(wù)都在forge和fabric反混淆完成之后執(zhí)行,主模塊只要在三個(gè)feedback任務(wù)之后再打包jar就可以了。
最后
以上給出了編譯一個(gè)forge/fabric兼容mod的原理和核心代碼,完整實(shí)現(xiàn)還需要進(jìn)一步的工作,包括為forge和fabric模塊進(jìn)行進(jìn)一步的配置和優(yōu)化。這部分可以參考我的mod,或者下載TLDR中的示例代碼。注意我的mod和示例代碼都沒有使用forge推薦的反混淆表,因?yàn)镸ojang官方提供的反混淆表存在法律問題 。如果你已知曉風(fēng)險(xiǎn)并仍然希望使用官方反混淆表,可以將
mapping_channel=snapshot
mapping_version=20210309-1.16.5
改成
mapping_channel=official
mapping_version=1.20