feat: add RIFF container support

This commit is contained in:
GonnaHetzMe committed 2026-04-07 22:35:17 +02:00
1 parent f3ed687f00
commit 7cfc30c7a1
2 files changed
+110 -4

No files matched your search

@@ -1,5 +1,6 @@
import sharp from "sharp";
import { AssetUploader } from "../core/github/assetUploader";
import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect";
import { vfs } from "./constants";
import { ConsumablesCommon, ProvisionsCommon } from "./definitions";
@@ -57,8 +58,35 @@ export async function consumableProvisionIcons() {
.replace(".txt", "");
if (await vfs.resolve(`Data/${configPath}.packed.webp`)) {
const image = sharp(await vfs.file(`Data/${configPath}.packed.webp`));
const content = await image.trim({ threshold: 100 }).toBuffer();
const packedBuffer = await vfs.file(`Data/${configPath}.packed.webp`);
const consumablesTexture = sharp(packedBuffer);
const sizes = parsePackedSpriteRect(packedBuffer);
let content: Buffer;
if (sizes) {
try {
content = await consumablesTexture
.clone()
.extract({
left: sizes[0],
top: sizes[1],
width: sizes[2],
height: sizes[3],
})
.toBuffer();
} catch {
console.warn(
`Failed to extract consumable ${consumable.icon} from RIFF data; falling back to trim...`,
);
content = await consumablesTexture
.trim({ threshold: 100 })
.toBuffer();
}
} else {
content = await consumablesTexture
.trim({ threshold: 100 })
.toBuffer();
}
await uploader.add({
path: `icons/consumables/${consumable.id}.webp`,
@@ -122,8 +150,33 @@ export async function consumableProvisionIcons() {
.replace(".txt", "");
if (await vfs.resolve(`Data/${configPath}.packed.webp`)) {
const image = sharp(await vfs.file(`Data/${configPath}.packed.webp`));
const content = await image.trim({ threshold: 100 }).toBuffer();
const packedBuffer = await vfs.file(`Data/${configPath}.packed.webp`);
const provisionsTexture = sharp(packedBuffer);
const sizes = parsePackedSpriteRect(packedBuffer);
let content: Buffer;
if (sizes) {
try {
content = await provisionsTexture
.clone()
.extract({
left: sizes[0],
top: sizes[1],
width: sizes[2],
height: sizes[3],
})
.toBuffer();
} catch {
console.warn(
`Failed to extract provision ${provision.icon} from RIFF data; falling back to trim...`,
);
content = await provisionsTexture
.trim({ threshold: 100 })
.toBuffer();
}
} else {
content = await provisionsTexture.trim({ threshold: 100 }).toBuffer();
}
await uploader.add({
path: `icons/provisions/${provision.id}.webp`,
@@ -0,0 +1,53 @@
const textDecoder = new TextDecoder();
function readFourCC(buffer: Uint8Array, offset: number) {
return textDecoder.decode(buffer.subarray(offset, offset + 4));
}
export function parsePackedSpriteRect(buffer: Uint8Array): number[] | null {
if (
buffer.length < 12 ||
readFourCC(buffer, 0) !== "RIFF" ||
readFourCC(buffer, 8) !== "WEBP"
) {
return null;
}
const view = new DataView(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength,
);
let offset = 12;
while (offset + 8 <= buffer.length) {
const chunkId = readFourCC(buffer, offset);
const chunkSize = view.getUint32(offset + 4, true);
const dataStart = offset + 8;
const dataEnd = dataStart + chunkSize;
if (dataEnd > buffer.length) return null;
if (chunkId === "extr") {
const lines = textDecoder
.decode(buffer.subarray(dataStart, dataEnd))
.replace(/\0/g, "")
.split(/\r?\n/)
.map((line) => line.trim().split(/\s+/).map(Number));
for (const values of lines) {
if (values.length < 4) continue;
if (!values.slice(0, 4).every(Number.isFinite)) continue;
if (values[2] <= 0 || values[3] <= 0) continue;
return values;
}
return null;
}
offset = dataEnd + (chunkSize % 2);
}
return null;
}