Skip to content

Instantly share code, notes, and snippets.

@giulioz
Last active December 18, 2024 04:27
Show Gist options
  • Save giulioz/39e96282371ffb5059e112f6281efa60 to your computer and use it in GitHub Desktop.
Save giulioz/39e96282371ffb5059e112f6281efa60 to your computer and use it in GitHub Desktop.
Descrambler/decoder for FCE-DPCM Roland ROM encoding (JD-800/JV-1080/SC55), based on https://github.com/hackyourlife/srscramble
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
u8 descramble_data8(u8 word) {
return (word & 0x0002) << 6 | (word & 0x0008) << 3 | (word & 0x0040) >> 1 |
(word & 0x0080) >> 3 | (word & 0x0020) >> 2 | (word & 0x0010) >> 2 |
(word & 0x0001) << 1 | (word & 0x0004) >> 2;
}
u16 descramble_data16(u16 word) {
return (word & 0x0002) << 6 | (word & 0x0008) << 3 | (word & 0x0040) >> 1 |
(word & 0x0080) >> 3 | (word & 0x0020) >> 2 | (word & 0x0010) >> 2 |
(word & 0x0001) << 1 | (word & 0x0004) >> 2 | (word & 0x0200) << 6 |
(word & 0x0800) << 3 | (word & 0x4000) >> 1 | (word & 0x8000) >> 3 |
(word & 0x2000) >> 2 | (word & 0x1000) >> 2 | (word & 0x0100) << 1 |
(word & 0x0400) >> 2;
}
u32 descramble_addr(u32 addr, int width) {
if (width == 8) {
return (addr & 0x00000001) << 1 | (addr & 0x00000002) << 3 |
(addr & 0x00000004) >> 2 | (addr & 0x00000008) >> 1 |
(addr & 0x00000010) >> 1 | (addr & 0x00000020) << 10 |
(addr & 0x00000040) << 4 | (addr & 0x00000080) << 10 |
(addr & 0x00000100) << 6 | (addr & 0x00000200) >> 4 |
(addr & 0x00000400) >> 3 | (addr & 0x00000800) << 1 |
(addr & 0x00001000) << 4 | (addr & 0x00002000) >> 7 |
(addr & 0x00004000) << 4 | (addr & 0x00008000) >> 4 |
(addr & 0x00010000) >> 3 | (addr & 0x00020000) >> 8 |
(addr & 0x00040000) >> 10 | (addr & 0xFFF80000);
} else {
return (addr & 0x00000002) << 3 | (addr & 0x00000010) >> 3 |
(addr & 0x00000020) << 3 | (addr & 0x00000040) << 6 |
(addr & 0x00000080) >> 1 | (addr & 0x00000100) << 5 |
(addr & 0x00000200) << 2 | (addr & 0x00000400) >> 1 |
(addr & 0x00000800) << 5 | (addr & 0x00001000) >> 5 |
(addr & 0x00002000) >> 8 | (addr & 0x00008000) << 2 |
(addr & 0x00010000) >> 6 | (addr & 0x00020000) >> 2 |
(addr & 0xFFFC400D);
}
}
void write_little_endian(unsigned int word, int num_bytes, FILE *wav_file) {
unsigned buf;
while (num_bytes > 0) {
buf = word & 0xff;
fwrite(&buf, 1, 1, wav_file);
num_bytes--;
word >>= 8;
}
}
void write_wav(const char *filename, unsigned long num_samples, int32_t *data,
int s_rate, uint32_t note, bool hasLoop, uint32_t loopType,
uint32_t loopStart, uint32_t loopEnd) {
FILE *wav_file;
unsigned int sample_rate;
unsigned int num_channels;
unsigned int bytes_per_sample;
unsigned int byte_rate;
unsigned long i; /* counter for samples */
num_channels = 1; /* monoaural */
bytes_per_sample = 4;
if (s_rate <= 0)
sample_rate = 44100;
else
sample_rate = (unsigned int)s_rate;
byte_rate = sample_rate * num_channels * bytes_per_sample;
wav_file = fopen(filename, "w");
// assert(wav_file); /* make sure it opened */
/* write RIFF header */
fwrite("RIFF", 1, 4, wav_file);
write_little_endian(36 + bytes_per_sample * num_samples * num_channels +
(hasLoop ? 68 : 44),
4, wav_file);
fwrite("WAVE", 1, 4, wav_file);
/* write fmt subchunk */
fwrite("fmt ", 1, 4, wav_file);
write_little_endian(16, 4, wav_file); /* SubChunk1Size is 16 */
write_little_endian(1, 2, wav_file); /* PCM is format 1 */
write_little_endian(num_channels, 2, wav_file);
write_little_endian(sample_rate, 4, wav_file);
write_little_endian(byte_rate, 4, wav_file);
write_little_endian(num_channels * bytes_per_sample, 2,
wav_file); /* block align */
write_little_endian(8 * bytes_per_sample, 2, wav_file); /* bits/sample */
/* write smpl subchunk */
fwrite("smpl", 1, 4, wav_file);
write_little_endian(hasLoop ? 60 : 36, 4, wav_file); /* SubChunk2Size is 36 */
write_little_endian(0, 4, wav_file); // dwManufacturer
write_little_endian(0, 4, wav_file); // dwProduct
write_little_endian(0, 4, wav_file); // dwSamplePeriod
write_little_endian(note, 4, wav_file); // dwMIDIUnityNote
write_little_endian(0, 4, wav_file); // dwMIDIPitchFraction
write_little_endian(0, 4, wav_file); // dwSMPTEFormat
write_little_endian(0, 4, wav_file); // dwSMPTEOffset
write_little_endian(hasLoop ? 1 : 0, 4, wav_file); // cSampleLoops
write_little_endian(0, 4, wav_file); // cbSamplerData
if (hasLoop) {
write_little_endian(0, 4, wav_file); // dwIdentifier
write_little_endian(loopType, 4, wav_file); // dwType
write_little_endian(loopStart, 4, wav_file); // dwStart
write_little_endian(loopEnd, 4, wav_file); // dwEnd
write_little_endian(0, 4, wav_file); // dwFraction
write_little_endian(0, 4, wav_file); // dwPlayCount
}
/* write data subchunk */
fwrite("data", 1, 4, wav_file);
write_little_endian(bytes_per_sample * num_samples * num_channels, 4,
wav_file);
for (i = 0; i < num_samples; i++) {
write_little_endian((unsigned int)(data[i]), bytes_per_sample, wav_file);
}
fclose(wav_file);
}
void saveSample(size_t nSamples, uint8_t *descrambledRom, size_t startPtr,
const char *outputName, int rootKey = 0, size_t loopStart = 0) {
size_t outFileSize = nSamples * 4;
int32_t *audioOutBuf = (int32_t *)malloc(outFileSize);
memset(audioOutBuf, 0, outFileSize);
int32_t currentValue = 0;
for (size_t j = 0; j < nSamples; j++) {
size_t address = startPtr + j;
int8_t data_byte = descrambledRom[address];
uint8_t shift_byte =
descrambledRom[((address & 0xFFFFF) >> 5) | (address & 0xF00000)];
uint8_t shift_nibble =
(address & 0x10) ? (shift_byte >> 4) : (shift_byte & 0x0F);
int32_t final = ((data_byte << shift_nibble) << 14);
currentValue += final;
audioOutBuf[j] = currentValue;
}
write_wav(outputName, nSamples, audioOutBuf, 32000, rootKey, true, 0,
loopStart - startPtr, nSamples);
free(audioOutBuf);
}
int main(int argc, char **argv) {
const char *filename_in = argv[1];
FILE *f = fopen(filename_in, "rb");
if (!f) {
printf("Error: cannot open %s: %s\n", filename_in, strerror(errno));
return 1;
}
fseek(f, 0, SEEK_END);
size_t fsize = ftell(f);
fseek(f, 0, SEEK_SET);
u8 *buf = (u8 *)malloc(fsize);
u8 *outbuf = (u8 *)malloc(fsize);
fread(buf, fsize, 1, f);
fclose(f);
int width;
int isJv80 = 0;
int isGSS = 0;
const char *type;
// figure out ROM type
if (!strncmp((char *)buf, "ROLAND GSS", 10)) {
width = 8;
type = "GSS";
isGSS = true;
} else if (strncmp((char *)buf, "Roland", 6)) {
if (strncmp((char *)buf, "JP-800", 6)) {
printf("Invalid ROM: %c%c%c%c%c%c\n", buf[0], buf[1], buf[2], buf[3],
buf[4], buf[5]);
width = 8;
} else {
width = 8;
type = "JP-800";
}
} else if (!strncmp((char *)&buf[0xC], "O\xB0S", 3)) {
width = 8;
isJv80 = 1;
type = "SR-JV80";
} else if (!strncmp((char *)&buf[0xC], "O\xB0X", 3)) {
width = 16;
type = "SRX";
} else {
printf("Unknown ROM type: %c%c%c\n", buf[0xC], buf[0xD], buf[0xE]);
return 1;
}
printf("ROM size: %lu [%d bit data]\n", fsize, width);
printf("ROM type: %s\n", type);
//////////////////////////////////////////////
// descramble the whole ROM
if (width == 16) {
// performance improvement: descramble 16bit words
// this saves half of the address scrambling operations
u16 *buf16 = (u16 *)buf;
u16 *outbuf16 = (u16 *)outbuf;
for (size_t i = 0; i < fsize; i += 2) {
u32 addr = descramble_addr(i, width);
u16 tmp = descramble_data16(buf16[i >> 1]);
outbuf16[addr >> 1] = tmp;
}
} else {
// no optimization for 8bit ROMs, because A[0] is scrambled too
for (size_t i = 0; i < fsize; i++) {
u32 addr = descramble_addr(i, width);
u16 tmp = descramble_data8(buf[i]);
// u32 addr = unscramble_address_scc(i);
// u16 tmp = unscramble_byte_scc(buf[i]);
outbuf[addr] = tmp;
}
}
//////////////////////////////////////////////
// print ROM info
if (width == 8) {
// SR-JV80 / JP-800
printf("ROM ID: ");
for (int i = 0x20; i < 0x26; i++) {
printf("%c", outbuf[i]);
}
printf("\n");
printf("Date: ");
for (int i = 0x30; i < 0x3A; i++) {
printf("%c", outbuf[i]);
}
printf("\n");
} else if (width == 16) {
// SRX
printf("ROM ID: ");
for (int i = 0x20; i < 0x30; i++) {
printf("%c", outbuf[i]);
}
printf("\n");
printf("Date: ");
for (int i = 0x30; i < 0x3A; i++) {
printf("%c", outbuf[i]);
}
printf("\n");
}
//////////////////////////////////////////////
if (isGSS) {
saveSample(fsize - 0x8000, outbuf, 0x8000, "gss.wav");
} else {
uint8_t *smplsTable =
outbuf + __builtin_bswap32(*((uint32_t *)&outbuf[0x80]));
size_t smplsCount = outbuf[0x60] << 8 | outbuf[0x61];
printf("smplsCount: %u\n", smplsCount);
for (size_t i = 0; i < smplsCount; i++) {
int smpLen = isJv80 ? 0x12 : 0x10;
size_t ptr = smplsTable[i * smpLen + 1] << 16 |
smplsTable[i * smpLen + 2] << 8 |
smplsTable[i * smpLen + 3] << 0;
size_t len =
smplsTable[i * smpLen + 4] << 24 | smplsTable[i * smpLen + 5] << 16 |
smplsTable[i * smpLen + 6] << 8 | smplsTable[i * smpLen + 7] << 0;
size_t loopStart = 0;
uint8_t loopType = 0;
uint8_t rootKey = 0;
if (isJv80) {
size_t end =
(smplsTable[i * smpLen + 7] << 16 |
smplsTable[i * smpLen + 8] << 8 | smplsTable[i * smpLen + 9] << 0);
len = end - ptr;
loopStart =
(smplsTable[i * smpLen + 4] << 16 |
smplsTable[i * smpLen + 5] << 8 | smplsTable[i * smpLen + 6] << 0);
loopType = smplsTable[i * smpLen + 0xC];
rootKey = smplsTable[i * smpLen + 0xD];
}
printf("Sample %i: ptr %x len %x ls %x loop %x\n", i, ptr, len,
loopStart, loopType);
std::string filename_out = "samp" + std::to_string(i) + ".wav";
saveSample(len, outbuf, ptr, filename_out.c_str(), rootKey, loopStart);
}
}
// FILE *out = fopen(filename_out.c_str(), "wb");
// if (!out) {
// printf("Error: cannot open %s: %s\n", filename_out, strerror(errno));
// return 1;
// }
// fwrite(outbuf, fsize, 1, out);
// fclose(out);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment