+#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 "virtualxt/lib/vxt/cpu.h"
#else
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) {
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':
break;
}
break;
- case 6:
- // Direct console I/O
+ case 6: // DOS 1+ - DIRECT CONSOLE INPUT/OUTPUT
dx &= 0xff;
if (dx == 0xff) {
int data = getchar();
break;
}
break;
- case 9:
- // Display string
+ case 9: // DOS 1+ - WRITE STRING TO STANDARD OUTPUT
for (
int data;
(data = mem[dx + (ds << 4)]) != '$';
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;
// 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));
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);
}
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
);