diff --git a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts index d933ae28e..a9031bb24 100644 --- a/packages/scripts/src/buildAssets/consumableProvisionIcons.ts +++ b/packages/scripts/src/buildAssets/consumableProvisionIcons.ts @@ -14,6 +14,42 @@ interface Mappings { const listItemsPattern = //g; +async function extractPackedIcon( + texture: sharp.Sharp, + sizes: number[], + context: string, +) { + const [left, top, width, height] = sizes; + const bounds = [left, top, width, height]; + + if (!bounds.every(Number.isInteger)) { + throw new Error( + `Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`, + ); + } + + const metadata = await texture.metadata(); + + if (metadata.width === undefined || metadata.height === undefined) { + throw new Error(`Failed to read image dimensions for ${context}`); + } + + if ( + left < 0 || + top < 0 || + width <= 0 || + height <= 0 || + left + width > metadata.width || + top + height > metadata.height + ) { + throw new Error( + `Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`, + ); + } + + return await texture.clone().extract({ left, top, width, height }).toBuffer(); +} + export async function consumableProvisionIcons() { console.log("Building consumable and provision icons..."); @@ -64,28 +100,13 @@ export async function consumableProvisionIcons() { 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(); - } + content = await extractPackedIcon( + consumablesTexture, + sizes, + `consumable ${consumable.icon}`, + ); } else { - content = await consumablesTexture - .trim({ threshold: 100 }) - .toBuffer(); + content = await consumablesTexture.trim({ threshold: 80 }).toBuffer(); } await uploader.add({ @@ -156,26 +177,13 @@ export async function consumableProvisionIcons() { 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(); - } + content = await extractPackedIcon( + provisionsTexture, + sizes, + `provision ${provision.icon}`, + ); } else { - content = await provisionsTexture.trim({ threshold: 100 }).toBuffer(); + content = await provisionsTexture.trim({ threshold: 80 }).toBuffer(); } await uploader.add({