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();