Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@tcdw
Last active April 2, 2019 18:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tcdw/9932bda4f8124ba2442f1d2d75aae636 to your computer and use it in GitHub Desktop.
Save tcdw/9932bda4f8124ba2442f1d2d75aae636 to your computer and use it in GitHub Desktop.
Dump song directly from Classic Road II
#!/usr/bin/env node
/*
Classic Road II music ripper
by tcdw
Usage:
1. Get the Classic Road II ROM
2. Get your template SPC file:
2.1. Dump a SPC via your SNES emulator from the game, and hex edit x1F4 to 0x00
2.2. Open the SPC file with SNES SPC700 PLAYER, and save the snapshot when
the music suddenly become slient. Now you get the template SPC file.
3. Open terminal, run the script like this:
node read_song.js "Classic Road II (J).smc" template.spc
4. You will get all SPC files in your current working directory
This script is released under MIT License
*/
'use strict';
const version = '1.0.1';
const fs = require('fs');
const path = require('path');
const argv = process.argv.slice(2);
if (argv.length < 2) {
console.error('usage: read_song.js cr2_rom_file template_spc_file');
process.exit(1);
}
const snes2pc = (bank, addr) => {
if (bank < 0x80 || bank > 0xFF || addr < 0x8000 || addr > 0xFFFF) {
return null;
}
return (bank - 0x80) * 0x8000 + addr - 0x8000;
}
const pad = (s, len) => {
const str = String(s).toUpperCase();
if (str.length >= len) {
return str;
}
return '0'.repeat(len - str.length) + str;
}
const writeStrings = (tar, str, pos, maxlen) => {
const buffer = Buffer.from(str, { encoding: 'utf8' });
if (buffer.length > maxlen) {
console.warn('Warning: String \x1b[93m"' + str + '"\x1b[0m has more than \x1b[93m' + maxlen + '\x1b[0m bytes. All of extra bytes will be cut.');
}
for (let i = 0; i < maxlen; i++) {
tar.writeUInt8(i >= buffer.length ? 0 : buffer.readUInt8(i), pos + i);
}
};
const songListPointer = 0x43C;
const rom = fs.readFileSync(path.resolve(process.cwd(), argv[0]));
const template = fs.readFileSync(path.resolve(process.cwd(), argv[1]));
let curPtr = songListPointer;
let i = 0;
while (true) {
// $01 $00 $38 $A7 $BF $87 $DE $A5
// ******* ----------- ===========
//
// * : ???
// - : Song Address, Little Endian, SNES Address ($BF:A738)
// = : Sample Bank Address, Little Endian, SNES Address ($A5:DE87)
const songPtr = rom.readUInt16LE(curPtr + 2);
const songBank = rom.readUInt8(curPtr + 4);
const songPCAddr = snes2pc(songBank, songPtr);
const samplePtr = rom.readUInt16LE(curPtr + 5);
const sampleBank = rom.readUInt8(curPtr + 7);
const samplePCAddr = snes2pc(sampleBank, samplePtr);
if (songPCAddr === null) {
break;
}
const newSPC = Buffer.alloc(template.length);
template.copy(newSPC);
let length = 0;
let target = 0;
let current = songPCAddr;
let sampleRead = false;
while (true) {
length = rom.readUInt16LE(current);
target = rom.readUInt16LE(current + 2);
if (length <= 0) {
if (sampleRead) {
break;
} else {
current = samplePCAddr;
sampleRead = true;
continue;
}
}
let written = 0;
// console.log(`Going to write \x1b[93m${length.toString(16)}\x1b[0m bytes to position \x1b[93m${target.toString(16)}\x1b[0m`);
current += 4;
while (written < length) {
newSPC.writeUInt8(rom.readUInt8(current + written), target + written + 0x100);
written++;
}
current += written;
}
const songInfo = `Seq: $${pad(songBank.toString(16), 2)}:${pad(songPtr.toString(16), 4)}, Sample: $${pad(sampleBank.toString(16), 2)}:${pad(samplePtr.toString(16), 4)}`;
const now = new Date();
writeStrings(newSPC, pad(now.getMonth() + 1, 2) + "/" + pad(now.getDate(), 2) + "/" + now.getFullYear(), 0x9E, 11);
writeStrings(newSPC, `Track $${pad(songBank.toString(16), 2)}:${pad(songPtr.toString(16), 4)}`, 0x2E, 32);
writeStrings(newSPC, 'Classic Road II', 0x4E, 32);
writeStrings(newSPC, 'tcdw', 0x6E, 16);
writeStrings(newSPC, songInfo, 0x7E, 32);
writeStrings(newSPC, '', 0xB1, 32);
newSPC.writeUInt8(1, 0x1F4);
console.log(`Song ${i} - ${songInfo}`);
fs.writeFileSync(path.resolve(process.cwd(), `cr2_${i}.spc`), newSPC);
curPtr += 8;
i++;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment