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

105
scaffold/RenderProbe.java Normal file
View File

@ -0,0 +1,105 @@
import com.mojang.blaze3d.pipeline.MainTarget;
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 org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.Proxy;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.OptionalInt;
/**
* mcfold render probe —— 起 MC 渲染栈,渲一帧,读像素写 PNG。
* 全程:不连服、不登录、不进主循环、不 gameplay。
*/
public class RenderProbe {
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");
String assetIdx = arg(args, "--assetIndex", "1.20.1");
String out = arg(args, "--out", "/tmp/scaffold-frame.png");
int width = Integer.parseInt(arg(args, "--width", "854"));
int height = Integer.parseInt(arg(args, "--height", "480"));
SharedConstants.tryDetectVersion();
SharedConstants.enableDataFixerOptimizations();
Bootstrap.bootStrap();
File runDir = new File(gameDir);
User user = new User("mcfold_cam", "0".repeat(32), "mcfold_cam",
Optional.empty(), 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(runDir, new File(runDir, "resourcepacks"), new File(assets), assetIdx),
new GameConfig.GameData(false, "mcfold", "release", false, false),
new GameConfig.QuickPlayData(null, null, null, null)
);
RenderSystem.initRenderThread();
RenderSystem.beginInitialization();
Minecraft mc = new Minecraft(cfg);
RenderSystem.finishInitialization();
System.out.println("[probe] constructed. w=" + mc.getWindow().getWidth()
+ " h=" + mc.getWindow().getHeight());
// ★ 渲染一帧:照抄 Minecraft.render() 的实测序列(反编译 nout2/.../Minecraft.java:1110-1140)
MainTarget target = (MainTarget) mc.getMainRenderTarget();
for (int frame = 0; frame < 3; frame++) {
RenderSystem.clear(16640, Minecraft.ON_OSX);
target.bindWrite(true);
mc.gameRenderer.render(0.0F, 0L, true);
target.unbindWrite();
target.blitToScreen(mc.getWindow().getWidth(), mc.getWindow().getHeight());
mc.getWindow().updateDisplay();
System.out.println("[probe] frame " + frame + " rendered");
}
// ★ 读像素:从 GL 后台缓冲直接读(比截屏更可靠,且不依赖 X)
GL11.glReadBuffer(GL11.GL_BACK);
int w = mc.getWindow().getWidth(), h = mc.getWindow().getHeight();
ByteBuffer buf = org.lwjgl.BufferUtils.createByteBuffer(w * h * 4);
GL11.glReadPixels(0, 0, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
// GL 原点在左下,PNG 在左上,需翻转
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int i = ((h - 1 - y) * w + x) * 4;
int r = buf.get(i) & 0xFF, g = buf.get(i + 1) & 0xFF, b = buf.get(i + 2) & 0xFF;
img.setRGB(x, y, (r << 16) | (g << 8) | b);
}
}
ImageIO.write(img, "png", new File(out));
System.out.println("[probe] wrote " + out + " (" + w + "x" + h + ")");
// 统计非黑像素,证明真的画了东西(而非空缓冲)
long nonBlack = 0; int total = w * h;
for (int p : img.getRGB(0, 0, w, h, null, 0, w)) if ((p & 0xFFFFFF) != 0) nonBlack++;
System.out.println("[probe] non-black pixels = " + nonBlack + "/" + total
+ " (" + (100.0 * nonBlack / total) + "%)");
GLFW.glfwTerminate();
System.out.println("[probe] DONE");
System.exit(0);
}
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;
}
}