reader.ts•2.06 kB
import { crc32 } from 'crc';
import { decompressBlock } from 'lz4js';
import { readFile } from 'fs/promises';
const FOOTER_SIZE = 20;
const MAGIC = 'DVPL';
interface DVPLFooter {
originalSize: number;
compressedSize: number;
checksum: number;
type: number;
}
function parseFooter(buffer: Buffer): DVPLFooter {
const footer = buffer.subarray(buffer.length - FOOTER_SIZE);
if (footer.toString('utf8', 16, 20) !== MAGIC) {
throw new Error('Invalid DVPL file: missing magic bytes');
}
return {
originalSize: footer.readUInt32LE(0),
compressedSize: footer.readUInt32LE(4),
checksum: footer.readUInt32LE(8),
type: footer.readUInt32LE(12),
};
}
export function decodeDVPL(buffer: Buffer): Buffer {
const footer = parseFooter(buffer);
const data = buffer.subarray(0, buffer.length - FOOTER_SIZE);
if (data.length !== footer.compressedSize) {
throw new Error(`DVPL size mismatch: expected ${footer.compressedSize}, got ${data.length}`);
}
if (crc32(data) !== footer.checksum) {
throw new Error('DVPL CRC32 mismatch');
}
if (footer.type === 0) {
if (footer.originalSize !== footer.compressedSize) {
throw new Error('DVPL uncompressed size mismatch');
}
return data;
}
if (footer.type === 1 || footer.type === 2) {
const dest = new Uint8Array(footer.originalSize);
const decompressed = decompressBlock(new Uint8Array(data), dest, 0, data.length, 0);
if (decompressed !== footer.originalSize) {
throw new Error(`Decompression size mismatch: expected ${footer.originalSize}, got ${decompressed}`);
}
return Buffer.from(dest);
}
throw new Error(`Unknown DVPL type: ${footer.type}`);
}
export async function readDVPLFile(path: string): Promise<Buffer> {
const filePath = path.endsWith('.dvpl') ? path : `${path}.dvpl`;
const content = await readFile(filePath);
return decodeDVPL(content);
}
export async function readDVPLString(path: string): Promise<string> {
const buffer = await readDVPLFile(path);
return buffer.toString('utf8');
}