Scrambling

From dreamcast.wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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;
}