pw-guild-icon-parser
Version:
Parser for Perfect World guild icon lists - converts PNG icons to DDS atlas format with DXT5 compression
83 lines • 3.27 kB
JavaScript
import { readFile } from 'fs/promises';
import { decompressDXT5 } from './dxt5-decompress.js';
export async function readDDS(ddsPath) {
const data = await readFile(ddsPath);
if (data.length < 128) {
throw new Error('DDS file too small to contain header');
}
// Read magic number
const magic = data.readUInt32LE(0);
if (magic !== 0x20534444) { // "DDS "
throw new Error('Invalid DDS magic number');
}
// Read DDS header
const header = readDDSHeader(data);
// Validate DXT5 format
const fourCC = header.pixelFormat.fourCC.trim();
if (fourCC !== 'DXT5') {
// Show hex representation for debugging (FourCC is at offset 88)
const fourCCBytes = data.subarray(88, 92);
const hexStr = Array.from(fourCCBytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' ');
const asciiStr = fourCCBytes.toString('ascii').replace(/\0/g, '');
throw new Error(`Expected DXT5 format, got "${fourCC}" (bytes at offset 88: ${hexStr}, ASCII: "${asciiStr}")`);
}
// Extract compressed data
const compressedData = data.subarray(128);
// Decompress to RGBA
const rgbaData = decompressDXT5(header.width, header.height, compressedData);
return {
header,
width: header.width,
height: header.height,
rgbaData
};
}
function readDDSHeader(data) {
const header = {
magic: data.readUInt32LE(0),
size: data.readUInt32LE(4),
flags: data.readUInt32LE(8),
height: data.readUInt32LE(12),
width: data.readUInt32LE(16),
pitchOrLinearSize: data.readUInt32LE(20),
depth: data.readUInt32LE(24),
mipMapCount: data.readUInt32LE(28),
reserved: [],
pixelFormat: readPixelFormat(data.subarray(72, 104)), // pixelFormat is 32 bytes, starts at offset 72
caps: data.readUInt32LE(108),
caps2: data.readUInt32LE(112),
caps3: data.readUInt32LE(116),
caps4: data.readUInt32LE(120),
reserved2: data.readUInt32LE(124)
};
// Read reserved array (11 integers)
for (let i = 0; i < 11; i++) {
header.reserved.push(data.readUInt32LE(32 + i * 4));
}
return header;
}
function readPixelFormat(data) {
// DDS_PIXELFORMAT structure starts at offset 0 of this buffer
// In the full DDS file, pixelFormat starts at offset 72 (after 4+4+4+4+4+4+4+4+44 = 76)
// But this function receives a buffer starting at offset 72
// Read FourCC as 4 bytes (offset 8-11 in pixelFormat structure = offset 80-83 in full file)
const fourCCBytes = data.subarray(8, 12);
let fourCC = '';
for (let i = 0; i < 4; i++) {
const byte = fourCCBytes[i];
if (byte !== 0 && byte >= 32 && byte <= 126) { // Printable ASCII
fourCC += String.fromCharCode(byte);
}
}
return {
size: data.readUInt32LE(0),
flags: data.readUInt32LE(4),
fourCC: fourCC.trim() || fourCCBytes.toString('ascii').replace(/\0/g, ''),
rgbBitCount: data.readUInt32LE(12),
rBitMask: data.readUInt32LE(16),
gBitMask: data.readUInt32LE(20),
bBitMask: data.readUInt32LE(24),
aBitMask: data.readUInt32LE(28)
};
}
//# sourceMappingURL=reader.js.map