111 lines
2.8 KiB
JavaScript
111 lines
2.8 KiB
JavaScript
|
|
const fs = require("fs");
|
||
|
|
|
||
|
|
const ALLOWED_U5 = new Set([0x20, 0x22, 0x30]);
|
||
|
|
|
||
|
|
function readU32LE(buffer, offset) {
|
||
|
|
return buffer.readUInt32LE(offset);
|
||
|
|
}
|
||
|
|
|
||
|
|
function readU16LE(buffer, offset) {
|
||
|
|
return buffer.readUInt16LE(offset);
|
||
|
|
}
|
||
|
|
|
||
|
|
function isStructuredCandidate(record) {
|
||
|
|
if (record[0] >= 0x200) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (record[1] === 0 && record[2] === 0) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (record[1] >= 0x4000 || record[2] >= 0x4000) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if (record[3] > 0x20 || record[4] > 0x04) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return ALLOWED_U5.has(record[5]);
|
||
|
|
}
|
||
|
|
|
||
|
|
function probeFile(filePath) {
|
||
|
|
const data = fs.readFileSync(filePath);
|
||
|
|
const headerSize = readU32LE(data, 0);
|
||
|
|
const audioSize = readU32LE(data, 4);
|
||
|
|
const sectionSizes = [];
|
||
|
|
for (let offset = 8; offset < 0x38; offset += 4) {
|
||
|
|
sectionSizes.push(readU32LE(data, offset));
|
||
|
|
}
|
||
|
|
|
||
|
|
let cursor = headerSize + audioSize;
|
||
|
|
const sections = [];
|
||
|
|
for (let index = 0; index < sectionSizes.length; index += 1) {
|
||
|
|
const size = sectionSizes[index];
|
||
|
|
const start = cursor;
|
||
|
|
const end = start + size;
|
||
|
|
const bytes = data.subarray(start, end);
|
||
|
|
cursor = end;
|
||
|
|
|
||
|
|
let rowCount = null;
|
||
|
|
let rootHits = 0;
|
||
|
|
let bulkHits = 0;
|
||
|
|
|
||
|
|
if (bytes.length >= 4) {
|
||
|
|
rowCount = readU32LE(bytes, 0);
|
||
|
|
for (let rowIndex = 0; rowIndex < Math.min(rowCount, 5000); rowIndex += 1) {
|
||
|
|
const base = 4 + rowIndex * 24;
|
||
|
|
if (base + 24 > bytes.length) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
const words = [];
|
||
|
|
for (let wordIndex = 0; wordIndex < 12; wordIndex += 1) {
|
||
|
|
words.push(readU16LE(bytes, base + wordIndex * 2));
|
||
|
|
}
|
||
|
|
const left = [words[4], words[5], words[0], words[1], words[2], words[3]];
|
||
|
|
const right = [words[10], words[11], words[6], words[7], words[8], words[9]];
|
||
|
|
if (isStructuredCandidate(left)) {
|
||
|
|
rootHits += 1;
|
||
|
|
}
|
||
|
|
if (isStructuredCandidate(right)) {
|
||
|
|
rootHits += 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const usableSize = bytes.length - (bytes.length % 24);
|
||
|
|
for (let offset = 0; offset < usableSize; offset += 24) {
|
||
|
|
for (const sideOffset of [0, 12]) {
|
||
|
|
const base = offset + sideOffset;
|
||
|
|
const record = [
|
||
|
|
readU16LE(bytes, base),
|
||
|
|
readU16LE(bytes, base + 2),
|
||
|
|
readU16LE(bytes, base + 4),
|
||
|
|
readU16LE(bytes, base + 6),
|
||
|
|
readU16LE(bytes, base + 8),
|
||
|
|
readU16LE(bytes, base + 10)
|
||
|
|
];
|
||
|
|
if (isStructuredCandidate(record)) {
|
||
|
|
bulkHits += 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sections.push({
|
||
|
|
index,
|
||
|
|
start,
|
||
|
|
size,
|
||
|
|
rowCount,
|
||
|
|
rootHits,
|
||
|
|
bulkHits
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
filePath,
|
||
|
|
headerSize,
|
||
|
|
audioSize,
|
||
|
|
sections
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const filePath of process.argv.slice(2)) {
|
||
|
|
console.log(JSON.stringify(probeFile(filePath), null, 2));
|
||
|
|
}
|