/* file6502.c - File I/O Command Processor for run6502.c */ #include #include #include #include #include #include #include #include #include "error.h" #include "lib6502.h" #include "file6502.h" typedef uint8_t byte; typedef uint16_t word; int debug; #define STRLEN 128 #define STRSIZ STRLEN+1 /* DOS File Attributes */ #define _A_ARCH 0x20 //Archive. Set whenever the file is changed, and cleared by the BACKUP command. #define _A_HIDDEN 0x02 //Hidden file. Not normally seen with the DIR command, unless the /AH option is used. #define _A_RDONLY 0x01 //Read-only. File cannot be opened for writing. #define _A_SUBDIR 0x10 //Subdirectory. #define _A_SYSTEM 0x04 //System file. Not normally seen with the DIR command, unless the /AS option is used. /* I/O Blocks for File I/O */ #define MAXIOB 15 #define TDIR 'D' #define TFILE 'F' typedef struct dirent dirent; struct iocb { int opened; //Flag: file opened char type; //Channel Type: TFILE, TDIR char mode; //File Mode: 'R', 'W' int recsize; //Record Size (0=None) FILE *fp; //File Pointer DIR *dp; //Directory Pointer int errnum; //Last Error Number char name[STRSIZ]; //File/Directory Name }; static struct iocb iocbs[MAXIOB]; static char filename[STRSIZ]; //File name for open static char filebuff[256]; //File I/O Buffer static word fileaddr; //File Read/Write Address static word fileindx; //File Record Size/Number extern void setdebug(int dbg) { debug = dbg; } static int save(M6502 *mpu, word address, unsigned length, const char *path) { FILE *file= 0; int count= 0; if (!(file= fopen(path, "wb"))) return 0; while ((count= fwrite(mpu->memory + address, 1, length, file))) { address += count; length -= count; } fclose(file); return address; } static int load(M6502 *mpu, word address, const char *path) { FILE *file= 0; int count= 0; size_t max= 0x10000 - address; if (!(file= fopen(path, "rb"))) return 0; if (debug) fprintf(stderr, "loading %s\nstart address %04x\n", path, address); while ((count= fread(mpu->memory + address, 1, max, file)) > 0) { address += count; max -= count; } fclose(file); if (debug) fprintf(stderr, "end address %04x\n", address); return address; } int setiocb(int chan, int opened, char type, char mode, int recsize, FILE *fp, DIR *dp, char *name) { iocbs[chan].opened = opened; iocbs[chan].type = type; iocbs[chan].mode = mode; iocbs[chan].recsize = recsize; iocbs[chan].fp = fp; iocbs[chan].dp = dp; iocbs[chan].errnum = 0; strcpy(iocbs[chan].name, name); return 0; } void initiocb(int chan) { setiocb(chan, 0, ' ', ' ', 0, NULL, NULL, ""); if (debug) fprintf(stderr, "cleared iocb for channel %d\n", chan); } void initiocbs(void) { filename[0] = 0; //Set filename to "" for (int chan=0; chan<=MAXIOB; chan++) initiocb(chan); } /* Set Error Number and Error Message */ static int seterror(int chan, int errnum) { if (debug) fprintf(stderr, "setting channel %d error to %d\n", chan, errnum); iocbs[chan].errnum = errnum; return errnum; } /* Set File Name */ static int setname(M6502 *mpu, word addr, char *name) { int i; if (debug) fprintf(stderr, "copying name from address $%04x\n", addr); for (i=0; imemory[addr + i & 0xFFFF]; if (c) name[i] = c; else break; } name[i] = 0; //Terminate String if (debug) fprintf(stderr, "copied %d characters\n", i); return i; } /* Normalize Drive ID */ static char driveid(char drive) { char id; if (debug) fprintf(stderr, "normalizing drive $%02x\n", drive); if (drive == 0 || strchr(" 0@", drive)) id = 0; //Default Drive else id = toupper(drive); if (debug) fprintf(stderr, "returning driveid $%02x\n", id); return id; } /* Convert Drive ID to Drive Number * * Args: driveid - Drive ID (A-Z) * * Returns: Drive Number (1-126, * * 0 for Default, or -errno */ static int driveno(char drvid) { int id = driveid(drvid); if (debug) fprintf(stderr, "converting drive id %02x\n", drvid); if (id == 0 ) return 0; if (id >='A' && id <='Z') return id - '@'; return -seterror(0, ENXIO); //No such device or address } /* Change Current Drive * * Args: drivno - drive number (1-26) * * Returns: Error (0=None) */ static int chgdrive(char drvno) { if (debug) fprintf(stderr, "changing default drive to %d: ", drvno); int r = _chdrive(drvno); if (r) r = seterror(0, errno); else if (debug) fprintf(stderr, "default drvno changed\n"); return r; } /* Build File Spec */ static int setspec(char *spec, char driveid, char* name, char* dflt) { if (debug) fprintf(stderr, "building filespec for drive $%02x, name \"%s\"\n", driveid, name); spec[0] = 0; //Set filespec to "" int drive = driveno(driveid); if (drive < 0) return seterror(0, -drive); for (int i = 0; i MAXIOB) {chan = -24; break;} } return chan; } /* Validate Channel */ static int valchan(int chan, char valtype) { int errnum = 0; //Error (none) if (debug) fprintf(stderr, "validating channel %d\n", chan); if (chan > MAXIOB) errnum = 44; //Channel number out of range else if (iocbs[chan].opened == 0) errnum = 9; //Bad file descriptor else if (iocbs[chan].type != valtype) { if (debug) fprintf(stderr, "invalid channel type '%c'\n", iocbs[chan].type); switch (iocbs[chan].type) { case TDIR: errnum = EISDIR; //Is a directory case TFILE: errnum = ENOTDIR; //Not a directory default: errnum = EBADFD; //File descriptor in bad state } } return errnum; } /* Write Buffer to Memory */ static int writemem(M6502 *mpu, char* buffer, int count) { int i; if (debug) fprintf(stderr, "writing to address %04x\n", fileaddr); for (i = 0; imemory[fileaddr+i] = buffer[i]; if (debug) fprintf(stderr, "wrote %d bytes\n", i); return i; } /* Write String to Memory */ static int writestr(M6502 *mpu, char* buffer, int count) { int i; if (debug) fprintf(stderr, "writing '%s' to address %04x\n", buffer, fileaddr); for (i = 0; imemory[fileaddr+i] = c; } mpu->memory[fileaddr+i] = 0; //Terminate String if (debug) fprintf(stderr, "wrote %d characters\n", i); return i; } /* Convert tm struct to readable time */ void strtm(char *s, tm *time) { sprintf(s, "%d-%02d-%02d %02d:%02d:%02d", time->tm_year+1900, time->tm_mon, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec); } int copystring(char *b, char *s, int ofs) { if (debug) fprintf(stderr, "copying string \"$s\" to buffer at offset %d\n", s, ofs); for (int i = 0; i < STRLEN; i++) { if (s[i]) b[ofs++] = s[i]; else break; } b[ofs++] = 0; return ofs; } int copytime(char *b, tm *t, int ofs) { char s[STRLEN]; strtm(s, t); t->tm_mon = t->tm_mon + 1; //Adjust Zero-based Month if (debug) fprintf(stderr, "copying timestamp %s to buffer at offset %d\n", s, ofs); b[ofs++] = t->tm_year; b[ofs++] = t->tm_mon; b[ofs++] = t->tm_mday; b[ofs++] = t->tm_hour; b[ofs++] = t->tm_min; b[ofs++] = t->tm_sec; return ofs; } int copyword(char *b, word w, int ofs) { if (debug) fprintf(stderr, "copying word %04x to buffer at offset %d\n", w, ofs); b[ofs++] = w & 0xFF; b[ofs++] = w >> 8; return ofs; } int copyattrs(char *b, int attr, int ofs) { b[ofs++] = (attr & _A_ARCH) ? 0xFF : 00; b[ofs++] = (attr & _A_HIDDEN) ? 0xFF : 00; b[ofs++] = (attr & _A_RDONLY) ? 0xFF : 00; b[ofs++] = (attr & _A_SUBDIR) ? 0xFF : 00; b[ofs++] = (attr & _A_SYSTEM) ? 0xFF : 00; return ofs; } /* Copy dirent to character array matching run6502.h02 * * struct dirent { * * struct {char arch, hidden, system, rdonly, subdir, system} attr * * struct {char year, month, day, hour, minute, second;} time; * * int size, char name[128];} */ int setentry(char *buffer, dirent *de) { int offset = 0; char ts[STRSIZ]; strtm(ts, de->d_time); if (debug) fprintf(stderr, "copying entry \"%s\", attr=$%02x, size=%d, time=%s\n", de->d_name, de->d_attr, de->d_size, ts); offset = copyattrs(buffer, de->d_attr, offset); offset = copytime(buffer, de->d_time, offset); offset = copyword(buffer, de->d_size, offset); offset = copystring(buffer, de->d_name, offset); return offset; } /* Emulate fileio at addr */ extern int filecmd(M6502 *mpu, word addr, byte data) { const char modes[8][4] = {"r", "rb", "w", "wb", "a", "ab", "a+", "ab+"}; const char ddesc[2][9] = {"creating", "removing"}; const char fdesc[2][10] = {"file", "directory"}; const char qdesc[2][7] = {"string", "line"}; int chan, e, i; char c, d, m, n, t, mode[4]; char *buffer; char filespec[STRSIZ], newspec[STRSIZ] ; struct tm time; dirent *de; byte a = mpu->registers->a; byte x = mpu->registers->x; byte y = mpu->registers->y; byte p = mpu->registers->p; word yx = y << 8 | x; if (debug) fprintf(stderr, "executing file command '%c' with options %02x,%02x,%02x\n", a, y, x, p); switch (a) { //File I/O Command case 'A': //Set filebuffer address - Y.X = address if (p & 1) { fileindx = yx; if (debug) fprintf(stderr, "file index set to %04x\n", fileindx); } else { fileaddr = yx; if (debug) fprintf(stderr, "file address set to %04x\n", fileaddr); } break; //case 'B': //Unused case 'C': //Close Channel - Y = channel, CC = File, CS = Directory chan = y; m = p & 1; t = (m) ? TDIR : TFILE; if (debug) fprintf(stderr, "closing %s channel %d\n", fdesc[m], chan); y = valchan(chan, t); if (y) break; if (m) e = closedir(iocbs[chan].dp); else e = fclose(iocbs[chan].fp); if (e) y = seterror(chan, errno); else initiocb(chan); if (debug) fprintf(stderr, "channel closed\n"); break; case 'D': //Read Directory - Y = Channel, CC = Entry, CS = Header chan = y; x = 0; //Return Value (Read Failed) y = valchan(chan, TDIR); if (y) break; if (p & 1) { if (debug) fprintf(stderr, "retrieving directory name\n"); x = writestr(mpu, iocbs[chan].name, STRLEN); } else { if (debug) fprintf(stderr, "reading directory entry\n"); de = readdir(iocbs[chan].dp); if (de) {x = setentry(filebuff, de); writemem(mpu, filebuff, x);} else if (errno != 2) y = seterror(chan, errno); } break; case 'E': //EOF - Y = channel chan = y; y = valchan(chan, TFILE); if (y) break; y = feof(iocbs[chan].fp); break; case 'F': //Flush File - Y = Channel chan = y; y = valchan(chan, TFILE); if (y) break; x = fflush(iocbs[chan].fp); if (x) y = seterror(chan, errno); break; case 'G': //Get Character - Y = channel chan = y; x = 0; //Character read (none) y = valchan(chan, TFILE); if (y) break; c = fgetc(iocbs[chan].fp); if (feof(iocbs[chan].fp)) {y = EOF; break;} if (c == EOF) {y = seterror(chan, errno); break;} x = c & 0xFF; break; case 'H': //Get String - Y = channel chan = y; x = 0; //Number of Characters read y = valchan(chan, TFILE); if (y) break; char *s = fgets(filebuff, STRLEN, iocbs[chan].fp); if (s == NULL) {y = seterror(chan, errno); break;} if (debug) fprintf(stderr, "read string '%s'\n", filebuff); writestr(mpu, filebuff, STRLEN); x = strlen(s); fprintf(stderr, "returning %d bytes read\n", x); break; case 'I': //Init File System initiocbs(); //Initialize I/O Control Blocks break; case 'J': //Get/Put Word - Y = Channel, Carry Set = Put, Clear = Get chan = y; a = valchan(chan, TFILE); if (y) break; if (p & 1) { y = fileaddr >> 8; x = fileaddr & 0xFF; e = fputc(x, iocbs[chan].fp); if (e == EOF) {a = seterror(chan, errno); break;} e = fputc(y, iocbs[chan].fp); if (e == EOF) {a = seterror(chan, errno); break;} } else { c = fgetc(iocbs[chan].fp); if (feof(iocbs[chan].fp)) {a = EOF; break;} if (c == EOF) {a = seterror(chan, errno); break;} x = c & 0xFF; c = fgetc(iocbs[chan].fp); if (feof(iocbs[chan].fp)) {a = EOF; break;} if (c == EOF) {a = seterror(chan, errno); break;} y = c & 0xFF; } break; case 'K': //REMOVE - Delete File - Y,X = Filename d = driveid(y); y = setspec(filespec, d, filename, "."); if (y) break; if (debug) fprintf(stderr, "removing file '%s'\n", filespec); e = remove(filespec); if (e) y=seterror(0, errno); break; case 'L': //Load file //ALLOW SPECIFYING DRIVE ID a = ENOERROR; //Error (none) if (debug) fprintf(stderr, "loading file at %04h\n", fileaddr); e = load(mpu, fileaddr, filename); if (!e) {a = seterror(0, errno); break;} y = e >> 8; x = e & 0xff; break; case 'M': //MOVE - Rename File - Y = Drive ID d = driveid(y); //Not Implemented Yet y = setspec(filespec, d, filename, "."); if (y) break; y = setspec(newspec, d, filebuff, "."); if (y) break; if (debug) fprintf(stderr, "renaming file \"%s\" to \"%s\"\n", filename, filebuff); e = rename(filespec, newspec); if (e) y=seterror(0, errno); else y=0; break; case 'N': //Set Name - Y,X = string address, CC = filename, CS = filebuff m = p & 1; if (m) { x = setname(mpu, yx, filebuff); //Set filebuff and Return Length if (debug) fprintf(stderr, "filebuff set to \"%s\"\n", filename); } else { x = setname(mpu, yx, filename); //Set filename and Return Length if (debug) fprintf(stderr, "filename set to \"%s\"\n", filename); } break; case 'O': //Open file/directory - Y = Drive#, X = Mode, CC = File, CS = Directory if (p & 1) { x = 0; //File channel (none) d = driveid(y); //Normalize Drive ID y = setspec(filespec, d, filename, "."); if (y) {seterror(0, y); break;} chan = uniocb(); if (chan <0) {y = -chan; break;} if (debug) fprintf(stderr, "opening directory '%s'\n", filespec, mode); DIR *dp = opendir(filespec); if (dp == NULL) { y = seterror(0, errno); break;} if (debug) fprintf(stderr, "directory opened on channel %d\n", chan); y = setiocb(chan, -1, TDIR, ' ', 0, NULL, dp, filespec); //Setup IOCB if (y == 0) x = chan; } else { m = x >> 5 & 7; //Get Mode Index x = 0; //File channel (none) y = ENOERROR; //Error code (none) strcpy(mode, modes[m]); //Set Mode from Mode Index chan = uniocb(); if (chan <0) {y = seterror(0, -chan); break;} if (debug) fprintf(stderr, "opening file '%s' with mode '%s'\n", filename, mode); FILE *fp = fopen(filename, mode); if (fp == NULL) { y = seterror(0, errno); break;} if (debug) fprintf(stderr, "file opened on channel %d\n", chan); i = (m > 5) ? fileindx : 0; //Set Records Size if Mode RECORD y = setiocb(chan, -1, TFILE, mode[0], i, fp, NULL, filename); //Setup IOCB if (y == 0) x = chan; } break; case 'P': //Put character - Y = channel; X = character chan = y; c = x; if (debug) fprintf(stderr, "writing '%c' to channel %d\n", c, chan); x = 0xFF; //Character written (Error) y = valchan(chan, TFILE); if (y) break; e = fputc(c, iocbs[chan].fp); if (e == EOF) {y = seterror(chan, errno); break;} x = e & 0xFF; break; case 'Q': //Put String - Y = channel, Carry Set = putline chan = y; x = 0; //Number of characters written y = valchan(chan, TFILE); if (y) break; for (i = 0; i<128; i++) { c = mpu->memory[fileaddr+i]; if (c) filebuff[i] = c; else break; } filebuff[i] = 0; if (debug) fprintf(stderr, "writing %s '%s'\n", qdesc[p&1], filebuff); if (p & 1) strcat(filebuff, "\n"); e = fputs(filebuff, iocbs[chan].fp); if (e == EOF) {y = seterror(chan, errno); break;} x = strlen(filebuff); break; case 'R': //Read bytes - Y = channel, X=Number of Bytes m = p & 1; //Set Mode: 0 = fread, 1 = fgetr chan = y; if (m) { a = valchan(chan, TFILE); if (a) break; if (debug) fprintf(stderr, "selecting record #%d\n", fileindx); n = iocbs[chan].recsize; i = fileindx * n; if (debug) fprintf(stderr, "setting position to %d\n", i); e = fseek(iocbs[chan].fp, i, SEEK_SET); if (e) {a = seterror(chan, errno); break;} } else { y = valchan(chan, TFILE); if (y) break; n = x; } if (debug) fprintf(stderr, "reading %d bytes\n", n); x = fread(filebuff, 1, n, iocbs[chan].fp); if (debug) fprintf(stderr, "read %d bytes\n", x); e = ferror(iocbs[chan].fp); if (e) {seterror(chan, errno); if (m) a = e; else y = e; break; } writemem(mpu, filebuff, x); if (m) { fileindx++; y = fileindx >> 8; x = fileindx & 0xff; if (debug) fprintf(stderr, "returning record #%d\n", fileindx); } break; case 'S': //Save file Y,X = end address //CHANGE INPUT PARAMETER TO DRIVE ID a = ENOERROR; //Error (none) if (debug) fprintf(stderr, "saving file from %04x to %04x\n", fileaddr, yx); e = save(mpu, fileaddr, yx-fileaddr-1, filename); if (!e) {a = seterror(0, errno); break;} y = e >> 8; x = e & 0xff; break; //case 'T': //Unused case 'U': //Get/Set Current Directory - Y = Drive/Disk ID, CC=Get. CS=Set if (p & 1) { //Change Directory d = driveid(y); y = setspec(filespec, d, filename, "."); if (y) break; if (debug) fprintf(stderr, "changing directory to \"%s\"\n", filespec); if (_chdir(filespec)) {y = seterror(0, errno);} } else { //Get Directory x = 0; filename[0] = 0; //Clear filename, Set Length to 0 d = driveno(y); if (d < 0) {y=seterror(0, -d); break;} e = _getdrive(); if (e == 0) {y=seterror(0, errno); break;} if (d && e != d ) {y = chgdrive(d); if (y) break;} if (debug) fprintf(stderr, "getting current working directory for drive %d\n", d); if (_getcwd(filename, STRLEN) == NULL) y = seterror(0, errno); else { if (debug) fprintf(stderr, "current working directory ie \"%s\"\n", filename); x = writestr(mpu, filename, STRLEN); } if (d && e != d ) {e = chgdrive(e); if (e) seterror(0, errno);} } break; case 'V': //Get or Set Current Drive, Y=Drive, Carry=Get/Set if (p & 1) { if (debug) fprintf(stderr, "changing drive to %c\n", y+'@'); x = _chdrive(y); if (x) y=seterror(0, errno); else y=0; } else { x = _getdrive(); if (x) y=0; else y=seterror(0, errno); if (debug) fprintf(stderr, "current drive is %c\n", x+'@'); } break; case 'W': //Write bytes - Y = channel, X = Number of Bytes m = p & 1; //Set Mode: 0 = fread, 1 = fgetr chan = y; if (m) { a = valchan(chan, TFILE); if (a) break; if (debug) fprintf(stderr, "selecting record #%d\n", fileindx); n = iocbs[chan].recsize; i = fileindx * n; if (debug) fprintf(stderr, "setting position to %d\n", i); e = fseek(iocbs[chan].fp, i, SEEK_SET); if (e) {a = seterror(chan, errno); break;} i = ftell(iocbs[chan].fp); if (debug) fprintf(stderr, "position set to %d\n", i); } else { y = valchan(chan, TFILE); if (y) break; n = x; } for (i = 0; imemory[fileaddr+i]; if (debug) fprintf(stderr, "writing %d bytes\n", n); e = fwrite(filebuff, n, 1, iocbs[chan].fp); if (debug) fprintf(stderr, "fwrite() returned %d\n", e); if (e == 0) {e = seterror(chan, errno); if (m) a = e; else y = e; break; } if (m) { fileindx++; y = fileindx >> 8; x = fileindx & 0xff; if (debug) fprintf(stderr, "returning record #%d\n", fileindx); } break; case 'X': //Make/Remove Directory - Y = Drive ID, CC = RMDIR, CS = MKDIR d = driveid(y); m = p & 1; y = setspec(filespec, d, filename, "."); if (y) break; if (debug) fprintf(stderr, "%s directory '%s'\n", ddesc[m], filespec); if (m) e = _rmdir(filespec); else e = _mkdir(filespec); if (e) y=seterror(0, errno); break; case 'Y': //Get Last Error: Y=chan chan = y; y = ENOERROR; //Set Error to None x = 0xFF; //Set Result to Invalid if (chan > MAXIOB) {y = 44; break;} if (debug) fprintf(stderr, "getting last error for channel %d\n", chan); x = iocbs[chan].errnum; char *msg = strerror(x); if (debug) fprintf(stderr, "retrieved error %d, '%s'\n", x, msg); writestr(mpu, msg, STRLEN); break; case 'Z': //File Position: Y=chan, fileaddr = position, Carry = seek/tell chan = y; y = valchan(chan, TFILE); if (y) break; if (p & 1) { e = ftell(iocbs[chan].fp); if (e < 0) {a = seterror(chan, errno);} else { if (debug) fprintf(stderr, "returning position %d\n", e); a = ENOERROR; } y = e >> 8; x = e & 0xff; } else { if (fileindx == 0xFFFF) {i = 0; m = SEEK_END;} else {i = fileindx, m = SEEK_SET;} if (debug) fprintf(stderr, "seeking position %d\n", i); e = fseek(iocbs[chan].fp, i, m); if (e) y = seterror(chan, errno); } break; default: y = 22; //Error - invalid argument } if (debug) fprintf(stderr, "returning values %02x, %02x, %02x, %02x\n", a, y, x, p); mpu->registers->a = a; mpu->registers->x = x; mpu->registers->y = y; } /* Emulate system command dispatch at addr */ extern int syscmd(M6502 *mpu, word addr, byte data) { struct tm time; dirent *de; byte a = mpu->registers->a; byte x = mpu->registers->x; byte y = mpu->registers->y; byte p = mpu->registers->p; word yx = y << 8 | x; if (debug) fprintf(stderr, "executing system command '%c' with options %02x,%02x,%02x\n", a, y, x, p); switch(a) { case 'C': //Get System Clock if (debug) fprintf(stderr, "reading system clock\n"); clock_t clk = clock(); //Get System Clock if (debug) fprintf(stderr, "system clock = %d ticks\n", clk); int i = (clk / 50) & 0xFFFFFF; //Convert to 24-bit integer, 50 ticks per second a = i >> 16; y = i >> 8; x = i; //with resolution of 50 ticks per second if (debug) fprintf(stderr, "returning $%02x%02x%02x (%d)\n", a, y, x, clk); break; case 'T': //Get/Set Date and Time - YX = tm address; CC = Get, CS = Set if (p & 1) { y = 0xFF; //Return Error - Not Implemented } else { fileaddr = yx; //Set Struct Address _getsystime(&time); x = copytime(filebuff, &time, 0); //Convert time struct writemem(mpu, filebuff, x); //Write to Memory y = 0; //Return Error = None } break; default: y = 22; //Error - invalid argument } if (debug) fprintf(stderr, "returning values %02x, %02x, %02x, %02x\n", a, y, x, p); mpu->registers->a = a; mpu->registers->x = x; mpu->registers->y = y; }