#include #include #include #include "v9t9_common.h" #include "v9t9.h" #include "roms.h" #include "cru.h" #include "command.h" #include "memory.h" #include "dsr.h" #include "vdp.h" #include "9900.h" #include "moduleconfig.h" #include "v9t9.h" #include "timer.h" #include "pab.h" #include "fiad.h" #define _L LOG_EMUDISK | LOG_INFO #define _LL LOG_EMUDISK | LOG_INFO | LOG_USER #define EMUDISKCRUBASE 0x1000 /******************************************/ #define VDP_PTR(x) FLAT_MEMORY_PTR(md_video, (x)&0x3fff) #define VDP_ADDR(p) ((p) - VDP_PTR(0)) /* These are the codes we add to OP_DSR to specify which DSR function we're emulating. */ /* If these numbers change, fix DSR ROM files */ enum { /* this first group doubles as device codes */ D_DSK = 0, // standard file operation on DSK.XXXX.[YYYY] D_DSK1 = 1, // standard file operation on DSK1.[YYYY] D_DSK2 = 2, // ... D_DSK3 = 3, // ... D_DSK4 = 4, // ... D_DSK5 = 5, // ... D_INIT = 6, // initialize disk DSR D_DSKSUB = 7, // subroutines D_SECRW = 7, // sector read/write (10) D_FMTDISK = 8, // format disk (11) D_PROT = 9, // file protection (12) D_RENAME = 10, // rename file (13) D_DINPUT = 11, // direct input file (14) D_DOUTPUT = 12, // direct output file (15) D_16 = 13, // um... I forgot (16) D_FILES = 14 // setup # files (CALL FILES) }; /* Error codes for subroutines */ enum { es_okay = 0, es_outofspace = 0x4, es_cantopenfile = 0x1, es_filenotfound = 0x1, es_badfuncerr = 0x7, // format es_fileexists = 0x7, // rename es_badvalerr = 0x1, es_hardware = 0x6 // ??? made this up }; #define MAXFILES 9 /* max # open files */ #define MAXDRIVE 5 /* max drive number */ /******************************************/ char emudiskfilename[OS_NAMESIZE] = "emudisk.bin"; char emu2diskfilename[OS_NAMESIZE] = "emu2disk.bin"; u8 emudiskdsr[8192]; OSPathSpec emudiskpath[5]; int keepfileformat = 1; int newfileformat = F_V9t9; int unknownfileistext = 0; int repairbadfiles = 0; int fixupoldv9t9filenames = 1; int generateoldv9t9filenames = 0; int allowlongcatalogs = 0; u16 vdpnamebuffer; // location of VDP name buffer (for compatibility) /* Info maintained for open files */ typedef struct fiad_pabfile { u8 *fnptr; // pointer to filename part of PAB in VDP RAM, // the key for distinguishing open files pabrec pab; // copy of PAB used for local operations fiad_tifile tf; // info about open file bool is_catalog; // does pab represent catalog? } fiad_pabfile; /* Our array of open files */ static fiad_pabfile files[MAXFILES]; /* Our disk catalogs (via DSKx. or sector access) */ static fiad_catalog DskCat[MAXDRIVE]; /* Flags set by timer, used to throttle directory reads */ static bool DskCatFlag[MAXDRIVE]; static int DskCatTag[MAXDRIVE]; // last sector read on each drive, used for catalog -- // = if we read a sector beyond the catalog limit // or re-read sectors 0 or 1, we force a reread of catalog // from local disk // = set to 0xffff when known invalid static u16 last_sector_read[MAXDRIVE]; /* Translations for DSKx. files' catalog function from FDR type to catalog value (pairs) */ u8 DrcTrans[5][2] = { {0, 1}, {ff_program, 5}, {ff_internal, 3}, {ff_variable, 2}, {ff_variable + ff_internal, 4} }; /* Dirty the VDP where we messed around */ static void VDPUpdate(u16 addr, u16 len) { while (len--) vdp_touch(addr++); } /* Copy data safely to VDP */ static u16 VDPCopy(u16 addr, u8 * data, u32 len) { while (len--) { domain_write_byte(md_video, addr++, *data++); } return addr; } /* Copy data safely from VDP */ static u16 VDPRead(u8 * data, u16 addr, u32 len) { while (len--) { *data++ = domain_read_byte(md_video, addr++); } return addr; } //////////////////////////////////// /* Set error directly to PAB in VDP; this allows short-circuit subroutine exit without storing PAB back to VDP. */ static void pab_set_vdp_error(u8 *fnptr, u8 paberror) { u16 addr = VDP_ADDR(fnptr) - 9 + 1; u8 byt = domain_read_byte(md_video, addr); domain_write_byte(md_video, addr, (byt & ~m_error) | (paberror & m_error)); } /* Set error in pabfile's PAB. */ static void pab_set_error(fiad_pabfile *pf, u8 paberror) { pab_set_vdp_error(pf->fnptr, paberror); pf->pab.pflags = (pf->pab.pflags & ~m_error) | (paberror & m_error); } static void pab_vdp_error(u8 *fn, OSError err, char *str, u8 paberror) { pab_set_vdp_error(fn, paberror); if (paberror) { logger(_L | L_1, "FIAD server: %.*s: got PAB error %d [%s (%s)]\n", *fn, fn+1, paberror >> 5, str, err ? OS_GetErrText(err) : ""); } } static void pab_error(fiad_pabfile *pf, OSError err, char *str, u8 paberror) { pab_set_error(pf, paberror); pab_vdp_error(pf->fnptr, err, str, paberror); } /* Match an open pab file. We distinguish open files by their fnptr being set. */ static fiad_pabfile * pab_find_open_file(u8 *fn) { fiad_pabfile *pf = files; while (pf < files + MAXFILES) { if (pf->fnptr == fn && (pf->tf.open || pf->is_catalog)) return pf; pf++; } return NULL; } /* Allocate a new pab file. */ static fiad_pabfile * pab_get_new_file(u8 *fn) { fiad_pabfile *pf = files; while (pf < files + MAXFILES) { if (pf->fnptr == fn) { // already open, close it if (pf->tf.open) { logger(_L|LOG_ERROR, "%.*s: existing PAB not closed\n", *fn, fn+1); fiad_tifile_close_file(&pf->tf); } pf->is_catalog = false; return pf; } else if (pf->fnptr == NULL) { pf->fnptr = fn; pf->is_catalog = false; fiad_tifile_clear(&pf->tf); return pf; } pf++; } return NULL; } /* * Copy PAB from VDP into file struct before a file operation * * If opening is true, allow flags/reclen to be updated */ static void pab_fetch_from_vdp(fiad_pabfile *pf, bool opening) { pabrec mypab; VDPRead((void *)&mypab, VDP_ADDR(pf->fnptr-9), sizeof(pabrec)); pf->pab.opcode = mypab.opcode; pf->pab.addr = TI2HOST(mypab.addr); pf->pab.charcount = mypab.charcount; pf->pab.recnum = TI2HOST(mypab.recnum); pf->pab.scrnoffs = mypab.scrnoffs; pf->pab.namelen = mypab.namelen; /* TI BASIC appears to trash these bytes after opening the file. We stubbornly continue to use them, however, so don't reread from the real PAB after opening. */ if (opening) { pf->pab.pflags = mypab.pflags; pf->pab.preclen = mypab.preclen; logger(_L | L_3, "PAB contents: flags=>%02X, reclen=%d, addr=>%04X, charcount=%d, recnum=%d\n", pf->pab.pflags, pf->pab.preclen, pf->pab.addr, pf->pab.charcount, pf->pab.recnum); } else { logger(_L | L_3, "PAB contents: addr=>%04X, charcount=%d, recnum=%d\n", pf->pab.addr, pf->pab.charcount, pf->pab.recnum); } } /* * Copy PAB from file struct into VDP after a file operation */ static void pab_store_to_vdp(fiad_pabfile *pf) { pabrec mypab; mypab.opcode = pf->pab.opcode; mypab.addr = HOST2TI(pf->pab.addr); mypab.charcount = pf->pab.charcount; mypab.recnum = HOST2TI(pf->pab.recnum); mypab.scrnoffs = pf->pab.scrnoffs; mypab.namelen = pf->pab.namelen; mypab.pflags = pf->pab.pflags; mypab.preclen = pf->pab.preclen; VDPCopy(VDP_ADDR(pf->fnptr-9), (u8 *)&mypab, sizeof(pabrec)); } /////////////// #if 0 static u8 sub_error_map[] = { es_outofspace, // E_nobuffer es_hardware, // E_cantmakespec [disk paths are bad] es_filenotfound, // E_filenotfound es_badvalerr, // E_cantmodifyfile es_hardware, // E_formaterror es_hardware, // E_shortread [should find data] es_outofspace, // E_shortwrite es_hardware, // E_sectornotfound [should find data] es_hardware, // E_unexpectederror [whatever] es_badfuncerr, // E_illegalop es_hardware // E_endoffile }; #endif static void sub_set_error(u8 errcode) { memory_write_byte(0x8350, errcode); if (errcode) { logger(_L | L_1, "FIAD server: got subroutine error %d\n", errcode); } } static void sub_error(fiad_tifile * tf, OSError err, char *str, int errcode) { sub_set_error( errcode); if (errcode) { logger(_L | L_1, "FIAD server: got subroutine error %d [%s (%s)]\n", errcode, str, err ? OS_GetErrText(err) : ""); fiad_tifile_close_file(tf); } } /* Close a PAB file */ static void pab_close_file(fiad_pabfile *pf) { // not true for catalogs if (pf->tf.open) { fiad_tifile_close_file(&pf->tf); } pf->fnptr = 0L; } /* Close all files */ static void pab_close_all_files(void) { fiad_pabfile *pf = files; int dsk; while (pf < files + MAXFILES) { pab_close_file(pf); pf++; } for (dsk = 0; dsk < MAXDRIVE; dsk++) { last_sector_read[dsk] = 0xffff; } } ///////////////////////////////////// /* Read the FDR from a file, return 0 if it's bad. */ static int pab_read_fdr(fiad_pabfile * pf) { int ret = fiad_tifile_read_fdr(&pf->tf); if (!ret) { pab_set_error(pf, e_hardwarefailure); } return ret; } /* Write FDR to file, return 0 and report error if error. */ static int pab_write_fdr(fiad_pabfile * pf) { int ret = fiad_tifile_write_fdr(&pf->tf); if (!ret) { pab_set_error(pf, e_hardwarefailure); } return ret; } /* Compare the FDR with the PAB, making sure the PAB operation is compatile with the FDR, and that record sizes and file types match. If mismatching, report the error and return 0, else return 1. */ static int pab_compare_fdr_and_pab(fiad_pabfile * pf) { u8 pflags, fflags; char *pptr; u8 len; pabrec *pab = &pf->pab; fiad_tifile *tf = &pf->tf; /* compare name */ len = fiad_filename_strlen(tf->fdr.filenam); pptr = (char *) pf->fnptr + pab->namelen; while (pptr > (char *) pf->fnptr && *(pptr - 1) != '.') pptr--; if (strncasecmp(tf->fdr.filenam, pptr, len)) logger(_LL | LOG_WARN, "FIAD server: filename in FDR doesn't match filename ('%.*s' | '%.*s')\n", len, tf->fdr.filenam, pab->namelen - (pptr - (char *)pf->fnptr), pptr); pflags = pab->pflags; fflags = tf->fdr.flags; /* program files are easy */ if (pab->opcode == f_load || pab->opcode == f_save) if (fflags & ff_program) return 1; /* both must be fixed or variable */ if (!!(pflags & fp_variable) == !!(fflags & ff_variable)) { /* fixup the PAB if it doesn't know record size */ if (!pab->preclen) pab->preclen = tf->fdr.reclen; /* both must have same record length */ if (pab->preclen != tf->fdr.reclen) { logger(_L | L_1, "DSKcomparefdrandpab: record length differs\n"); } else { /* and no "var,relative" files */ if ((pflags & (fp_relative | fp_variable)) == (fp_relative | fp_variable)) { logger(_L | L_1, "DSKcomparefdrandpab: var + relative file\n"); } else return 1; } } else { logger(_L | L_1, "DSKcomparefdrandpab: fixed/variable flag differs\n"); } pab_error(pf, 0, "file type mismatch on file open", e_badfiletype); return 0; } /* Compare the FDR with the ten-byte filename at fptr, making sure the filename matches. Always return 1. */ static int DSKcomparefdrandsub(fiad_tifile * tf, char *fptr) { if (tf->format == F_V9t9 && strncasecmp(tf->fdr.filenam, fptr, 10)) { logger(_LL, "DSKcomparefdrandsub: filenames don't match ('%.*s' | '%.*s')\n", 10, tf->fdr.filenam, 10, fptr); } return 1; } /* Create an FDR from a PAB. The filename is in the FDR already. */ static void pab_make_fdr(fiad_pabfile * pf) { fiad_tifile *tf = &pf->tf; pabrec *pab = &pf->pab; fiad_fdr_setup(&tf->fdr, pab->opcode == f_save, ((pab->pflags & fp_variable) ? ff_variable : 0) | ((pab->pflags & fp_internal) ? ff_internal : 0), pab->preclen, 0 /*size*/); pab->preclen = tf->fdr.reclen; } /* Read a sector from the PAB file */ static int pab_read_sector(fiad_pabfile * pf) { int ret = fiad_tifile_read_sector(&pf->tf); if (!ret) { char msg[64]; sprintf(msg, "reading sector %d", pf->tf.cursec); pab_error(pf, pf->tf.error, msg, e_hardwarefailure); } return ret; } /* Write current sector to PAB file */ static int pab_write_sector(fiad_pabfile * pf) { int ret = fiad_tifile_write_sector(&pf->tf); if (!ret) { char msg[64]; sprintf(msg, "writing sector %d", pf->tf.cursec); pab_error(pf, 0, msg, e_hardwarefailure); } return ret; } //////////////////// /* Routine to create the OSSpec for the file using the PAB and the current disk, and copy the name into the file's FDR. Return 0 for success, 1 if the filename is empty, which indicates a catalog request on a disk, or -1 if the path was invalid. */ static int pab_setup_file(fiad_pabfile *pf, u8 dev, u8 *fn) { /* 0x8356 holds a pointer into VDP RAM to the end of the device name (RS232|, DSK|., DSK1|.ed) */ u8 len; u16 fnaddr; char *fname; OSError err; pf->fnptr = fn; fiad_tifile_clear(&pf->tf); len = *fn; /* length of device+filename */ logger(_L | L_2, "getfilespec_pab: pab len = %d\n", len); fnaddr = memory_read_word(0x8356) + 1; /* addr of filename (skip period) */ logger(_L | L_2, "getfilespec_pab: fnaddr++ at 0x8356 = %20.20s\n", VDP_PTR(0) + fnaddr); len -= memory_read_word(0x8354) + 1; /* minus length of device + period */ logger(_L | L_2, "getfilespec_pab: length of device 0x8354 = %04X\n", memory_read_word(0x8354)); fname = (char *) VDP_PTR(fnaddr); if (memchr(fname, '.', len) != NULL) { pab_error(pf, 0, "bad characters in filename", e_badfiletype); return -1; } err = fiad_tifile_setup_spec_with_file(&pf->tf, &emudiskpath[dev - 1], fname, len); if (err != OS_NOERR) { pab_error(pf, err, "couldn't make spec", e_badfiletype); return -1; } else if (len == 0) return 1; /* no filename means catalog */ else return 0; } /* Create the host OSSpec for the ten-character filename, as seen in a subroutine call. */ static int sub_setup_file(fiad_tifile *tf, u8 dsknum, u16 fnaddr) { u8 len = 0; const char *chptr = (char *)VDP_PTR(fnaddr); while (len < 10 && (*chptr != ' ' && *chptr != 0 && *chptr != '.')) { len++; chptr++; } // a dot is illegal if (*chptr == '.') { sub_error(tf, 0, "bad characters in filename", es_filenotfound); return -1; } if (fiad_filename_to_spec(&emudiskpath[dsknum - 1], (char *) VDP_PTR(fnaddr), len, &tf->spec) != OS_NOERR) return -1; else if (len == 0) return 1; /* no filename */ else return 0; } //////////////////////////////////// /* Match a emulated disk by volume name. Return length of volume plus dot, and disk device (>0) or 0 if not matched. We're passed a name terminated by a period, space, or 0. */ static u8 matchdrive(char *volume, int *len) { u8 dev; *len = 1; while (volume[*len - 1] != '.' && volume[*len - 1] != ' ' && volume[*len - 1] != 0) (*len)++; /* we don't match DSK1 and DSK2 if the real disk is going */ dev = dsr_is_emu_disk(1) ? 1 : 3; while (dev <= MAXDRIVE) { char path[OS_PATHSIZE]; char *ptr; char tivol[10]; int len; int idx; int matched; OS_PathSpecToString2(&emudiskpath[dev - 1], path); logger(_L | L_1, "trying to match '%s'\n", path); /* clear trailing slash, colon, etc */ path[strlen(path) - 1] = 0; /* ptr has leaf name */ ptr = (char *) OS_GetFileNamePtr(path); len = fiad_filename_host2ti(ptr, tivol); VDPCopy(vdpnamebuffer, (u8 *)tivol, 10); /* compare */ idx = 0; matched = 1; while (idx < 10 && volume[idx] && volume[idx] != '.' && volume[idx] != 0) { /* don't be picky about capitalization, since these things are hard to figure out ;) */ if (tolower(volume[idx]) != tolower(tivol[idx])) { matched = 0; break; } idx++; } if (matched) return dev; dev++; } return 0; } //////////////////////////////////// #if 0 #pragma mark - #pragma mark "Catalog read" #endif /* Convert and push an integer into a TI floating point record: [8 bytes] [0x40+log num] 9*[sig figs, 0-99] Return pointer past end of float. */ static u8 * int_to_tifloat(unsigned x, u8 * buf) { u8 *start; *buf++ = 8; // bytes in length memset(buf, 0, 8); if (x == 0) return buf + 8; start = buf; buf[0] = 0x3F; while (x > 0) { buf[0]++; memmove(buf + 2, buf + 1, 6); buf[1] = x % 100; x /= 100; } return buf + 8; } /* Setup a pab file for use as a catalog iterator. */ static int DSKinitcatalog(fiad_pabfile * pf, u8 dsk) { OSError err; int max; char path[OS_PATHSIZE]; OSPathSpec *spec; pf->is_catalog = dsk--; spec = &emudiskpath[dsk]; /* Don't re-read catalog more often than necessary. */ if (!OS_EqualPathSpec(spec, &DskCat[dsk].path) || DskCatFlag[dsk]) { logger(_L|L_1, "Re-reading catalog for DSK%d\n",dsk+1); OS_PathSpecToString2(spec, path); if ((err = fiad_catalog_read_catalog(&DskCat[dsk], path)) != OS_NOERR) { pab_error(pf, err, "couldn't open directory for catalog", e_hardwarefailure); return 0; } /* Sort by name */ fiad_catalog_sort_catalog(&DskCat[dsk], FIAD_CATALOG_SORT_BY_NAME, true /*ascending*/); /* Set timer to prevent over-reading catalog (Microsoft Multiplan does this) */ DskCatFlag[dsk] = 0; TM_SetEvent(DskCatTag[dsk], TM_HZ*100, 0, 0 /*flags*/, &DskCatFlag[dsk]); } /* Restrict number of entries we return */ max = (allowlongcatalogs ? 32767 : 127); if (DskCat[dsk].entries > max) DskCat[dsk].entries = max; return 1; } /* For a file-based catalog, we are guaranteed 128 records. The first is the volume, and the rest are for files. Each record is a maximum of 38 bytes long, but is fixed at the file's open record length by padding it with zeroes. For a volume record, the format is: [length of volume] "volume" [8] [float: 0] [8] [float: total # sectors] [8] [float: remaining # sectors] [0...] For a file record, the format is: [length of filename] "filename" [8] [float: type] [8] [float: # sectors used + FDR] [8] [float: record length] Assume that any errors are due to reading a non-V9t9 file, and return 0 for those. */ static int DSKreadcatalog(fiad_pabfile *pf, u8 dsk, u8 *cr) { OSError err; fdrrec fdr; u8 *ptr; fiad_catalog *cat = &DskCat[dsk - 1]; logger(_L | L_1, "reading catalog entry\n"); memset(cr, 0, pf->pab.preclen); // volume record? if (pf->pab.recnum == 0) { /* Get volume name from path. */ ptr = cr; *ptr = fiad_path_disk2ti(&cat->path, (char *)ptr + 1); // copy disk name to name buffer VDPCopy(vdpnamebuffer, ptr+1, 10); ptr += *ptr + 1; // zero field ptr = int_to_tifloat(0, ptr); // total space ptr = int_to_tifloat(cat->total_sectors, ptr); // free space ptr = int_to_tifloat(cat->free_sectors, ptr); return 1; } // read file record; restrict it to 127 entries // in case naive programs will die... err = 0; if (pf->pab.recnum >= cat->entries) { logger(_L | L_1, "DSKreadcatalog: reached end of directory\n"); // make an empty record ptr = cr; *ptr++ = 0; ptr = int_to_tifloat(0, ptr); ptr = int_to_tifloat(0, ptr); ptr = int_to_tifloat(0, ptr); return 1; } // Get file info if (!fiad_catalog_get_file_info(cat, pf->pab.recnum, &fdr)) return 0; // first field is the string representing the // file or volume name ptr = cr; *ptr = fiad_filename_strlen(fdr.filenam); memcpy(ptr+1, fdr.filenam, *ptr); VDPCopy(vdpnamebuffer, (u8 *)fdr.filenam, 10); ptr += *ptr + 1; // second field is file type { int idx; for (idx = 0; idx < sizeof(DrcTrans) / sizeof(DrcTrans[0]); idx++) if (DrcTrans[idx][0] == (fdr.flags & (ff_internal | ff_program | ff_variable))) { ptr = int_to_tifloat(DrcTrans[idx][1], ptr); break; } // no match == program if (idx >= sizeof(DrcTrans) / sizeof(DrcTrans[0])) { ptr = int_to_tifloat(1, ptr); } } // third field is file size, one sector for fdr ptr = int_to_tifloat(1 + fdr.secsused, ptr); // fourth field is record size ptr = int_to_tifloat(fdr.reclen, ptr); return 1; } //////////////////////////////////// /* These DSKXxx routines handle file operations on PABs. */ static void DSKClose(u8 dev, u8 *fn); static void DSKOpen(u8 dev, u8 *fn) { fiad_pabfile *pf; fiad_tifile *tf; pabrec *pab; int ret; OSError err; logger(_L | L_1, "DSKOpen\n"); /* get a pabfile for PAB */ if ((pf = pab_get_new_file(fn)) == NULL) { pab_vdp_error(fn, 0, "no free files", e_outofspace); return; } tf = &pf->tf; pab = &pf->pab; /* read PAB */ pab_fetch_from_vdp(pf, true /*opening*/); /* sanity check */ if (pab->preclen == 255 && (pab->pflags & fp_variable)) { pab_error(pf, 0, "can't have variable record size of 255", e_badopenmode); goto open_error; } /* Fix PAB reclen to default if needed */ // if (!pab->preclen) { // pab->preclen = 80; // } /* get local file spec */ ret = pab_setup_file(pf, dev, fn); /* failure? */ if (ret < 0) goto open_error; /* catalog request? */ if (ret > 0) { /* make sure it's a legal open mode */ if ((pab->pflags & (fp_internal | m_input)) != (fp_internal | m_input)) { pab_error(pf, 0, "bad catalog open flags", e_badopenmode); goto open_error; } if (!DSKinitcatalog(pf, dev)) goto open_error; fiad_tifile_init_file_pointers(tf); pf->pab.preclen = 38; /* store the PAB */ pab_store_to_vdp(pf); return; } /* normal file open */ pab_set_error(pf, 0); switch (pab->pflags & m_openmode) { // update/append mode: either create new file, or // open existing one. case m_append: // can't append to FIXED file if (!(pab->pflags & fp_variable) /*&& !(pab->pflags & fp_internal)*/) { pab_error(pf, 0, "can't append to FIXED file", e_badopenmode); goto open_error; } // fall through case m_update: err = fiad_tifile_open_file(tf, newfileformat, true /*create*/, false /*always*/, false /*readonly*/); if (err != OS_NOERR) { pab_error(pf, err, "could not open for update", err == OS_PERMERR ? e_readonly : e_badopenmode); goto open_error; } // tf->changed set if we know we created it; // if not, and we can't read the FDR, create it anyway if (!tf->changed && pab_read_fdr(pf)) { logger(_L | L_1, "read FDR successfully\n"); if (!pab_compare_fdr_and_pab(pf)) goto open_error; } else { logger(_L | L_1, "setting up new FDR\n"); pab_set_error(pf, 0); pab_make_fdr(pf); if (!pab_write_fdr(pf)) { pab_set_error(pf, e_hardwarefailure); goto open_error; } my_assert(pab_compare_fdr_and_pab(pf)); } // when appending, start at end if ((pab->pflags & m_openmode) == m_append) { fiad_tifile_seek_to_end(tf); } // cache sector (may not exist for new file) if (!fiad_tifile_read_sector(tf)) { pab_set_error(pf, e_hardwarefailure); goto open_error; } break; // output mode: always create new file case m_output: err = fiad_tifile_open_file(tf, newfileformat, true /*create*/, true /*always*/, false /*readonly*/); if (err != OS_NOERR) { pab_error(pf, err, "could not open for output", err == OS_PERMERR ? e_readonly : e_badopenmode); goto open_error; } logger(_L | L_1, "setting up new FDR\n"); pab_make_fdr(pf); my_assert(pab_compare_fdr_and_pab(pf)); break; // input mode: always open existing file case m_input: err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, true /*readonly*/); if (err != OS_NOERR) { pab_error(pf, err, "could not open for input", e_badopenmode); goto open_error; } if (!pab_compare_fdr_and_pab(pf)) goto open_error; // cache first sector if (!fiad_tifile_read_sector(tf)) { pab_set_error(pf, e_hardwarefailure); goto open_error; } break; } /* store the PAB back to VDP */ pab_store_to_vdp(pf); return; open_error: pf->fnptr = NULL; } void DSKClose(u8 dev, u8 *fn) { fiad_pabfile *pf; logger(_L | L_1, "DSKClose\n"); // if the file isn't open, um... if ((pf = pab_find_open_file(fn)) == NULL) pab_set_vdp_error(fn, e_badfiletype); else pab_close_file(pf); } #if 0 #pragma mark "File read" #endif static void DSKRead(u8 dev, u8 *fn) { fiad_pabfile *pf; fiad_tifile *tf; pabrec *pab; u8 len; int err; logger(_L | L_1, "DSKRead\n"); if ((pf = pab_find_open_file(fn)) == NULL) { pab_vdp_error(fn, 0, "DSKRead: error due to not finding open file", e_badfiletype); return; } tf = &pf->tf; pab = &pf->pab; /* check operation */ if ((pab->pflags & m_openmode) == m_output || (pab->pflags & m_openmode) == m_append) { pab_error(pf, 0, "read from append/output file", e_illegal); return; } /* get pab info */ pab_fetch_from_vdp(pf, false /*opening*/); /* catalog read? */ if (pf->is_catalog) { u8 cr[256]; int x; DSKreadcatalog(pf, dev, cr); VDPCopy(pab->addr, cr, pab->preclen); logger(_L | L_3, "dump of catalog record:"); for (x = 0; x < pab->preclen; x++) { logger(_L | 0 | L_3, "%02X ", cr[x]); } logger(_L | L_3, "\n"); tf->currec++; pab->charcount = pab->preclen; pab->recnum = tf->currec; pab_store_to_vdp(pf); return; } /* normal file read */ /* for fixed file, limit record # and update */ if (!(pab->pflags & fp_variable)) { pf->pab.recnum &= 0x7fff; tf->currec = pf->pab.recnum; err = fiad_tifile_read_record(tf, VDP_PTR(pab->addr), &len); pf->pab.recnum = tf->currec; } else { /* variable file */ err = fiad_tifile_read_record(tf, VDP_PTR(pab->addr), &len); } if (err == 0) { VDPUpdate(pab->addr, len); pab->charcount = len; } else if (err < 0) { // hardware failure pab_set_error(pf, e_hardwarefailure); } else { // end of file pab_set_error(pf, e_endoffile); pab_close_file(pf); } /* save changed PAB info */ pab_store_to_vdp(pf); } static void DSKWrite(u8 dev, u8 *fn) { fiad_pabfile *pf; fiad_tifile *tf; pabrec *pab; int err; logger(_L | L_1, "DSKWrite\n"); if ((pf = pab_find_open_file(fn)) == NULL) { pab_vdp_error(fn, 0, "DSKWrite: error due to not finding open file", e_badfiletype); return; } tf = &pf->tf; pab = &pf->pab; /* check operation */ if ((pab->pflags & m_openmode) == m_input) { pab_error(pf, 0, "writing to input file", e_illegal); return; } if (pf->is_catalog) { pab_error(pf, 0, "writing to catalog", e_illegal); return; } /* get pab info */ pab_fetch_from_vdp(pf, false /*opening*/); /* for fixed file, limit record # and update */ if (!(pab->pflags & fp_variable)) { pf->pab.recnum &= 0x7fff; tf->currec = pf->pab.recnum; err = fiad_tifile_write_record(tf, VDP_PTR(pab->addr), pab->preclen); pf->pab.recnum = tf->currec; } else { /* variable file */ err = fiad_tifile_write_record(tf, VDP_PTR(pab->addr), pab->charcount); } if (err == 0) { // no error } else if (err < 0) { // hardware failure pab_set_error(pf, e_hardwarefailure); } else if (err > 0) { // disk full pab_set_error(pf, e_outofspace); } /* save changed PAB info */ pab_store_to_vdp(pf); } static void DSKSeek(u8 dev, u8 *fn) { fiad_pabfile *pf; fiad_tifile *tf; pabrec *pab; int err; logger(_L | L_1, "DSKSeek\n"); if ((pf = pab_find_open_file(fn)) == NULL) { pab_vdp_error(fn, 0, "DSKSeek: error due to not finding open file", e_badfiletype); return; } tf = &pf->tf; pab = &pf->pab; if (pf->is_catalog) { pab_error(pf, 0, "DSKSeek: error due to seeking catalog", e_illegal); return; } /* get pab info */ pab_fetch_from_vdp(pf, false /*opening*/); /* variable files are restored to record 0, and this can only be done in update/input mode */ if ((pab->pflags & fp_variable) && ((pab->pflags & m_openmode) == m_append || (pab->pflags & m_openmode) == m_output)) { pab_error(pf, 0, "append/output mode can't seek", e_illegal); return; } if (!(pab->pflags & fp_variable)) { // maximum record # is 32767 pab->recnum &= 0x7fff; err = fiad_tifile_seek_to_record(tf, pab->recnum); } else { pab->recnum = 0; err = fiad_tifile_seek_to_record(tf, 0); } if (err == 0) { // no error } else { pab_set_error(pf, (err > 0) ? e_outofspace : e_hardwarefailure); } /* save changed PAB info */ pab_store_to_vdp(pf); } #if 0 #pragma mark "LOAD MEMORY IMAGE" #endif static void DSKLoad(u8 dev, u8 *fn) { fiad_pabfile pf; fiad_tifile *tf; pabrec *pab; int ret; OSError err; u16 len; logger(_L | L_0, "DSKLoad\n"); tf = &pf.tf; pab = &pf.pab; /* get local file spec */ ret = pab_setup_file(&pf, dev, fn); /* failure? */ if (ret < 0) return; /* catalog request? */ if (ret > 0) { pab_error(&pf, 0, "can't open catalog as binary", e_illegal); return; } logger(_L | L_1, "trying to open binary image\n"); err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, true /*readonly*/); if (err != OS_NOERR) { pab_error(&pf, err, "could not open for input", e_badfiletype); return; } /* read PAB */ pab_fetch_from_vdp(&pf, true /*opening*/); if (!pab_compare_fdr_and_pab(&pf)) return; pab_set_error(&pf, 0); ret = fiad_tifile_read_binary_image(tf, VDP_PTR(pab->addr), pab->recnum, &len); VDPUpdate(pab->addr, len); if (ret >= 0) { // no error or EOF (which is okay for DSKLoad) pab->recnum = len; } else { // failure pab_set_error(&pf, e_hardwarefailure); } /* save changed PAB info */ pab_store_to_vdp(&pf); /* close file */ pab_close_file(&pf); } #if 0 #pragma mark "SAVE MEMORY IMAGE" #endif static void DSKSave(u8 dev, u8 *fn) { fiad_pabfile pf; fiad_tifile *tf; pabrec *pab; int ret; OSError err; u16 len; logger(_L | 0, "DSKSave\n"); tf = &pf.tf; pab = &pf.pab; /* get local file spec */ ret = pab_setup_file(&pf, dev, fn); /* failure? */ if (ret < 0) return; /* catalog request? */ if (ret > 0) { pab_error(&pf, 0, "can't save catalog as binary", e_illegal); return; } logger(_L | L_1, "trying to save binary image\n"); err = fiad_tifile_open_file(tf, newfileformat, true /*create*/, true /*always*/, false /*readonly*/); if (err != OS_NOERR) { pab_error(&pf, err, "could not open for output", err == OS_PERMERR ? e_readonly : e_badfiletype); return; } /* read PAB */ pab_fetch_from_vdp(&pf, true /*opening*/); logger(_L | L_1, "setting up new FDR\n"); pab_make_fdr(&pf); my_assert(pab_compare_fdr_and_pab(&pf)); pab_set_error(&pf, 0); ret = fiad_tifile_write_binary_image(tf, VDP_PTR(pab->addr), pab->recnum, &len); if (ret == 0) { // no error } else { // failure pab_set_error(&pf, ret > 0 ? e_outofspace : e_hardwarefailure); } /* no pab changes */ /* close file */ pab_close_file(&pf); } static void DSKDelete(u8 dev, u8 *fn) { fiad_pabfile pf; fiad_tifile *tf; pabrec *pab; int ret; OSError err; logger(_L | 0, "DSKDelete\n"); tf = &pf.tf; pab = &pf.pab; /* read PAB */ pab_fetch_from_vdp(&pf, true /*opening*/); /* get local file spec */ ret = pab_setup_file(&pf, dev, fn); /* failure? */ if (ret < 0) return; /* catalog request? */ if (ret > 0) { pab_error(&pf, 0, "can't delete catalog", e_illegal); return; } logger(_L | L_1, "trying to delete file\n"); pab_set_error(&pf, 0); err = OS_Delete(&tf->spec); if (err != OS_NOERR) { pab_error(&pf, err, "deleting file\n", err == OS_PERMERR ? e_readonly : e_badfiletype); } } static void DSKScratch(u8 dev, u8 *fn) { logger(_L | 0, "DSKScratch"); // nothing supports this pab_set_vdp_error(fn, e_illegal); } /* Get status bits for a file */ static u8 fiad_tifile_get_status(fiad_tifile *tf, u8 status) { bool is_protected = true; OSSize total, free, blocksize; if ((tf->fdr.flags & ff_protected) || OS_CheckProtection(&tf->spec, &is_protected) == OS_NOERR) status |= is_protected ? st_readonly : 0; if (tf->open) { if (tf->fdr.flags & ff_internal) status |= st_internal; if (tf->fdr.flags & ff_program) status |= st_program; if (tf->fdr.flags & ff_variable) status |= st_variable; } if (OS_GetDiskStats(&tf->spec.path, &blocksize, &total, &free) == OS_NOERR && total == free) status |= st_diskfull; // check EOF if (status & st_variable) { if (tf->cursec + 1 >= tf->fdr.secsused && tf->sector[tf->curoffs] == 0xff) { status |= st_eof; logger(_L | L_2, "DSKStatus: EOF on variable file"); } } else { if (tf->currec >= tf->fdr.numrecs) { status |= st_eof; logger(_L | L_2, "DSKStatus: EOF on fixed file\n"); } } return status; } static void DSKStatus(u8 dev, u8 *fn) { int file_was_open = 0; fiad_pabfile *pf, local; fiad_tifile *tf; pabrec *pab; u8 status = 0; int ret; OSError err; logger(_L | L_2, "DSKStatus\n"); if ((pf = pab_find_open_file(fn)) == NULL) { /* file is not open, open it now */ logger(_L | L_2, "DSKStatus: no PAB file yet...\n"); /* use local file */ pf = &local; tf = &pf->tf; pab = &pf->pab; /* get local file spec */ ret = pab_setup_file(pf, dev, fn); /* failure? */ if (ret < 0) { logger(_L | L_2, "DSKStatus: file not found\n"); status = st_filenotfound; pab->scrnoffs = status; pab_store_to_vdp(pf); return; } /* catalog request? */ if (ret > 0) { logger(_L | L_2, "DSKStatus: catalog status\n"); status = st_internal | st_readonly; pab->scrnoffs = status; pab_store_to_vdp(pf); return; } logger(_L | L_2, "DSKStatus: opening file\n"); /* open file to look at it */ err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, true /*readonly*/); if (err != OS_NOERR) { logger(_L | L_2, "DSKStatus: file not found\n"); status |= st_filenotfound; pab->scrnoffs = status; pab_store_to_vdp(pf); return; } } else { /* file was already open */ logger(_L | L_2, "DSKStatus: file was open\n"); tf = &pf->tf; pab = &pf->pab; // they might have seeked on us if (!(pab->pflags & fp_variable)) { pab->recnum &= 0x7fff; fiad_tifile_seek_to_record(tf, pab->recnum); } file_was_open = 1; } status |= fiad_tifile_get_status(tf, status); logger(_L | L_1, "DSKStatus: flags=%02X\n\n", status); pab->scrnoffs = status; pab_set_error(pf, 0); /* save changed PAB bits */ pab_store_to_vdp(pf); /* close file if we just opened it */ if (!file_was_open) { pab_close_file(pf); } } //////////////////////////////////// #if 0 #pragma mark - #pragma mark "Fake catalog sectors routines" #endif static void setup_catalog(u8 dsk) { char path[OS_PATHSIZE]; OS_PathSpecToString2(&emudiskpath[dsk], path); fiad_catalog_read_catalog(&DskCat[dsk], path); } /* * Don't always free the catalog, since fiad.c will * free it for us next time we read it */ static void free_catalog(u8 dsk) { fiad_catalog_free_catalog(&DskCat[dsk]); } static void create_volume_sector(u8 dsk, u8 *sector) { int len; fiad_catalog *cat = &DskCat[dsk]; logger(_L | L_1, "creating catalog sector 0\n"); // reread catalog at sector 0 if (!DskCat[dsk].entries || !OS_EqualPathSpec(&emudiskpath[dsk], &DskCat[dsk].path) || (last_sector_read[dsk] != 0 && last_sector_read[dsk] != 1)) { setup_catalog(dsk); } memset(sector, 0, 256); // use directory name as volume name len = fiad_path_disk2ti(&cat->path, (char *)sector); // copy to VDP name buffer VDPCopy(vdpnamebuffer, sector, 10); // fill in info for a 90k disk sector[10] = 0x1; sector[11] = 0x68; sector[12] = 9; // secs per track sector[13] = 'D'; sector[14] = 'S'; sector[15] = 'K'; sector[17] = 40; // # tracks sector[18] = 1; // # sides? (may be density) sector[19] = 1; // density? (may be sides) // disk is full memset(sector + 56, 0xff, 256 - 56); } static void create_index_sector(u8 dsk, u16 * sector) { int x,l; logger(_L | L_1, "creating catalog sector 1\n"); // reread catalog at sector 1 if (!DskCat[dsk].entries || !OS_EqualPathSpec(&emudiskpath[dsk], &DskCat[dsk].path) || (last_sector_read[dsk] != 0 && last_sector_read[dsk] != 1)) { setup_catalog(dsk); } memset(sector, 0, 256); /* entries were already sorted */ l = DskCat[dsk].entries > 127 ? 127 : DskCat[dsk].entries; for (x = 0; x < l; x++) { u16 sec = x+2; sector[x] = HOST2TI(sec); } } static int create_fdr_sector(u8 dsk, u16 ent, u8 * sector) { fdrrec fdr; v9t9_fdr *v9f; fiad_catalog *cat = &DskCat[dsk]; logger(_L | L_1, "creating catalog directory sector %d\n", ent + 2); // reread catalog at unknown FDR sector if (!DskCat[dsk].entries || !OS_EqualPathSpec(&emudiskpath[dsk], &DskCat[dsk].path) || (last_sector_read[dsk] == 0xffff)) { setup_catalog(dsk); } if (!fiad_catalog_get_file_info(cat, ent, &fdr)) return 0; memset(sector, 0, 256); v9f = (v9t9_fdr *) sector; memcpy(v9f->filenam, fdr.filenam, 10); VDPCopy(vdpnamebuffer, (u8 *)fdr.filenam, 10); v9f->flags = fdr.flags; v9f->recspersec = fdr.recspersec; v9f->secsused = HOST2TI(fdr.secsused); v9f->byteoffs = fdr.byteoffs; v9f->reclen = fdr.reclen; v9f->numrecs = SWAPTI(fdr.numrecs); return 1; } #if 0 #pragma mark "Read/write sector" #endif /* Read/write sector. Only allow access to the volume sector, index sector, and catalog sectors; all of by faking it. The catalogs DskCat[] contain a list of files scanned in the directory when sector 1 is read. If any sector greater than 1 is read without reading sector 1 first, there will be invalid sector info returned. in: byte 0x834C: drive byte 0x834d: 0xff=read, 0x00=write word 0x834E: vdp addr word 0x8350: sector # out: word 0x834A: sector read byte 0x8350: status */ static void DskSectorRW(u8 dev, u8 rw, u16 addr, u16 secnum) { u8 sector[256]; logger(_L | L_1, "DskSectorRW\n"); // don't write! if (!rw) { logger(_LL | 0, "FIAD server: ignoring sector write request"); sub_set_error(0); return; } if (secnum == 0) { // make volume sector create_volume_sector(dev - 1, sector); } else if (secnum == 1) { // make index sector create_index_sector(dev - 1, (u16 *) sector); } else /* (secnum >= 2) */ { // make fdr sector if (!create_fdr_sector(dev - 1, secnum - 2, sector)) { free_catalog(dev-1); memset(sector, 0, 256); secnum = 0xffff; return; } } last_sector_read[dev-1] = secnum; VDPCopy(addr, sector, 256); sub_set_error(0); } #if 0 #pragma mark "Format disk" #endif /* Format disk. in: byte 0x834C: drive byte 0x834d: # tracks word 0x834e: track layout byte 0x8350: density (1,2) byte 0x8351: sides (1,2) out: byte 0x8350: status */ static void DskFormatDisk(u8 dev, u8 tracks, u16 tinfo, u16 densid) { logger(_L | 0, "DskFormatDisk\n"); sub_set_error(e_illegal); } #if 0 #pragma mark "Modify file protection" #endif /* Modify file protection in: byte 0x834c: drive byte 0x834d: 0=none, <>0=locked word 0x834e: filename */ static void DskProtect(u8 dev, u8 lock, u16 fname, u16 addr2) { fiad_tifile local, *tf = &local; OSError err; logger(_L | 0, "DskProtect\n"); if (sub_setup_file(tf, dev, fname) != 0) { sub_set_error(es_badfuncerr /*es_filenotfound */ ); return; } err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, false /*readonly*/); if (err != OS_NOERR) { sub_error(tf, err, "could not open to modify protection", err == OS_PERMERR ? es_hardware : es_badfuncerr /* es_filenotfound */); return; } DSKcomparefdrandsub(tf, (char *) VDP_PTR(fname)); // update FDR tf->fdr.flags = (tf->fdr.flags & ~ff_protected) | (lock ? ff_protected : 0); tf->changedfdr = true; fiad_tifile_close_file(tf); // do NOT modify the real file sub_set_error(es_okay); } #if 0 #pragma mark "Rename file" #endif /* Rename file in: byte 0x834c: drive word 0x834e: new name word 0x8350: old name out: byte 0x8350: status */ static void DskRename(u8 dev, u8 unk, u16 newname, u16 oldname) { fiad_tifile local, *tf = &local; char newpath[OS_PATHSIZE]; OSSpec orig; OSSpec renamed; OSError err; logger(_L | 0, "DskRename\n"); if (sub_setup_file(tf, dev, oldname) != 0) { sub_set_error(es_badfuncerr /*es_filenotfound */ ); return; } // save original spec orig = tf->spec; // rename real file OS_PathSpecToString2(&orig.path, newpath); fiad_filename_ti2host((char *) VDP_PTR(newname), strcspn((char *) VDP_PTR(newname), " ."), newpath + strlen(newpath)); // can't fathom new name? if ((err = OS_MakeFileSpec(newpath, &renamed)) != OS_NOERR) { sub_set_error(es_hardware); return; } // destination already exists? if (OS_Status(&renamed) == OS_NOERR) { sub_set_error(es_fileexists); return; } if ((err = OS_Rename(&orig, &renamed)) != OS_NOERR) { sub_set_error(es_hardware); return; } // now open the file and rename the FDR if (sub_setup_file(tf, dev, newname) != 0) { sub_set_error(es_badfuncerr /*es_filenotfound */ ); return; } err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, false /*readonly*/); if (err != OS_NOERR) { sub_error(tf, err, "could not open to modify filename", err == OS_PERMERR ? es_hardware : es_badfuncerr /* es_filenotfound */); return; } // update FDR memcpy(tf->fdr.filenam, VDP_PTR(newname), 10); tf->changedfdr = true; logger(_L | 0, "Renamed '%s' to '%s'\n\n\n", OS_SpecToString1(&orig), newpath); fiad_tifile_close_file(tf); sub_set_error(es_okay); } #if 0 #pragma mark "Direct input file" #endif /* Access direct input file in: byte 0x834c: drive byte 0x834d: 0=get FDR info, <>0=get # sectors word 0x834e: filename byte 0x8350: 0x8300+# = ptr to: -- if get FDR info: word , word <# sectors>, byte , byte , byte , byte , word -- if read sectors: word , word out: byte 0x834D: # sectors read byte 0x8350: status */ static void DskDirectInput(u8 dev, u8 secs, u16 fname, u16 parms) { fiad_tifile local, *tf = &local; OSError err; logger(_L | 0, "DskDirectInput\n"); // error condition memory_write_byte(0x834d, 0); if (sub_setup_file(tf, dev, fname) != 0) { sub_set_error(es_filenotfound); return; } err = fiad_tifile_open_file(tf, F_UNKNOWN, false /*create*/, false /*always*/, true /*readonly*/); if (err != OS_NOERR) { sub_error(tf, err, "could not open to read sectors", es_badfuncerr /* es_filenotfound */); return; } DSKcomparefdrandsub(tf, (char *) VDP_PTR(fname)); parms = 0x8300 + (parms >> 8); if (!secs) { // read FDR info memory_write_word(parms + 2, tf->fdr.secsused); memory_write_byte(parms + 4, tf->fdr.flags); memory_write_byte(parms + 5, tf->fdr.recspersec); memory_write_byte(parms + 6, tf->fdr.byteoffs); memory_write_byte(parms + 7, tf->fdr.reclen); memory_write_word(parms + 8, HOST2TI(tf->fdr.numrecs)); sub_set_error(0); } else { // read sectors u16 vaddr = memory_read_word(parms); u16 secnum = memory_read_word(parms + 2); u16 red = 0; int ret; logger(_L | L_1, "reading %d sectors from sector #%d from file, storing to >%04X\n\n", secs, secnum, vaddr); //debugger_enable(true); sub_set_error(0); if (!fiad_tifile_seek_to_sector(tf, secnum)) { sub_error(tf, tf->error, "DskDirectInput: Could not seek to sector", es_hardware); } else { ret = fiad_tifile_read_binary_image(tf, VDP_PTR(vaddr), secs * 256, &red); if (ret < 0) { sub_error(tf, tf->error, "DskDirectInput: Could not read sectors", es_hardware); sub_set_error(es_hardware); } // error will be set if sector read failed memory_write_byte(0x834D, (red + 255) >> 8); } } fiad_tifile_close_file(tf); } #if 0 #pragma mark "Direct output file" #endif /* Access direct output file in: byte 0x834c: drive byte 0x834d: 0=make FDR, <>0=write # sectors word 0x834e: filename byte 0x8350: +0x8300 = ptr to: -- if make FDR: word , word <# sectors>, byte , byte , byte , byte , word -- if write sectors: word , word out: byte 0x834d: # sectors written (or 0) byte 0x8350: status */ static void DskDirectOutput(u8 dev, u8 secs, u16 fname, u16 parms) { fiad_tifile local, *tf = &local; OSError err; logger(_L | 0, "DskDirectOutput\n"); if (sub_setup_file(tf, dev, fname) != 0) { sub_set_error(es_outofspace); return; } err = fiad_tifile_open_file(tf, newfileformat, 2 /*create*/, false /*always*/, false /*readonly*/); if (err != OS_NOERR) { sub_error(tf, err, "could not open to write sectors", err == OS_PERMERR ? es_hardware : es_badfuncerr /* es_filenotfound */); return; } DSKcomparefdrandsub(tf, (char *) VDP_PTR(fname)); parms = 0x8300 + (parms >> 8); if (!secs) { // write FDR info tf->fdr.secsused = memory_read_word(parms + 2); tf->fdr.flags = memory_read_byte(parms + 4); tf->fdr.recspersec = memory_read_byte(parms + 5); tf->fdr.byteoffs = memory_read_byte(parms + 6); tf->fdr.reclen = memory_read_byte(parms + 7); tf->fdr.numrecs = TI2HOST(memory_read_word(parms + 8)); // get filename memcpy(tf->fdr.filenam, VDP_PTR(fname), 10); tf->changedfdr = true; logger(_L|L_2, "Setting size of '%s' to %d sectors\n", OS_SpecToString1(&tf->spec), tf->fdr.secsused); // set file to given length if (!fiad_tifile_seek_to_sector(tf, tf->fdr.secsused) || !fiad_tifile_set_file_size(tf)) { sub_set_error(es_hardware); } else sub_set_error(0); // Reset any bogus flags in here tf->changedfdr = fiad_fdr_repair(&tf->fdr, tf->fdr.secsused * 256); //logger(_L|L_2, "Number of sectors now %d\n", tf->fdr.secsused); } else { // write sectors u16 vaddr = memory_read_word(parms); u16 secnum = memory_read_word(parms + 2); u16 wrt = 0; int ret; logger(_L | L_1, "writing %d sectors at sector #%d to file, reading from >%04X\n\n", secs, secnum, vaddr); sub_set_error(0); if (!fiad_tifile_seek_to_sector(tf, secnum)) { sub_set_error(es_hardware); } else { ret = fiad_tifile_write_binary_image(tf, VDP_PTR(vaddr), secs*256, &wrt); if (ret < 0) { sub_set_error(es_hardware); } memory_write_byte(0x834D, (wrt + 255) >> 8); } } fiad_tifile_close_file(tf); } //////////////////////////////////// extern vmModule emuDiskDSR; mrstruct dsr_rom_emudisk_handler = { emudiskdsr, emudiskdsr, NULL, /* ROM */ NULL, NULL, NULL, NULL }; static u32 cruwEmuDiskROM(u32 addr, u32 data, u32 num) { if (data) { logger(_L | L_3, "emu disk DSR on\n"); report_status(STATUS_DISK_ACCESS, 0, true); // debugger_enable(true); dsr_set_active(&emuDiskDSR); SET_AREA_HANDLER(0x4000, 0x2000, &dsr_rom_emudisk_handler); logger(_L | L_3, "DSR Read >4000 = >%2X\n\n", memory_read_byte(0x4000)); } else { logger(_L | L_3, "emu disk DSR off\n"); report_status(STATUS_DISK_ACCESS, 0, false); // debugger_enable(false); dsr_set_active(NULL); SET_AREA_HANDLER(0x4000, 0x2000, &zero_memory_handler); } return 0; } /********************************************/ static vmResult emudisk_getcrubase(u32 * base) { *base = EMUDISKCRUBASE; return vmOk; } static vmResult emudisk_filehandler(u32 code) { /* If the real disk DSR is also installed, we limit our functional drives from DSK3 to DSK5, and pass references to DSK1 and DSK2 to the real DSR. (The order of the CRU allows this, since we're EMUDISKCRUBASE and the real DSR is 0x1100.) */ my_assert(sizeof(pabrec) == 10); logger(_L | L_3, "handlefileoperations\n"); #ifdef REAL_DISK_DSR logger(_L | L_3, "code = %d\n\n", code); /* pass control when the code is for the real disk we didn't skip the return at *R11, so the TI ROM will continue to next DSR */ /* this shouldn't happen unless non-shared emulated disk DSR was loaded. */ if (dsr_is_real_disk(code - D_DSK1 + 1)) { logger(_L | L_3, "passing control to real disk"); return vmOk; } /* extended ops store drive [1-x] in low nybble */ if (code >= D_DSKSUB && dsr_is_real_disk(memory_read_byte(0x834C) & 0xf)) { logger(_L | L_3, "passing control to real disk\n"); return vmOk; } #endif logger(_L | L_3, "handling code %d\n\n", code); /* match any of the drives for the volume name at the VDP address in >8356. After matching the volume, add the length of the name to >8354 and >8356. */ if (code == D_DSK) { char *vname = (char *) VDP_PTR(memory_read_word(0x8356)); u8 dev; int len; if (*vname++ != '.') return vmOk; /* bad device */ dev = matchdrive(vname, &len); if (dev == 0) { logger(_L | L_1, "did not match DSK.%.*s\n", len, vname); return vmOk; /* not matched */ } else { logger(_L | L_1, "matched DSK.%.*s on DSK%d\n", len, vname, dev); } /* Update ptrs */ memory_write_word(0x8356, memory_read_word(0x8356) + len); memory_write_word(0x8354, memory_read_word(0x8354) + len); /* use this device */ code = dev; } switch (code) { /* PAB file operation on DSKx */ case D_DSK1: case D_DSK2: case D_DSK3: case D_DSK4: case D_DSK5: { u16 pabaddr = memory_read_word(0x8356) - memory_read_word(0x8354) - sizeof(pabrec); u8 opcode; u8 *fnptr; fnptr = VDP_PTR(pabaddr+9); opcode = *(fnptr-9); /* illegal opcode? */ if (opcode > 9) pab_set_vdp_error(fnptr, e_illegal); else { static void (*opcodehandlers[]) (u8 dev, u8 *fn) = { DSKOpen, DSKClose, DSKRead, DSKWrite, DSKSeek, DSKLoad, DSKSave, DSKDelete, DSKScratch, DSKStatus }; logger(_L | L_2, "doing operation %d on DSK%d\n", opcode, code); opcodehandlers[opcode] (code, fnptr); } /* return, indicating that the DSR handled the operation */ register(11) += 2; break; } /* init disk dsr */ case D_INIT: { int x; for (x = 0; x < MAXFILES; x++) { memset((void *)&files[x], 0, sizeof(files[0])); } /* Set up timer stuff for catalogs */ for (x = 0; x < MAXDRIVE; x++) { DskCatFlag[x] = 1; if (!DskCatTag[x]) DskCatTag[x] = TM_UniqueTag(); } /* also steal some RAM for the name compare buffer, so dependent programs can function */ vdpnamebuffer = memory_read_word(0x8370) - 9; memory_write_word(0x8370, vdpnamebuffer - 1); break; } /* ???? */ case D_16: { memory_write_byte(0x8350, 0); /* no error */ register(11) += 2; break; } /* call files(x) just ignore it */ case D_FILES: memory_write_word(0x832C, memory_read_word(0x832C) + 12); memory_write_byte(0x8342, 0); memory_write_byte(0x8350, 0); register(11) += 2; break; case D_SECRW: case D_FMTDISK: case D_PROT: case D_RENAME: case D_DINPUT: case D_DOUTPUT: { static void (*handlers[]) (u8 dev, u8 opt, u16 addr1, u16 addr2) = { DskSectorRW, DskFormatDisk, DskProtect, DskRename, DskDirectInput, DskDirectOutput }; u8 dev; u8 opt; u16 addr1, addr2; dev = memory_read_byte(0x834c); opt = memory_read_byte(0x834d); addr1 = memory_read_word(0x834e); addr2 = memory_read_word(0x8350); logger(_L | L_2, "doing operation %d on DSK%d [%d, %04X, %04X]\n", code, dev, opt, addr1, addr2); if (dev <= MAXDRIVE) { handlers[code - D_SECRW] (dev, opt, addr1, addr2); register(11) += 2; } break; } default: logger(_L | L_1, "ignoring code = %d\n", code); return vmNotAvailable; } return vmOk; } /*************************************************/ // Called to load/save each of the files[]. // // Since we're using one variable for each of these fields, // the second argument must be a string. Sigh. // Since the arg is a string buffer, we can use a simple routine // to format numeric strings for us. // // A special config var should close all the file[] entries // so we can sanely open them up at fixed positions again. static char *num2str(int x) { static char buf[32]; sprintf(buf, "%d", x); return buf; } static int str2num(const char *str) { int x; if (!strcasecmp(str,"true") || !strcasecmp(str, "on")) return 1; else if (!strcasecmp(str,"false") || !strcasecmp(str, "off")) return 0; x = atoi(str); return x; } // Number of fields to save: // fiad_pabfile.fnptr (VDP addr) // fiad_pabfile.pab (binary) // fiad_pabfile.is_catalog (0/1) // fiad_pabfile.tf: // spec (pathname) // open (0/1) // readonly (0/1) // error (number) // cursec (number) // cureof (number) // curnrecs (number) // currec (number) // // handle, changed, changedfdr, fdr, sector // are reconstructed at re-load time #define fiad_tifile_ENTS 12 static DECL_SYMBOL_ACTION(emudisk_config_files) { fiad_pabfile *f; char tmp[256]; char *field; char *str; int val; OSError err; if (task == csa_READ) { if (iter >= (fiad_tifile_ENTS * (sizeof(files)/sizeof(fiad_tifile)))) return 0; f = &files[iter / fiad_tifile_ENTS]; // first arg is file # command_arg_set_num(SYM_ARG_1st, iter / fiad_tifile_ENTS); switch (iter % fiad_tifile_ENTS) { case 0: // fnptr // prework: save changed stuff if necessary fiad_tifile_flush(&f->tf); command_arg_set_string(SYM_ARG_2nd, "vdp_filename_addr"); command_arg_set_string(SYM_ARG_3rd, !f->fnptr ? num2str(-1) : num2str(f->fnptr - VDP_PTR(0))); break; case 1: // pab command_arg_set_string(SYM_ARG_2nd, "vdp_pab_data"); emulate_bin2hex((u8 *)&f->pab, tmp, sizeof(f->pab)); command_arg_set_string(SYM_ARG_3rd, tmp); break; case 2: // is_catalog command_arg_set_string(SYM_ARG_2nd, "is_catalog"); command_arg_set_string(SYM_ARG_3rd, num2str(f->is_catalog)); break; // start of fiad_pabfile stuff case 3: // spec command_arg_set_string(SYM_ARG_2nd, "path"); command_arg_set_string(SYM_ARG_3rd, OS_SpecToString1(&f->tf.spec)); break; case 4: // open command_arg_set_string(SYM_ARG_2nd, "is_open"); command_arg_set_string(SYM_ARG_3rd, f->tf.open ? "true" : "false"); break; case 5: // readonly command_arg_set_string(SYM_ARG_2nd, "is_read_only"); command_arg_set_string(SYM_ARG_3rd, f->tf.readonly ? "true" : "false"); break; case 6: // error command_arg_set_string(SYM_ARG_2nd, "error"); command_arg_set_string(SYM_ARG_3rd, num2str(f->tf.error)); break; case 7: // cursec command_arg_set_string(SYM_ARG_2nd, "sector"); command_arg_set_string(SYM_ARG_3rd, num2str(f->tf.cursec)); break; case 8: // curoffs command_arg_set_string(SYM_ARG_2nd, "sector_offset"); command_arg_set_string(SYM_ARG_3rd, num2str(f->tf.curoffs)); break; case 9: // curnrecs command_arg_set_string(SYM_ARG_2nd, "sector_record_count"); command_arg_set_string(SYM_ARG_3rd, num2str(f->tf.curnrecs)); break; case 10: // currec command_arg_set_string(SYM_ARG_2nd, "record_number"); command_arg_set_string(SYM_ARG_3rd, num2str(f->tf.currec)); break; case 11: // reopen (trigger) // this tells the loader to reopen the file command_arg_set_string(SYM_ARG_2nd, "reopen"); command_arg_set_string(SYM_ARG_3rd, ""); break; default: my_assert("error in emudisk_config_files"); } return 1; } // Load from config file // spec should be the first thing we see command_arg_get_num(SYM_ARG_1st, &val); if (val < 0 || val >= sizeof(files) / sizeof(fiad_tifile)) { logger(_L | LOG_USER | LOG_ERROR, "Invalid file number\n"); return 0; } f = &files[val]; command_arg_get_string(SYM_ARG_2nd, &field); command_arg_get_string(SYM_ARG_3rd, &str); if (!strcasecmp(field, "vdp_filename_addr")) { int addr = str2num(str); f->fnptr = (addr == -1) ? 0L : VDP_PTR(addr & 0x3fff); } else if (!strcasecmp(field, "vdp_pab_data")) { emulate_hex2bin(str, (u8 *)&f->pab, sizeof(f->pab)); } else if (!strcasecmp(field, "is_catalog")) { f->is_catalog = str2num(str); } else if (!strcasecmp(field, "path")) { if (OS_MakeSpec(str, &f->tf.spec, NULL) != OS_NOERR) { logger(_L | LOG_USER | LOG_ERROR, "Invalid file name '%s'\n", str); return 0; } } else if (!strcasecmp(field, "is_open")) { f->tf.open = !!str2num(str); } else if (!strcasecmp(field, "is_read_only")) { f->tf.readonly = !!str2num(str); } else if (!strcasecmp(field, "error")) { f->tf.error = str2num(str); } else if (!strcasecmp(field, "sector")) { f->tf.cursec = str2num(str); } else if (!strcasecmp(field, "sector_offset")) { f->tf.curoffs = str2num(str) & 0xff; } else if (!strcasecmp(field, "sector_record_count")) { f->tf.curnrecs = str2num(str); } else if (!strcasecmp(field, "record_number")) { f->tf.currec = str2num(str); } else if (!strcasecmp(field, "reopen")) { if (f->is_catalog) { if (!DSKinitcatalog(f, f->is_catalog)) { logger(_L | LOG_ERROR | LOG_USER, "Could not reopen catalog at '%s'\n", OS_PathSpecToString1(&f->tf.spec.path)); return 0; } } else if (f->tf.open) { err = fiad_tifile_reopen_file(&f->tf, newfileformat, f->tf.readonly /*readonly*/); if (err != OS_NOERR) { logger(_L | LOG_ERROR | LOG_USER, "Could not reopen file '%s'\n", OS_SpecToString1(&f->tf.spec)); return 0; } } // read fdr and current sector for normal file if (f->tf.open) { if (!fiad_tifile_read_fdr(&f->tf)) return 0; if (!fiad_tifile_read_sector(&f->tf)) return 0; } } else { logger(_L | LOG_USER | LOG_ERROR, "Unrecognized file field: '%s'\n", field); return 0; } return 1; } static DECL_SYMBOL_ACTION(emudisk_close_files) { pab_close_all_files(); return 1; } /*************************************************/ static DECL_SYMBOL_ACTION(emudisk_check_disk) { int dsknum = sym->name[3] - '0'; if (!dsr_is_emu_disk(dsknum)) logger(_LL | LOG_WARN, "FIAD server: DSK%d (%s) is inaccessible when real (DOAD) disk DSR is loaded\n", dsknum, OS_PathSpecToString1(&emudiskpath[dsknum - 1])); return 1; } /*************************************************/ static vmResult emudisk_detect(void) { return vmOk; } static vmResult emudisk_init(void) { command_symbol_table *emudiskcommands = command_symbol_table_new("Emulated Disk DSR Options", "These commands control the emulated files-in-a-directory (FIAD) emulation", command_symbol_new("DSK1Path", "Set DSK1 directory", c_STATIC, emudisk_check_disk /* action */ , RET_FIRST_ARG, command_arg_new_pathspec ("dir", "directory containing V9t9 files", NULL /* action */ , &emudiskpath[0], NULL /* next */ ) , command_symbol_new("DSK2Path", "Set DSK2 directory", c_STATIC, emudisk_check_disk /* action */ , RET_FIRST_ARG, command_arg_new_pathspec ("dir", "directory containing V9t9 files", NULL /* action */ , &emudiskpath[1], NULL /* next */ ) , command_symbol_new("DSK3Path", "Set DSK3 directory", c_STATIC, emudisk_check_disk /* action */ , RET_FIRST_ARG, command_arg_new_pathspec ("dir", "directory containing V9t9 files", NULL /* action */ , &emudiskpath[2], NULL /* next */ ) , command_symbol_new("DSK4Path", "Set DSK4 directory", c_STATIC, emudisk_check_disk /* action */ , RET_FIRST_ARG, command_arg_new_pathspec ("dir", "directory containing V9t9 files", NULL /* action */ , &emudiskpath[3], NULL /* next */ ) , command_symbol_new("DSK5Path", "Set DSK5 directory", c_STATIC, emudisk_check_disk /* action */ , RET_FIRST_ARG, command_arg_new_pathspec ("dir", "directory containing V9t9 files", NULL /* action */ , &emudiskpath[4], NULL /* next */ ) , command_symbol_new("EmuDiskDSRFileName", "Name of emulated DSR ROM image which fits in the CPU address space >4000...>5FFF; " "this DSR defines DSK1 through DSK5", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_string("file", "name of binary image", NULL /* action */ , ARG_STR(emudiskfilename), NULL /* next */ ) , command_symbol_new("EmuDiskSharedDSRFileName|Emu2DiskDSRFileName", "Name of emulated DSR ROM image which fits in the CPU address space >4000...>5FFF; " "this DSR defines DSK3 through DSK5 and can share space with the real (DOAD) disk DSR", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_string("file", "name of binary image", NULL /* action */ , ARG_STR(emu2diskfilename), NULL /* next */ ) , command_symbol_new("KeepFileFormat", "Toggle preservation of original file type (V9t9 or TIFILES)", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_num("on|off", "on: don't change existing file's type; " "off: change type to NewFileFormat", NULL /* action */, ARG_NUM(keepfileformat), NULL /* next */ ) , command_symbol_new("NewFileFormat", "Select type for new files or converted files", c_STATIC, NULL /* action */, RET_FIRST_ARG, command_arg_new_num("F_V9t9|F_TIFILES", "v9t9: original V9t9 file type; " "tifiles: TIFILES (XMODEM) format", NULL /* action */, ARG_NUM (newfileformat), NULL /* next */ ) , /* NOTE: these two symbols cannot be seen unless surrounded in parentheses; I've gone ahead and added tokens for these */ command_symbol_new ("F_V9t9", NULL, c_DONT_SAVE, NULL /* action */, command_arg_new_num (NULL, NULL, NULL /* action */ , NEW_ARG_CONST_NUM(F_V9t9), NULL /* next */), NULL /* args */ , command_symbol_new ("F_TIFILES", NULL, c_DONT_SAVE, NULL /* action */ , command_arg_new_num (NULL, NULL, NULL /* action */ , NEW_ARG_CONST_NUM (F_TIFILES), NULL /* next */ ), NULL /* args */ , command_symbol_new("UnknownFileIsText", "Toggle treatment of unknown (non-V9t9 and non-TIFILES) files as " "DOS/Unix/Mac text files", c_STATIC, NULL /* action */ , RET_FIRST_ARG, command_arg_new_num("on|off", "on: read unknown file as text; off: generate error", NULL /* action */ , ARG_NUM(unknownfileistext), NULL /* next */ ), command_symbol_new ("AllowLongCatalogs", "Allow catalogs read through DSKx. to return more than 127 records; " "some programs may depend on this limit", c_STATIC, NULL/* action */ , RET_FIRST_ARG, command_arg_new_num("on|off", "on: allow up to 32767 entries, off: restrict to 127 entries", NULL /* action */, ARG_NUM(allowlongcatalogs), NULL /* next */ ), command_symbol_new ("RepairDamagedFiles", "Repair files with bad file sizes, even when opened read-only. " "This is a bit dangerous if you try to open a non-V9t9 file, " "which will (obviously) appear damaged. " "V9t9 will try to rule out files that don't pass enough sanity checks, though.", c_STATIC|c_CONFIG_ONLY, NULL/* action */ , RET_FIRST_ARG, command_arg_new_num("on|off", "on: repair damaged files, off: leave them alone", NULL /* action */, ARG_NUM(repairbadfiles), NULL /* next */ ), command_symbol_new ("FixupOldV9t9Filenames", "Rename older V9t9 files which were mangled to fit in " "the DOS 8.3 filename format. These files were split at " "the 8th character with a '.' and all illegal DOS characters " "(" DOS_illegalchars ") were biased by 128 to make them " "representable on that filesystem.\n" "New V9t9 file mangling rules assume filesystems that " "allow long filenames, so there is no splitting at " "the 8th character, and illegal characters are translated " "HTML-like into '&#xx;' where 'xx' is the hexadecimal " "ASCII code for the characters.\n" "Files renamed to the new format will not be compatible " "with older versions of V9t9, unless, under Windows, " "you refer to the files with the short format " "(i.e., 'longfilenm' --> 'longfi~1').", c_STATIC|c_CONFIG_ONLY, NULL/* action */ , RET_FIRST_ARG, command_arg_new_num("on|off", "on: rename old V9t9 files, " "off: leave them alone", NULL /* action */, ARG_NUM(fixupoldv9t9filenames), NULL /* next */ ), command_symbol_new ("GenerateOldV9t9Filenames", "Generate filenames that conform to the old V9t9 DOS-" "mangled format (see above) instead of the new format. " "Not recommended unless you actively use the DOS version.", c_STATIC|c_CONFIG_ONLY, NULL/* action */ , RET_FIRST_ARG, command_arg_new_num("on|off", "on: generate old V9t9 filenames, " "off: generate current V9t9 filenames", NULL /* action */, ARG_NUM(generateoldv9t9filenames), NULL /* next */ ), // these commands must precede EmuDiskFileInfo // so that reloading a session will do the right thing command_symbol_new("CloseAllFiles", NULL /* help */, c_STATIC|c_SESSION_ONLY, emudisk_close_files, NULL /* ret */, NULL /* args */, command_symbol_new("dsrEmuDiskTopOfRam", "Top address used in VDP RAM", c_STATIC, NULL /* action */, RET_FIRST_ARG, command_arg_new_num("address", "VDP RAM address, minus one", NULL /* action */, ARG_NUM(vdpnamebuffer), NULL /* next */) , command_symbol_new("EmuDiskFileInfo", NULL /* help */, c_DYNAMIC|c_SESSION_ONLY, emudisk_config_files, NULL /* ret */, command_arg_new_num("file number", "index of files[] array", NULL /* action */, NEW_ARG_NUM(u8), command_arg_new_string("field name", "name of field in pab_tifile to save", NULL /* action */, NEW_ARG_NEW_STRBUF, command_arg_new_string("field contents", "contents of field", NULL /* action */, NEW_ARG_NEW_STRBUF, NULL /* next */))), NULL /* next */ ))))))))))))))))))), NULL /* sub */ , NULL /* next */ ); command_symbol_table_add_subtable(universe, emudiskcommands); features |= FE_emudisk; return vmOk; } static vmResult emudisk_enable(void) { logger(_L | L_1, "setting up emulated disk DSR ROM\n"); /* load shared ROM if real disk is also loaded */ if (0 == loaddsr(romspath, systemromspath, #ifdef REAL_DISK_DSR (realDiskDSR.runtimeflags & vmRTInUse) ? emu2diskfilename : #endif emudiskfilename, "Emulated disk DSR ROM", emudiskdsr)) return vmNotAvailable; if (cruadddevice(CRU_WRITE, EMUDISKCRUBASE, 1, cruwEmuDiskROM)) { return vmOk; } else return vmNotAvailable; } static vmResult emudisk_disable(void) { crudeldevice(CRU_WRITE, EMUDISKCRUBASE, 1, cruwEmuDiskROM); return vmOk; } static vmResult emudisk_restart(void) { return vmOk; } static vmResult emudisk_restop(void) { return vmOk; } static vmResult emudisk_term(void) { return vmOk; } static vmDSRModule emuDiskModule = { 1, emudisk_getcrubase, emudisk_filehandler }; vmModule emuDiskDSR = { 3, "Emulated disk DSR", "dsrEmuDisk", vmTypeDSR, vmFlagsNone, emudisk_detect, emudisk_init, emudisk_term, emudisk_enable, emudisk_disable, emudisk_restart, emudisk_restop, {(vmGenericModule *) & emuDiskModule} };