2020-03-17 20:40:39 +01:00
|
|
|
const fs = require("fs");
|
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
//This is adapted from https://github.com/mon/SDVX-Song-Extractor/blob/master/bm2dx.py.
|
|
|
|
//I'm personally not very good at data exploration.
|
|
|
|
|
|
|
|
class Sample {
|
2020-03-17 20:40:39 +01:00
|
|
|
constructor(data, offset, key_no) {
|
|
|
|
const header = data.toString("ascii", offset, offset+4);
|
|
|
|
offset += 4;
|
|
|
|
const header_lead = data.readUInt32LE(offset);
|
|
|
|
offset += 4;
|
|
|
|
if (header !== "2DX9" || header_lead != 24) {
|
|
|
|
throw "Invalid 2DX header.";
|
|
|
|
}
|
|
|
|
|
|
|
|
const size = data.readUInt32LE(offset);
|
|
|
|
offset += 6;
|
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
//Previously the keysound number was stored here.
|
|
|
|
//In popn files this doesn't appear to be the case.
|
|
|
|
//So we assume keysounds are sequential in these files.
|
2020-03-17 20:40:39 +01:00
|
|
|
this.key_no = key_no;
|
2020-03-17 21:41:05 +01:00
|
|
|
|
|
|
|
//These two bytes are set to 0000 on keysounds which are
|
|
|
|
//used as background tracks. This keysound needs to be
|
|
|
|
//identified as it should be at the start of the container.
|
2020-03-17 20:40:39 +01:00
|
|
|
this.is_bg = data.toString("hex", offset, offset+2) == "0000";
|
|
|
|
offset += 2;
|
2020-03-17 21:41:05 +01:00
|
|
|
|
2020-03-17 20:40:39 +01:00
|
|
|
//These values were for attenuation and loop point in SDVX 2dxs.
|
|
|
|
//I have no clue how to make use of these.
|
|
|
|
this.unk1 = data.readUInt16LE(offset);
|
|
|
|
offset += 2;
|
|
|
|
this.unk2 = data.readUInt16LE(offset);
|
|
|
|
offset += 6;
|
2020-03-17 21:41:05 +01:00
|
|
|
|
|
|
|
|
2020-03-17 20:40:39 +01:00
|
|
|
this.data = data.slice(offset, offset+size);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class Twodx {
|
2020-03-17 21:41:05 +01:00
|
|
|
|
2020-03-17 20:40:39 +01:00
|
|
|
constructor(path) {
|
|
|
|
this.path = path;
|
2020-03-17 21:41:05 +01:00
|
|
|
this.data = fs.readFileSync(path);
|
2020-03-17 20:40:39 +01:00
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
this.offset = 0;
|
2020-03-17 20:40:39 +01:00
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
this.name = this.data.toString("ascii", 0, 16);
|
|
|
|
this.offset += 16;
|
|
|
|
this.header_len = this.data.readUInt32LE(this.offset);
|
|
|
|
this.offset += 4;
|
|
|
|
this.file_count = this.data.readUInt32LE(this.offset);
|
|
|
|
this.offset += 52;
|
2020-03-17 20:40:39 +01:00
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
const offsets = this.generateOffsets();
|
|
|
|
this.keysounds = this.generateSamples(offsets);
|
|
|
|
}
|
2020-03-17 20:40:39 +01:00
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
generateOffsets() {
|
|
|
|
return [...Array(this.file_count).keys()].map((_) => {
|
|
|
|
const ind = this.data.readUInt32LE(this.offset);
|
|
|
|
this.offset += 4;
|
2020-03-17 20:40:39 +01:00
|
|
|
return ind;
|
|
|
|
});
|
2020-03-17 21:41:05 +01:00
|
|
|
}
|
2020-03-17 20:40:39 +01:00
|
|
|
|
2020-03-17 21:41:05 +01:00
|
|
|
generateSamples(offsets) {
|
|
|
|
const keysounds = [];
|
|
|
|
for (let i = 0; i<offsets.length; i++) {
|
|
|
|
const keysound = new Sample(this.data, offsets[i]);
|
2020-03-17 20:40:39 +01:00
|
|
|
if (keysound.is_bg) {
|
2020-03-17 21:41:05 +01:00
|
|
|
//BG tracks are placed at the start of the list
|
|
|
|
//as this makes it easier to deal with keysound
|
|
|
|
//indices in chart files.
|
2020-03-17 20:40:39 +01:00
|
|
|
this.late_bg = i != 0;
|
2020-03-17 21:41:05 +01:00
|
|
|
keysounds.unshift(keysound);
|
2020-03-17 20:40:39 +01:00
|
|
|
} else {
|
2020-03-17 21:41:05 +01:00
|
|
|
keysounds.push(keysound);
|
2020-03-17 20:40:39 +01:00
|
|
|
}
|
|
|
|
}
|
2020-03-17 21:41:05 +01:00
|
|
|
return keysounds;
|
2020-03-17 20:40:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Twodx;
|