Files
agentmc/scaffold/LOADERS.md
JianFeeeee 6827f4d0e1 实测 Fabric 客户端可启动 + 加载器后端设计文档
用户判断: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 ❌
2026-09-25 19:55:13 +08:00

3.8 KiB
Raw Blame History

加载器差异与后端设计(实测)

用户判断(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 光影 ❌