86 lines
3.3 KiB
JavaScript
86 lines
3.3 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { parseWowAdtTerrain } from "./wow-adt-terrain-audit.mjs";
|
|
import { decodeWowAdtAlphaMap } from "./wow-adt-terrain-material.mjs";
|
|
|
|
function chunk(name, payload) {
|
|
const header = Buffer.alloc(8);
|
|
Buffer.from(name, "ascii").reverse().copy(header);
|
|
header.writeUInt32LE(payload.length, 4);
|
|
return Buffer.concat([header, payload]);
|
|
}
|
|
|
|
function rleFill(value) {
|
|
const bytes = [];
|
|
let remaining = 4096;
|
|
while (remaining) {
|
|
const count = Math.min(127, remaining);
|
|
bytes.push(0x80 | count, value);
|
|
remaining -= count;
|
|
}
|
|
return Buffer.from(bytes);
|
|
}
|
|
|
|
function zeroSizedMcalAdt({ declaredPayloadBytes = null } = {}) {
|
|
const mtex = Buffer.from("Base.blp\0Overlay.blp\0", "utf8");
|
|
const layers = Buffer.alloc(32);
|
|
layers.writeUInt32LE(0, 0);
|
|
layers.writeUInt32LE(0, 4);
|
|
layers.writeUInt32LE(1, 16);
|
|
layers.writeUInt32LE(0x300, 20);
|
|
layers.writeUInt32LE(0, 24);
|
|
|
|
const alpha = rleFill(91);
|
|
const payloadLength = declaredPayloadBytes ?? alpha.length;
|
|
const alphaSubchunk = Buffer.alloc(8 + alpha.length);
|
|
Buffer.from("MCAL", "ascii").reverse().copy(alphaSubchunk);
|
|
alphaSubchunk.writeUInt32LE(0, 4);
|
|
alpha.copy(alphaSubchunk, 8);
|
|
|
|
const mcnkHeader = Buffer.alloc(128);
|
|
mcnkHeader.writeUInt32LE(0, 4);
|
|
mcnkHeader.writeUInt32LE(0, 8);
|
|
mcnkHeader.writeUInt32LE(2, 12);
|
|
mcnkHeader.writeUInt32LE(136, 28);
|
|
mcnkHeader.writeUInt32LE(176, 36);
|
|
mcnkHeader.writeUInt32LE(8 + payloadLength, 40);
|
|
mcnkHeader.writeUInt16LE(0xa55a, 60);
|
|
mcnkHeader.writeUInt16LE(0x1234, 62);
|
|
mcnkHeader.writeUInt16LE(0xabcd, 64);
|
|
const mcnk = Buffer.concat([mcnkHeader, chunk("MCLY", layers), alphaSubchunk]);
|
|
const mver = Buffer.alloc(4);
|
|
mver.writeUInt32LE(18);
|
|
return Buffer.concat([chunk("MVER", mver), chunk("MTEX", mtex), chunk("MCNK", mcnk)]);
|
|
}
|
|
|
|
test("MVER 18 zero-sized MCAL headers use the enclosing MCNK size span", () => {
|
|
const source = zeroSizedMcalAdt();
|
|
const parsed = parseWowAdtTerrain(source, { label: "zero-sized MCAL fixture" });
|
|
const target = parsed.chunks[0];
|
|
const overlay = target.layers[1];
|
|
|
|
assert.equal(target.alphaChunkHeaderSize, 0);
|
|
assert.equal(target.alphaChunkSizeSource, "mcnk-header");
|
|
assert.equal(target.holeMask, 0xa55a);
|
|
assert.equal(target.highResolutionHoles, false);
|
|
assert.equal(target.lowResolutionTextureMap[0], 0xabcd);
|
|
assert.equal(target.alphaSizeMatchesHeader, true);
|
|
assert.equal(target.alphaChunkWithinMcnk, true);
|
|
assert.equal(overlay.alphaMapEncoding, "rle-8");
|
|
assert.equal(overlay.alphaMapAvailable, true);
|
|
assert.equal(overlay.alphaMapBytes, target.alphaBytes);
|
|
const decoded = decodeWowAdtAlphaMap(source, target, overlay);
|
|
assert.equal(decoded.length, 4096);
|
|
assert.equal(decoded[0], 91);
|
|
assert.equal(decoded[4095], 91);
|
|
});
|
|
|
|
test("MVER 18 MCAL spans still fail closed when the declared payload leaves MCNK", () => {
|
|
const source = zeroSizedMcalAdt({ declaredPayloadBytes: 0xffff });
|
|
const parsed = parseWowAdtTerrain(source, { label: "overrun MCAL fixture" });
|
|
const target = parsed.chunks[0];
|
|
assert.equal(target.alphaChunkWithinMcnk, false);
|
|
assert.equal(target.layers[1].alphaMapAvailable, false);
|
|
assert.throws(() => decodeWowAdtAlphaMap(source, target, target.layers[1]), /unsupported or invalid MCAL span/);
|
|
});
|