ts-ritofile
Version:
TypeScript library for reading and writing League of Legends game file formats
193 lines (154 loc) • 5.29 kB
text/typescript
/**
* WPK (Wwise Package) format implementation
* Handles Wwise audio package files containing WEM audio data
*/
import { BinStream } from '../stream/bin-stream';
import { JsonSerializable } from '../core/json-encoder';
import * as fs from 'fs';
export class WPKWem implements JsonSerializable {
public id?: number;
public offset?: number;
public size?: number;
constructor(id?: number, offset?: number, size?: number) {
this.id = id;
this.offset = offset;
this.size = size;
}
toJSON(): any {
return {
id: this.id,
offset: this.offset,
size: this.size
};
}
}
export class WPK implements JsonSerializable {
public signature?: string;
public version?: number;
public wems: WPKWem[] = [];
constructor(signature?: string, version?: number, wems?: WPKWem[]) {
this.signature = signature;
this.version = version;
this.wems = wems || [];
}
toJSON(): any {
return {
signature: this.signature,
version: this.version,
wems: this.wems
};
}
private stream(path?: string, mode?: string, raw?: Buffer | boolean): BinStream {
if (raw !== undefined && raw !== null) {
if (raw === true) {
return BinStream.create();
} else if (Buffer.isBuffer(raw)) {
return BinStream.fromBuffer(raw);
}
}
if (!path) {
throw new Error('Path is required when raw is not provided');
}
return BinStream.fromFile(path, mode || 'rb');
}
read(path?: string, raw?: Buffer | boolean): void {
const bs = this.stream(path, 'rb', raw);
// Read signature
this.signature = bs.readString(4);
if (this.signature !== 'r3d2') {
throw new Error(`pyRitoFile: Failed: Read WPK ${path}: Wrong signature file: ${this.signature}`);
}
// Read version
this.version = bs.readUInt32()[0];
// Read WEM count and offsets
const wemCount = bs.readUInt32()[0];
this.wems = [];
// Read WEM offsets
const wemOffsets: number[] = [];
for (let i = 0; i < wemCount; i++) {
wemOffsets.push(bs.readUInt32()[0]);
}
// Filter out WEMs with offset 0
const validOffsets = wemOffsets.filter(offset => offset !== 0);
// Read WEM info for each valid offset
for (const offset of validOffsets) {
bs.seek(offset);
const wem = new WPKWem();
// Read data offset and size
wem.offset = bs.readUInt32()[0];
wem.size = bs.readUInt32()[0];
// Read WEM ID string
const idLength = bs.readUInt32()[0];
const idString = bs.readString(idLength - 1); // -1 for null terminator
bs.readUInt8(); // Skip null terminator
// Convert ID string to number (remove .wem extension)
wem.id = parseInt(idString.replace('.wem', ''));
this.wems.push(wem);
}
}
write(path?: string, wemDatas?: Buffer[], raw?: Buffer | boolean): Buffer | null {
const bs = this.stream(path, 'wb', raw);
// Write signature and version
bs.writeString('r3d2');
bs.writeUInt32(1);
// Write WEM count
const wemCount = this.wems.length;
bs.writeUInt32(wemCount);
// Reserve space for WEM offsets (will be filled later)
const offsetsPosition = bs.tell();
for (let i = 0; i < wemCount; i++) {
bs.writeUInt32(0); // Placeholder
}
// Write WEM info sections
const wemOffsets: number[] = [];
const wemDataOffsets: number[] = [];
for (let i = 0; i < this.wems.length; i++) {
const wem = this.wems[i];
// Align to 4-byte boundary
bs.pad(4);
wemOffsets.push(bs.tell());
// Update size from provided data
if (wemDatas && wemDatas[i]) {
wem.size = wemDatas[i].length;
}
// Reserve space for data offset (will be filled later)
const dataOffsetPosition = bs.tell();
wemDataOffsets.push(dataOffsetPosition);
bs.writeUInt32(0); // Placeholder for data offset
// Write size
bs.writeUInt32(wem.size || 0);
// Write WEM ID string
const wemId = `${wem.id}.wem`;
bs.writeUInt32(wemId.length + 1); // +1 for null terminator
bs.writeString(wemId);
bs.writeUInt8(0); // Null terminator
}
// Write WEM data
if (wemDatas) {
for (let i = 0; i < wemDatas.length; i++) {
const dataOffset = bs.tell();
// Update the data offset in the WEM info section
const currentPos = bs.tell();
bs.seek(wemDataOffsets[i]);
bs.writeUInt32(dataOffset);
bs.seek(currentPos);
// Write the actual WEM data
bs.writeBuffer(wemDatas[i]);
}
}
// Update WEM offsets in header
const finalPos = bs.tell();
bs.seek(offsetsPosition);
for (const wemOffset of wemOffsets) {
bs.writeUInt32(wemOffset);
}
bs.seek(finalPos);
if (raw !== undefined) {
return bs.getBuffer();
}
if (path) {
fs.writeFileSync(path, bs.getBuffer());
}
return null;
}
}