P8: scaffold —— 反混淆 + MC 渲染栈离屏初始化 + 真渲染出图

实测链路(全绿):
  1. 官方 ProGuard 映射 → SRG → SpecialSource 重映射 → client-named.jar
  2. 反编译确认 MC 渲染器挂在 Minecraft 单例上,不能独立 new
  3. 权威序列:tryDetectVersion → Bootstrap → RenderSystem → new Minecraft(cfg)
  4. 渲 3 帧 + glReadPixels → PNG,画面是 Mojang 加载屏(真渲染,非清屏)

三个坑(实测证据):
  - 漏 tryDetectVersion → Game version not set
  - FolderData 参数顺序错 → icons/ 404
  - assetIndex 用 id(5) 而非落盘名(1.20.1) → 资源全找不到

下一步:ClientLevel 构造只需 listener.registryAccess(),
ClientChunkCache.replaceWithPacketData 接收 map_chunk 原始字节
(与 chunk.py 同格式)⇒ 这就是数据喂渲染器的落点。
This commit is contained in:
JianFeeeee
2026-09-25 15:39:41 +08:00
parent 5038c7f679
commit e223be2571
6 changed files with 472 additions and 0 deletions

91
scaffold/Scaffold.java Normal file
View File

@ -0,0 +1,91 @@
import com.mojang.blaze3d.platform.DisplayData;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.authlib.properties.PropertyMap;
import net.minecraft.SharedConstants;
import net.minecraft.client.Minecraft;
import net.minecraft.client.User;
import net.minecraft.client.main.GameConfig;
import net.minecraft.server.Bootstrap;
import java.io.File;
import java.net.Proxy;
import java.util.OptionalInt;
/**
* mcfold scaffold —— 只起 MC 的**渲染栈**,不进游戏主循环。
*
* 依据(反编译 net.minecraft.client.main.Main 的实测序列):
* Bootstrap.bootStrap()
* RenderSystem.initRenderThread(); beginInitialization();
* new Minecraft(gameConfig) ← 构造即完成 GL/资源/渲染器初始化
* RenderSystem.finishInitialization()
* 之后 Main 才起 Game thread 跑主循环 —— 我们**不**走那步。
*/
public class Scaffold {
public static void main(String[] args) throws Exception {
String gameDir = arg(args, "--gameDir", "/home/newqqagent/mcview/instances/default");
String assets = arg(args, "--assets", "/home/newqqagent/mcview/store/vanilla-1.20.1/assets");
// ★ 索引名必须是 assets/indexes/<name>.json 中真实存在的那个。
// version.json 的 assetIndex.id 是 "5",但 MCLC 落盘用 version.number(1.20.1)
// —— 传 "5" 会导致索引加载失败,所有资源/icons 全部找不到(实测踩过)。
String assetIdx = arg(args, "--assetIndex", "1.20.1");
int width = Integer.parseInt(arg(args, "--width", "1280"));
int height = Integer.parseInt(arg(args, "--height", "720"));
System.out.println("[scaffold] bootstrap...");
// ★ 官方 Main 的实测序列:tryDetectVersion 必须在 bootStrap 之前,
// 否则 DataFixers.<clinit> 抛 "Game version not set"
SharedConstants.tryDetectVersion();
SharedConstants.enableDataFixerOptimizations();
Bootstrap.bootStrap();
System.out.println("[scaffold] bootstrap ok, mcVersion=" + SharedConstants.getCurrentVersion().getName());
File runDir = new File(gameDir);
File assetsDir = new File(assets);
User user = new User("mcfold_cam", "0".repeat(32), "mcfold_cam",
java.util.Optional.empty(), java.util.Optional.empty(), User.Type.LEGACY);
PropertyMap props = new PropertyMap();
GameConfig cfg = new GameConfig(
new GameConfig.UserData(user, props, props, Proxy.NO_PROXY),
new DisplayData(width, height, OptionalInt.empty(), OptionalInt.empty(), false),
new GameConfig.FolderData(
// ★ 顺序是 (gameDirectory, resourcePackDirectory, assetDirectory, assetIndex)
// 反着写会把 assets 当游戏目录,导致资源全找不到(实测踩过:icons/icon_16x16.png 404)
runDir, new File(runDir, "resourcepacks"), assetsDir, assetIdx),
new GameConfig.GameData(
false, // demo
"mcfold", // version
"release", // versionType
false, // disableMultiplayer
false), // disableChat
new GameConfig.QuickPlayData(null, null, null, null)
);
System.out.println("[scaffold] RenderSystem init...");
RenderSystem.initRenderThread();
RenderSystem.beginInitialization();
System.out.println("[scaffold] new Minecraft(cfg) ...");
Minecraft mc = new Minecraft(cfg);
RenderSystem.finishInitialization();
System.out.println("[scaffold] OK Minecraft constructed");
// 检查渲染栈关键对象是否就绪(不启主循环)
System.out.println("[scaffold] window=" + mc.getWindow().getWidth() + "x" + mc.getWindow().getHeight());
System.out.println("[scaffold] levelRenderer=" + (mc.levelRenderer != null ? "present" : "null"));
System.out.println("[scaffold] gameRenderer=" + (mc.gameRenderer != null ? "present" : "null"));
System.out.println("[scaffold] level=" + (mc.level != null ? "present" : "null (не connected)"));
System.out.println("[scaffold] DONE (no main loop)");
}
private static String arg(String[] a, String key, String def) {
for (int i = 0; i < a.length - 1; i++)
if (a[i].equals(key)) return a[i + 1];
return def;
}
}