Scrambling
As a form of piracy protection, when booting from MIL-CD formatted discs, the Dreamcast loads the main program binary specified in the IP.BIN using a unique "de-scramble" decryption algorithm. Therefore, in order to load a program into memory from disc correctly, the binary must be encrypted using the correct "scramble" algorithm. This process does not apply to programs stored on GD-ROM media.
When pirate groups in the warez scene started releasing "self-boot" versions of Dreamcast games, they chose the alternative route of implementing the algorithm inside the IP.BIN, so that the program is stored normally on disc, becomes "de-scrambled" into memory by the system at load time, and then is "re-scrambled" by the injected IP.BIN code just before execution.
A scramble/descramble algorithm was published by Dreamcast hobbyist pioneer Marcus Comstedt into the public domain. His algorithm is published below.
#include <stdio.h>
#include <stdlib.h>
#define MAXCHUNK (2048*1024)
static unsigned int seed;
void my_srand(unsigned int n)
{
seed = n & 0xffff;
}
unsigned int my_rand()
{
seed = (seed * 2109 + 9273) & 0x7fff;
return (seed + 0xc000) & 0xffff;
}
void load(FILE *fh, unsigned char *ptr, unsigned long sz)
{
if(fread(ptr, 1, sz, fh) != sz)
{
fprintf(stderr, "Read error!\n");
exit(1);
}
}
void load_chunk(FILE *fh, unsigned char *ptr, unsigned long sz)
{
static int idx[MAXCHUNK/32];
int i;
/* Convert chunk size to number of slices */
sz /= 32;
/* Initialize index table with unity,
so that each slice gets loaded exactly once */
for(i = 0; i < sz; i++)
idx[i] = i;
for(i = sz-1; i >= 0; --i)
{
/* Select a replacement index */
int x = (my_rand() * i) >> 16;
/* Swap */
int tmp = idx[i];
idx[i] = idx[x];
idx[x] = tmp;
/* Load resulting slice */
load(fh, ptr+32*idx[i], 32);
}
}
void load_file(FILE *fh, unsigned char *ptr, unsigned long filesz)
{
unsigned long chunksz;
my_srand(filesz);
/* Descramble 2 meg blocks for as long as possible, then
gradually reduce the window down to 32 bytes (1 slice) */
for(chunksz = MAXCHUNK; chunksz >= 32; chunksz >>= 1)
while(filesz >= chunksz)
{
load_chunk(fh, ptr, chunksz);
filesz -= chunksz;
ptr += chunksz;
}
/* Load final incomplete slice */
if(filesz)
load(fh, ptr, filesz);
}
void read_file(char *filename, unsigned char **ptr, unsigned long *sz)
{
FILE *fh = fopen(filename, "rb");
if(fh == NULL)
{
fprintf(stderr, "Can't open \"%s\".\n", filename);
exit(1);
}
if(fseek(fh, 0, SEEK_END)<0)
{
fprintf(stderr, "Seek error.\n");
exit(1);
}
*sz = ftell(fh);
*ptr = malloc(*sz);
if( *ptr == NULL )
{
fprintf(stderr, "Out of memory.\n");
exit(1);
}
if(fseek(fh, 0, SEEK_SET)<0)
{
fprintf(stderr, "Seek error.\n");
exit(1);
}
load_file(fh, *ptr, *sz);
fclose(fh);
}
void save(FILE *fh, unsigned char *ptr, unsigned long sz)
{
if(fwrite(ptr, 1, sz, fh) != sz)
{
fprintf(stderr, "Write error!\n");
exit(1);
}
}
void save_chunk(FILE *fh, unsigned char *ptr, unsigned long sz)
{
static int idx[MAXCHUNK/32];
int i;
/* Convert chunk size to number of slices */
sz /= 32;
/* Initialize index table with unity,
so that each slice gets saved exactly once */
for(i = 0; i < sz; i++)
idx[i] = i;
for(i = sz-1; i >= 0; --i)
{
/* Select a replacement index */
int x = (my_rand() * i) >> 16;
/* Swap */
int tmp = idx[i];
idx[i] = idx[x];
idx[x] = tmp;
/* Save resulting slice */
save(fh, ptr+32*idx[i], 32);
}
}
void save_file(FILE *fh, unsigned char *ptr, unsigned long filesz)
{
unsigned long chunksz;
my_srand(filesz);
/* Descramble 2 meg blocks for as long as possible, then
gradually reduce the window down to 32 bytes (1 slice) */
for(chunksz = MAXCHUNK; chunksz >= 32; chunksz >>= 1)
while(filesz >= chunksz)
{
save_chunk(fh, ptr, chunksz);
filesz -= chunksz;
ptr += chunksz;
}
/* Save final incomplete slice */
if(filesz)
save(fh, ptr, filesz);
}
void write_file(char *filename, unsigned char *ptr, unsigned long sz)
{
FILE *fh = fopen(filename, "wb");
if(fh == NULL)
{
fprintf(stderr, "Can't open \"%s\".\n", filename);
exit(1);
}
save_file(fh, ptr, sz);
fclose(fh);
}
void descramble(char *src, char *dst)
{
unsigned char *ptr = NULL;
unsigned long sz = 0;
FILE *fh;
read_file(src, &ptr, &sz);
fh = fopen(dst, "wb");
if(fh == NULL)
{
fprintf(stderr, "Can't open \"%s\".\n", dst);
exit(1);
}
if( fwrite(ptr, 1, sz, fh) != sz )
{
fprintf(stderr, "Write error.\n");
exit(1);
}
fclose(fh);
free(ptr);
}
void scramble(char *src, char *dst)
{
unsigned char *ptr = NULL;
unsigned long sz = 0;
FILE *fh;
fh = fopen(src, "rb");
if(fh == NULL)
{
fprintf(stderr, "Can't open \"%s\".\n", src);
exit(1);
}
if(fseek(fh, 0, SEEK_END)<0)
{
fprintf(stderr, "Seek error.\n");
exit(1);
}
sz = ftell(fh);
ptr = malloc(sz);
if( ptr == NULL )
{
fprintf(stderr, "Out of memory.\n");
exit(1);
}
if(fseek(fh, 0, SEEK_SET)<0)
{
fprintf(stderr, "Seek error.\n");
exit(1);
}
if( fread(ptr, 1, sz, fh) != sz )
{
fprintf(stderr, "Read error.\n");
exit(1);
}
fclose(fh);
write_file(dst, ptr, sz);
free(ptr);
}
int main(int argc, char *argv[])
{
int opt = 0;
if(argc > 1 && !strcmp(argv[1], "-d"))
opt ++;
if(argc != 3+opt)
{
fprintf(stderr, "Usage: %s [-d] from to\n", argv[0]);
exit(1);
}
if(opt)
descramble(argv[2], argv[3]);
else
scramble(argv[1], argv[2]);
return 0;
}