From: Nick Downing Date: Thu, 4 Dec 2025 11:14:02 +0000 (+1100) Subject: In /emu_8080.c, implement enough of the BDOS for MBASIC load and save commands X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=68d2ebc7a9919fe7c63815e7067ae8a43cfa7e2b;p=multi_emu.git In /emu_8080.c, implement enough of the BDOS for MBASIC load and save commands --- diff --git a/emu_8080.c b/emu_8080.c index c220af6..3bf7d4f 100644 --- a/emu_8080.c +++ b/emu_8080.c @@ -1,8 +1,12 @@ +#include +#include +#include #include #include #include #include #include +#include #if ALT_BACKEND #include "8080/i8080.h" #else @@ -57,6 +61,21 @@ uint8_t mem[MEM_SIZE]; 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) { @@ -172,10 +191,97 @@ int load_ihx(char *name) { 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': @@ -188,8 +294,7 @@ int bdos(int a, int c, int de, uint16_t *hl) { } 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(); @@ -221,8 +326,7 @@ int bdos(int a, int c, int de, uint16_t *hl) { } 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': @@ -234,10 +338,103 @@ int bdos(int a, int c, int de, uint16_t *hl) { 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; @@ -450,6 +647,9 @@ int main(int argc, char **argv) { 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);