/*
 * BB.c - bootblock management: reads, installs and fix checksums.
 *
 * Bruno Costa - 5 Dec 89 - 22 Jul 91
 *
 */

#include <exec/types.h>
#include <exec/io.h>
#include <devices/bootblock.h>
#include <devices/trackdisk.h>
#include <exec/memory.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <ctype.h>
#include <string.h>

typedef unsigned char byte;

#if 0
#include "bootblock.h"
#else
byte bootblock[] = {		/* standard AmigaDOS bootblock */
 'D', 'O', 'S', 0,
 0xc0, 0x20, 0x0f, 0x19, 0x00, 0x00, 0x03, 0x70, 0x43, 0xfa, 0x00, 0x18,
 0x4e, 0xae, 0xff, 0xa0, 0x4a, 0x80, 0x67, 0x0a, 0x20, 0x40, 0x20, 0x68,
 0x00, 0x16, 0x70, 0x00, 0x4e, 0x75, 0x70, 0xff, 0x60, 0xfa, 0x64, 0x6f,
 0x73, 0x2e, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x00, 0x00, 0x00,
 0x00, 0x00
};
#endif

byte doPAL[] = {
 0x33, 0xfc, 0x00, 0x20, 0x00, 0xdf, 0xf1, 0xdc   /* move.W  #$20,$dff1dc */
};


#define BBSIZE	1024
#define MAXNAME 100

#define FIX	1
#define SHOW	2
#define INSTALL	3
#define PAL	4
#define READ	5


void print (char *msg);
void newline (void);
void xprint (unsigned long n);
void copyright (void);
void help (void);
void notify (char *msg);
void bbdump (byte *buffer);
unsigned long checksum (byte *buffer);
int bbinstall (struct IOStdReq *disk, byte *buffer);
int bbread (struct IOStdReq *disk, byte *buffer);
int fileread (char *name, byte *buf);
int filewrite (char *name, byte *buf);
void waitenter (void);


#define abort(x) {errnum = x; goto cleanup;}


void main (int argc, char *argv[])
{
 byte *buf = NULL;			/* disk buffer for bootblocks */
 struct Port *diskport = NULL;		/* trackdisk's message port   */
 struct IOStdReq *diskreq = NULL;	/* trackdisk's IO request     */
 int errnum = 0;
 int unit = -1;
 int batch = TRUE, action = SHOW;
 char bbname[MAXNAME], data[MAXNAME];
 long error;
 int c;

 /*
  *  Command line processing
  */

 if (argc == 1)
 {
   copyright ();
   help ();
   abort (5);
 }

 while (c = getoption (argc, argv, "ir", "acshpd", data))
   if (c < 0)
   {
     notify ("unknown option");
     help ();
     abort (10);
   }
   else if (c == 1)
   {
     if (isdigit (data[0]))
       unit = data[0] - '0';
     else if (tolower (data[0]) == 'd'  &&  tolower (data[1]) == 'f'  &&
              isdigit (data[2]))
       unit = data[2] - '0';
     else
     {
       help ();
       abort (10);
     }
   }
   else
     switch (c)
     {
       case 'i':
         action = INSTALL;
         strcpy (bbname, data);
         break;

       case 'd':
         action = INSTALL;
         bbname[0] = '\0';
         break;

       case 'r':
         action = READ;
         strcpy (bbname, data);
         break;

       case 'a':
         batch = FALSE;
         break;

       case 'c':
         action = FIX;
         break;

       case 's':
         action = SHOW;
         break;

       case 'p':
         action = PAL;
         break;

       case 'h':
         help ();
         abort (10);
         break;
     }

 if (unit < 0  ||  unit > 3)
 {
   notify ("Bad drive number");
   abort (10);
 }


 /*
  *  Trackdisk I/O setup
  */

 buf = AllocMem (BBSIZE, MEMF_CHIP | MEMF_CLEAR);
 diskport = CreatePort (0, 0);
 diskreq = CreateStdIO (diskport);
 if (!buf  ||  !diskport  ||  !diskreq)
 {
   notify ("memory");
   abort (30);
 }

 error = OpenDevice ("trackdisk.device", (long)unit, diskreq, 0);
 if (error > 0)
 {
   notify ("trackdisk");
   abort (20);
 }

 if (!batch)    /*           1         2          */
 {              /* 012345678901234567890123456789 */
   char tmp[60] = "Please insert disk in DF9: and press ENTER ...";

   tmp[24] = unit + '0';
   print (tmp);

   waitenter ();
 }


 /*
  *  Action processing
  */
 switch (action)
 {
   case FIX:
     if (!bbread (diskreq, buf))
       abort (20);

     print ("Old checksum = 0x");
     xprint (((unsigned long *)buf)[1]);
     newline ();

     checksum (buf);

     print ("New checksum = 0x");
     xprint (((unsigned long *)buf)[1]);
     newline ();

     if (!bbinstall (diskreq, buf))
       abort (20);
     break;

   case PAL:
     print ("warning: this may make your bootblock unusable!\n");
     print ("         remove disk or press ENTER to continue:");
     waitenter ();

     if (!bbread (diskreq, buf))
       abort (20);

     memmove (buf + 12 + sizeof (doPAL), buf + 12, BBSIZE - 12 - sizeof (doPAL));
     memcpy (buf + 12, doPAL, sizeof (doPAL));

     checksum (buf);
     if (!bbinstall (diskreq, buf))
       abort (20);
     break;

   case INSTALL:
     if (bbname[0] == '\0')
       CopyMem (bootblock, buf, sizeof(bootblock));	/* my bootblock */
     else
       if (!fileread (bbname, buf))
         abort (20);

     checksum (buf);			/* be sure that checksum is correct */
     if (!bbinstall (diskreq, buf))
       abort (20);
     break;

   case READ:
     if (bbname[0] == '\0')
       notify ("no filename given");
     else
       if (!bbread (diskreq, buf)  ||  !filewrite (bbname, buf))
         abort (20);
     break;

   case SHOW:
     if (!bbread (diskreq, buf))
       abort (20);
     bbdump (buf);
     break;
 }


cleanup:

 if (diskreq)
 {
   if (diskreq->io_Device)
     CloseDevice (diskreq);
   DeleteStdIO (diskreq);
 }
 if (diskport)
   DeletePort (diskport);
 if (buf)
   FreeMem (buf, BBSIZE);
}


int fileread (char *name, byte *buf)
{
 BPTR f;
 long error;

 f = Open (name, MODE_OLDFILE);
 if (f)
 {
   error = Read (f, buf, BBSIZE);
   Close (f);
 }

 if (!f  ||  error == 0  ||  error == -1)
 {
   notify ("could not read custom bootblock file");
   return FALSE;
 }
 else
   return TRUE;
}


int filewrite (char *name, byte *buf)
{
 BPTR f;
 long error;

 f = Open (name, MODE_NEWFILE);
 if (f)
 {
   error = Write (f, buf, BBSIZE);
   Close (f);
 }

 if (!f  ||  error != BBSIZE)
 {
   notify ("could not write custom bootblock file");
   return FALSE;
 }
 else
   return TRUE;
}


/*
 * waits for the ENTER key to be pressed
 */
void waitenter (void)
{
 char dummy[2];

 (void)Read (Input(), dummy, 2);
}


/*
 * prints any message
 */
void print (char *msg)
{
 Write (Output(), msg, strlen (msg));
}


/*
 * prints a newline
 */
void newline (void)
{
 Write (Output(), "\n", 1);
}


/*
 * prints a copyright message
 */
void copyright (void)
{
 print ("\x1b[33mBB v2.0\x1b[31m - \xa9 1989 by Bruno Costa\n");
}


/*
 * prints a help message
 */
void help (void)
{
 print ("usage: BB [-acshpd] [-i<filename>] [-r<filename>] <drivename>\n");
 print ("\tr = read bootblock\n");
 print ("\ti = install new bootblock\n");
 print ("\td = install a DOS bootblock\n");
 print ("\tc = fix bootblock checksum\n");
 print ("\ts = show bootblock (default)\n");
 print ("\tp = adds PAL booter to bootblock\n");
 print ("\ta = asks for disk insertion before actions\n");
 print ("\th = prints this help message\n");
}


/*
 * notifies an error
 */
void notify (char *msg)
{
 print ("BB: ");
 print (msg);
 newline ();
}


/*
 * prints an hexadecimal number
 */
void xprint (unsigned long n)
{
 int i;
 char tmp[10];
 static char xdigit[17] = "0123456789ABCDEF";

 for (i = 7; i >= 0; i--)
 {
   tmp[i] = xdigit[n & 0x0000000f];
   n = n >> 4;
 }
 tmp[8] = '\0';
 print (tmp);
}


/*
 * dumps the given bootblock in a readable form
 */
void bbdump (byte *buffer)
{
 register int i, j;
 char c, line[80];
 unsigned long cksum;

 cksum = ((unsigned long *)buffer)[1];

 print ("  Current checksum = 0x");
 xprint (cksum);
 print ("      Correct checksum = 0x");
 xprint (checksum (buffer));

 ((unsigned long *)buffer)[1] = cksum;		/* restore old checksum */

 newline ();
 newline ();

 for (i = 0; i < 16; i++)
 {
   for (j = 0; j < 64; j++)
   {
     c = buffer[i * 64 + j];
     if (isprint (c))
       line[j] = c;
     else
       line[j] = '.';
   }
   line[j] = '\0';
   print ("  ");
   print (line);
   newline ();
 }
}


/*
 * updates bootblock checksum in given buffer
 */
unsigned long checksum (byte *buffer)
{
 register long sum, lastsum;
 register int a;
 unsigned long *ptr;

 ptr = (long *)buffer;

 if (ptr[0] != ID_DOS_DISK)	/* not a DOS0 disk ? */
   return 0;

 ptr[1] = 0;		/* clear buffer checksum */

 sum = 0;
 for (a = 0; a < 256; a++)
 {
   lastsum = sum;
   sum = sum + ptr[a];
   if (lastsum > sum)
     sum++;
 }

 if (lastsum > 0)
   ptr[1] = - sum - 1;
 else
   ptr[1] = - sum;	/* sum + checksum must be 0 */

 return ptr[1];
}


/*
 * install bootblock in buffer into given disk
 */
int bbinstall (struct IOStdReq *disk, byte *buffer)
{
 int error;

 disk->io_Command = TD_PROTSTATUS;		/* check write protection */
 DoIO (disk);

 if (disk->io_Actual != 0)
 {
   notify ("disk is write-protected");
   return (FALSE);
 };

 error = 0;
 disk->io_Length  = BBSIZE;
 disk->io_Data    = (APTR)buffer;
 disk->io_Command = CMD_WRITE;
 disk->io_Offset  = 0;
 DoIO (disk);

 error = disk->io_Error;

 if (error < 19)
 {
   disk->io_Command = CMD_UPDATE;	/* flush buffer to disk */
   DoIO (disk);
   error = disk->io_Error;
 };

 if (error < 19)
 {
   disk->io_Length  = 0;
   disk->io_Command = ETD_MOTOR;	/* turn motor off */
   DoIO (disk);
   error = disk->io_Error;
 };

 if (error < 19)
   return (TRUE);
 else
 {
   notify ("could not install bootblock");
   return (FALSE);
 }
}


/*
 *  reads a bootblock (from given disk IO request) into buffer
 */
int bbread (struct IOStdReq *disk, byte *buffer)
{
 disk->io_Command = CMD_READ;
 disk->io_Data    = (APTR)buffer;
 disk->io_Length  = BBSIZE;
 disk->io_Offset  = 0;
 DoIO (disk);

 if (disk->io_Error > 0)
 {
   notify ("could not read bootblock");
   return (FALSE);
 }
 else
 {
   disk->io_Length  = 0;
   disk->io_Command = TD_MOTOR;
   DoIO (disk);
   return (TRUE);
 }
}
