Fail fast on missing terrain assets and recover empty renders

This commit is contained in:
2026-10-04 22:32:11 +05:00
parent 79d0f53a79
commit 763cf8713c
15 changed files with 842 additions and 21 deletions

View File

@@ -1,36 +1,90 @@
import { mkdir, readFile, writeFile, access } from "node:fs/promises";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import { unzipSync } from "fflate";
export const assetsRoot = ".cache/minecraft/1.21.11";
export async function prepareAssets() {
const completionMarker = `${assetsRoot}/.minegeo-assets.json`;
const requiredAssets = [
"blockstates/grass_block.json",
"blockstates/stone.json",
"models/block/block.json",
"models/block/cube.json",
"models/block/cube_all.json",
"textures/block/grass_block_top.png",
"textures/block/dirt.png",
"textures/block/stone.png",
"textures/block/water_still.png",
];
async function assetsReady() {
try {
await access(`${assetsRoot}/assets/minecraft/blockstates/grass_block.json`);
return;
} catch {}
const marker = JSON.parse(await readFile(completionMarker, "utf8"));
if (
marker.format !== "minegeo-assets/v1" ||
marker.minecraftVersion !== "1.21.11" ||
!marker.files
)
return false;
// A blockstate alone is insufficient: interrupted setup can leave models
// and textures absent. The marker is only written after all extraction.
for (const asset of requiredAssets) {
const bytes = await readFile(`${assetsRoot}/assets/minecraft/${asset}`);
if (asset.endsWith(".json")) JSON.parse(bytes.toString());
else if (
bytes.length < 8 ||
bytes.subarray(0, 8).toString("hex") !== "89504e470d0a1a0a"
)
return false;
}
return true;
} catch {
return false;
}
}
export async function prepareAssets() {
if (await assetsReady()) return;
console.log(
"Preparing Minecraft 1.21.11 models and textures for terrain rendering…",
);
const manifest = await (
await fetch(
"https://piston-meta.mojang.com/mc/game/version_manifest_v2.json",
{ signal: AbortSignal.timeout(60000) },
)
).json();
const entry = manifest.versions.find((v) => v.id === "1.21.11");
if (!entry) throw new Error("Official Minecraft 1.21.11 metadata missing");
const version = await (await fetch(entry.url)).json();
const version = await (
await fetch(entry.url, { signal: AbortSignal.timeout(60000) })
).json();
await mkdir(".cache/minecraft", { recursive: true });
const jarPath = ".cache/minecraft/client-1.21.11.jar";
let jar;
try {
jar = await readFile(jarPath);
} catch {
const response = await fetch(version.downloads.client.url);
if (!response.ok) throw new Error("Minecraft asset download failed");
jar = Buffer.from(await response.arrayBuffer());
await writeFile(jarPath, jar);
/* No cached client yet. */
}
if (
!jar ||
createHash("sha1").update(jar).digest("hex") !==
version.downloads.client.sha1
)
throw new Error("Minecraft client checksum mismatch");
version.downloads.client.sha1
) {
const response = await fetch(version.downloads.client.url, {
signal: AbortSignal.timeout(120000),
});
if (!response.ok)
throw new Error(
`Minecraft asset download failed: HTTP ${response.status}`,
);
jar = Buffer.from(await response.arrayBuffer());
if (
createHash("sha1").update(jar).digest("hex") !==
version.downloads.client.sha1
)
throw new Error(
"Minecraft client checksum mismatch; downloaded assets were not saved",
);
await writeFile(jarPath, jar);
}
const entries = unzipSync(jar, {
filter: (f) =>
/^assets\/minecraft\/(models|blockstates|textures\/block)\//.test(
@@ -42,6 +96,20 @@ export async function prepareAssets() {
await mkdir(path.slice(0, path.lastIndexOf("/")), { recursive: true });
await writeFile(path, bytes);
}
// Completion is recorded last, so a killed extraction is repaired next time.
await writeFile(
completionMarker,
JSON.stringify({
format: "minegeo-assets/v1",
minecraftVersion: "1.21.11",
clientSha1: version.downloads.client.sha1,
files: Object.keys(entries).length,
}),
);
if (!(await assetsReady()))
throw new Error(
"Minecraft rendering assets are incomplete or corrupt. Run npm run setup to repair them.",
);
console.log(
`Prepared ${Object.keys(entries).length} verified vanilla model/texture assets.`,
);