fix(nitro): normalise uploaded bundles so meta.image matches the texture
Gitea Actions Runner Test / test-job (push) Successful in 2s
CI / check (push) Successful in 39s
CI / tests-unit (push) Successful in 2m3s
CI / tests-integration (push) Successful in 2m7s
CI / tests-ui (push) Successful in 2m50s
CI / preflight (push) Skipped
CI / deploy (push) Failing after 1m45s

An uploaded .nitro was written to disk byte-for-byte, so a bundle from a
third-party tool that ships a WebP member while still pointing
spritesheet.meta.image at a .png was accepted and stored as-is. The client
resolves the spritesheet through that pointer, so the result was a file
that validates fine and then renders nothing.

Re-write the bundle through createNitroBundle on import, which labels the
member from the actual bytes and repairs the pointer. No texture is
re-encoded, so the bytes stay identical, and the member keeps the base
name it arrived with so `chair*2` colour variants that share the `chair`
library are not renamed.
This commit is contained in:
openhands committed 2026-09-27 15:47:44 +02:00
1 parent 17de94d984
commit 306e209e29
4 files changed
+36 -153

No files matched your search

+15 -2
View File
@@ -17,7 +17,10 @@ import {
IMPORTED_PAGE_CAPTION_SAVE,
} from "@/lib/services/furni-import";
import { nitroAnimationStatesCount } from "@/lib/services/furni-real-interaction";
import { parseNitroBundle } from "@/lib/services/swf/nitro-builder";
import {
createNitroBundle,
parseNitroBundle,
} from "@/lib/services/swf/nitro-builder";
import { getRuntimePath, getRuntimeResolve } from "@/lib/utils/runtime-path";
export interface UploadResult {
@@ -275,9 +278,19 @@ export async function uploadSingleFurni(params: {
}
let nitroMeta: Record<string, unknown> | null = null;
// The client resolves the spritesheet through `spritesheet.meta.image`, so
// that pointer must name the member we actually ship. A bundle from a
// third-party tool can arrive with a stale pointer (say `.png` next to a
// WebP member), which stores fine and then renders nothing. Re-writing
// through the builder repairs it without touching a single texture byte.
// The member keeps its own base name, so `chair*2` colour variants that
// share the `chair` library are not renamed.
let storedNitro = nitroBuffer;
try {
const parsed = parseNitroBundle(nitroBuffer);
nitroMeta = parsed.json;
const memberBase = parsed.textureFileName.replace(/\.(?:png|webp)$/i, "");
storedNitro = createNitroBundle(parsed.json, parsed.texture, memberBase);
} catch {
return { ok: false, classname, warnings, error: "invalid .nitro bundle" };
}
@@ -299,7 +312,7 @@ export async function uploadSingleFurni(params: {
error: ".nitro file already exists on disk",
};
}
await fs.writeFile(nitroPath, nitroBuffer);
await fs.writeFile(nitroPath, storedNitro);
let iconFileName: string | null = null;
let iconPath: string | null = null;