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:
105
scaffold/RenderProbe.java
Normal file
105
scaffold/RenderProbe.java
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user