/*********************************************************************
*             ucsdtran  -- ucsd to cp/m transfer                     *
**********************************************************************
*     Copyright (c) 1983     Ben Goldfarb, Orlando, FL               *
*            All rights reserved                                     *
*                                                                    *
*     Permission is hereby granted to copy and distribute all parts  *
*     of this software program and associated documents for any      *
*     non-commercial purpose.  Any use of this material for          *
*     commercial advantage without prior written consent of          *
*     the author is prohibited.                                      *
**********************************************************************
*     This program is designed to read files from a 8" (3740) floppy *
*     disk that has been formatted and written by a UCSD Pascal      *
*     System and write them to files on the CP/M system.  Following  *
*     is the sequence of actions:                                    *
*         o  Get drive designation for UCSD disk & set up drives     *
*         o  Read and print UCSD disk directory                      *
*         o  Get filename of file to transfer and target file        *
*         o  Do the transfer                                         *
*         o  Repeat the above til done                               *
*                                                                    *
*     UCSDTRAN properly handles UCSD Textfiles, converting them      *
*     to CP/M-compatible ascii files.  Compressed blanks are         *
*     expanded and the editor formatting is undone.  No conversion   *
*     is done to UCSD Codefiles, Datafiles, etc.                     *
*                                                                    *
*                                                                    *
*     Installation requires BDS C Version 1.42 or later.  If         *
*     a version prior to 1.50 is used, delete '#include <hardware.h>'*
*                                                                    *
*                                                                    *
*     UCSDTRAN makes use of the alloc() function for dynamic memory  *
*     allocation, so be certain this feature is enabled in your      *
*     BDS C compiler before you attempt to compile the program.      *
*     (See the BDS C compiler documentation.)                        *
*                                                                    *
*     Customization:  The only #define that may be necessary to      *
*     change is SECT0.  If your disk system calls the first sector   *
*     on each track '0', rather than '1', you must leave SECT0       *
*     defined.  Otherwise, comment it out or delete the #define.     *
*     This is necessary because the program directly addresses the   *
*     appropriate track and sector on the UCSD disk; all mapping is  *
*     done by the program.  The Godbout DISK 1 controller numbers    *
*     sectors from 0; most others we've seen in CP/M systems start   *
*     with 1.                                                        *
*                                                                    *
**********************************************************************
*     UCSD is a registered trademark of the Regents of the           *
*          University of California.                                 *
*     CP/M is a trademark of Digital Research, Inc.                  *
*     BDS is a trademark of BD Software, Inc.                        *
*                                                                    *
**********************************************************************
*                                                                    *
*              Author:  Ben Goldfarb                                 *
*                       Orlando, FL                                  *
*                                                                    *
*              Date:    02/11/83                                     *
*                                                                    *
*   Bug reports, comments, and suggestions for improvements to:      *
*                 USENET:  ...!duke!ucf-cs!goldfarb                  *
*                 ARPANET: goldfarb.ucf-cs@Rand-Relay                *
*                                                                    *
**********************************************************************/

/*
*          Revision Log
*
*   $Log:      /usr1/grad/goldfarb/cpm/src/ucsdtran.c,v $
* Revision 1.2  83/04/06  14:31:51  goldfarb
* Added facility for transferring non-Textfiles.  Previously, only
* Textfiles were handled, so code was added to circumvent reformatting
* of editor text 'pages.'  Type of file is detected by examining the
* 'kind' field in the UCSD directory entry for the file in question.
*
* Revision 1.1  83/04/06  13:54:34  goldfarb
* Initial revision
*
*/


#include <bdscio.h>
#include <hardware.h>

#define VERSION 12

/*
*  Godbout controller kludge (see title page)
*/

#define SECT0


/*  for versions with no screen handling  */
#ifdef VANILLA
#undef CLEARS
#undef INTOREV
#undef OUTAREV
#define CLEARS ""
#define INTOREV ""
#define OUTAREV ""
#endif

/*  Misc. ASCII defines */
#define DLE 0x10
#define CR  0x0d
#define LF  0x0a

/*
*      Definitions for various CP/M 2.2 data structures.
*/

/*  Absolute memory loocations  */
#define BIOS_LOC 0x0001
#define IOBYTE   0x0003
#define LOGDRV   0x0004
#define BDOS_LOC 0x0006
#define FCB_1    0x005c
#define TBUFF    0x0080


/*   Disk Parameter Block  */

struct dpb {
       unsigned spt;
       char bsf;
       char bm;
       char nm;
       unsigned dsk_siz;
       unsigned dir_max;
       char al0;
       char al1;
       unsigned chk_siz;
       unsigned trk_ofs;
};


/*   Disk Parameter Header  */

struct dph {
       char *pst_tab;
       int scratch[3];
       char *pdir_buf;
       struct dpb *pdpb;
       char *pchk;
       char *ppalv;
};


/*  BIOS calls */
#define SEL_DSK 9
#define SET_TRK 10
#define SET_SEC 11
#define READ 13
#define WRITE 14

/*  BDOS calls  */
#define INIT_BDOS 13
#define SEL_BDOS 14
#define SET_DMA 26

/*  Miscellaneous CP/M constants  */
#define LOG 0
#define NO_LOG 255

/*  Miscellaneous UCSD constants  */
#define PAGESIZE 1024
#define DIRENTRIES 77
#define DIRENTSIZE 26

/*  UCSD directory structures  */
struct direntry {
   int firstblk, lastblk, kind;
   char title[16];
   int bytesinlast;
   char moddate[2];
};

struct dirhdr {
   int dfirstblk, dlastblk, dkind;
   char dtitle[8];
   int lastblkvol, filesindir, loadtime, access;
   char pad[4];
};

/*
*  Global Variables
*/

int ucsddrive, cpmdrive;                /*  Disk drive codes            */
int Godbout;                            /*  Kludge for Disk 1           */
char string[16];                        /*  Misc. string space          */
int dirp;                               /*  Ptr. to current dir. entry  */
struct dph *pdph;                       /*  Ptr. to disk parm header    */
char buff[PAGESIZE];                    /*  Input buffer                */
char cpmbuff[BUFSIZ];                   /*  Output buffer               */
struct direntry dir[DIRENTRIES - 1];    /*  Directory buffer            */
struct dirhdr volid;                    /*  Directory header            */

main()
{
       char c, error, morefiles;


       /*  Print program ident  */
       printf(CLEARS);
       printf("\n\nUCSDtran version %d.%d\n",VERSION/10,VERSION%10);
       printf("\n\nUCSD Pascal to CP/M transfer utility.\n\n");
       printf("Copyright (c) 1983, Ben Goldfarb, Orlando, FL.\n\n");

#ifdef SECT0
       Godbout = TRUE;
#else
       Godbout = FALSE;
#endif

       /*  Initialize alligator  */
       _allocp = 0;

       /*  Input drive names  */
       ucsddrive = getdrive("UCSD");
       cpmdrive = getdrive("CP/M");

       /*  Tell user where to go and reset system  */
       do {
               error = FALSE;
               printf("\n\nPut UCSD disk in Drive %c: ", ucsddrive + 'A');
               printf("and CP/M disk in Drive %c:", cpmdrive + 'A');
               printf("\nHit <CR> when ready: ");
               while((c = getchar()) != '\n');
               if ((pdph = seldsk(ucsddrive, LOG)) == NULL) {
                       printf("Message from drive %c:  ", ucsddrive + 'A');
                       eprint("You better put it in!");
                       error = TRUE;
               }
               else {
                       if (pdph->pdpb->spt != 26 || pdph->pdpb->dsk_siz > 243 ) {
                               eprint("UCSD disk must be standard 3740.");
                               error = TRUE;
                               continue;
                       }
               }
       } while (error);

       readdir();  /*  get UCSD directory  */

       morefiles = TRUE;
       do {
          error = FALSE;
          do {
               printf(CLEARS);
               printdir();  /*   print directory  */
               printf("Transfer what file: ");
               if ((dirp = getucsd()) == -1) {
                       printf("\007No such file\n");
                       error = TRUE;
               }
          } while (error);
          tocpm();
          printf("Transfer more files? ");
          morefiles = yesno();
       } while (morefiles);
}

tocpm()    /*  Transfer UCSD file to CP/M file  */
{
       char name[30], istext;
       int fd, i, n;
       unsigned track, sect, nsects;

       /*  note: skip first 1024 byte "page" of text file  */
       nsects = ((dir[dirp].lastblk - dir[dirp].firstblk ) * 4);
       if (istext = (dir[dirp].kind == 3))
       /*  skip editor page for Textfile  */
               nsects -= 8;
       block(dir[dirp].firstblk + (istext ? 2 : 0), &track, &sect);

       /*  Get filename and set up file  */
       printf("\nEnter CP/M file name: ");
       gets(name);
       printf("Creating %s...",name);
       if ((fd = fcreat(name, cpmbuff)) < 0) {
               printf("\n\007Can't create %s.\n",name);
               return(eprint(""));
       }

       /*  Perform the transfer  */
       while (nsects > 0) {
               if (rwfor(&track, &sect, buff, (istext ? 8 : 4), READ) < 0)
                       return(eprint("Read error on UCSD disk."));
               if (istext)  /* do textfile conversion */
                       cpmconvert();
               else   /*  copy raw data  */
                       for (i = 0; i < 512; i++) putc(buff[i], cpmbuff);

               nsects -= (istext? 8 : 4);
       }

       if (istext) putc(CPMEOF, cpmbuff);
       fflush(cpmbuff);
       printf("\nTransfer complete\n");
       fclose(cpmbuff);
}

cpmconvert()  /*  output translated UCSD 1024 byte buffer to cp/m file */
{
       int i, nospaces, bufptr;
       char c;

       bufptr = 0;
       while (bufptr < 1024) {

               switch(c = buff[bufptr++]) {
                  case DLE:
                       nospaces = buff[bufptr++] - 32;
                       for (i = 0; i < nospaces; i++)
                               putc(' ', cpmbuff);
                       break;
                  case CR:
                       putc(c, cpmbuff);
                       putc(LF, cpmbuff);
                       break;
                  case '\0':
                       break;  /* ignore  */
                  default:
                       putc(c, cpmbuff);
                       break;
               }
       }
}


rwfor(t,s,buff,n,rw)   /*  Read or write a sector from UCSD disk  */
unsigned *t,*s,n,rw;
char *buff;
{
       char *buffa;

       /*  Reset buffer  */
       buffa = buff;

       /*  Select UCSD Disk  */
       seldsk(ucsddrive, NO_LOG);

       /*  Now read or write n sectors  */
       while (n-- > 0) {
               register unsigned ps;

               bios(SET_TRK, *t);
               ps = map(*s, *t);
               /*  Set sector (note Godbout kludge)  */
               bios(SET_SEC, ps + ((Godbout) ? 0 : 1));
               bdos(SET_DMA, buffa);
               if ((bios(rw, 0) & 1) == 1)
                       return(ERROR);

               /*  Bump track, if necessary  */
               if (++(*s) > 25) {
                       *s = 0;
                       (*t)++;
               }
               buffa += 128;
       }
       bdos(SET_DMA, buff);
       seldsk(cpmdrive, NO_LOG);
       return(OK);
}

getdrive(s)  /*  get disk drive designation and validate  */
char *s;
{
       char c;

       for (;;) {  /*  until the dummy gets it right!  */
               printf("\n\nDrive for %s disk: (A - P) ", s);
               if ((c = toupper(getchar())) > 'P' || c < 'A')
                       eprint("Drive value out of range!");
               else
                       return(c - 'A');
       }
}

yesno()   /*  ask for a response and return true for 'yes'  */
{
       char c;

       return(((c = toupper(getchar())) == 'Y') ? TRUE : FALSE);
}

eprint(s)  /*  Print error message, hold screen, and return error  */
char *s;
{
       printf("\n\007");
       rprintf(s);
       printf("\nHit <CR> to continue: ");
       getchar();
       bdos(INIT_BDOS);
       return(ERROR);
}


rprintf(s)   /*  Print in reverse video  */
char *s;
{
       printf(INTOREV);
       printf(s);
       printf(OUTAREV);
}

struct dph
*seldsk(dn, log)   /*  BIOS function to select drive and return dph */
char dn, log;
{
       unsigned *pbios;
       unsigned *psel_dsk;

       pbios = BIOS_LOC;
       psel_dsk = *pbios + 0x0019;
       return(call(*psel_dsk, dn, 0, dn, log));
}

block(blk, track, sect)   /*  translates UCSD block to logical track/sector  */
int blk, *track, *sect;
{
       int abssector;

       abssector = blk * 4;
       *track = (abssector / 26) + 1;
       *sect = abssector % 26;
}



map(secno, trkno)   /*  maps logical to physical sector  */
int secno, trkno;
{
       int s;

       if (secno > 12)
               s = ((secno * 2) + 1 + ((trkno-1) * 6 )) % 26;
       else
               s = ((secno * 2) + ((trkno-1) * 6)) % 26;
       return(s);
}

readdir()   /*  read the directory on the UCSD disk   */
{
       int track, sect;
       char *alloc(), *dirbuf;

       if ((dirbuf = alloc(DIRENTRIES * DIRENTSIZE)) == 0) {
               printf("Not enough memory -- aborting.\n");
               exit(ERROR);
       }

       block(2, &track, &sect);   /*  2 is the directory block  */
       rwfor(&track, &sect, dirbuf, 4*4, READ);

       /* big kludge because no casts in BDS  */
       movmem(dirbuf, &volid, DIRENTSIZE);
       movmem(dirbuf + DIRENTSIZE, &dir[0], (DIRENTRIES - 1) * DIRENTSIZE);
       free(dirbuf);
}

printdir()  /*  print UCSD disk's directory  */
{
       int i;
       char *s;

       printf("Volume ID: %s\n", makestr(volid.dtitle));

       for (i = 0; i < volid.filesindir; i++) {
          printf("%-14s\t%3d", makestr(dir[i].title),
               dir[i].lastblk - dir[i].firstblk);
          printf("\t");
          printdate(dir[i].moddate);
          printf("\t");
          switch(dir[i].kind) {  /*  Ain't BDS nice ? */
               case 0:  printf("Datafile"); break;
               case 1:  printf("Xdskfile"); break;
               case 2:  printf("Codefile"); break;
               case 3:  printf("Textfile"); break;
               case 4:  printf("Infofile"); break;
               case 5:  printf("Datafile"); break;
               case 6:  printf("Graffile"); break;
               case 7:  printf("Fotofile"); break;
               default: printf("Foofile"); break;
          }
          printf("\n");
       }
       printf("\n");
}

char *
makestr(s)  /*  make a C string from UCSD variable string  */
char *s;
{
       int i;
       char *t;

       t = &string[0];
       for (i = 0;  i < s[0]; i++)
               t[i] = s[i+1];
       t[i] = '\0';
       return(&string[0]);
}

printdate(d) /*  prints a UCSD date record  */
char d[];
{

       int t1;

       /*
        *  The UCSD format is:
        *
        *  DATEREC = PACKED RECORD
        *       MONTH: 0..12;
        *       DAY:   0..31;
        *       YEAR:  0..100
        *  END;
        *
        *  This routine expects a char[2] array with the
        *  information stored as above.
        */

       t1 = (d[0] >> 4) | ((d[1] & 1) << 4);
       printf("%2d", t1);
       t1 = d[0] & 0x0f;
       printf("-");
       switch (t1) {  /*  because of no init in BDS  */
          case 1:  printf("Jan"); break;
          case 2:  printf("Feb"); break;
          case 3:  printf("Mar"); break;
          case 4:  printf("Apr"); break;
          case 5:  printf("May"); break;
          case 6:  printf("Jun"); break;
          case 7:  printf("Jul"); break;
          case 8:  printf("Aug"); break;
          case 9:  printf("Sep"); break;
          case 10: printf("Oct"); break;
          case 11: printf("Nov"); break;
          case 12: printf("Dec"); break;
          default: printf("Foo"); break;
       }
       printf("-");
       t1 = d[1] >> 1;
       printf("%02d", t1);
}

getucsd()   /*  returns a ptr to dir entry if valid, else -1 */
{
       char temp[81];
       int i, j, l, found;


       gets(temp);

       /*  see if we can find it in dir  */
       for (i = 0; i < volid.filesindir; i++) {
               if ((l = strlen(temp)) != dir[i].title[0])
                       continue;
               else {
                       found = TRUE;
                       for (j = 1; j <= l; j++) {
                               if (toupper(temp[j-1]) != dir[i].title[j]){
                                       found = FALSE;
                                       break;
                               }
                       }
                       if (found) return(i);
               }
       }
       return(-1);
}