+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
#if ALT_BACKEND
#include "8080/i8080.h"
#else
uint8_t io_read[IO_SIZE];
uint8_t io_write[IO_SIZE];
+// BDOS emulation
+uint16_t bdos_dma;
+
+// drive to directory mapping
+// string from bdos_drives[] is prepended to filename from FCB
+// string must be less than ~0xf0 bytes long so eventual path doesn't overflow
+#define N_BDOS_DRIVES 1
+char *bdos_drives[N_BDOS_DRIVES] = {""};
+
+#define N_BDOS_FILES 10
+struct bdos_file {
+ int fd; // -1 if not in use
+ uint16_t fcb;
+} bdos_files[N_BDOS_FILES];
+
int load_ihx(char *name) {
FILE *fp = fopen(name, "r");
if (fp == NULL) {
return entry_point;
}
+// extract path from FCB
+bool fcb_path(uint16_t fcb, char *path) {
+ int drive = mem[fcb];
+ if (drive >= N_BDOS_DRIVES) {
+ errno = ENOTDIR;
+ return false;
+ }
+ char *q = path;
+ for (char *p = bdos_drives[drive]; *p; ++p)
+ *q++ = *p;
+ char *p = q;
+ for (int i = 0; i < 8; ++i)
+ *q++ = tolower(mem[(fcb + 1 + i) & 0xffff] & 0x7f);
+ while (q > p && q[-1] == ' ')
+ --q;
+ *q++ = '.';
+ p = q;
+ for (int i = 0; i < 3; ++i)
+ *q++ = tolower(mem[(fcb + 9 + i) & 0xffff] & 0x7f);
+ while (q > p && q[-1] == ' ')
+ --q;
+ if (q == p)
+ --q;
+ *q = 0;
+ return true;
+}
+
+bool fcb_open(uint16_t fcb, char *path, int flags, int mode) {
+ for (int i = 0; i < N_BDOS_FILES; ++i)
+ if (bdos_files[i].fd < 0) {
+ int fd = open(path, flags, mode);
+ if (fd == -1)
+ return false;
+ bdos_files[i] = (struct bdos_file){.fd = fd, .fcb = fcb};
+ return true;
+ }
+ errno = EMFILE;
+ return false;
+}
+
+int fcb_read(uint16_t fcb, uint16_t dma) {
+ for (int i = 0; i < N_BDOS_FILES; ++i)
+ if (bdos_files[i].fd >= 0 && bdos_files[i].fcb == fcb) {
+ uint8_t buf[0x80];
+ int count = (int)read(bdos_files[i].fd, buf, 0x80);
+ if (count == -1)
+ return -1;
+ if (count == 0)
+ return 0;
+ int j;
+ for (j = 0; j < count; ++j)
+ mem[(dma + j) & 0xffff] = buf[j];
+ for (; j < 0x80; ++j)
+ mem[(dma + j) & 0xffff] = 0x1a;
+ return 1;
+ }
+ errno = EBADFD;
+ return -1;
+}
+
+int fcb_write(uint16_t fcb, uint16_t dma) {
+ for (int i = 0; i < N_BDOS_FILES; ++i)
+ if (bdos_files[i].fd >= 0 && bdos_files[i].fcb == fcb) {
+ uint8_t buf[0x80];
+ for (int j = 0; j < 0x80; ++j)
+ buf[j] = mem[(dma + j) & 0xffff];
+ int count = (int)write(bdos_files[i].fd, buf, 0x80);
+ if (count == -1)
+ return -1;
+ if (count < 0x80)
+ return 0;
+ return 1;
+ }
+ errno = EBADFD;
+ return false;
+}
+
+bool fcb_close(uint16_t fcb) {
+ for (int i = 0; i < N_BDOS_FILES; ++i)
+ if (bdos_files[i].fd >= 0 && bdos_files[i].fcb == fcb) {
+ close(bdos_files[i].fd);
+ bdos_files[i].fd = -1;
+ return true;
+ }
+ errno = EBADFD;
+ return false;
+}
+
int bdos(int a, int c, int de, uint16_t *hl) {
switch (c) {
- case 2:
- // Character output
+ case 2: // C_WRITE - Console output
de &= 0xff;
switch (de) {
case '\n':
}
break;
#if 0 // unlike MS-DOS version, the CP/M BASIC-80 uses direct BIOS calls
- case 6:
- // Direct console I/O
+ case 6: // C_RAWIO - Direct console I/O
de &= 0xff;
if (de == 0xff) {
int data = getchar();
}
break;
#endif
- case 9:
- // Display string
+ case 9: // C_WRITESTR - Output string
for (int data; (data = mem[de]) != '$'; de = (de + 1) & 0xffff)
switch (data) {
case '\n':
break;
}
break;
- case 0xc:
- // Get CP/M version
+ case 0xc: // S_BDOSVER - Return version number
*hl = 0x22;
break;
+ case 0xf: // F_OPEN - Open file
+ {
+ char path[0x100];
+ if (!fcb_path(de, path)) {
+ perror(path);
+ a = 0xff;
+ break;
+ }
+ fcb_close(de);
+ if (!fcb_open(de, path, O_RDWR, 0)) {
+ perror(path);
+ a = 0xff;
+ break;
+ }
+ a = 0;
+ }
+ break;
+ case 0x10: // F_CLOSE - Close file
+ if (!fcb_close(de)) {
+ a = 0xff;
+ break;
+ }
+ a = 0;
+ break;
+ case 0x13: // F_DELETE - delete file
+ // note: for now, we don't treat the ? character as a wildcard
+ {
+ char path[0x100];
+ if (!fcb_path(de, path)) {
+ perror(path);
+ a = 0xff;
+ break;
+ }
+ if (unlink(path) == -1) {
+ perror(path);
+ a = 0xff;
+ break;
+ }
+ fcb_close(de);
+ a = 0;
+ }
+ break;
+ case 0x14: // F_READ - read next record
+ switch (fcb_read(de, bdos_dma)) {
+ case -1:
+ perror("fcb_read()");
+ a = 0xff;
+ break;
+ case 0:
+ a = 1; // end of file
+ break;
+ case 1:
+ a = 0;
+ break;
+ default:
+ abort();
+ }
+ break;
+ case 0x15: // F_WRITE - write next record
+ switch (fcb_write(de, bdos_dma)) {
+ case -1:
+ perror("fcb_write()");
+ a = 0xff;
+ break;
+ case 0:
+ a = 2; // disc full
+ break;
+ case 1:
+ a = 0;
+ break;
+ default:
+ abort();
+ }
+ break;
+ case 0x16: // F_MAKE - create file
+ {
+ char path[0x100];
+ if (!fcb_path(de, path)) {
+ perror(path);
+ a = 0xff;
+ break;
+ }
+ fcb_close(de);
+ if (!fcb_open(de, path, O_RDWR | O_CREAT, 0666)) {
+ perror(path);
+ a = 0xff;
+ break;
+ }
+ a = 0;
+ }
+ break;
+ case 0x1a: // F_DMAOFF - Set DMA address
+ bdos_dma = de;
+ break;
case 0x29: // DRV_ROVEC - Return bitmap of read-only drives
*hl = 0;
break;
mem[BIOS_LISTST] = 0xc9; // ret
mem[BIOS_SECTRAN] = 0xc9; // ret
+ for (int i = 0; i < N_BDOS_FILES; ++i)
+ bdos_files[i].fd = -1;
+
#if ALT_BACKEND
i8080 cpu;
i8080_init(&cpu);