forked from phenom/healer-man
63 lines
2.3 KiB
JavaScript
63 lines
2.3 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
|
|
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
|
|
/** Validate a legacy DXT1 DDS and describe every source mip without decoding or resampling it. */
|
|
export function inspectNativeWaterDxt1Dds(bytes) {
|
|
if (!Buffer.isBuffer(bytes)) bytes = Buffer.from(bytes);
|
|
if (bytes.length < 128 || bytes.toString("ascii", 0, 4) !== "DDS ") {
|
|
throw new Error("Native water frame is not a complete DDS file.");
|
|
}
|
|
const headerSize = bytes.readUInt32LE(4);
|
|
const height = bytes.readUInt32LE(12);
|
|
const width = bytes.readUInt32LE(16);
|
|
const pixelFormatSize = bytes.readUInt32LE(76);
|
|
const formatFourCC = bytes.toString("ascii", 84, 88);
|
|
const mipCount = Math.max(1, bytes.readUInt32LE(28));
|
|
if (headerSize !== 124 || pixelFormatSize !== 32) {
|
|
throw new Error(`Unsupported DDS header sizes ${headerSize}/${pixelFormatSize}.`);
|
|
}
|
|
if (formatFourCC !== "DXT1") throw new Error(`Unsupported native water frame format ${formatFourCC}.`);
|
|
if (width < 1 || height < 1 || width % 4 !== 0 || height % 4 !== 0) {
|
|
throw new Error(`Native water DXT1 dimensions must be positive multiples of four; got ${width}x${height}.`);
|
|
}
|
|
const maxMipCount = 1 + Math.floor(Math.log2(Math.max(width, height)));
|
|
if (mipCount > maxMipCount) throw new Error(`DDS declares ${mipCount} mips for ${width}x${height}.`);
|
|
|
|
let offset = 128;
|
|
let mipWidth = width;
|
|
let mipHeight = height;
|
|
const mips = [];
|
|
for (let level = 0; level < mipCount; level += 1) {
|
|
const byteLength = Math.ceil(mipWidth / 4) * Math.ceil(mipHeight / 4) * 8;
|
|
const end = offset + byteLength;
|
|
if (end > bytes.length) throw new Error(`DDS mip ${level} is truncated.`);
|
|
const data = bytes.subarray(offset, end);
|
|
mips.push({
|
|
level,
|
|
width: mipWidth,
|
|
height: mipHeight,
|
|
offset,
|
|
byteLength,
|
|
sha256: sha256(data),
|
|
});
|
|
offset = end;
|
|
mipWidth = Math.max(1, Math.floor(mipWidth / 2));
|
|
mipHeight = Math.max(1, Math.floor(mipHeight / 2));
|
|
}
|
|
if (offset !== bytes.length) {
|
|
throw new Error(`DDS has ${bytes.length - offset} trailing bytes after its declared mip chain.`);
|
|
}
|
|
return {
|
|
width,
|
|
height,
|
|
mipCount,
|
|
formatFourCC,
|
|
headerBytes: 128,
|
|
fileBytes: bytes.length,
|
|
sha256: sha256(bytes),
|
|
mips,
|
|
exactMipChainConsumption: offset === bytes.length,
|
|
};
|
|
}
|