gen_1_21 实测启动成功 + provision 工具

决定性验证:gen_1_21 后端在 1.21.1 上收到 {"event":"ready"}
  ⇒ 世代分派架构成立:同一套 Python 层,两个独立编译的 Java 后端

运行时发现第 12 处世代差异(编译期看不出):
  1.20.x  registryAccess 是可变 LayeredRegistryAccess → 事后反射替换
  1.21.x  registryAccess 是 final RegistryAccess$Frozen → 反射抛
          "Can not set final field"
  处理:gen_1_21 改为先载注册表 → 经 CommonListenerCookie.receivedRegistries
        传入 → 再构造 listener。比 1.20 的反射做法更干净。

新增 mcfold/provision.py(从官方 manifest 装任意版本,版本无关):
  - 跳过音频资源:1.21.1 实测 3911 条里跳过 3744 条
  - natives 两种形态都认(1.21 起是独立库条目,非 classifiers)
  - 实测 1.21.1:274 文件 / 200MB / 32s / 0 失败
This commit is contained in:
JianFeeeee
2026-09-25 18:35:28 +08:00
parent 8c7d5b9d38
commit 253146cffe
8 changed files with 316 additions and 40 deletions

View File

@ -524,35 +524,13 @@ public class McfoldRenderServer {
reloadResources(mc);
System.err.println("[mcfold] resources reloaded");
Connection conn = new Connection(PacketFlow.CLIENTBOUND);
GameProfile profile = new GameProfile(UUID.randomUUID(), "mcfold_cam");
// 初始注册表:官方静态层(1.21 的 cookie 需要它,随后会被真实注册表替换)
net.minecraft.core.RegistryAccess.Frozen listenerRegistryAccess =
net.minecraft.client.multiplayer.ClientRegistryLayer.createRegistryAccess()
.compositeAccess();
WorldSessionTelemetryManager telemetry = new WorldSessionTelemetryManager(
TelemetryEventSender.DISABLED, false, Duration.ZERO, "");
// ★ 世代差异(1.21+):ClientPacketListener(Minecraft, Connection, CommonListenerCookie)
// 1.20.x 是 (Minecraft, Screen, Connection, ServerData, GameProfile, TelemetryManager)。
// ★★ 这是本世代最大的一处变化 —— 参数个数与语义都不同。
var cookie = new net.minecraft.client.multiplayer.CommonListenerCookie(
profile,
telemetry,
listenerRegistryAccess, // 后面注册表加载后会被替换
net.minecraft.world.flag.FeatureFlags.DEFAULT_FLAGS,
null, // serverBrand
null, // serverData
null, // postDisconnectScreen
java.util.Map.of(), // serverCookies
null, // chatState
false, // strictErrorHandling
java.util.Map.of(), // customReportDetails
net.minecraft.server.ServerLinks.EMPTY);
ClientPacketListener listener = new ClientPacketListener(mc, conn, cookie);
// ★ 世代差异(1.21+):createPackRepository(Path, DirectoryValidator)
// DirectoryValidator 没有 NONE 常量,构造要 PathMatcher;
// 传一个「全部拒绝」的 matcher 最安全(我们不加载外来符号链接数据包)
// ★★ 世代差异(1.21+):注册表经 CommonListenerCookie.receivedRegistries 传入,
// 并在 ClientPacketListener 构造时赋给 **final** 字段
// (实测:1.20.x 是可变 LayeredRegistryAccess,可事后反射替换;
// 1.21 是 final RegistryAccess$Frozen,反射会抛
// "Can not set final ... field")。
// ⇒ 本世代的正确顺序:**先加载注册表,再构造 cookie + listener**。
// 这比 1.20 的反射注入更干净。
var packs = net.minecraft.server.packs.repository.ServerPacksSource.createPackRepository(
new File(runDir, "datapacks").toPath(),
new net.minecraft.world.level.validation.DirectoryValidator(p -> false));
@ -563,13 +541,27 @@ public class McfoldRenderServer {
var all = new java.util.ArrayList<RegistryDataLoader.RegistryData<?>>();
all.addAll(RegistryDataLoader.WORLDGEN_REGISTRIES);
all.addAll(RegistryDataLoader.DIMENSION_REGISTRIES);
RegistryAccess.Frozen loaded = RegistryDataLoader.load(dataRm, listener.registryAccess(), all);
var layered = net.minecraft.client.multiplayer.ClientRegistryLayer.createRegistryAccess()
.replaceFrom(net.minecraft.client.multiplayer.ClientRegistryLayer.REMOTE,
new RegistryAccess.Frozen[]{loaded});
var rf = ClientPacketListener.class.getDeclaredField("registryAccess");
rf.setAccessible(true);
rf.set(listener, layered);
RegistryAccess.Frozen base = net.minecraft.client.multiplayer.ClientRegistryLayer
.createRegistryAccess().compositeAccess();
RegistryAccess.Frozen loaded = RegistryDataLoader.load(dataRm, base, all);
System.err.println("[mcfold] registries loaded: dimension_type="
+ loaded.registryOrThrow(Registries.DIMENSION_TYPE).size());
Connection conn = new Connection(PacketFlow.CLIENTBOUND);
GameProfile profile = new GameProfile(UUID.randomUUID(), "mcfold_cam");
WorldSessionTelemetryManager telemetry = new WorldSessionTelemetryManager(
TelemetryEventSender.DISABLED, false, Duration.ZERO, "");
var cookie = new net.minecraft.client.multiplayer.CommonListenerCookie(
profile,
telemetry,
loaded, // ← 注册表从这里进
net.minecraft.world.flag.FeatureFlags.DEFAULT_FLAGS,
null, null, null,
java.util.Map.of(), null, false, java.util.Map.of(),
net.minecraft.server.ServerLinks.EMPTY);
ClientPacketListener listener = new ClientPacketListener(mc, conn, cookie);
System.err.println("[mcfold] listener ok, registryAccess dims="
+ listener.registryAccess().registryOrThrow(Registries.DIMENSION_TYPE).size());
Holder<DimensionType> dimHolder = loaded.registryOrThrow(Registries.DIMENSION_TYPE)
.getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, ResourceLocation.parse("minecraft:overworld")));