Last active
December 18, 2024 04:27
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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