/*
* SYSCOPY - a portable replacement for the CP/M SYSGEN utility
* This program uses the disk parameter tables in CP/M 2.x if
* possible, but can also be configured to prompt for CP/M 1.x
* The input and output files are biased to 0900h so that they
* can be loaded and debugged with DDT/SID ala the documentation
*/
#include "bdscio.h"
#define INPUT   0
#define OUTPUT  1
#define UPDATE  2
#define SELDSK  9
#define SETTRK  10
#define SETSEC  11
#define SETDMA  12
#define READS   13
#define WRITES  14
#define VERSION 12
#define CURRENT 25
#define READR   33
#define WRITER  34
#define COMFSZ  35
#define dpb     struct DPB
struct  DPB     {
   int         dpb_spt;
   char        dpb_bsh;
   char        dpb_blm;
   char        dpb_exm;
   unsigned    dpb_dsm;
   unsigned    dpb_drm;
   char        dpb_al0;
   char        dpb_al1;
   int         dpb_cks;
   int         dpb_off;
   };
#define dph     struct DPH
struct  DPH     {
   unsigned    dph_xlt;
   char        dph_res11[6];
   char        *dph_dirbuf;
   dpb         *dph_dpb;
   char        *dph_csv;
   char        *dph_alv;
   };
#define DRIVES  "ABCDEFGHIJKLMNOP"
#define MAXINT  32767
#define MAXSYS  64

main(argc,argv)
int argc;
char *argv[];
   {
   char source[32],destination[32],number[32],reply[32]; /* plenty big */
   int fromdisk,todisk; /* signal file or direct I/O */
   int version,inres,outres,inspt,outspt,indrive,outdrive;
   int i,count,infd,outfd,insec,outsec,intrk,outtrk,insize,outsize;
   dph *indph,*outdph;
   dpb *indpb,*outdpb;
   char current,buffer[SECSIZ],system[MAXSYS][SECSIZ];

/*
* Output the signon message, init, and check for a help request
*/
   current = bdos(CURRENT,0); /* get current disk number */
   version = bdos(VERSION,0); /* get the CP/M version number */
   if ((argc > 1) && (argv[1][0] == '?'))
       {
       printf("Syscopy utility version 1.0 - by Stephen M. Kenton\n\n");
       printf("Syntax: syscpy [input [output [count]]]\n");
       printf("Input and output may be either a disk or a filename\n");
       printf("If specified, a colon ':' must be included with a disk\n");
       printf("name, or it will be considered to be a short file name\n");
       printf("Count is the number of sectors to be read and written\n");
       printf("It will normally be 1 for the boot block or '*' for all\n");
       printf("Count may be in decimal, or preceeded by a $ for hex\n");
       printf("Any argument(s) that are omitted will be prompted for\n");
       exit(0);
       }
   printf("Portable system copy utility. For help, enter 'syscopy ?'\n\n");

/*
* Get the input arguments from the command line or the user
*/
   if (argc > 1)
       strcpy(source,argv[1]);
   else
       {
       printf("Enter source, or return for current drive: ");
       gets(source);
       }
   if (strlen(source) == 0)
       {
       source[0] = DRIVES[current]; /* fabricate a string */
       strcpy(source+1,":");
       }
   if ((strlen(source) == 2) && (source[1] == ':'))
       {
       fromdisk = TRUE;
       indrive = index(DRIVES,toupper(source[0]));
       }
   else
       fromdisk = FALSE;

   if (argc > 2)
       strcpy(destination,argv[2]);
   else
       {
       printf("Enter destination, or return for current drive: ");
       gets(destination);
       }
   if (strlen(destination) == 0)
       {
       destination[0] = DRIVES[current]; /* fabricate a string */
       strcpy(source+1,":");
       }
   if ((strlen(destination) == 2) && (destination[1] == ':'))
       {
       todisk = TRUE;
       outdrive = index(DRIVES,toupper(destination[0]));
       }
   else
       todisk = FALSE;

   if (argc > 3)
       strcpy(number,argv[3]);
   else
       {
       printf("Enter number of sectors, or return for all: ");
       gets(number);
       }
   if (strlen(number) == 0)
       {
       strcpy(number,"*"); /* asterisk means copy all sectors */
       }
   if (number[0] == '*') /* wild card */
       count = MAXINT; /* largest possible value */
   else if (number[0] == '$') /* hexidecimal */
       sscanf(number+1,"%x",&count);
   else /* decimal */
       sscanf(number,"%d",&count);

/*
* Now determine the characteristics of the drives or files
*/
   if ((version == 0) || (version > 255)) /* CP/M 1.x or MP/M */
       {
       printf("MP/M does not allow access to some disk I/O calls,  and\n");
       printf("CP/M 1.x does not supply some needed information\n");
       printf("for this utility to run, use CP/M 2.x to run it\n");
       exit(1);
       }
   else
       {
       if (fromdisk) /* if reading from the system tracks */
           {
           indph = dphaddr(indrive); /* DPH address */
           if (indph == 0)
               {
               printf("Invalid source drive %s selected\n",source);
               exit(1);
               }
           indpb = indph->dph_dpb; /* DPB address */
           inres = indpb->dpb_off; /* number of reserved tracks */
           inspt = indpb->dpb_spt; /* number of sectors per track */
           insize = inres * inspt; /* number of reserved sectors */
           if (inres > 3)
               {
               printf("There are more than 3 reserved tracks on this drive\n");
               printf("This normally indicates that it is a logical disk\n");
               printf("rather than a bootable physical disk volume\n");
               printf("If this is the case, reading the reserved tracks\n");
               printf("will result in garbage being in the buffer\n");
               printf("Do you with to continue? (Y/N): ");
               scanf("%s",reply); /* get the response */
               if (toupper(reply[0]) != 'Y')
                   exit(1);
               }
           }
       else /* if reading from a file */
           {
           if ((infd = open(source,INPUT)) == ERROR)
               {
               printf("Can not open source file %s\n",source);
               exit(1);
               }
           insize = rcfsiz(infd) - 16; /* file size - bias */
           for (i=0; i<16; i++)
               read(infd,buffer,1); /* dump the bias sectors */
           }

       if (todisk) /* if writing to the system tracks */
           {
           outdph = dphaddr(outdrive); /* DPH address */
           if (outdph == 0)
               {
               printf("Invalid destination drive %s selected\n",destination);
               exit(1);
               }
           outdpb = outdph->dph_dpb; /* DPB address */
           outres = outdpb->dpb_off;
           outspt = outdpb->dpb_spt;
           outsize = outres * outspt; /* number of reserved sectors */
           if (inres > 3)
               {
               printf("There are more than 3 reserved tracks on this drive\n");
               printf("This normally indicates that it is a logical disk\n");
               printf("rather than a bootable physical disk volume\n");
               printf("If this is the case, writing on the reserved\n");
               printf("tracks will destroy another logical disk\n");
               printf("Do you with to continue? (Y/N): ");
               scanf("%s",reply); /* get the response */
               if (toupper(reply[0]) != 'Y')
                   exit(1);
               }
           }
       else /* if writing to a file */
           {
           if ((outfd = creat(destination)) == ERROR)
               {
               printf("Can not open destination file %s\n",destination);
               exit(1);
               }
           outsize = MAXINT; /* largest possible value */
           setmem(buffer,SECSIZ,'\0'); /* clear the buffer */
           for (i=0; i<16; i++)
               write(outfd,buffer,1); /* write the bias sectors */
           }
       }
   insize = (count < insize) ? count : insize; /* choose  the smaller */
   if (insize > outsize)
       {
       printf("Source is longer than destination, truncate? (Y/N): ");
       scanf("%s",reply); /* check answer */
       if (toupper(reply[0]) == 'Y')
           insize = outsize;
       else
           exit(1);
       }
   if (insize > MAXSYS)
       {
       printf("System size exceeds memory buffer limit\n");
       exit(1);
       }

/*
* Copy the source to the destination
*/
   outsize = insize; /* write only as much as was specified */
   if (fromdisk) /* read from the system tracks */
       {
       bios(SELDSK,indrive); /* select the input drive */
       for (intrk=0; intrk<inres; intrk++)
           {
           bios(SETTRK,intrk); /* select the track */
           for (insec=1; insec<=inspt; insec++)
               {
               count = intrk*inspt+insec-1; /* number of sectors */
               if (count > insize)
                   goto stopin; /* we just hit the limit */
               bios(SETSEC,insec); /* set the sector number */
               bios(SETDMA,system[count]); /* set DMA address */
               bios(READS,0); /* read the sector in */
               }
           }
       stopin:
       bios(SELDSK,current); /* select the origianal drive */
       }
   else /* read from a file */
       {
       if (read(infd,system,insize) < insize) /* read what you need */
           {
           printf("Error reading %s\n",source);
           exit(1);
           }
       }

   if (todisk) /* write to the system tracks */
       {
       bios(SELDSK,outdrive); /* select the output drive */
       for (outtrk=0; outtrk<outres; outtrk++)
           {
           bios(SETTRK,outtrk); /* select the track */
           for (outsec=1; outsec<=outspt; outsec++)
               {
               count = outtrk*outspt+outsec-1; /* number of sectors */
               if (count > outsize)
                   goto stopout; /* we just hit the limit */
               bios(SETSEC,outsec); /* set the sector */
               bios(SETDMA,system[count]); /* set DMA address */
               bios(WRITES,0); /* write the sector */
               }
           }
       stopout:
       bios(SELDSK,current); /* select the original drive */
       }
   else /* write to a file */
       {
       if (write(outfd,system,outsize) < outsize) /* write what you have */
           {
           printf("Error writing %s\n",destination);
           exit(1);
           }
       }

/*
* Clean up and exit
*/
   if (!fromdisk) /* if reading from a file */
       close(infd);
   if (!todisk) /* if writing to a file */
       close(outfd);
   printf("%d sectors were copied from %s to %s\n",insize,source,destination);
   }

/*
* Index - return the index of the first occurance of a char in a
* string, or ERROR if it is not found
*/
index(str,c)
char *str,c;
   {
   int i;
   for (i=0; str[i]; i++)
       {
       if (str[i] == c)
           return(i); /* found it */
       }
   return(ERROR); /* did not find it */
   }

/*
* Dphaddr - return the address of a disk parameter header
* This is performed by bios call 9, but the Bios() function
* can not be used, because it returns a <A> not <HL>
*/
dphaddr(drive)
int drive;
   {
   unsigned *warmstart,seldsk,result;
   char current;

   current = bdos(CURRENT,0); /* save the current drive */
   warmstart = 1;
   seldsk = *warmstart + 24; /* address of SELDSK routine */
   result = call(seldsk,0,0,drive,0); /* call the bios seldsk routine */
   bios(SELDSK,current); /* restore the original disk */
   return(result);
   }