197 lines
No EOL
6.4 KiB
JavaScript
197 lines
No EOL
6.4 KiB
JavaScript
const fs = require("fs");
|
|
|
|
function readU32LE(buffer, offset) {
|
|
return buffer.readUInt32LE(offset);
|
|
}
|
|
|
|
function readU16LE(buffer, offset) {
|
|
return buffer.readUInt16LE(offset);
|
|
}
|
|
|
|
function parseLsetWdl(data) {
|
|
const headerSize = readU32LE(data, 0);
|
|
if (headerSize !== 0x34 || headerSize > data.length) {
|
|
throw new Error(`unexpected header size ${headerSize}`);
|
|
}
|
|
|
|
const headerWords = [];
|
|
for (let offset = 0; offset < headerSize; offset += 4) {
|
|
headerWords.push(readU32LE(data, offset));
|
|
}
|
|
|
|
const audioSize = headerWords[1];
|
|
const sectionSizes = [];
|
|
for (let offset = 0x08; offset < 0x38; offset += 4) {
|
|
sectionSizes.push(readU32LE(data, offset));
|
|
}
|
|
|
|
const sections = [];
|
|
let cursor = headerSize + audioSize;
|
|
for (let index = 0; index < sectionSizes.length; index += 1) {
|
|
const size = sectionSizes[index];
|
|
if (size <= 0 || cursor + size > data.length) {
|
|
break;
|
|
}
|
|
sections.push({
|
|
index,
|
|
name: `post_audio_section_${String(index).padStart(2, "0")}`,
|
|
offset: cursor,
|
|
size
|
|
});
|
|
cursor += size;
|
|
}
|
|
|
|
return { headerWords, sections };
|
|
}
|
|
|
|
function readSectionBytes(data, section) {
|
|
return data.subarray(section.offset, section.offset + section.size);
|
|
}
|
|
|
|
function parseTypedSection8(data, section) {
|
|
const bytes = readSectionBytes(data, section);
|
|
if (bytes.length < 8) {
|
|
return null;
|
|
}
|
|
const recordCount = readU32LE(bytes, 0);
|
|
const payloadBytes = readU32LE(bytes, 4);
|
|
const headerOffset = 8 + payloadBytes;
|
|
if (recordCount <= 0 || recordCount > 0x400) {
|
|
return null;
|
|
}
|
|
if (payloadBytes < 0 || headerOffset + recordCount * 8 > bytes.length) {
|
|
return null;
|
|
}
|
|
|
|
let payloadCursor = 8;
|
|
const records = [];
|
|
for (let index = 0; index < recordCount; index += 1) {
|
|
const descriptorOffset = headerOffset + index * 8;
|
|
const blockSize = readU32LE(bytes, descriptorOffset);
|
|
const typeId = readU32LE(bytes, descriptorOffset + 4);
|
|
if (blockSize < 0 || payloadCursor + blockSize > headerOffset) {
|
|
return null;
|
|
}
|
|
const payload = bytes.subarray(payloadCursor, payloadCursor + blockSize);
|
|
const payloadDwords = [];
|
|
for (let offset = 0; offset + 4 <= payload.length; offset += 4) {
|
|
payloadDwords.push(readU32LE(payload, offset));
|
|
}
|
|
records.push({ index, typeId, blockSize, payloadDwords });
|
|
payloadCursor += blockSize;
|
|
}
|
|
|
|
return { section, recordCount, payloadBytes, records };
|
|
}
|
|
|
|
function parseTypedSection16(data, section) {
|
|
const bytes = readSectionBytes(data, section);
|
|
if (bytes.length < 8) {
|
|
return null;
|
|
}
|
|
const recordCount = readU32LE(bytes, 0);
|
|
const payloadBytes = readU32LE(bytes, 4);
|
|
const headerOffset = 8 + payloadBytes;
|
|
if (recordCount <= 0 || recordCount > 0x400) {
|
|
return null;
|
|
}
|
|
if (payloadBytes < 0 || headerOffset + recordCount * 16 > bytes.length) {
|
|
return null;
|
|
}
|
|
|
|
let payloadCursor = 8;
|
|
const records = [];
|
|
for (let index = 0; index < recordCount; index += 1) {
|
|
const descriptorOffset = headerOffset + index * 16;
|
|
const d4Size = readU32LE(bytes, descriptorOffset);
|
|
const ccSize = readU32LE(bytes, descriptorOffset + 4);
|
|
const d0Size = readU32LE(bytes, descriptorOffset + 8);
|
|
const typeId = readU16LE(bytes, descriptorOffset + 12);
|
|
const variantTypeId = readU16LE(bytes, descriptorOffset + 14);
|
|
const ccPayload = bytes.subarray(payloadCursor, payloadCursor + ccSize);
|
|
const d0Payload = bytes.subarray(payloadCursor + ccSize, payloadCursor + ccSize + d0Size);
|
|
const d4Payload = bytes.subarray(payloadCursor + ccSize + d0Size, payloadCursor + ccSize + d0Size + d4Size);
|
|
if (payloadCursor + ccSize + d0Size + d4Size > headerOffset) {
|
|
return null;
|
|
}
|
|
records.push({
|
|
index,
|
|
typeId,
|
|
variantTypeId,
|
|
ccSize,
|
|
d0Size,
|
|
d4Size,
|
|
ccDwords: readDwords(ccPayload),
|
|
d0Dwords: readDwords(d0Payload),
|
|
d4Dwords: readDwords(d4Payload)
|
|
});
|
|
payloadCursor += ccSize + d0Size + d4Size;
|
|
}
|
|
|
|
return { section, recordCount, payloadBytes, records };
|
|
}
|
|
|
|
function readDwords(payload) {
|
|
const values = [];
|
|
for (let offset = 0; offset + 4 <= payload.length; offset += 4) {
|
|
values.push(readU32LE(payload, offset));
|
|
}
|
|
return values;
|
|
}
|
|
|
|
function main() {
|
|
const filePath = process.argv[2];
|
|
const wantedTypes = new Set(process.argv.slice(3).map((value) => Number.parseInt(value, 16)));
|
|
const data = fs.readFileSync(filePath);
|
|
const parsed = parseLsetWdl(data);
|
|
|
|
const section8 = parsed.sections
|
|
.map((section) => parseTypedSection8(data, section))
|
|
.filter(Boolean)
|
|
.sort((left, right) => right.recordCount - left.recordCount || right.payloadBytes - left.payloadBytes)[0];
|
|
const section16 = parsed.sections
|
|
.map((section) => parseTypedSection16(data, section))
|
|
.filter(Boolean)
|
|
.sort((left, right) => right.recordCount - left.recordCount || right.payloadBytes - left.payloadBytes)[0];
|
|
|
|
const summary = {
|
|
filePath,
|
|
section8: section8 ? {
|
|
section: section8.section.name,
|
|
offset: `0x${section8.section.offset.toString(16)}`,
|
|
size: `0x${section8.section.size.toString(16)}`,
|
|
recordCount: section8.recordCount,
|
|
wanted: section8.records
|
|
.filter((record) => wantedTypes.has(record.typeId))
|
|
.map((record) => ({
|
|
index: record.index,
|
|
typeId: `0x${record.typeId.toString(16)}`,
|
|
blockSize: `0x${record.blockSize.toString(16)}`,
|
|
payloadDwords: record.payloadDwords.map((value) => `0x${value.toString(16)}`)
|
|
}))
|
|
} : null,
|
|
section16: section16 ? {
|
|
section: section16.section.name,
|
|
offset: `0x${section16.section.offset.toString(16)}`,
|
|
size: `0x${section16.section.size.toString(16)}`,
|
|
recordCount: section16.recordCount,
|
|
wanted: section16.records
|
|
.filter((record) => wantedTypes.has(record.typeId) || wantedTypes.has(record.variantTypeId))
|
|
.map((record) => ({
|
|
index: record.index,
|
|
typeId: `0x${record.typeId.toString(16)}`,
|
|
variantTypeId: `0x${record.variantTypeId.toString(16)}`,
|
|
ccSize: `0x${record.ccSize.toString(16)}`,
|
|
d0Size: `0x${record.d0Size.toString(16)}`,
|
|
d4Size: `0x${record.d4Size.toString(16)}`,
|
|
ccDwords: record.ccDwords.map((value) => `0x${value.toString(16)}`),
|
|
d0Dwords: record.d0Dwords.map((value) => `0x${value.toString(16)}`),
|
|
d4Dwords: record.d4Dwords.map((value) => `0x${value.toString(16)}`)
|
|
}))
|
|
} : null
|
|
};
|
|
|
|
console.log(JSON.stringify(summary, null, 2));
|
|
}
|
|
|
|
main(); |