/*
cpm

CP/M emulator.
Written by D'Arcy J.M. Cain
darcy@druid

*/

#define         CPM_DATA

#include        <stdio.h>
#include        <string.h>
#include        <stdlib.h>
#include        <unistd.h>
#include        <ctype.h>
#include        <errno.h>
#include        <termio.h>
#include        <signal.h>
#include        <time.h>
#include        <sys/stat.h>
#include        <fcntl.h>
#include        "cpm.h"

#define         FILLER_SIZE     (16 - sizeof(FILE *))

FILE    **fcb_fp(byte *fcb)
{
       return((FILE **)(fcb + 16));
}

#define         FCB_DR(x)               (x)[0]          /* drive: 0 = def, 1 = A, 2 = B, etc */
#define         FCB_NAME(x)             ((x) + 1)       /* file name up to 8 characters */
#define         FCB_TYP(x)              ((x) + 9)       /* file type up to 3 characters */
#define         FCB_EX(x)               (x)[12]         /* extent number */
#define         FCB_S1(x)               (x)[13]
#define         FCB_S2(x)               (x)[14]
#define         FCB_RC(x)               (x)[15]         /* record count for extent "ex" */
#define         FCB_FP(x)               (*fcb_fp(x))/* internal use only */
#define         FCB_CR(x)               (x)[32]         /* current record */
#define         FCB_R0(x)               (x)[33]         /* record number */
#define         FCB_R1(x)               (x)[34]         /* R0 + (R1 << 8) + ((R2  & 1) << 16) */
#define         FCB_R2(x)               (x)[35]
#define         FCB_RR(x)               (long)((x)[33] + ((x)[34]<<8) + (((x)[35] & 1)<<16))

#ifdef          CPM_DEBUG
#define         dump_registers(output)
       fprintf(output, reg_dump, A, BC, DE, HL, SP, PC,
                                       SIGN ? 'S' : '-',
                                       ZERO ? 'Z' : '-',
                                       HALF_CARRY ? 'H' : '-',
                                       PARITY ? 'P' : '-',
                                       BCD ? 'N' : '-',
                                       CARRY ? 'C' : '-',
                                       ram[PC], dasm

static int              dasm_flag = 0;
static const char       *reg_dump =
       "A=%02.2x BC=%4.4x DE=%04.4x HL=%04.4x SP=%04.4x PC=%04.4x %c%c%c%c%c%c %02.2x %srn";
#else
#define         dump_registers(output)
#endif

extern char             *optarg;
struct termio   old_term, termp;
static int              user_break;

#ifndef         COMPILE_TEST
static byte             *dma;
static char             *tail;
static int              out_delim = '$', def_drive = 1;
static FILE             *reader = NULL, *punch = NULL, *list = NULL;
#endif

/* clean up routine */
static void     cleanup(int sig)
{
       if (sig == SIGINT)
       {
               user_break = 1;
               signal(SIGINT, cleanup);
               return;
       }

       ioctl(0, TCSETA, &old_term);
       printf("nWe now return you to your regularly scheduled OSn");
       exit(0);
}

#ifndef         COMPILE_TEST

/* get a character from the terminal */
static int              getch(int check)
{
#if 0
       byte    c = 0;
       int             ret_val;

       while ((ret_val = read(0, &c, 1)) != 1)
               if (ret_val == -1 && errno == EINTR)
                       return(-1);

       return(c);
#else
       static unsigned char    buf[1024];
       static int                              index = 0, sz = 0;
       int                                             err_ret;

       if (index != sz)
               return(buf[check ? index : index++]);

       index = 0;

       /* first try to get everything that's waiting */
       fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NDELAY);
       sz = read(0, buf, 1024);
       err_ret = errno;

       if (fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NDELAY) < 0)
               return(-1);

       /* error other than no data ready? */
       if (sz == -1)
       {
               sz = 0;
               return(-1);
       }

       /* no data ready */
       if (!sz)
       {
               if (check)
                       return(0);

               /* protect against signals */
               while ((sz = read(0, buf, 1)) == 0)
                       ;

               if (sz != 1)
               {
                       sz = 0;
                       return(-1);
               }
       }

       return(buf[check ? index : index++]);
#endif
}


/* How CP/M drives map to Unix: */
/* below is an array of 17 strings.  This corresponds to the allowable */
/* drive names in CP/M (A to P) plus the default drive which actually */
/* is a duplicate of one of the next 16.  At startup, The current Unix */
/* directory is copied into cpm_drive[1] and becomes drive A.  This may */
/* be modified by the startup sequence.  As well, the other drives may */
/* be set up to other directories.  At the end of the startup sequence */
/* the "strcpy(cpm_drive[0], cpm_drive[1]) causes drive A to be the CP/M */
/* default drive.  From that point on, a switch to a new drive involves */
/* simply copying that drive's directory into cpm_drive[0].  I did this */
/* in this way since I expect changing drives to occur less frequently */
/* than accessing files. */

static char             cpm_drive[17][128];

/* take a string, terminate it at the first white space and return the
  string that follows.  I.E: "DIR *.COM" puts a 0 in the first space
  and returns a pointer to "*.COM".  Note that "DIR" returns a pointer
  to a NULL string - NOT a NULL pointer. */
static char     *chop_cmd(char *buf)
{
       char    *ptr = buf;

       /* discard leading space */
       while (isspace(*ptr))
               ptr++;

       /* quad left the string */
       strcpy(buf, ptr);

       /* terminate first word */
       ptr = buf;
       while (!isspace(*ptr) && *ptr)
               ptr++;

       /* is there more? */
       if (*ptr)
       {
               /* terminate first word */
               *ptr++ = 0;

               /* skip any leading space */
               while (isspace(*ptr))
                       ptr++;

       }

       return(ptr);
}

/* given a drive unit (0 - 16) and a file name, returns Unix file name */
static char *mk_name(int dr, char *fname)
{
       static char     full_name[148];

       sprintf(full_name, "%s/%s", cpm_drive[dr], fname);
       return(full_name);
}

/* given a file spec in standard CP/M format returns Unix file name */
static char     *real_name(char *fname)
{
       /* does it include a drive letter? */
       if (fname[1] == ':')
               return(mk_name(*fname & 0x0f, fname + 2));

       /* else use default drive */
       /* return(mk_name(0, fname)); */
       return(fname);
}


/* given a pointer to an FCB, returns real file name */
static char     *fcb2real(byte *buf)
{
       char    temp[16], *p = temp;
       int             k = 0;

       for (k = 0; k < 8; k++)
               if (!isspace(FCB_NAME(buf)[k]))
                       *p++ = tolower(FCB_NAME(buf)[k]);

       *p++ = '.';

       for (k = 0; k < 3; k++)
               if (!isspace(FCB_TYP(buf)[k]))
                       *p++ = tolower(FCB_TYP(buf)[k]);

       *p-- = 0;

       if (*p == '.')
               *p = 0;

       return(mk_name(FCB_DR(buf), temp));
}

/* calls system command with CP/M file name converted to Unix */
static void             fsystem(const char *s, char *file)
{
       char    command[256];

       sprintf(command, s, real_name(file));
       ioctl(0, TCSETAW, &old_term);
       system(command);
       ioctl(0, TCSETAW, &termp);
}

/* formats a CP/M file name into an FCB */
static void     mk_fcb(byte *buf, char *fname)
{
       char    *p = fname;
       int             k, l;

       /* clear FCB to start with */
       memset(buf, 0, 16);

       /* check for drive name */
       if (p[1] == ':')
       {
               FCB_DR(buf) = *p & 0x0f;
               p += 2;
       }

       k = l = 0;

       /* format primary name */
       for (k = 0; k < 8; k++)
       {
               if ((p[l] == '.') || (p[l] == 0))
                       while (k < 8)
                               FCB_NAME(buf)[k++] = ' ';
               else if (p[l] == '*')
               {
                       while (k < 8)
                               FCB_NAME(buf)[k++] = '?';

                       while (p[l] && (p[l] != '.'))
                               l++;
               }
               else
                       FCB_NAME(buf)[k] = p[l];

               l++;
       }

       /* format file type */
       for (k = 0; k < 3; k++)
       {
               if ((p[l] == '.') || (p[l] == 0))
                       while (k < 3)
                               FCB_TYP(buf)[k++] = ' ';
               else if (p[l] == '*')
                       while (k < 3)
                               FCB_TYP(buf)[k++] = '?';
               else
                       FCB_TYP(buf)[k] = p[l];

               l++;
       }

       return;
}

/* add extension to file name.  replace current one if necessary */
static void     addext(char *s1, const char *s2)
{
       char    *p;

       if ((p = strchr(s1, '.')) == NULL)
               strcat(s1, ".");

       strcat(s1, s2);
}


/* get a string */
static int              get_str(char *buffer, int maxlen)
{
       int             k = 0, c;

       /* break will interrupt input as if nothing entered */
       while ((c = getch(0)) != 'r' && c != 'n' && c != -1)
       {
               if (k == maxlen)
                       c = 'a';
               else if (c == 'b')
               {
                       if (k)
                       {
                               fprintf(stderr, "b b");
                               k--;
                       }
               }
               else
               {
                       fputc(c, stdout);
                       buffer[k++] = c;
               }

       }

       fprintf(stderr, "rn");
       return(c == -1 ? 0 : k);
}

/* see if character waiting */
#define         kbhit()         ioctl(0, FIORDCHK, NULL)

/* Convert string to lower case */
static void             strtolow(char *s)
{
       while (*s)
       {
               *s = tolower(*s);
               s++;
       }
}

/* Convert string to upper case */
static void             strtoup(char *s)
{
       while (*s)
       {
               *s = toupper(*s);
               s++;
       }
}


#ifdef  CPM_DEBUG
#define         is_breakpoint(x)        breakpoint(0, (x))
#define         list_breakpoints()      breakpoint(0, 0)
#define         add_breakpoint(x)       breakpoint(1, (x))
#define         del_breakpoint(x)       breakpoint(2, (x))

static int              breakpoint(int cmd, int bpoint)
{
       static int      bp[64];
       int                     k;

       switch(cmd)
       {
               case 0:         /* set breakpoint if not 0 or print all */
                       for (k = 0; k < 64; k++)
                       {
                               if (bp[k])
                               {
                                       if (!bpoint)
                                               fprintf(stderr, "Breakpoint %2d: 0x%04.4xrn");
                                       else if (bp[k] == bpoint)
                                               return(1);
                               }
                       }

                       return(0);

               case 1:         /* add breakpoint */
                       /* check if already in table */
                       for (k = 0; k < 64; k++)
                               if (bp[k] == bpoint)
                                       return(k);

                       /* else put it there if there is room */
                       for (k = 0; k < 64; k++)
                       {
                               if (!bp[k])
                               {
                                       bp[k] = bpoint;
                                       return(k);
                               }
                       }

                       fprintf(stderr, "Too many breakpointsrn");
                       return(-1);

               case 2:         /* delete a breakpoint */
                       for (k = 0; k < 64; k++)
                               if (bp[k] == bpoint)
                                       bp[k] = 0;

                       return(0);
       }

       return(-1);
}


static int              debugger(void)
{
       char    entry[128], *ptr;
       int             c;

       user_break = 0;

       for (;;)
       {
               fprintf(stderr, "rnDEBUG> ");
               ptr = entry;

               while ((c = getch(0)) != 'n')
               {
                       if (c == -1)
                               return(1);

                       if ((*ptr = c) == 'b')
                       {
                               if (ptr > entry)
                               {
                                       fprintf(stderr, "b b");
                                       ptr--;
                               }
                       }
                       else
                               fputc(*ptr++, stdout);
               }

               *ptr = 0;
               strtolow(entry);
               fprintf(stderr, "rn");

               if (!*entry)
                       ;
               else if (*entry == 'g')
                       return(0);
               else if (*entry == 'q')
                       return(1);
               else if (*entry == 'r')
                       dump_registers(stdout);
               else if (*entry == '+')
                       add_breakpoint(atoi(entry + 1));
               else if (*entry == '-')
                       del_breakpoint(atoi(entry + 1));
               else if (*entry == 'l')
                       list_breakpoints();
               else if (isdigit(*entry))
                       dasm_flag = *entry - '0';
               else if (*entry == '?')
               {
                       printf("  g     Run from current PCrn");
                       printf("  q     Quit to command interpreterrn");
                       printf("  r     Dump registersrn");
                       printf("  +###  Add breakpoint at numberrrn");
                       printf("  -###  Delete breakpoint at numberrn");
                       printf("  l     list current breakpointsrn");
                       printf("  ###   Set debug level to numberrn");
               }
               else
                       fprintf(stderr, "aUnknown command: %cn", *entry);
       }
}
#endif  /* CPM_DEBUG */

/* run a program */
static int      run(char *program)
{
       byte    *mem_ptr = ram + 0x100;
       char    *fn, fn2[128];
       int             c, k, pc;
       FILE    *fp;
       byte    *fcb = NULL;
       long    f_pos;
       struct stat     s;


       /* find the program name */
       strcpy((char *)(mem_ptr), program);
       addext((char *)(mem_ptr), "com");

       /* open the command file - return error if not found */
       if ((fp = fopen((char *)(mem_ptr), "rb")) == NULL)
               return(-1);

       /* load command into memory */
       while (fread(mem_ptr, 1, 0x100, fp))
       {
               if (mem_ptr > (ram + 0xf000))
               {
                       fprintf(stderr, "aCommand file too bigrn");
                       return(-2);
               }

               mem_ptr += 0x100;
       }

       fclose(fp);
       PC = 0x100;

       /* BDOS, BIOS and default stack */
#if defined(USUAL_MICROSOFT_STUPIDITY)
       for (k = BIOS; k < 0x10000; k += 3)
       {
               ram[k] = 0xc3;  /* JP */
               ram[k + 1] = k & 0xff;
               ram[k + 2] = k >> 8;
       }

       for (k = BDOS - 0x10; k < BDOS; k++)
               ram[k] = 0;

       SP = BDOS - 0x10;
#else
       for (k = 0xfff0; k < 0x10000; k++)
               ram[k] = 0;

       SP = 0xfff0;
#endif

       strcpy((char *)(ram + 0x80), tail);
       mem_ptr = (byte *)(chop_cmd(tail));
       mk_fcb(ram + 0x5c, tail);
       mk_fcb(ram + 0x6c, (char *)(mem_ptr));
       memcpy(ram, page_zero, sizeof(page_zero));
       dma = ram + 0x80;


       /* run program.  loop stops if PC = 0 - "JP 0" e.g. */
       while (PC)
       {

#ifdef  CPM_DEBUG
               if (dasm_flag > 1)
                       dump_registers(stderr);

               if ((user_break && debugger()) || is_breakpoint(PC))
#else
               if (user_break)
#endif
               {
                       fprintf(stderr, "rnna* Program Interrupted by user *rn", ram[PC]);
                       dump_registers(stderr);
                       return(-5);
               }

               pc = PC;

               /* check if PC = BDOS entry point */
               if (PC == BDOS)
               {
                       /* do CP/M service if so */
                       switch (C)
                       {
                               case 0:                                         /* system reset */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: System resetrn");
#endif
                                       return(0);

                               case 1:                                         /* conin */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Console inrn");
#endif

                                       if ((A = getch(0)) == -1)
                                               A = '*';

                                       fputc(A, stdout);
                                       break;

                               case 2:                                         /* conout */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Console out (%c)rn", E >= ' ' ? E : '.');
#endif

                                       fputc(E, stdout);
                                       break;

                               case 3:                                         /* RDR */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Reader inrn");
#endif

                                       if (reader != NULL)
                                               A = fgetc(reader);
                                       break;

                               case 4:                                         /* PUN */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Punch out (%c)rn", E >= ' ' ? E : '.');
#endif

                                       if (punch != NULL)
                                               fputc(E, punch);
                                       break;

                               case 5:                                         /* LST */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: List out (%c)rn", E >= ' ' ? E : '.');
#endif

                                       if (list != NULL)
                                               fputc(E, list);
                                       break;

                               case 6:                                         /* CONIO */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                       {
                                               fprintf(stderr, "BDOS: Conio ");
                                               if (E == 0xff)
                                                       fprintf(stderr, "inrn");
                                               else
                                                       fprintf(stderr, "out (%c)rn", E >= ' ' ? E : '.');
                                       }
#endif

                                       if (E == 0xff)
                                       {
                                               if ((A = getch(1)) != 0)
                                                       A = getch(0);
                                       }
                                       else
                                               fputc(E, stdout);

                                       break;

                               case 7:                                         /* get IOBYTE */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Get IOBYTErn");
#endif

                                       A = 0x95;
                                       break;

                               case 8:                                         /* set IOBYTE */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Set IOBYTErn");
#endif

                                       break;

                               case 28:                                        /* write protect disk */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Write protect diskrn");
#endif

                                       break;

                               case 9:                                         /* prstr */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Print stringrn");
#endif

                                       mem_ptr = ram + DE;
                                       while (*mem_ptr != out_delim)
                                               fputc(*mem_ptr++, stdout);
                                       break;

                               case 10:                                        /* rdstr */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Read console bufferrn");
#endif

                                       ram[DE + 1] = get_str((char *)(ram) + DE + 2, ram[DE]);
                                       break;

                               case 11:                                /* CONSTAT */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Get console statusrn");
#endif

                                       A = kbhit() ? 0xff : 0;
                                       break;

                               case 12:                                /* VERSION */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Return version numberrn");
#endif

                                       HL = 0x0022;
                                       break;

                               case 13:                                /* RSTDSK */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Reset disk systemrn");
#endif

                                       break;

                               case 14:                                /* SELDSK */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Select disk %c:rn", E + 'A');
#endif

                                       k = E + 1;
                                       A = 0xff;

                                       if ((k < 1) || (k > 16))
                                               H = 4;
                                       else if (*cpm_drive[k] == 0)
                                               H = 1;
                                       else
                                       {
                                               def_drive = k;
                                               strcpy(cpm_drive[0], cpm_drive[k]);
                                               A = 0;
                                       }
                                       break;

                               case 15:                                /* OPENF */
                                       fcb = ram + DE;
                                       fn = fcb2real(fcb);
                                       memset(&FCB_FP(fcb), 0, 24);

#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Open file %srn", fn);
#endif

                                       A = 0xff;

                                       if (strchr(fn, '?') != NULL)
                                               HL = 9;
                                       else if ((FCB_DR(fcb) < 0) || (FCB_DR(fcb) > 16))
                                               HL = 4;
                                       else if (*cpm_drive[FCB_DR(fcb)] == 0)
                                               HL = 1;
                                       else if ((FCB_FP(fcb) = fopen(fn, "r+")) == NULL)
                                               HL = 0;
                                       else
                                               A = HL = 0;

                                       break;

                               case 16:
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Close filern");
#endif

                                       fcb = ram + DE;

                                       if (FCB_FP(fcb) != NULL)
                                               fclose(FCB_FP(fcb));

                                       FCB_FP(fcb) = 0;
                                       break;

                               case 19:
                                       fcb = ram + DE;

#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Delete file %srn",
                                                                                                                       fcb2real(fcb));
#endif

                                       unlink(fcb2real(fcb));
                                       FCB_FP(fcb) = NULL;
                                       break;

                               case 20:                                        /* READ */
                               case 33:                                        /* READ RANDOM */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                       {
                                               fprintf(stderr, "BDOS: Read ");
                                               if (C == 20)
                                                       fprintf(stderr, "sequential");
                                               else
                                                       fprintf(stderr, "random");
                                       }
#endif

                                       fcb = ram + DE;
                                       memset(dma, 0x1a, 0x80);

                                       if (C == 33)
                                       {
                                               f_pos = FCB_RR(fcb);
                                               fseek(FCB_FP(fcb), f_pos * 0x80, SEEK_SET);
                                       }

                                       if (fread(dma, 1, 0x80, FCB_FP(fcb)) == 0)
                                               A = 1;
                                       else
                                               A = 0;

                                       break;

                               case 21:                                        /* WRITE */
                               case 34:                                        /* WRITE RANDOM */
                               case 40:                                        /* Write Random Zero Fill */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                       {
                                               fprintf(stderr, "BDOS: Write ");
                                               if (C == 21)
                                                       fprintf(stderr, "sequentialrn");

       else if (C == 34)
                                                       fprintf(stderr, "randomrn");
                                               else
                                                       fprintf(stderr, "random with zero fillrn");
                                       }
#endif

                                       fcb = ram + DE;

                                       if (C == 34)
                                       {
                                               f_pos = FCB_RR(fcb);
                                               fseek(FCB_FP(fcb), f_pos * 0x80, SEEK_SET);
                                       }

                                       if (fwrite(dma, 1, 0x80, FCB_FP(fcb)) == 0)
                                               A = 1;
                                       else
                                               A = 0;

                                       break;

                               case 22:                                        /* MAKEF */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Make file %srn",
                                                                                               fcb2real(fcb));
#endif

                                       fcb = ram + DE;
                                       fn = fcb2real(fcb);

                                       if ((FCB_FP(fcb) = fopen(fn, "r")) != NULL)
                                       {
                                               fclose(FCB_FP(fcb));
                                               A = 0xff;
                                               HL = 8;
                                               break;
                                       }

                                       memset(&FCB_FP(fcb), 0, 24);
                                       A = 0xff;

                                       if (strchr(fn, '?') != NULL)
                                               HL = 9;
                                       else if ((FCB_DR(fcb) < 0) || (FCB_DR(fcb) > 16))
                                               HL = 4;
                                       else if (*cpm_drive[FCB_DR(fcb)] == 0)
                                               HL = 1;
                                       else if ((FCB_FP(fcb) = fopen(fn, "w")) == NULL)
                                               HL = 1;
                                       else
                                               A = HL = 0;

#ifdef  CPM_DEBUG
                                       if (HL == 1 && dasm_flag)
                                               fprintf(stderr, "Can't open %s: %srn",
                                                                                               fn, strerror(errno));
#endif
                                       break;

                               case 23:                                        /* RENAME */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Rename filern");
#endif

                                       fcb = ram + DE;
                                       strcpy(fn2, fcb2real(fcb));
                                       fn = fcb2real(fcb + 16);

                                       if (link(fn2, fn) == -1)
                                               A = 0xff;
                                       else
                                       {
                                               unlink(fn2);
                                               A = 0;
                                       }
                                       break;

                               case 24:                                        /* get log in vector */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Get login vectorrn");
#endif

                                       c = 1;
                                       HL = 0;

                                       for (k = 1; k <= 16; k++)
                                       {
                                               if (*cpm_drive[k])
                                                       HL |= c;

                                               c <<= 1;
                                       }

                                       A = L;
                                       break;

                               case 25:
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Return current diskrn");
#endif

                                       A = def_drive - 1;
                                       break;

                               case 26:
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Set DMA addressrn");
#endif

                                       dma = ram + DE;
                                       break;

                               case 29:                                        /*  get R/O vector */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Get read only vectorrn");
#endif

                                       HL = 0;
                                       break;

                               case 32:                                        /* set/get user code */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Set/get user codern");
#endif
                                       if (E == 0xff)
                                               A = 0;

                                       break;

                               case 35:                                        /* get file size */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Compute file sizern");
#endif
                                       fcb = ram + DE;
                                       if (stat(fcb2real(fcb), &s) == -1)
                                       {
                                               A = 0xff;
                                               break;
                                       }

                                       A = 0;
                                       /* fall through */

                               case 36:                                        /* set random record */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Set random recordrn");
#endif

                                       if (C == 36)
                                       {
                                               fcb = ram + DE;
                                               s.st_size = ftell(FCB_FP(fcb));
                                       }

                                       s.st_size >>= 7;
                                       FCB_R0(fcb) = s.st_size & 0xff;
                                       s.st_size >>= 8;
                                       FCB_R1(fcb) = s.st_size & 0xff;
                                       s.st_size >>= 8;
                                       FCB_R2(fcb) = s.st_size & 1;

                                       break;

                               case 37:                                        /* reset drive */
#ifdef  CPM_DEBUG
                                       if (dasm_flag)
                                               fprintf(stderr, "BDOS: Reset drivern");
#endif

                                       A = 0;
                                       break;

                               default:
                                       fprintf(stderr, "arnInvalid BDOS call %drn", C);
                                       return(-3);
                       }
               }
               else if (PC >= BIOS)
               {
                       if (PC % 3)
                       {
                               fprintf(stderr, "arnInvalid BIOS jump 0%04.4xrn", pc);
                               PC = pc;
                               dump_registers(stderr);
                               return(-5);
                       }

#ifdef  CPM_DEBUG
                       if (dasm_flag)
                               fprintf(stderr, "BIOS: Function %drn",
                                                       (PC - BIOS)/(unsigned)(3));
#endif

                       switch (PC)
                       {
                               case bios(0):   /* BOOT */
                               case bios(1):   /* WBOOT */
                                       return(0);

                               case bios(2):   /* CONST */
                                       if ((A = getch(1)) != 0)
                                               A = 0xff;

                                       break;

                               case bios(3):   /* CONIN */
                                       A = getch(0);
                                       break;

                               case bios(4):   /* CONOUT */
                                       fputc(C, stdout);
                                       break;

                               case bios(5):   /* LIST */
                                       fputc(C, list);
                                       break;

                               case bios(6):   /* PUNCH */
                                       fputc(C, punch);
                                       break;

                               case bios(7):   /* READER */
                                       A = fgetc(reader);
                                       break;

                               case bios(15):  /* LISTST */
                                       A = 0xff;               /* it's always ready */
                                       break;

                               default:
                                       PC = pc;
                                       fprintf(stderr, "Unimplemented BIOS jmp 0%04.4xH (%d)rn",
                                                                               PC, (PC - BIOS)/(size_t)(3));
                                       dump_registers(stderr);
                                       return(-6);
                       }
               }

               if (decode())
               {
                       PC = pc;
                       fprintf(stderr, "arnInvalid processor instruction 0x%02.2xrn", ram[PC]);
                       dump_registers(stderr);
                       return(-4);
               }

#ifdef  CPM_DEBUG
               if (dasm_flag > 1 && pc >= BDOS)
                       getch(0);
#endif
       }

       return(0);
}
#endif

#ifndef COMPILE_TEST
static FILE     *open_device(const char *dev, const char *typ)
{
       FILE    *fp;

       if (*dev == '!')
               fp = popen(dev + 1, typ);
       else
               fp = fopen(dev, typ);

       if (fp != NULL)
               return(fp);

       fprintf(stderr, "Error on %srn", dev);
       perror("Can't open virtual device");
       exit(1);
       return(NULL);
}

static int      do_command(char *cmd_str)
{
       char    entry[256];
       FILE    *fp;

       /* allow comments */
       if ((*cmd_str == ';') || (*cmd_str == '#'))
               return(0);

       /* get our own copy of the command */
       strcpy(entry, cmd_str);

       /* request for Unix shell escape? */
       if (*entry == '!')
       {
               int             r;

               ioctl(0, TCSETA, &old_term);
               r = system(entry + 1);
               ioctl(0, TCSETA, &termp);
               return(r);
       }

       strtolow(entry);
       tail = chop_cmd(entry);
       user_break = 0;

       /* check for request to change default drive */
       if ((isspace(entry[2]) || (entry[2] == 0)) && (entry[1] == ':'))
       {
               *entry &= 0x0f;

               if ((*entry < 1) || (*entry > MAX_DRIVES) || *cpm_drive[*entry] == 0)
               {
                       fprintf(stderr, "arnInvalid drive specificationrn");
                       return(-1);
               }

               if (chdir(cpm_drive[*entry]))
               {
                       fprintf(stderr, "arnCan't change to drive %c: %srn",
                                               entry + '@', strerror(errno));
                       return(-1);
               }

               def_drive = *entry;
               strcpy(cpm_drive[0], cpm_drive[def_drive]);
               entry[0] = entry[1] = ' ';
               tail = chop_cmd(entry);
       }

       if (*entry == 0)
               return(0);

       /* handle some builtins */
       if (strcmp(entry, "dir") == 0)
               fsystem("ls -C %s", tail);
       else if (strcmp(entry, "dump") == 0)
               fsystem("hd %s", tail);
       else if (strcmp(entry, "ed") == 0)
               fsystem("$EDITOR %s", tail);
       else if (strcmp(entry, "era") == 0)
               fsystem("rm %s", tail);
#ifdef  CPM_DEBUG
       else if (strcmp(entry, "dasm") == 0)
       {
               if(++dasm_flag > 2)
                       dasm_flag = 0;

               fprintf(stderr, "DASM is %drn", dasm_flag);
       }
#endif
       else if (strcmp(entry, "save") == 0)
       {
               char    *fname = chop_cmd(tail);
               int             p = atoi(tail);

               if ((p == 0) || (*fname == 0))
                       fprintf(stderr, "Usage: SAVE #pages filenamern");
               else
               {
                       if ((fp = fopen(real_name(fname), "wb")) == NULL)
                               perror("aCan't open save file");
                       else
                       {
                               if (fwrite(ram + 256, 256, p, fp) != p)
                                       perror("aCan't write to file");

                               fclose(fp);
                       }
               }
       }
       else if (strcmp(entry, "type") == 0)
       {
               char    *ptr;

               while (*tail)
               {
                       ptr = tail;
                       tail = chop_cmd(ptr);
                       fsystem("cat %s", ptr);
               }
       }
       else if (strcmp(entry, "exit") == 0)
               cleanup(0);
       else
#ifdef  CPM_DEBUG
       {
               time_t  start = time(NULL);
               int             r = run(real_name(entry));

               fprintf(stderr, "Run took %ld secondsnr", time(NULL) - start);
               return(r);
       }
#else
               return(run(real_name(entry)));
#endif

       return(0);
}
#endif

int             main(int argc, char **argv)
{
#ifdef  COMPILE_TEST
       /* test code useful for testing different architectures */
       int             test;

       dasm_flag = 1;
       test = (int)(&acc);
       printf("Position of A = %dn", (int)(&A) - test);
       printf("Position of FLAGS = %dn", (int)(&FLAGS) - test);

       test = (int)(&gr);
       printf("Position of B = %dn", (int)(&B) - test);
       printf("Position of C = %dn", (int)(&C) - test);
       printf("Position of BC = %dn", (int)(&BC) - test);
       printf("Position of D = %dn", (int)(&D) - test);
       printf("Position of E = %dn", (int)(&E) - test);
       printf("Position of DE = %dn", (int)(&DE) - test);
       printf("Position of H = %dn", (int)(&H) - test);
       printf("Position of L = %dn", (int)(&L) - test);
       printf("Position of HL = %dn", (int)(&HL) - test);

       AF = 0x1234;
       printf("AF = %04.4x, A = %02.2x, FLAGS = %02.2xn", AF, A, FLAGS);
       printf("Flags: S=%d Z=%d H=%d P=%d N=%d C=%dn",
                       SIGN, ZERO, HALF_CARRY, PARITY, BCD, CARRY);
       acc_bank = 1;
       AF = 0x4321;
       printf("AF = %04.4x, A = %02.2x, FLAGS = %02.2xn", AF, A, FLAGS);
       printf("Flags: S=%d Z=%d H=%d P=%d N=%d C=%dn",
                       SIGN, ZERO, HALF_CARRY, PARITY, BCD, CARRY);

       BC = 0x2345;
       printf("BC = %04.4x, B = %02.2x, C = %02.2xn", BC, B, C);
       gr_bank = 1;
       BC = 0x5432;
       printf("BC = %04.4x, B = %02.2x, C = %02.2xn", BC, B, C);
       gr_bank = 0;

       DE = 0x3456;
       printf("DE = %04.4x, D = %02.2x, E = %02.2xn", DE, D, E);
       gr_bank = 1;
       DE = 0x6543;
       printf("DE = %04.4x, D = %02.2x, E = %02.2xn", DE, D, E);
       gr_bank = 0;

       HL = 0x4567;
       printf("HL = %04.4x, H = %02.2x, L = %02.2xn", HL, H, L);
       gr_bank = 1;
       HL = 0x7654;
       printf("HL = %04.4x, H = %02.2x, L = %02.2xn", HL, H, L);
       gr_bank = 0;

       A = BC = DE = HL = SP = PC = 0;

       while (PC < TEST_SIZE)
       {
               dump_registers(stdout);

               if (decode())
               {
                       printf("* * * Processor error * * *n");
                       exit(1);
               }
       }

       dump_registers(stdout);

       for (test = 0; test < 0x10; test++)
               printf("%02.2x ", ram[test]);

       printf("n");
       for (test = 0xfff0; test < 0x10000; test++)
               printf("%02.2x ", ram[test]);

       printf("nTest code ended normallyn");
       return(0);
#else   /* COMPILE_TEST */

       char    entry[256] = "";
       int             c;

       /* make current directory the default one */
       getcwd(cpm_drive[1], 127);

       while ((c = getopt(argc, argv, "d:c:r:p:h")) != -1)
       {
               char    *ptr;
               int             k;

               switch (c)
               {
                       case 'd':
                               ptr = optarg + 2;

                               if (optarg[1] == ':')
                                       k = *optarg & 0x0f;
                               else
                               {
                                       k = 1;
                                       ptr = optarg;

                                       while ((k < 17) && (*cpm_drive[k]))
                                               k++;
                               }

                               if ((k < 1) || (k > 16))
                               {
                                       fprintf(stderr, "Can't set up %sn", optarg);
                                       exit(1);
                               }

                               strcpy(cpm_drive[k], ptr);
                               break;

                       case 'c':
                               strcpy(entry, optarg);
                               break;

                       case 'r':
                               reader = open_device(optarg, "r");
                               break;

                       case 'p':
                               punch = open_device(optarg, "w");
                               break;

                       case 'l':
                               list = open_device(optarg, "w");
                               break;

                       default:
                               fprintf(stderr, "Usage:n");
                               fprintf(stderr, "cpm [options]n");
                               fprintf(stderr, "  Options:n");
                               fprintf(stderr,
"    -d [d:]directory    map CP/M drive to Unix directory.  If then");
                               fprintf(stderr,
"                        second character is not a colon then the nextn");
                               fprintf(stderr,
"                        available drive letter is used otherwise then");
                               fprintf(stderr,
"                        letter preceding the colon is used.n");
                               fprintf(stderr,
"    -c command          runs the command file then exits.  builtinsn");
                               fprintf(stderr,
"                        not supported.  COM file must existn");
                               fprintf(stderr,
"    -[r|p|l] dev        This allows the I/O to be routed through then");
                               fprintf(stderr,
"                        Unix file system.  The devices mapped are asn");
                               fprintf(stderr,
"                        follows: r = RDR input, p = PUN output and l forn");
                               fprintf(stderr,
"                        LST output.  The dev argument is opened as a Unixn");
                               fprintf(stderr,
"                        file and I/O for specified device is done throughn");
                               fprintf(stderr,
"                        it.  If the first character is '!' then the restn");
                               fprintf(stderr,
"                        of the line is taken as a command and popen() isn");
                               fprintf(stderr,
"                        called to handle the I/O.n");
                               fprintf(stderr,
"    -h                  Show this help screenn");

                               exit(1);
               }
       }

       strcpy(cpm_drive[0], cpm_drive[1]);
       def_drive = 1;

       /* set up terminal */
       if (ioctl(0, TCGETA, &old_term) == -1)
       {
               perror("Can't get terminal parameters");
               exit(-1);
       }

       termp = old_term;
       termp.c_oflag =  0;
       termp.c_lflag =  ISIG;
       termp.c_cc[VEOF] = 1;
       termp.c_cc[VSWTCH] = -1;

       if (ioctl(0, TCSETAW, &termp) == -1)
       {
               perror("Can't set terminal parameters");
               exit(1);
       }

       signal(SIGHUP, cleanup);
       signal(SIGINT, cleanup);
       signal(SIGQUIT, cleanup);
       signal(SIGTERM, cleanup);

       setbuf(stdout, NULL);

       /* tell them who we are - note stderr */
       fprintf(stderr, "nCP/U - Control Program for Unixrn");
       fprintf(stderr, "CP/M emulator Version 0.920rn");
       fprintf(stderr, "Written by D'Arcy J.M. Cainrn");
       fprintf(stderr, "[email protected]");

       /* see if we have a command to run */
       if (*entry)
       {
               do_command(entry);
               ioctl(0, TCSETA, &old_term);
               return(0);
       }

       for (;;)
       {
               fprintf(stderr, "%c> ", def_drive + '@');
               entry[get_str(entry, 128)] = 0;

               if (*entry)
               {
                       if (do_command(entry) == -1)
                       {
                               chop_cmd(entry);
                               strtoup(entry);
                               fprintf(stderr, "%s?rn", entry);
                       }
               }
       }
#endif  /* COMPILE_TEST */
}