Validate battle map WebP dimensions

This commit is contained in:
2026-07-05 15:27:10 +09:00
parent 1b58fc774d
commit 11d450fc04

View File

@@ -4,6 +4,8 @@ import { createServer } from 'vite';
const battleMapDir = join('src', 'assets', 'images', 'battle');
const battleMapAssetsSourcePath = join('src', 'game', 'data', 'battleMapAssets.ts');
const minimumRasterMapWidth = 1024;
const minimumRasterMapHeight = 768;
const server = await createServer({
logLevel: 'error',
@@ -185,6 +187,18 @@ function validateMapFile(errors, context, path) {
bytes.subarray(8, 12).toString('ascii') !== 'WEBP'
) {
errors.push(`${context}: "${path}" does not have a WebP RIFF header`);
return;
}
const dimensions = readWebpDimensions(bytes);
if (!dimensions) {
errors.push(`${context}: "${path}" does not expose readable WebP dimensions`);
return;
}
if (dimensions.width < minimumRasterMapWidth || dimensions.height < minimumRasterMapHeight) {
errors.push(
`${context}: "${path}" is ${dimensions.width}x${dimensions.height}, expected at least ${minimumRasterMapWidth}x${minimumRasterMapHeight}`
);
}
return;
}
@@ -192,6 +206,58 @@ function validateMapFile(errors, context, path) {
errors.push(`${context}: "${path}" uses an unsupported map asset extension`);
}
function readWebpDimensions(bytes) {
let offset = 12;
while (offset + 8 <= bytes.length) {
const chunkType = bytes.subarray(offset, offset + 4).toString('ascii');
const chunkSize = bytes.readUInt32LE(offset + 4);
const dataOffset = offset + 8;
if (dataOffset + chunkSize > bytes.length) {
return undefined;
}
if (chunkType === 'VP8X' && chunkSize >= 10) {
return {
width: readUInt24LE(bytes, dataOffset + 4) + 1,
height: readUInt24LE(bytes, dataOffset + 7) + 1
};
}
if (chunkType === 'VP8L' && chunkSize >= 5 && bytes[dataOffset] === 0x2f) {
const bits =
bytes[dataOffset + 1] |
(bytes[dataOffset + 2] << 8) |
(bytes[dataOffset + 3] << 16) |
(bytes[dataOffset + 4] << 24);
return {
width: (bits & 0x3fff) + 1,
height: ((bits >>> 14) & 0x3fff) + 1
};
}
if (
chunkType === 'VP8 ' &&
chunkSize >= 10 &&
bytes[dataOffset + 3] === 0x9d &&
bytes[dataOffset + 4] === 0x01 &&
bytes[dataOffset + 5] === 0x2a
) {
return {
width: bytes.readUInt16LE(dataOffset + 6) & 0x3fff,
height: bytes.readUInt16LE(dataOffset + 8) & 0x3fff
};
}
offset = dataOffset + chunkSize + (chunkSize % 2);
}
return undefined;
}
function readUInt24LE(bytes, offset) {
return bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16);
}
function assetUrlToPath(url) {
const marker = 'src/assets/images/battle/';
const index = url.indexOf(marker);