用户定向:在 python 层写 switch,调用不同 java 后端(而非一个文件里 switch)。
新增:
mcfold/backends/{__init__,base,gen_1_20,gen_1_21}.py 后端体系与分派
scaffold/java/gen_1_20/ 对 1.20.x client jar 编译
scaffold/java/gen_1_21/ 对 1.21.1 client jar 编译(✅ 实测编译通过)
scaffold/java/VERSION-DIFFS.md 实测差异全表
判据:DeltaTracker 类是否存在(1.21 引入,正是 render 签名变化的根源)
⇒ 比硬编码版本号稳
实测差异共 11 处(21 个检查点里 13 个跨世代完全一致):
setLevel / GameRenderer.render / countRendered* / createPackRepository /
ResourceLocation 构造 / enableDataFixerOptimizations(移除) / User 构造 /
ChunkStatus 包路径 / ClientPacketListener 构造(参数个数语义都变) /
loadEffect(变 private)
为什么必须分后端:DeltaTracker 在 1.20.x jar 中不存在,
两代调用无法共存于一份源码(实测编译报 cannot find symbol)。
1.21.1 反混淆已验证(29MB named jar,DeltaTracker 3 处)
3.0 KiB
3.0 KiB
世代差异清单(实测,非推断)
来源:官方 client_mappings.txt 逐版本对比(1.20.1 / 1.20.2 / 1.21.1),
以及两侧源码对各自 client jar 的实际编译结果。
判据
net.minecraft.client.DeltaTracker 类是否存在 ⇒ 1.21+。
这是 1.21 引入的时间抽象,也正是 GameRenderer.render 签名变化的根源。
比硬编码版本号稳(官方改版本号不影响判据)。
实现:mcfold/backends/__init__.py:detect_generation()
差异全表(8 处,覆盖 21 个检查点中的 13 个「完全一致」之外的部分)
| # | API | 1.20.x | 1.21+ | 处理 |
|---|---|---|---|---|
| 1 | Minecraft.setLevel |
(ClientLevel) |
(ClientLevel, ReceivingLevelScreen$Reason) |
gen_1_21 传 Reason.OTHER |
| 2 | GameRenderer.render |
(float,long,boolean) |
(DeltaTracker, boolean) |
gen_1_21 用 mc.getTimer() |
| 3 | 区块计数 | countRenderedChunks() |
countRenderedSections() |
各世代用各自名字 |
| 4 | 同理 | hasRenderedAllChunks() |
hasRenderedAllSections() |
同上 |
| 5 | ServerPacksSource.createPackRepository |
(Path) |
(Path, DirectoryValidator) |
gen_1_21 构造 validator |
| 6 | ResourceLocation 构造 |
new ResourceLocation(s) |
ResourceLocation.parse(s) |
静态工厂 |
| 7 | SharedConstants.enableDataFixerOptimizations |
有 | 已移除 | gen_1_21 不调 |
| 8 | User 构造 |
(String,String sessionId,...) |
(String,UUID,...) |
传 UUID |
| 9 | ChunkStatus |
world.level.chunk.ChunkStatus |
world.level.chunk.status.ChunkStatus |
包路径变了 |
| 10 | ClientPacketListener 构造 |
(Minecraft,Screen,Connection,ServerData,GameProfile,Telemetry) |
(Minecraft,Connection,CommonListenerCookie) |
★ 参数个数与语义都变 |
| 11 | GameRenderer.loadEffect |
package-private(同包可调) | private(同包也调不到) | gen_1_21 用反射 |
★ 关键结论:差异集中在 11 处,而不是「每个版本一套」。
21 个检查点里 13 个(如 tick/allChanged/replaceWithPacketData/
updateViewCenter/getChunkSource/PackRepository.*)跨世代完全一致。
为什么必须分后端(而不是一个文件里 switch)
Java 编译期绑定签名。实测:
世代 B 的调用对 1.20.1 jar 编译:
error: cannot find symbol class DeltaTracker
DeltaTracker 在 1.20.x 的 jar 里不存在 ⇒ 两代的调用无法共存于一份源码。
所以每个世代独立编译,Python 层做分派。
加新世代的做法(用户 2026-09-25 定向)
scaffold/java/gen_<ver>/建目录,拷上一代源码- 按上表改差异点(对着 javap 输出改,不要凭印象写 API 名)
mcfold/backends/gen_<ver>.py声明markers(判据类)mcfold/backends/__init__.py的GENERATIONS加一条- 各自对对应版本的 named jar 编译
⚠️ 教训(本轮踩过):Reason.LEVEL_LOAD、DirectoryValidator.NONE
都是凭印象写出来的、实际不存在的 API;loadEffect 的可见性也没验证。
改之前先 javap。