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 { sections }; } function parseTypedSection16(data, section, startOffset) { const bytes = data.subarray(section.offset + startOffset, section.offset + section.size); 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 endOffset = payloadCursor + ccSize + d0Size + d4Size; if (endOffset > headerOffset) { return null; } records.push({ index, typeId, variantTypeId, ccSize, d0Size, d4Size, payloadCursor }); payloadCursor = endOffset; } return { sectionName: section.name, startOffset, recordCount, payloadBytes, headerOffset, records }; } function summarizeCandidate(candidate, wantedTypes) { return { sectionName: candidate.sectionName, startOffset: `0x${candidate.startOffset.toString(16)}`, recordCount: candidate.recordCount, payloadBytes: `0x${candidate.payloadBytes.toString(16)}`, wanted: candidate.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)}`, payloadCursor: `0x${record.payloadCursor.toString(16)}` })) }; } 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 candidates = []; for (const section of parsed.sections) { for (let startOffset = 0; startOffset < Math.min(section.size, 0x800); startOffset += 4) { const candidate = parseTypedSection16(data, section, startOffset); if (!candidate) { continue; } const summary = summarizeCandidate(candidate, wantedTypes); if (summary.wanted.length > 0) { candidates.push(summary); } } } console.log(JSON.stringify(candidates, null, 2)); } main();