From 75ed93a4da7c720cd574f57d99dc2c8cdd04c40c Mon Sep 17 00:00:00 2001 From: Nick Downing Date: Fri, 12 Dec 2025 00:02:51 +1100 Subject: [PATCH] In /emu_8086.c, implement enough of MSDOS for MSBASIC load and save commands --- emu_8086.c | 653 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 629 insertions(+), 24 deletions(-) diff --git a/emu_8086.c b/emu_8086.c index c130fe5..37b3843 100644 --- a/emu_8086.c +++ b/emu_8086.c @@ -1,8 +1,12 @@ +#include +#include +#include #include #include #include #include #include +#include #if ALT_BACKEND #include "virtualxt/lib/vxt/cpu.h" #else @@ -21,6 +25,23 @@ uint8_t mem[MEM_SIZE]; uint8_t io_read[IO_SIZE]; uint8_t io_write[IO_SIZE]; +// MSDOS emulation +uint16_t msdos_dta_offset, msdos_dta_segment; + +// drive to directory mapping +// string from msdos_drives[] is prepended to filename from FCB +// string must be less than ~0xf0 bytes long so eventual path doesn't overflow +#define N_MSDOS_DRIVES 1 +char *msdos_drives[N_MSDOS_DRIVES] = {""}; +int msdos_default_drive; + +#define N_MSDOS_FILES 10 +struct msdos_file { + int fd; // -1 if not in use + uint16_t fcb_offset; + uint16_t fcb_segment; +} msdos_files[N_MSDOS_FILES]; + int load_ihx(char *name) { FILE *fp = fopen(name, "r"); if (fp == NULL) { @@ -145,10 +166,298 @@ int load_ihx(char *name) { return entry_point; } -int msdos(int ax, int dx, int ds, bool *zf) { - switch (ax >> 8) { - case 2: - // Character output +// extract path from FCB +bool fcb_path(uint16_t fcb_offset, uint16_t fcb_segment, char *path) { + int drive = mem[fcb_offset + (fcb_segment << 4)]; + if (drive == 0) + drive = msdos_default_drive; + else + --drive; + if (drive >= N_MSDOS_DRIVES) { + errno = ENOTDIR; + return false; + } + char *q = path; + for (char *p = msdos_drives[drive]; *p; ++p) + *q++ = *p; + char *p = q; + for (int i = 0; i < 8; ++i) + *q++ = tolower( + mem[((fcb_offset + 1 + i) & 0xffff) + (fcb_segment << 4)] & 0x7f + ); + while (q > p && q[-1] == ' ') + --q; + *q++ = '.'; + p = q; + for (int i = 0; i < 3; ++i) + *q++ = tolower( + mem[((fcb_offset + 9 + i) & 0xffff) + (fcb_segment << 4)] & 0x7f + ); + while (q > p && q[-1] == ' ') + --q; + if (q == p) + --q; + *q = 0; + return true; +} + +int fcb_get_logical_record_size(uint16_t fcb_offset, uint16_t fcb_segment) { + return + mem[((fcb_offset + 0xe) & 0xffff) + (fcb_segment << 4)] | + (mem[((fcb_offset + 0xf) & 0xffff) + (fcb_segment << 4)] << 8); +} + +long fcb_get_random_record_number( + uint16_t fcb_offset, + uint16_t fcb_segment, + int logical_record_size +) { + long random_record_number = + mem[((fcb_offset + 0x21) & 0xffff) + (fcb_segment << 4)] | + (mem[((fcb_offset + 0x22) & 0xffff) + (fcb_segment << 4)] << 8) | + (mem[((fcb_offset + 0x23) & 0xffff) + (fcb_segment << 4)] << 16); + if (logical_record_size <= 0x40) + random_record_number |= + mem[((fcb_offset + 0x24) & 0xffff) + (fcb_segment << 4)] << 24; + return random_record_number; +} + +void fcb_set_random_record_number( + uint16_t fcb_offset, + uint16_t fcb_segment, + long random_record_number, + int logical_record_size +) { + mem[((fcb_offset + 0x21) & 0xffff) + (fcb_segment << 4)] = + (uint8_t)random_record_number; + mem[((fcb_offset + 0x22) & 0xffff) + (fcb_segment << 4)] = + (uint8_t)(random_record_number >> 8); + mem[((fcb_offset + 0x23) & 0xffff) + (fcb_segment << 4)] = + (uint8_t)(random_record_number >> 16); + if (logical_record_size <= 0x40) + mem[((fcb_offset + 0x24) & 0xffff) + (fcb_segment << 4)] = + (uint8_t)(random_record_number >> 24); +} + +bool fcb_open( + uint16_t fcb_offset, + uint16_t fcb_segment, + char *path, + int flags, + int mode +) { + for (int i = 0; i < N_MSDOS_FILES; ++i) + if (msdos_files[i].fd < 0) { + int fd = open(path, flags, mode); + if (fd == -1) + return false; + msdos_files[i] = (struct msdos_file){ + .fd = fd, + .fcb_offset = fcb_offset, + .fcb_segment = fcb_segment, + }; + return true; + } + errno = EMFILE; + return false; +} + +#if 0 // for some reason msbasic.ihx uses random read/write +int fcb_read( + uint16_t fcb_offset, + uint16_t fcb_segment, + uint16_t dma_off, + uint16_t dma_seg, + int logical_record_size +) { + for (int i = 0; i < N_MSDOS_FILES; ++i) + if ( + msdos_files[i].fd >= 0 && + msdos_files[i].fcb_offset == fcb_offset && + msdos_files[i].fcb_segment == fcb_segment + ) { +#if 0 + printf("read %04x:%04x", fcb_offset, fcb_segment); + for (int i = 0xc; i < 0x25; ++i) + printf(" %02x", mem[((fcb_offset + i) & 0xffff) + (fcb_segment << 4)]); + printf("\n"); +#endif + uint8_t *buf = malloc(logical_record_size); + if (buf == NULL) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + int count = (int)read(msdos_files[i].fd, buf, logical_record_size); + if (count == -1) { + free(buf); + return -1; + } + if (count == 0) { + free(buf); + return 0; + } + int j; + for (j = 0; j < count; ++j) + mem[((dma_off + j) & 0xffff) + (dma_seg << 4)] = buf[j]; + for (; j < 0x80; ++j) + mem[((dma_off + j) & 0xffff) + (dma_seg << 4)] = 0; + free(buf); + return 1; + } + errno = EBADFD; + return -1; +} + +int fcb_write( + uint16_t fcb_offset, + uint16_t fcb_segment, + uint16_t dma_off, + uint16_t dma_seg, + int logical_record_size +) { + for (int i = 0; i < N_MSDOS_FILES; ++i) + if ( + msdos_files[i].fd >= 0 && + msdos_files[i].fcb_offset == fcb_offset && + msdos_files[i].fcb_segment == fcb_segment + ) { +#if 0 + printf("write %04x:%04x", fcb_offset, fcb_segment); + for (int i = 0xc; i < 0x25; ++i) + printf(" %02x", mem[((fcb_offset + i) & 0xffff) + (fcb_segment << 4)]); + printf("\n"); +#endif + uint8_t *buf = malloc(logical_record_size); + if (buf == NULL) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + for (int j = 0; j < logical_record_size; ++j) + buf[j] = mem[((dma_off + j) & 0xffff) + (dma_seg << 4)]; + int count = (int)write(msdos_files[i].fd, buf, logical_record_size); + free(buf); + if (count == -1) + return -1; + if (count < logical_record_size) + return 0; + return 1; + } + errno = EBADFD; + return false; +} +#endif + +int fcb_read_random( + uint16_t fcb_offset, + uint16_t fcb_segment, + uint16_t dma_off, + uint16_t dma_seg, + long random_record_number, + int logical_record_size +) { + for (int i = 0; i < N_MSDOS_FILES; ++i) + if ( + msdos_files[i].fd >= 0 && + msdos_files[i].fcb_offset == fcb_offset && + msdos_files[i].fcb_segment == fcb_segment + ) { + if ( + lseek( + msdos_files[i].fd, + random_record_number * logical_record_size, + SEEK_SET + ) == (off_t)-1 + ) { + perror("fseek()"); + exit(EXIT_FAILURE); + } + uint8_t *buf = malloc(logical_record_size); + if (buf == NULL) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + int count = (int)read(msdos_files[i].fd, buf, logical_record_size); + if (count == -1) { + free(buf); + return -1; + } + if (count == 0) { + free(buf); + return 0; + } + int j; + for (j = 0; j < count; ++j) + mem[((dma_off + j) & 0xffff) + (dma_seg << 4)] = buf[j]; + for (; j < 0x80; ++j) + mem[((dma_off + j) & 0xffff) + (dma_seg << 4)] = 0; + free(buf); + return 1; + } + errno = EBADFD; + return -1; +} + +int fcb_write_random( + uint16_t fcb_offset, + uint16_t fcb_segment, + uint16_t dma_off, + uint16_t dma_seg, + long random_record_number, + int logical_record_size +) { + for (int i = 0; i < N_MSDOS_FILES; ++i) + if ( + msdos_files[i].fd >= 0 && + msdos_files[i].fcb_offset == fcb_offset && + msdos_files[i].fcb_segment == fcb_segment + ) { + if ( + lseek( + msdos_files[i].fd, + random_record_number * logical_record_size, + SEEK_SET + ) == (off_t)-1 + ) { + perror("fseek()"); + exit(EXIT_FAILURE); + } + uint8_t *buf = malloc(logical_record_size); + if (buf == NULL) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + for (int j = 0; j < logical_record_size; ++j) + buf[j] = mem[((dma_off + j) & 0xffff) + (dma_seg << 4)]; + int count = (int)write(msdos_files[i].fd, buf, logical_record_size); + free(buf); + if (count == -1) + return -1; + if (count < logical_record_size) + return 0; + return 1; + } + errno = EBADFD; + return false; +} +# +bool fcb_close(uint16_t fcb_offset, uint16_t fcb_segment) { + for (int i = 0; i < N_MSDOS_FILES; ++i) + if ( + msdos_files[i].fd >= 0 && + msdos_files[i].fcb_offset == fcb_offset && + msdos_files[i].fcb_segment == fcb_segment + ) { + close(msdos_files[i].fd); + msdos_files[i].fd = -1; + return true; + } + errno = EBADFD; + return false; +} + +int msdos(int func, int ax, int dx, int ds, bool *zf, uint16_t *cx) { + switch (func) { + case 2: // DOS 1+ - WRITE CHARACTER TO STANDARD OUTPUT dx &= 0xff; switch (dx) { case '\n': @@ -160,8 +469,7 @@ int msdos(int ax, int dx, int ds, bool *zf) { break; } break; - case 6: - // Direct console I/O + case 6: // DOS 1+ - DIRECT CONSOLE INPUT/OUTPUT dx &= 0xff; if (dx == 0xff) { int data = getchar(); @@ -193,8 +501,7 @@ int msdos(int ax, int dx, int ds, bool *zf) { break; } break; - case 9: - // Display string + case 9: // DOS 1+ - WRITE STRING TO STANDARD OUTPUT for ( int data; (data = mem[dx + (ds << 4)]) != '$'; @@ -210,24 +517,301 @@ int msdos(int ax, int dx, int ds, bool *zf) { break; } break; - case 0x26: - // Create PSP -- ignore + case 0xe: // DOS 1+ - SELECT DEFAULT DRIVE + msdos_default_drive = dx & 0xff; + ax = N_MSDOS_DRIVES | (ax & 0xff00); break; - case 0x30: - // Get DOS version - ax = 0x211; + case 0xf: // DOS 1+ - OPEN FILE USING FCB + { + char path[0x100]; + if (!fcb_path(dx, ds, path)) { + perror(path); + ax |= 0xff; + break; + } + fcb_close(dx, ds); + if (!fcb_open(dx, ds, path, O_RDWR, 0)) { + perror(path); + ax |= 0xff; + break; + } + ax &= 0xff00; + } break; - case 0x33: - // Get or set Ctrl-Break -- ignore + case 0x10: // DOS 1+ - CLOSE FILE USING FCB + if (!fcb_close(dx, ds)) { + ax |= 0xff; + break; + } + ax &= 0xff00; break; - case 0x25: - // Set interrupt vector -- ignore + case 0x13: // DOS 1+ - DELETE FILE USING FCB + // note: for now, we don't treat the ? character as a wildcard + { + char path[0x100]; + if (!fcb_path(dx, ds, path)) { + perror(path); + ax |= 0xff; + break; + } + if (unlink(path) == -1) { + perror(path); + ax |= 0xff; + break; + } + fcb_close(dx, ds); + ax &= 0xff00; + } break; - case 0x35: - // Get interrupt vector -- ignore +#if 0 // for some reason msbasic.ihx uses random read/write + case 0x14: // DOS 1+ - SEQUENTIAL READ FROM FCB FILE + // we don't bother with the sequential position fields for + // now, so this can't be mixed with random access to file + switch ( + fcb_read( + dx, + ds, + msdos_dta_offset, + msdos_dta_segment, + fcb_get_logical_record_size(dx, ds) + ) + ) { + case -1: + perror("fcb_read()"); + ax |= 0xff; + break; + case 0: + ax = 1 | (ax & 0xff00); // end of file + break; + case 1: + ax &= 0xff00; + break; + default: + abort(); + } + break; + case 0x15: // DOS 1+ - SEQUENTIAL WRITE TO FCB FILE + // we don't bother with the sequential position fields for + // now, so this can't be mixed with random access to file + switch ( + fcb_write( + dx, + ds, + msdos_dta_offset, + msdos_dta_segment, + fcb_get_logical_record_size(dx, ds) + ) + ) { + case -1: + perror("fcb_write()"); + ax |= 0xff; + break; + case 0: + ax = 2 | (ax & 0xff00); // disc full + break; + case 1: + ax &= 0xff00; + break; + default: + abort(); + } + break; +#endif + case 0x16: // DOS 1+ - CREATE OR TRUNCATE FILE USING FCB + { + char path[0x100]; + if (!fcb_path(dx, ds, path)) { + perror(path); + ax |= 0xff; + break; + } + fcb_close(dx, ds); + if (!fcb_open(dx, ds, path, O_RDWR | O_CREAT, 0666)) { + perror(path); + ax |= 0xff; + break; + } + ax &= 0xff00; + } + break; + case 0x19: // DOS 1+ - GET CURRENT DEFAULT DRIVE + ax &= 0xff00; // always a: + break; + case 0x1a: // DOS 1+ - SET DISK TRANSFER AREA ADDRESS + msdos_dta_offset = dx; + msdos_dta_segment = ds; + break; + case 0x21: // DOS 1+ - READ RANDOM RECORD FROM FCB FILE + { + int logical_record_size = fcb_get_logical_record_size(dx, ds); + long random_record_number = fcb_get_random_record_number( + dx, + ds, + logical_record_size + ); + switch ( + fcb_read_random( + dx, + ds, + msdos_dta_offset, + msdos_dta_segment, + random_record_number, + logical_record_size + ) + ) { + case -1: + perror("fcb_read_random()"); + ax |= 0xff; + break; + case 0: + ax = 1 | (ax & 0xff00); // end of file + break; + case 1: + ax &= 0xff00; + // random record number is not updated after reading + break; + default: + abort(); + } + } + break; + case 0x22: // DOS 1+ - WRITE RANDOM RECORD TO FCB FILE + { + int logical_record_size = fcb_get_logical_record_size(dx, ds); + long random_record_number = fcb_get_random_record_number( + dx, + ds, + logical_record_size + ); + switch ( + fcb_write_random( + dx, + ds, + msdos_dta_offset, + msdos_dta_segment, + random_record_number, + logical_record_size + ) + ) { + case -1: + perror("fcb_write_random()"); + ax |= 0xff; + break; + case 0: + ax = 2 | (ax & 0xff00); // disc full + break; + case 1: + ax &= 0xff00; + // random record number is not updated after writing + break; + default: + abort(); + } + } + break; + case 0x25: // DOS 1+ - SET INTERRUPT VECTOR -- ignore + break; + case 0x26: // DOS 1+ - CREATE NEW PROGRAM SEGMENT PREFIX -- ignore + break; + case 0x27: // DOS 1+ - RANDOM BLOCK READ FROM FCB FILE + { + int logical_record_size = fcb_get_logical_record_size(dx, ds); + long random_record_number = fcb_get_random_record_number( + dx, + ds, + logical_record_size + ); + for (int i = 0; i < *cx; ++i) { + // for now we ignore the record number and just do it sequentially + switch ( + fcb_read_random( + dx, + ds, + msdos_dta_offset + i * logical_record_size, + msdos_dta_segment, + random_record_number + i, + logical_record_size + ) + ) { + case -1: + perror("fcb_read_random()"); + ax |= 0xff; + break; + case 0: + ax = 1 | (ax & 0xff00); // end of file + break; + case 1: + continue; + default: + abort(); + } + *cx = (uint16_t)i; + goto random_block_read_error; + } + ax &= 0xff00; // cx unchanged + random_block_read_error: + fcb_set_random_record_number( + dx, + ds, + random_record_number + *cx, + logical_record_size + ); + } + break; + case 0x28: // DOS 1+ - RANDOM BLOCK WRITE TO FCB FILE + { + int logical_record_size = fcb_get_logical_record_size(dx, ds); + long random_record_number = fcb_get_random_record_number( + dx, + ds, + logical_record_size + ); + for (int i = 0; i < *cx; ++i) { + // for now we ignore the record number and just do it sequentially + switch ( + fcb_write_random( + dx, + ds, + msdos_dta_offset + i * logical_record_size, + msdos_dta_segment, + random_record_number + i, + logical_record_size + ) + ) { + case -1: + perror("fcb_write_random()"); + ax |= 0xff; + break; + case 0: + ax = 2 | (ax & 0xff00); // disc full + break; + case 1: + continue; + default: + abort(); + } + *cx = (uint16_t)i; + goto random_block_write_error; + } + ax &= 0xff00; // cx unchanged + random_block_write_error: + fcb_set_random_record_number( + dx, + ds, + random_record_number + *cx, + logical_record_size + ); + } + break; + case 0x30: // DOS 2+ - GET DOS VERSION + ax = 0x211; + break; + case 0x33: // DOS 2+ - EXTENDED BREAK CHECKING -- ignore + break; + case 0x35: // DOS 2+ - GET INTERRUPT VECTOR -- ignore break; default: - fprintf(stderr, "unimplemented MSDOS call 0x%02x\n", ax >> 8); + fprintf(stderr, "unimplemented MSDOS call 0x%02x\n", func); exit(EXIT_FAILURE); } return ax; @@ -343,12 +927,15 @@ int main(int argc, char **argv) { // psp int code_segment = (entry_point >> 16) & 0xffff; - mem[5 + (code_segment << 4)] = 0xc3; // ret, for bdos emulation + mem[5 + (code_segment << 4)] = 0xc3; // ret, for msdos emulation mem[6 + (code_segment << 4)] = 0xfe; // memory top, lo byte mem[7 + (code_segment << 4)] = 0xff; // memory top, hi byte mem[0x80 + (code_segment << 4)] = 0; // command line length mem[0x81 + (code_segment << 4)] = '\r'; // command line sentinel + for (int i = 0; i < N_MSDOS_FILES; ++i) + msdos_files[i].fd = -1; + #if ALT_BACKEND struct cpu cpu; memset(&cpu, 0, sizeof(struct cpu)); @@ -400,13 +987,31 @@ int main(int argc, char **argv) { break; if (cpu.regs.ip == 5 && cpu.regs.cs == code_segment) { bool zf = (cpu.regs.flags >> 6) & 1; - cpu.regs.ax = msdos(cpu.regs.cl << 8, cpu.regs.dx, cpu.regs.ds, &zf); + uint16_t cx = cpu.regs.cx; + cpu.regs.ax = msdos( + cpu.regs.cl, + cpu.regs.ax, + cpu.regs.dx, + cpu.regs.ds, + &zf, + &cx + ); cpu.regs.flags = (zf << 6) | (cpu.regs.flags & ~0x40); + cpu.regs.cx = cx; } else if (cpu.regs.ip == 1 && cpu.regs.cs == 0x40) { bool zf = (cpu.regs.flags >> 6) & 1; - cpu.regs.ax = msdos(cpu.regs.ax, cpu.regs.dx, cpu.regs.ds, &zf); + uint16_t cx = cpu.regs.cx; + cpu.regs.ax = msdos( + cpu.regs.ax >> 8, + cpu.regs.ax, + cpu.regs.dx, + cpu.regs.ds, + &zf, + &cx + ); cpu.regs.flags = (zf << 6) | (cpu.regs.flags & ~0x40); + cpu.regs.cx = cx; } cpu_step(&cpu); } @@ -453,7 +1058,7 @@ int main(int argc, char **argv) { break; } if (cpu.regs.word.pc == 5) { - cpu.regs.word.de = bdos( + cpu.regs.word.de = msdos( cpu.regs.byte.c, cpu.regs.word.de ); -- 2.34.1