用户判断:mod 支持分 fabric/forge,且不同版本还有差异 ⇒ 各有独立后端。
实测支持此结论,且差异比方法签名更深一层。
实测成功(关键):
Fabric Loader 0.19.5 完整启动 1.20.1 客户端
'Loading Minecraft 1.20.1 with Fabric Loader 0.19.5' + Mixin 子系统 + 12 个资源图集
★ 必须剔除 1.20.1 自带的 asm 9.2/9.6,改用 Fabric 的 9.10.1
(否则 Knot.<clinit> 抛 duplicate ASM classes)
架构结论:后端身份是 (加载器, API 世代) 二元组
因为 Fabric/Forge 下**我们不拥有启动流程**(Loader 起游戏+跑主循环),
与我们「不跑主循环+手动喂数据」的模型根本冲突。
两条可行路线已写入 scaffold/LOADERS.md(A: 作为 mod 被加载;
B: 只借 Fabric 的注册表与资源包,保留我们的渲染循环)——均待实测。
已实现(零加载器,两个 vanilla 后端编译通过、待实测):
cmd=mods:Java 侧自己扫 jar/注册占位方块/挂资源包
覆盖:外观 ✅ state id ✅ 变体 ⚠️ 行为 ❌ Iris ❌
3.8 KiB
加载器差异与后端设计(实测)
用户判断(2026-09-25):mod 支持分 fabric/forge,两者不同版本还有差异, 所以各应有一个独立后端。 —— 实测支持这个结论,且差异比"方法签名"更深一层。
实测:Fabric 客户端能启动(2026-09-25)
命令(成功的形态):
# ★ 必须剔除 1.20.1 自带的 asm(9.2 + 9.6 两版并存),改用 Fabric 的 asm 9.10.1
# 否则 Knot.<clinit> 抛 "duplicate ASM classes found on classpath"
grep -v "/org/ow2/asm/" vanilla_libs.txt > base.txt
CP="$(paste -sd: base.txt):fabric-loader:asm*:sponge-mixin:intermediary:1.20.1.jar"
DISPLAY=:98 java -cp "$CP" -Djava.library.path=nativ \
net.fabricmc.loader.impl.launch.knot.KnotClient --gameDir ... --assets ... --assetIndex 1.20.1
实测输出(证明真的启动了):
[main/INFO]: Loading Minecraft 1.20.1 with Fabric Loader 0.19.5
[main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.7 ... Env=CLIENT
[Render thread/INFO]: Created: 1024x512x4 minecraft:textures/atlas/blocks.png-atlas
... 共 12 个资源图集
Fabric 必需的 8 个库(从 meta API 的 profile 拿,纯声明式): asm / asm-analysis / asm-commons / asm-tree / asm-util / sponge-mixin / intermediary / fabric-loader
为什么加载器必须各有独立后端(不只是版本差异)
| vanilla | fabric | forge | |
|---|---|---|---|
| 主类 | 我们自己的 McfoldRenderServer |
KnotClient(固定,不可替换) |
ForgeWrapper → FML |
| 我们的代码形态 | 直接 new Minecraft(cfg) |
必须作为 mod 被加载 | 必须作为 coremod/mod |
| 命名空间 | Mojang official(已反混淆) | intermediary(运行时重映射) | SRG/TSRG |
| 起 Minecraft 实例 | 自己 new | 由 Loader 创建,我们拿不到控制权 | 同 fabric |
★ 关键:Fabric/Forge 下我们不拥有启动流程 —— Loader 起游戏、进主菜单、 跑主循环。我们的「不跑主循环 + 手动喂世界数据」模型与它们根本冲突。
所以后端身份是 (加载器, API 世代) 二元组:
vanilla:
gen_1_20 已实测(渲染通过)
gen_1_21 已实测(渲染通过)
fabric:
gen_1_20 待设计(架构与 vanilla 不同,见下)
gen_1_21 待设计
forge:
gen_1_20 待设计
gen_1_21 待设计
Fabric 下的两条可行路线(都待实测)
路线 A:作为 Fabric mod 被加载
把我们的服务端打包成 fabric mod(fabric.mod.json + entrypoint),
由 Loader 在游戏初始化后调用我们。问题:Loader 会继续跑主循环,
我们要在 entrypoint 里接管渲染循环(或让它跑着、我们在 client tick 里注入数据)。
改造量大,但能得到完整的 mod 生态(Iris 也在内)。
路线 B:只借 Fabric 的注册表与资源包,不跑其主循环
用 Knot 启动(它会加载我们的 mod 并注册方块),但在我们的 mod entrypoint 里阻止进入主菜单(替换 Screen),然后按 vanilla 那套手动喂数据 + 渲染。 —— 即保留我们已验证的渲染循环,只从 Fabric 借「mod 加载 + 注册表」。
路线 B 更贴近现有架构(渲染循环不用重写),且已验证的部分(反混淆后的 渲染调用、setblocks、RLE 路由)能复用。
当前已具备的零加载器能力(不依赖上面两条)
cmd="mods"(已在两个 vanilla 后端实现,编译通过、待实测):
Java 侧自己扫 mods 目录 → 读 jar 的 assets/<ns>/blockstates/*.json
→ 解冻 BLOCK 注册表 → 注册占位方块 + state → jar 挂为资源包。
覆盖范围(诚实标注): 方块外观 ✅ / state id ✅ / 变体属性 ⚠️ / 行为 ❌ / Iris 光影 ❌