Validate battle map WebP dimensions
This commit is contained in:
@@ -4,6 +4,8 @@ import { createServer } from 'vite';
|
|||||||
|
|
||||||
const battleMapDir = join('src', 'assets', 'images', 'battle');
|
const battleMapDir = join('src', 'assets', 'images', 'battle');
|
||||||
const battleMapAssetsSourcePath = join('src', 'game', 'data', 'battleMapAssets.ts');
|
const battleMapAssetsSourcePath = join('src', 'game', 'data', 'battleMapAssets.ts');
|
||||||
|
const minimumRasterMapWidth = 1024;
|
||||||
|
const minimumRasterMapHeight = 768;
|
||||||
|
|
||||||
const server = await createServer({
|
const server = await createServer({
|
||||||
logLevel: 'error',
|
logLevel: 'error',
|
||||||
@@ -185,6 +187,18 @@ function validateMapFile(errors, context, path) {
|
|||||||
bytes.subarray(8, 12).toString('ascii') !== 'WEBP'
|
bytes.subarray(8, 12).toString('ascii') !== 'WEBP'
|
||||||
) {
|
) {
|
||||||
errors.push(`${context}: "${path}" does not have a WebP RIFF header`);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -192,6 +206,58 @@ function validateMapFile(errors, context, path) {
|
|||||||
errors.push(`${context}: "${path}" uses an unsupported map asset extension`);
|
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) {
|
function assetUrlToPath(url) {
|
||||||
const marker = 'src/assets/images/battle/';
|
const marker = 'src/assets/images/battle/';
|
||||||
const index = url.indexOf(marker);
|
const index = url.indexOf(marker);
|
||||||
|
|||||||
Reference in New Issue
Block a user