Files
agentmc/scaffold/RenderProbe.java
JianFeeeee e223be2571 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 同格式)⇒ 这就是数据喂渲染器的落点。
2026-09-25 15:39:41 +08:00

106 lines
4.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}