Crusader_Decomp/scripts/_tmp_inspect_psx_banks.js

197 lines
6.4 KiB
JavaScript
Raw Normal View History

2026-04-07 00:15:44 +02:00
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();