Skip to content

Instantly share code, notes, and snippets.

@giulioz
Last active November 27, 2025 13:41
Show Gist options
  • Select an option

  • Save giulioz/39e96282371ffb5059e112f6281efa60 to your computer and use it in GitHub Desktop.

Select an option

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;
}
@james120817-bit
Copy link

Hi Giulio. I have compiled this and run it on my Roland JV-1080 rom. It returns the following: -

ROM size: 2097152 [8 bit data]
ROM type: SR-JV80
ROM ID: Wffgfw
Date: Wffwfffffv
smplsCount: 30567
Sample 0: ptr a4e9eb len ff673277 ls 290ee3 loop ad

but no output files are generated. The rom consists of five .bin files. One prom file and 4 waverom files. Is there someting else I need to do or have I misunderstood how the program should work.

Thank you for your work and for any help you are able to provide.

Best Regards,
James Spadavecchia

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment