/* converts a raw disk image file into a dos-order disk image */
/* Copyright (c) 2001 by Christopher Bachmann (c-bachmann@nwu.edu)

   Feel free to redistribute it, port it, copy it around, but if you
   change it, improve it, base something else on it, etc.  Please
   let me know, so that I can keep this up to date, and incorporate
   the best of everything.

*/

#include <stdio.h>

#define MAX_TRACK_LEN 0x1b00

static unsigned char raw_input[0x2000];
static unsigned char raw_track[16][343];
static unsigned char decoded_track[16][256];
static long int descr[64];
static long int unkn[224];
static long int block0[2][50];
static long int start[2][50];		/* start positions for each track */
static long int length[2][50];		/* length of each track */
static long int block3[2][50];
static long int block4[2][50];
static long int block5[2][50];

static int side;
static int track;
static int sector;
static int input_position;		/* position in the raw input buffer */

unsigned char translation[] = {
  0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
  0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
  0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
  0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
  0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
  0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
  0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
  0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
};
unsigned char interleave[] = {
  0x00, 0x07, 0x0E, 0x06, 0x0D, 0x05, 0x0C, 0x04,
  0x0B, 0x03, 0x0A, 0x02, 0x09, 0x01, 0x08, 0x0F
};

unsigned char reverse[] = {0x00, 0x02, 0x01, 0x03};

/* open the file for reading as binary */
FILE *openfile(char *filename) {
  FILE *fileno;

  fileno = fopen(filename, "rb");
  return fileno;
}

void read_long_block (FILE *fileid, long int block_start, int block_length, long int arr[][50], int flag) {
  int i,j;

  fseek (fileid, block_start, 0);
  for (i=0; i < block_length; i++) {
    arr[0][i] = (long) 0;
    arr[1][i] = (long) 0;
    if (flag) {
      /* side 0 */
      arr[0][i] = ((((long) getc (fileid)) & 0xff) << 16) | arr[0][i];
      arr[0][i] = ((((long) getc (fileid)) & 0xff) << 8) | arr[0][i];
      /* side 1 */
      arr[1][i] = ((((long) getc (fileid)) & 0xff) << 16) | arr[1][i];
      arr[1][i] = ((((long) getc (fileid)) & 0xff) << 8) | arr[1][i];
    } else {
      /* side 0 */
      arr[0][i] = ((((long) getc (fileid)) & 0xff)) | arr[0][i];
      arr[0][i] = ((((long) getc (fileid)) & 0xff) << 8) | arr[0][i];
      /* side 1 */
      arr[1][i] = ((((long) getc (fileid)) & 0xff)) | arr[1][i];
      arr[1][i] = ((((long) getc (fileid)) & 0xff) << 8) | arr[1][i];
    }
  }
#if 0
  printf ("%8lx:\n",block_start);
  for (i=0,j=0; i < block_length; i++) {
    printf (" %4x %4x",arr[0][i],arr[1][i]);
    j++;
    if (j == 5) {
      j=0; printf ("\n");
    }
  }
#endif
}

/* read in information from the header */
void readheader (FILE *fileid) {
  int i;

  read_long_block (fileid, (long) 0x120, 50, block0, 0);
  read_long_block (fileid, (long) 0x2b0, 50, start, 1);
  read_long_block (fileid, (long) 0x440, 50, length, 0);
  read_long_block (fileid, (long) 0x5d0, 50, block3, 0);
  read_long_block (fileid, (long) 0x760, 50, block4, 0);
  read_long_block (fileid, (long) 0x8f0, 50, block5, 0);

  printf ("Track information:\n");
  for (i=0; i < 40; i++) {
    printf ("%x: %x %x\n",i,start[side][i],length[side][i]);
  }

}

/* print the short as 4 hex digits, with a line number */
void printbyte (short number) {
  int high,low;
  high = (number >> 8) & 0xff;
  low = number & 0xff;
/*
  if (high < 16)
    printf ("0%x",high);
  else
    printf ("%x",high);
*/
  if (low < 16)
    printf ("0%x ",low);
  else
    printf ("%x ",low);
}

/* print address information */
void print_address (int *address) {
  int i;
  int volume;
  int checksum;
  int check;

  check = 0;
  volume = (address[3] << 1 & 0xff | 0x01) & (address[4] & 0xff);
  check = volume ^ check;
  track  = (address[5] << 1 & 0xff | 0x01) & (address[6] & 0xff);
  check = track ^ check;
  sector = (address[7] << 1 & 0xff | 0x01) & (address[8] & 0xff);
  check = sector ^check;
  checksum = (address[9] << 1 &0xff | 0x01) & (address[10] & 0xff);

  if (checksum != check) {
    printf ("Error Volume %d Track %d Sector %d: Header Checksum Invalid = %d %d\n",
            volume, track, sector, checksum, check);
  }

  printf ("Address: V%x T%x S%x\n",volume,track,sector);
}

/* print output data information */
void print_output () {
  int i;

  for (i=0; i < 256; i++) {
    printf ("%x ",decoded_track[interleave[sector]][i]);
  }
  printf ("\n");
}

/* print sector data information */
void print_data (unsigned char *data) {
  int i,j;

  for (i=0; i < 343; i++) {
    printf ("%x ",data[i]);
  }
  printf ("\n");
}

/* finds the address header, and reads in the current track/sector */
read_address(FILE *fileid) {
  int address[14];	/* address information */
  int done = 0;		/* flag */
  int buffer = 0xffff;	/* bit buffer (like apple latch) */
  int input_byte;	/* input byte */
  int input_bit;	/* current bit */
  int bit_count;	/* number of bits shifted in so far */
  int index=0;		/* index into address array */

  for (index=0;index<14;index++) {
    address[index]=0;
  }
  index=0;
  while (!done) {
    /*    input_byte = getc(fileid); */
    input_byte = raw_input[input_position++];
    /*    if (input_byte == EOF) {	/* check for end of file ?? */
    if (input_position > MAX_TRACK_LEN) {
      done = 1;
    } else {
      for (input_bit = 0; input_bit < 8; input_bit++) {
	/* get current bit */
	buffer = ((buffer << 1) & 0xfffe) | ((input_byte >> 7) & 1);
	input_byte = input_byte << 1;

	/* update count */
	bit_count++;

	/* test for valid data */
	if ((bit_count >= 8) && ((buffer & 0x0080) != 0)) {
/*	  printbyte (buffer); */
	  /* find prolog of address block */
	  if ((index != 0) && (index < 3)){
	    address[index++] = (buffer & 0xff);
	    if (address[1] != 0xAA) {	/* make sure address prolog */
	      index = 0;
	    }
	    if (index == 3) {
	      if (address[2] != 0x96) {	/* make sure address prolog */
		index = 0;
	      }
	    }
	  } else if (index == 0) {	/* searching for 0xD5 */
	    if ((buffer & 0xff) == 0xD5) {
	      address[index++] = 0xD5;
	    }
	  } else {			/* reading address information */
	    address[index++] = (buffer & 0xff);
	    if (index == 14) {
	      done = 1;
	      input_bit = 8;
	    }
	  }
	  bit_count = 0;		/* reset bit counter */
	  buffer = 0xffff;        /*  and buffer */
	}	/* end valid data */
      }		/* end current byte */
    }		/* end EOF test */
  }		/* end while loop */

  print_address(address);	/* display current address */
}

/* finds the next data sector, and reads the encoded version in */
find_data(FILE *fileid) {
  int prolog[3];	/* prolog information */
  int done = 0;		/* flag */
  int buffer = 0xffff;	/* bit buffer (like apple latch) */
  int input_byte;	/* input byte */
  int input_bit;	/* current bit */
  int bit_count;	/* number of bits shifted in so far */
  int index=0;		/* index into prolog array */
  int dataindex=0;	/* index into sector array */

  for (dataindex=0;dataindex<343;dataindex++) {
    raw_track[sector][dataindex] = 0;
  }
  dataindex=0;
  while (!done) {
    /*    input_byte = getc(fileid); */
    /*    if (input_byte == EOF) {	/* check for end of file ?? */
    input_byte = raw_input[input_position++];
    if (input_position > MAX_TRACK_LEN) {
      done = 1;
    } else {
      for (input_bit = 0; input_bit < 8; input_bit++) {
	/* get current bit */
	buffer = ((buffer << 1) & 0xfffe) | ((input_byte >> 7) & 1);
	input_byte = input_byte << 1;

	/* update count */
	bit_count++;

	/* test for valid data */
	if ((bit_count >= 8) && ((buffer & 0x0080) != 0)) {
/*	  printbyte (buffer); */
	  /* find prolog of data block */
	  if ((index != 0) && (index < 3)){
	    prolog[index++] = (buffer & 0xff);
	    if (prolog[1] != 0xAA) {	/* make sure data prolog */
	      index = 0;
	    }
	    if (index == 3) {
	      if (prolog[2] != 0xAD) {	/* make sure data prolog */
		index = 0;
	      }
	    }
	  } else if (index == 0) {	/* searching for 0xD5 */
	    if ((buffer & 0xff) == 0xD5) {
	      prolog[index++] = 0xD5;
	    }
	  } else {			/* reading data information */
	    raw_track[sector][dataindex++] = (buffer & 0xff);
	    if (dataindex == 343) {
	      done = 1;
	      input_bit = 8;
	    }
	  }
	  bit_count = 0;		/* reset bit counter */
	  buffer = 0xffff;        /*  and buffer */
	}	/* end valid data */
      }		/* end current byte */
    }		/* end EOF test */
  }		/* end while loop */

}

/* decode the data and put it into the output disk */
void decode_data() {
  int i,j;
  int check;				/* checksum */
  unsigned char low[86];		/* temporary values */
  unsigned char high[256];
  unsigned char temp_output[256];	/* temporary output */

  check = 0;
  /* first translate raw bytes, and compute checksum */
  for (i=0;i<86;i++) {
    j = 0;
    while (translation[j] != raw_track[sector][i])
      j++;
    check = j^check;
    low[i] = check;
  }
  for (i=86;i<342;i++) {
    j = 0;
    while (translation[j] != raw_track[sector][i])
      j++;
    check = j^check;
    high[i-86] = check << 2;
  }
  j = 0;
  while (translation[j] != raw_track[sector][342])
    j++;
  check = check^j;

  /* checksum should equal zero */
  if (check != 0) {
    printf ("Error Track %d Sector %d: Sector Checksum invalid = %d\n",
	    track,sector,check);
  }

  /* decode bytes and store them into the buffer */
  for (i=0; i<256; i++) {
    temp_output[i] = 0;
    if (i<86)
      temp_output[i] = (high[i] | reverse[(low[i]&0x03)]);
    else if (i<172)
      temp_output[i] = (high[i] | reverse[((low[i-86]&0x0C)>>2)]);
    else
      temp_output[i] = (high[i] | reverse[((low[i-172]&0x30)>>4)]);
  }

  /* copy the bytes into the right sector on the disk */
  for (i=0; i<256; i++) {
    decoded_track[interleave[sector]][i] = temp_output[i];
  }

/*  print_output(); */
}

/* process a single track into encoded disk */
void read_track(FILE *fileid, int trk) {
  int i,j;
  unsigned char temp1, temp2;

  int buffer = 0xffff;  /* bit buffer (like apple latch) */
  int input_byte;       /* input byte */
  int input_bit;        /* current bit */
  int bit_count;        /* number of bits shifted in so far */

  fseek (fileid, start[side][trk], 0);	/* seek to start of track */
  /* load the track data */
  if (side) {
    /* fill array in reverse */
    for (i=0; i < MAX_TRACK_LEN; i++) {
      temp1 = getc(fileid) & 0xff;
      temp2 = 0;
      /* flip the bits in the byte */
      for (j=0; j < 8; j++) {
        temp2 = ((temp2 >> 1) & 0x7f);
	temp2 = temp2 | (temp1 & 0x80);
	temp1 = temp1 << 1;
      }
      raw_input [MAX_TRACK_LEN - i] = temp2;
    }
  } else {
    /* read straight */
    for (i=0; i < MAX_TRACK_LEN; i++) {
      raw_input[i] = getc (fileid) & 0xff;
    }
  }

  for (i=0; i < MAX_TRACK_LEN; i++) {
    input_byte = raw_input[i];
    for (input_bit = 0; input_bit < 8; input_bit++) {
      /* get current bit */
      buffer = ((buffer << 1) & 0xfffe) | ((input_byte >> 7) & 1);
      input_byte = input_byte << 1;

      /* update count */
      bit_count++;

      /* test for valid data */
      if ((bit_count >= 8) && ((buffer & 0x0080) != 0)) {
	/*
       	printbyte (buffer);
	j++; if (j > 25 ) { j=0; printf ("\n"); }
	*/

	bit_count = 0;                /* reset bit counter */
	buffer = 0xffff;        /*  and buffer */
      }         /* end current byte */
    }           /* end for test */
  }             /* end for loop */

  input_position = 0;		/* start reading at begining of track */
  fseek (fileid, start[side][trk], 0);	/* seek to start of track */
  for (i=0; i < 16; i++) {
    read_address(fileid);	/* find the address */
    find_data(fileid);		/* find next data */
    decode_data();		/* get the decoded data */
  }
}

/* process the file as if we are a real apple reading the disk */
void process(FILE *fileid, char *filename) {
  FILE *fileno;
  int trk;		/* track */
  int j,k;		/* sector/byte */

  printf ("Processing\n");
  fileno = fopen(filename, "wb");
  for (trk=0; trk < 40; trk++) {
    printf ("Track %d\n",trk);
/*    printf ("."); */
    read_track(fileid,trk);		/* read in track */
    for (j=0;j<16;j++) {
      for (k=0;k<256;k++) {
/*	fprintf (fileno,"%c",decoded_track[j][k]); */
	putc (decoded_track[j][k],fileno);
      }
    }
  }
  fclose (fileno);
  printf ("Done\n");
}

/* close the file after reading */
void closefile(FILE *fileid) {
  fclose (fileid);
}

int main(int argc, char *argv[]) {
  char *filename;
  FILE *fileid;

  if (argc < 4) {
    printf ("img2dsk <image file> <outputfile> <side>\n");
  } else {
    side = atoi(argv[3]);
    fileid = openfile(argv[1]);
    if (fileid == NULL) {
      printf ("Filename not found\n");
    } else {
      readheader (fileid);
      process (fileid, argv[2]);
      closefile(fileid);
    }
  }
  return 0;
}
