/*****************************************************************************/ /* */ /* */ /* CP/M emulator version 0.1 */ /* */ /* written by Michael Bischoff (mbi@mo.math.nat.tu-bs.de) */ /* June-1994 */ /* */ /* This file is distributed under the GNU COPYRIGHT */ /* see COPYRIGHT.GNU for Copyright details */ /* */ /* */ /*****************************************************************************/ #include "cpmemu.h" #include #include struct z80regs z80regs, z80regs0 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; unsigned short dmaaddr = 0x80; unsigned short usercode = 0x00; unsigned char z80mem[65536L+6]; static int SPT = 64; union zeropage { struct c { uchar jpbyte0; ushrt wbootvec; uchar iobyte; uchar usrdrv; uchar jpbyte5; ushrt bdosvec; uchar fill1[0x54]; uchar fcb[36]; uchar dma[128]; }; uchar z[256]; } zeropage; static char zeropage0[8] = { 0xc3, (BIOS+3) & 0xff, (BIOS+3) >> 8, 0x80, 0x00, 0xc3, BDOS & 0xff, BDOS >> 8 }; static char cpmsys[0x1600]; unsigned bios_dma = 0x80, bios_track = 0, bios_sector = 0; static FILE *bios_fp = NULL; static const char *cpmimage; /* BIOS data locations: */ #define DPH0 (BIOS+0x36U) #define DPB0 (DPH0+16U) #define DATA_START (DPB0+15U) #define DIRBUF 0xff80U /* fixed! */ static void make_jumpers(void) { int i; for (i = 0; i < 18; ++i) { /* make jump table at BIOS start. This HAS to be a true jump table, */ /* since some programs rely on this */ z80mem[BIOS+3*i] = 0xc3; /* Z80-jump instruction */ z80mem[BIOS+3*i+1] = DIRBUF - 20 + i; z80mem[BIOS+3*i+2] = DIRBUF >> 8; } z80mem[0x38] = 0xc9; /* no interrupt */ } /* just for information: */ static struct dph { ushrt xlat, scr1, scr2, scr3; ushrt dirbuf; ushrt dpb; ushrt csv; ushrt alv; } dph0 = { 0, 0, 0, 0, DIRBUF, DPB0, 0, 0 }; void cpm_init(const char *imagefile, const char *systemfile, struct dpb *dpb0) { register unsigned i; FILE *fp; cpmimage = imagefile; memset(z80mem+0x100, 0x76, sizeof(z80mem)-0x100); /* HALT insn */ if (!(fp = fopen(systemfile, "rb"))) { fprintf(stderr, "cpm: cannot open system file \"%s\"\n", systemfile); exit(1); } fread(cpmsys, 0x1600, 1, fp); if (!cpmsys[0] && !cpmsys[1] && !cpmsys[2]) { memmove(cpmsys, cpmsys+5, 0x806); memmove(cpmsys+0x806, cpmsys+0x806+7, 0xe00-13); } fclose(fp); for (i = 0; i <= NBREAKS; ++i) breakpoint[i].action = 0; for (i = 0; i < 8; ++i) zeropage.z[i] = zeropage0[i]; for (i = 8; i < 256; ++i) zeropage.z[i] = 0x76; memcpy(z80mem, zeropage.z, 0x100); memcpy(z80mem+BIOS-0x1600, cpmsys, 0x1600); dpb0->blm = (1 << dpb0->bsh) - 1; dpb0->exm = dpb0->blm >> 3; if (dpb0->dsm >= 256) dpb0->exm >>= 1; /* build alloc0, alloc1 bitmasks */ { int bits = ((int)dpb0->drm + 1) / ((int)dpb0->blm + 1) / 4; if (bits <= 8) { dpb0->alloc0 = 256 - (256 >> bits); dpb0->alloc1 = 0; } else { dpb0->alloc0 = 255; dpb0->alloc1 = 256 - (256 >> (bits-8)); } } SPT = dpb0->spt; /* Transfer dpb */ z80mem[DPB0+ 0] = dpb0->spt & 0xff; z80mem[DPB0+ 1] = dpb0->spt >> 8; z80mem[DPB0+ 2] = dpb0->bsh; z80mem[DPB0+ 3] = dpb0->blm; z80mem[DPB0+ 4] = dpb0->exm; z80mem[DPB0+ 5] = dpb0->dsm & 0xff; z80mem[DPB0+ 6] = dpb0->dsm >> 8; z80mem[DPB0+ 7] = dpb0->drm & 0xff; z80mem[DPB0+ 8] = dpb0->drm >> 8; z80mem[DPB0+ 9] = dpb0->alloc0; z80mem[DPB0+10] = dpb0->alloc1; z80mem[DPB0+11] = dpb0->cks & 0xff; z80mem[DPB0+12] = dpb0->cks >> 8; z80mem[DPB0+13] = dpb0->off & 0xff; z80mem[DPB0+14] = dpb0->off >> 8; i = DATA_START; /* begin allocating csv & alv */ dph0.csv = i; i += dpb0->cks; dph0.alv = i; i += (dpb0->dsm+1 + 7) >> 3; if (i > DIRBUF-16) { fprintf(stderr, "Error configuring BIOS tables: out of memory!\n"); exit(1); } /* Transfer dph */ memcpy(z80mem+DPH0, &dph0, 16); make_jumpers(); z80regs.sp = 0x80; } void loadfile(const char *name) { FILE *fp; unsigned p; if (!(fp = fopen(name, "rb"))) { fprintf(stderr, "cpm: cannot open file \"%s\"\n", name); exit(1); } p = 256; while (fread(z80mem+p, 1, 256, fp) == 256) p += 256; fclose(fp); } void cpminit2(int argc, char *argv[]) { z80mem[0x80] = 0; z80mem[0x81] = 0x0d; z80regs = z80regs0; z80regs.pc = BIOS-0x1600; z80regs.bc = 0; if (argc) { /* transfer command line into CCP */ int i, p; p = BIOS - 0x1600 + 8; for (i = 0; i < argc; ++i) { char *s; /* transfer one arg */ if (i) z80mem[p++] = ' '; for (s = argv[i]; *s; ) z80mem[p++] = *s++; if (p > BIOS - 0x1600 + 128) break; } z80mem[p] = 0; z80mem[BIOS - 0x1600 + 7] = p - (BIOS - 0x1600 + 8); { unsigned char *s; for (s = z80mem + BIOS - 0x1600 + 8; *s; ++s) if (islower(*s)) *s = toupper(*s); } silent_exit = 1; /* patch CCP return: */ for (p = BDOS; p > BIOS-0x1600; --p) if (z80mem[p] == 0xcd && z80mem[p+1] == 0x00 && z80mem[p+2] == 0x01) { z80mem[p+3] = 0xc3; z80mem[p+4] = (unsigned char)(BIOS & 0xffU); z80mem[p+5] = (unsigned char)(BIOS>>8); break; } if (p == BIOS-0x1600) { fprintf(stderr, "FATAL: cannot patch CCP return address\n"); exit(1); } } } /* return 0 = invalid (TRAP), 1 = BIOS hook */ extern jmp_buf mainloop; static int storedfps = 0; static void open_image(void) { if (!(bios_fp = fopen(cpmimage, "r+b"))) { fprintf(stderr, "cpm: cannot open disk image \"%s\"\n", cpmimage); exit(1); } } int check_BIOS_hook(void) { int i; static uchar buffbios[128]; static int currdsk = 0; if (z80regs.pc >= BIOS) { switch (z80regs.pc) { case BIOS: /* System Reset (coldboot) */ case DIRBUF-20: if (!silent_exit) printf("\nCp/M BIOS COLDBOOT takes you back to FreeBSD\n"); exit(0); case BIOS+3: /* System Reset (warmboot) */ case DIRBUF-19: if (silent_exit) exit(0); storedfps = 0; /* clean up! */ for (i = 0; i < 0x1600; ++i) z80mem[i+BIOS-0x1600] = cpmsys[i]; for (i = 0; i < 8; ++i) z80mem[i] = zeropage.z[i]; z80regs.bc = 0; z80regs.pc = BIOS-0x1600+3; z80regs.sp = 0x80; make_jumpers(); if (bios_fp) fflush(bios_fp); /* prepare for disk change */ return 1; case BIOS+6: /* Console Status */ case DIRBUF-18: z80regs.af = kbhit() ? 0xff : 0x00; ret1: z80mem[z80regs.pc=DIRBUF-1] = 0xc9; return 1; case BIOS+9: /* Console In */ case DIRBUF-17: z80regs.af = conin(); goto ret1; case BIOS+12: /* Console Out */ case DIRBUF-16: vt52(z80regs.bc & 0xff); goto ret1; case BIOS+15: /* List Out */ case DIRBUF-15: goto ret1; case BIOS+18: /* Punch Out */ case DIRBUF-14: goto ret1; case BIOS+21: /* Reader In */ case DIRBUF-13: z80regs.af = 0; goto ret1; case BIOS+24: /* Home */ case DIRBUF-12: bios_track = 0; bios_sector = 0; goto ret1; case BIOS+27: /* Seldsk */ case DIRBUF-11: currdsk = z80regs.bc & 0xff; if (z80regs.bc & 0xff) z80regs.hl = 0xffff; else z80regs.hl = DPH0; goto ret1; case BIOS+30: /* Settrack */ case DIRBUF-10: bios_track = z80regs.bc; goto ret1; case BIOS+33: /* Set sector */ case DIRBUF- 9: bios_sector = z80regs.bc & 0xff; goto ret1; case BIOS+36: /* Set dma */ case DIRBUF- 8: bios_dma = z80regs.bc; goto ret1; case BIOS+39: /* Read */ case DIRBUF- 7: if (currdsk) { fprintf(stderr, "BDOS Err on %c: Select\n", currdsk+'A'); exit(1); } if (!bios_fp) open_image(); fseek(bios_fp, ((long)bios_sector + (long)SPT * (long)bios_track) * 128L, 0); fread(buffbios, 1, 128, bios_fp); for (i = 0; i < 128; ++i) z80mem[i + bios_dma] = buffbios[i]; z80regs.af = 0; goto ret1; case BIOS+42: /* Write */ case DIRBUF- 6: if (currdsk) { fprintf(stderr, "BDOS Err on %c: Select\n", currdsk+'A'); exit(1); } if (!bios_fp) open_image(); fseek(bios_fp, ((long)bios_sector + (long)SPT * (long)bios_track) * 128L, 0); for (i = 0; i < 128; ++i) buffbios[i] = z80mem[i + bios_dma]; fwrite(buffbios, 1, 128, bios_fp); z80regs.af = 0; goto ret1; case BIOS+45: /* List Status */ case DIRBUF- 5: z80regs.af = 0; /* not ready */ goto ret1; case BIOS+48: /* Sectran */ /* no sectran */ case DIRBUF- 4: z80regs.hl = z80regs.bc & 0xff; goto ret1; default: printf("Program traps in BIOS:\n"); debug = 1; longjmp(mainloop, 1); } } return 0; } /* #include "cpmemu.h" */ #if 0 static struct FCB { char drive; char name[11]; char data[24]; } samplefcb; #endif static void FCB_to_filename(unsigned char *p, char *name) { int i; /* strcpy(name, "test/"); name += 5; */ for (i = 0; i < 8; ++i) if (p[i+1] != ' ') *name++ = tolower(p[i+1]); if (p[9] != ' ') { *name++ = '.'; for (i = 0; i < 3; ++i) if (p[i+9] != ' ') *name++ = tolower(p[i+9]); } *name = '\0'; } static struct stfps { FILE *fp; unsigned where; char name[12]; } stfps[100]; static void storefp(FILE *fp, unsigned where) { int i; int ind = -1; for (i = 0; i < storedfps; ++i) if (stfps[i].where == 0xffffU) ind = i; else if (stfps[i].where == where) { ind = i; goto putfp; } if (ind < 0) { if (++storedfps > 100) { fprintf(stderr, "out of fp stores!\n"); exit(1); } ind = storedfps - 1; } stfps[ind].where = where; putfp: stfps[ind].fp = fp; memcpy(stfps[ind].name, z80mem+z80regs.de+1, 11); stfps[ind].name[11] = '\0'; } #if 1 static FILE *getfp(unsigned where) { int i; for (i = 0; i < storedfps; ++i) if (stfps[i].where == where) { /* check name? */ return stfps[i].fp; } /* fcb not found. maybe it has been moved? */ for (i = 0; i < storedfps; ++i) if (stfps[i].where != 0xffffU && !memcmp(z80mem+z80regs.de+1, stfps[i].name, 11)) { stfps[i].where = where; /* moved FCB */ return stfps[i].fp; } fprintf(stderr, "error: cannot find fp entry for FCB at %04x" " fctn %d, FCB named %s\n", where, z80regs.bc & 0xff, z80mem+where+1); for (i = 0; i < storedfps; ++i) if (stfps[i].where != 0xffffU) printf("%s %04x\n", stfps[i].name, stfps[i].where); exit(1); } static void delfp(unsigned where) { int i; for (i = 0; i < storedfps; ++i) if (stfps[i].where == where) { stfps[i].where = 0xffffU; return; } fprintf(stderr, "error: cannot del fp entry for FCB at %04x\n", where); exit(1); } #endif #include #include #define ADDRESS (((long)z80mem[z80regs.de+33] + \ (long)z80mem[z80regs.de+34] * 256) * 128L) /* (long)z80mem[z80regs.de+35] * 65536L; */ static DIR *dp = NULL; static unsigned sfn = 0; /* emulation of BDOS calls */ void check_BDOS_hook(void) { int i; char name[32]; char name2[32]; struct stat stbuf; FILE *fp; char *s, *t; const char *mode; switch (z80regs.bc & 0xff) { case 0: /* System Reset */ if (silent_exit) { /* printf("\nProgram terminates normally (BDOS function 0)\n"); */ exit(0); } for (i = 0; i < 0x1600; ++i) z80mem[i+BIOS-0x1600] = cpmsys[i]; z80regs.bc = 0; z80regs.pc = BIOS-0x1600+3; z80regs.sp = 0x80; break; case 1: /* Console Input */ z80regs.af = z80regs.hl = conin(); if ((z80regs.hl & 0xff) < ' ') { switch(z80regs.hl) { case '\r': case '\n': case '\t': putch(z80regs.hl); break; default: putch('^'); putch((z80regs.hl & 0xff)+'@'); if (z80regs.hl == 3) { /* ctrl-C pressed */ z80regs.pc = BIOS+3; check_BIOS_hook(); return; } } } else { vt52(z80regs.hl); } break; case 2: /* Console Output */ vt52(z80regs.de & 0xff); break; case 6: /* direct I/O */ switch (z80regs.de & 0xff) { case 0xff: if (!kbhit()) { z80regs.af = z80regs.hl = 0; break; } case 0xfd: z80regs.af = z80regs.hl = conin(); break; case 0xfe: z80regs.af = z80regs.hl = kbhit() ? 0xff : 0; break; default: vt52(z80regs.de & 0xff); } break; case 9: /* Print String */ s = z80mem +z80regs.de; while (*s != '$') vt52(*s++); break; case 10: /* Read Command Line */ s = rdcmdline(*(t = z80mem+z80regs.de), 1); if (z80regs.pc == BIOS+3) { /* ctrl-C pressed */ check_BIOS_hook(); /* execute WBOOT */ return; } ++t; for (i = 0; i <= *s; ++i) t[i] = s[i]; break; case 12: /* Return Version Number */ z80regs.af = z80regs.hl = 0x22; /* emulate Cp/M 2.2 */ break; case 26: /* Set DMA Address */ dmaaddr = z80regs.de; break; case 32: /* Get/Set User Code */ if ((z80regs.de & 0xff) == 0xff) /* Get Code */ z80regs.af = z80regs.hl = usercode; else usercode = z80regs.de & 0x0f; break; /* dunno if these are correct */ case 11: /* Console Status */ z80regs.af = z80regs.hl = kbhit() ? 0xff : 0x00; break; case 13: /* reset disk system */ /* storedfps = 0; */ /* WS crashes then */ if (dp) closedir(dp); dp = NULL; dmaaddr = 0x80; /* select only A:, all r/w */ break; case 14: /* select disk */ break; case 15: /* open file */ mode = "r+b"; fileio: FCB_to_filename(z80mem+z80regs.de, name); if (!(fp = fopen(name, mode))) { if (*mode == 'r') { char ss[50]; sprintf(ss, "%s/%s", CPMLIBDIR, name); fp = fopen(ss, "rb"); } if (!fp) { /* still no success */ z80regs.af = z80regs.hl = 0xff; break; } } /* success */ memset(z80mem+z80regs.de+12, 0, 33-12); z80mem[z80regs.de+15] = 0; /* rc field of FCB */ if ((fstat(fileno(fp), &stbuf)==-1) || !S_ISREG(stbuf.st_mode)) { z80regs.af = z80regs.hl = 0xff; fclose(fp); break; } { unsigned long pos; pos = (stbuf.st_size + 127) >> 7; /* number of records */ if (pos > 128) z80mem[z80regs.de+15] = 0x80; else z80mem[z80regs.de+15] = pos; } z80regs.af = z80regs.hl = 0; /* where to store fp? */ storefp(fp, z80regs.de); /* printf("opening file %s\n", name); */ break; case 16: /* close file */ fp = getfp(z80regs.de); delfp(z80regs.de); fclose(fp); break; case 17: /* search for first */ if (dp) closedir(dp); if (!(dp = opendir("."))) { fprintf(stderr, "opendir fails\n"); exit(1); } sfn = z80regs.de; /* fall through */ case 18: /* search for next */ if (!dp) goto retbad; { struct dirent *de; unsigned char *p; const char *sr; int len; nocpmname: if (!(de = readdir(dp))) { closedir(dp); dp = NULL; retbad: z80regs.af = z80regs.hl = 0xff; break; } /* compare data */ memset(p = z80mem+dmaaddr, 0, 128); /* dmaaddr instead of DIRBUF!! */ if (*de->d_name == '.') goto nocpmname; len = strlen(de->d_name); if (strchr(sr = de->d_name, '.')) { if (len > 12) goto nocpmname; } else if (len > 8) goto nocpmname; /* seems OK */ for (i = 0; i < 8; ++i) if (*sr != '.' && *sr) { *++p = toupper(*sr); sr++; } else *++p = ' '; /* skip dot */ while (*sr && *sr != '.') ++sr; while (*sr == '.') ++sr; for (i = 0; i < 3; ++i) if (*sr != '.' && *sr) { *++p = toupper(*sr); sr++; } else *++p = ' '; /* OK, fcb block is filled */ /* match name */ p -= 11; sr = z80mem+sfn; for (i = 1; i <= 12; ++i) if (sr[i] != '?' && sr[i] != p[i]) goto nocpmname; /* yup, it matches */ z80regs.af = z80regs.hl = 0x00; /* always at pos 0 */ p[32] = p[64] = p[96] = 0xe5; } break; case 19: /* delete file (no wildcards yet) */ FCB_to_filename(z80mem+z80regs.de, name); unlink(name); break; case 20: /* read sequential */ fp = getfp(z80regs.de); readseq: if ((i = fread(z80mem+dmaaddr, 1, 128, fp)) > 0) { if (i != 128) memset(z80mem+dmaaddr+i, 0x1a, 128-i); z80regs.af = z80regs.hl = 0x00; } else z80regs.af = z80regs.hl = 0x1; /* ff => pip error */ break; case 21: /* write sequential */ fp = getfp(z80regs.de); writeseq: if (fwrite(z80mem+dmaaddr, 1, 128, fp) == 128) z80regs.af = z80regs.hl = 0x00; else z80regs.af = z80regs.hl = 0xff; break; case 22: /* make file */ mode = "w+b"; goto fileio; case 23: /* rename file */ FCB_to_filename(z80mem+z80regs.de, name); FCB_to_filename(z80mem+z80regs.de+16, name2); /* printf("rename %s %s called\n", name, name2); */ rename(name, name2); break; case 24: /* return login vector */ z80regs.af = z80regs.hl = 1; /* only A: online */ break; case 25: /* return current disk */ z80regs.af = z80regs.hl = 0; /* only A: */ break; case 29: /* return r/o vector */ z80regs.af = z80regs.hl = 0; /* none r/o */ break; case 33: /* read random record */ fp = getfp(z80regs.de); /* printf("data is %02x %02x %02x\n", z80mem[z80regs.de+33], z80mem[z80regs.de+34], z80mem[z80regs.de+35]); */ if(fseek(fp, ADDRESS, SEEK_SET)==-1) { z80regs.af = z80regs.hl = 0x1; /* ff => pip error */ } goto readseq; case 34: /* write random record */ fp = getfp(z80regs.de); /* printf("data is %02x %02x %02x\n", z80mem[z80regs.de+33], z80mem[z80regs.de+34], z80mem[z80regs.de+35]); */ if(fseek(fp, ADDRESS, SEEK_SET)==-1) { z80regs.af = z80regs.hl = 0x1; /* ff => pip error */ } goto writeseq; case 35: /* compute file size */ { long pos, oldpos; fp = getfp(z80regs.de); oldpos = ftell(fp); fseek(fp, 0L, SEEK_END); pos = ftell(fp) >> 7; /* native files may very well *not* be exact multiples of * 128 long, so add extra `record' (to simulate what the * length would be on CP/M) if it isn't. -rjm */ if ((ftell(fp) & 127) != 0) pos++; fseek(fp, oldpos, SEEK_SET); z80regs.af = z80regs.hl = 0x00; /* dunno, if necessary */ z80mem[z80regs.de+0x21] = pos & 0xff; z80mem[z80regs.de+0x22] = pos >> 8; z80mem[z80regs.de+0x23] = pos >> 16; } break; case 36: /* set random record */ fp = getfp(z80regs.de); { long pos; pos = ftell(fp) >> 7; z80regs.af = z80regs.hl = 0x00; /* dunno, if necessary */ z80mem[z80regs.de+0x21] = pos & 0xff; z80mem[z80regs.de+0x22] = pos >> 8; z80mem[z80regs.de+0x23] = pos >> 16; } break; case 41: for (s = z80mem+z80regs.de; *s; ++s) *s = tolower(*s); z80regs.af = z80regs.hl = restricted_mode || chdir(z80mem+z80regs.de) ? 0xff : 0x00; break; case 48: /* needs to be tolerated for ZDE not to bomb out * (CP/M 2.2 would just have ignored it, you see). * Presumably some other programs need this too. -rjm */ z80regs.af = z80regs.hl = 0; /* not really necessary but nicer */ break; default: printf("\n\nUnrecognized BDOS-Function:\n"); printf("AF=%04x BC=%04x DE=%04x HL=%04x SP=%04x\nStack =", z80regs.af, z80regs.bc, z80regs.de, z80regs.hl, z80regs.sp); for (i = 0; i < 8; ++i) printf(" %4x", z80mem[z80regs.sp+2*i] + 256 * z80mem[z80regs.sp+2*i+1]); printf("\n"); exit(1); } z80mem[z80regs.pc=DIRBUF-1] = 0xc9; return; }