This is tapeunit.c in view mode; [Download] [Up]
/* * tapeunit.c - (Guess what?) Tape unit emulation. * * Written by * Jouko Valta (jopi@stekt.oulu.fi) * * Patches by * Ettore Perazzoli (ettore@comm2000.it) * André Fachat (fachat@physik.tu-chemnitz.de) * Teemu Rantanen (tvr@cs.hut.fi) * * This file is part of VICE, the Versatile Commodore Emulator. * See README for copyright notice. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA. * */ /* tapeunit.c -- Cassette drive interface. The cassette interface consists of traps in tape access routines Find, Write Header, Send Data, and Get Data, so that actual operation can be controlled by C routines. To support turboloaders, it would be necessary to emulate the tape encoding used. That is much slower though. :( */ /* Warning! This implementation works only on the C64. Before trying to use with the other machines, check out all the `FIXME's and the constants #defined after the #includes. */ #include "vice.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <unistd.h> #include "tapeunit.h" #include "maincpu.h" #include "serial.h" #include "file.h" #include "drive.h" #include "tape.h" #include "c1541.h" #include "traps.h" #include "zfile.h" #include "tape.h" #include "utils.h" #include "resources.h" #include "charsets.h" /* #define DEBUG_TAPE */ #define SET_ST(b) mem_store(status, (mem_read(status) | b)) #define CAS_BUFFER_OFFSET (mem_read(bufpaddr) | (mem_read(bufpaddr+1) << 8)) /* CPU addresses for tape routine variables */ static int bufpaddr; static int status; static int verfl; static int irqtmp; static int stal; static int eal; static int kbdbuf; static int nkeys; static ADDRESS irqval; static int tape_is_initialized = 0; static int fn(void); static int t64_find_next(TAPE *tape, char *pattern, int type, BYTE *cbuf); static const trap_t *tape_traps; static TAPE *tape = NULL; static char *typenames[] = { "Tape Index", "CBM Binary", "SFX Archive", "LYNX Archive", "P00", "T64 Image", "X64 Disk Image" }; /* ------------------------------------------------------------------------- */ /* Initialize the tape emulation, using the traps in `trap_list'. */ int tape_init(int _bufpaddr, int _status, int _verfl, int _irqtmp, ADDRESS _irqval, int _stal, int _eal, const trap_t *trap_list, int _kbdbuf, int _nkeys) { const trap_t *p; /* Set addresses of tape routine variables. */ status = _status; bufpaddr = _bufpaddr; verfl = _verfl; irqtmp = _irqtmp; irqval = _irqval; stal = _stal; eal = _eal; kbdbuf = _kbdbuf; nkeys = _nkeys; tape_traps = trap_list; if (tape_traps != 0) { for (p = tape_traps; p->func != NULL; p++) traps_add(p); } if (tape_is_initialized) return 0; tape_is_initialized = 1; /* Create instance of the tape. */ tape = (TAPE *) xmalloc(sizeof(TAPE)); memset(tape, 0, sizeof(TAPE)); /* init all pointers */ tape->type = DT_TAPE; /* Add cassette drive to serial device list, so that Monitor can handle it. The device number (1) is hardcoded. */ if (serial_attach_device(1, (char *) tape, "Cassette unit", (int (*)(void *, BYTE *, int)) fn, (int (*)(void *, BYTE, int)) fn, (int (*)(void *, char *, int, int)) fn, (int (*)(void *, int)) fn, (void (*)(void *, int)) fn)) { printf("could not initialize Cassette ????\n"); return -1; } return 0; } /* ------------------------------------------------------------------------- */ /* Functions to attach the tape image files. */ void tape_detach_image(TAPE *tape) { if (!tape) return; if (tape->FileDs != NULL) { printf("Detaching tape image %s\n", tape->ActiveName); zfclose(tape->FileDs); tape->FileDs = NULL; tape->ActiveName[0] = 0; /* Name is used as flag */ } if (tape->directory) free(tape->directory); tape->directory = NULL; /* Tape detached, depress play. */ mem_set_tape_sense(0); } /* Check if this image is a CBM binary program file. */ static int is_valid_prgfile(FILE *fd) { int len, ret = 0; BYTE tmp[2]; /* size is right? */ fseek(fd, 0, SEEK_SET); len = file_length(fileno(fd)); if (len >= 65536 || len < 2) return 0; /* check load address */ if (fread(tmp, 1, 2, fd) != 2) return 1; if (tmp[0] == 1 && (tmp[1] == 8 /* C64 */ || tmp[1] == 4 /* PET */ || tmp[1] == 0x12 || tmp[1] == 0x10 /* VIC20 */ || tmp[1] == 0x1c || tmp[1] == 0x40)) /* C128 */ ret = 1; /* rewind */ fseek(fd, 0, SEEK_SET); return ret; } /* Attach a tape image. */ int tape_attach_image(TAPE *tape, const char *name, int mode) { char realname[24]; int reclen = 0; int format = -1, entries = 0; long flen; tape_detach_image(tape); if (!name || !*name) { printf("No name, detaching image.\n"); return -1; } if ((tape->FileDs = zfopen(name, mode ? "rwb" : "rb")) == NULL || (flen = file_length(fileno(tape->FileDs))) < 0) { perror(name); printf("Tape: cannot open file `%s'.\n", name); return FD_NOTRD; } /* Create new file for writing ? If so, write image header. IF no format is specified, try to figure it out of the extension. */ if (flen == 0) { if (!mode) { printf("Tape: Cannot open file `%s'.\n", name); return FD_BADIMAGE; } /* Writing: Try to get format out of filename ... */ if (format < 0) { if (is_pc64name(name) >= 0) format = TFF_P00; else if (is_t64name(name) > 0) format = TFF_TAPE; } switch (format) { case TFF_CBM: /* Do nothing */ break; case TFF_P00: /* Write P00 image header */ default: fprintf(stderr, "cannot create tape %s: %s format specified.\n", name, (format < 0) ? "No" : "Illegal"); return FD_BADIMAGE; } /* switch */ return FD_OK; } /* Define the format of Image File attached. */ if (read_pc64header(tape->FileDs, realname, &reclen) == FD_OK) { format = TFF_P00; entries = 1; } /* Check if the tape image contains valid T64 header. */ else if ((entries = check_t64_header(tape->FileDs)) > 0) { format = TFF_TAPE; /* Read the tape directory entries into memory. */ tape->directory = (BYTE *) malloc(TAPE_DIR_SIZE * entries); assert(tape->directory); fread(tape->directory, TAPE_DIR_SIZE, entries, tape->FileDs); tape->entry = -1; /* Number of current entry slot. */ } if (format < 0) { /* There is no header. */ if (is_valid_prgfile(tape->FileDs)) { fprintf(stderr, "Tape: Not Relocatable Program File.\n"); format = TFF_CBM; } else { #ifdef DEBUG_TAPE printf("Tape: realtype %d entries %d.\n", format, entries); #endif fprintf(stderr, "Tape: Invalid fileformat.\n"); return -1; } } tape->ImageFormat = format; tape->entries = entries; strcpy(tape->ActiveName, name); #ifdef DEBUG_TAPE printf("Tape: realtype %d entries %d.\n", format, entries); #endif printf("Tape file '%s' [%s] attached.\n", name, typenames[format]); /* Tape attached: press play. */ mem_set_tape_sense(1); return 0; } /* Argh. We could probably get rid of this. FIXME! */ int check_tape(void) { return 1; } /* ------------------------------------------------------------------------- */ /* Cassette device is not attached on the Serial Bus. */ static int fn() { return 0x80; } /* Find the next Tape Header and load it onto the Tape Buffer. Both Find-Any-Header and Find-Specific-Header (which calls the previous) only need to trap the Tape-Read-Block routine. An interface to select the image file is provided. Entry Format: 0 File type flag: 1 Binary Program or RAM image 3 Relocatable Program 4 Data File 5 End-of-Tape (EOT) 1-2 Start Address 3-4 End Address 5-21 Filename FYI: Simon's Basic supports filenames much longer than 16 characters. */ void findheader(void) { BYTE *s; int start, end; int sense = 0; int err = 0; if (tape->FileDs == NULL) { fprintf(stderr, "No Tape.\n"); err++; } #ifdef DEBUG_TAPE fprintf(stderr, "findheader\n"); #endif /* * Load next filename record */ s = ram + CAS_BUFFER_OFFSET; if (tape->FileDs != NULL) { ++sense; switch (tape->ImageFormat) { case TFF_P00: DirP00(tape->FileDs, (char *) (s + CAS_NAME_OFFSET), &start, &end); s[CAS_TYPE_OFFSET] = CAS_TYPE_PRG; s[CAS_STAD_OFFSET] = start & 0xFF; s[CAS_STAD_OFFSET + 1] = (start >> 8) & 0xFF; s[CAS_ENAD_OFFSET] = end & 0xFF; s[CAS_ENAD_OFFSET + 1] = (end >> 8) & 0xFF; break; case TFF_INDEX: ++err; break; /*case FS: */ case TFF_CBM: fseek(tape->FileDs, 0, SEEK_SET); if (fread(s + CAS_STAD_OFFSET, 1, 2, tape->FileDs) != 2) ++err; else { start = s[CAS_STAD_OFFSET] + s[CAS_STAD_OFFSET + 1] * 256; end = start + file_length(fileno(tape->FileDs)) - 2; s[CAS_TYPE_OFFSET] = CAS_TYPE_PRG; s[CAS_ENAD_OFFSET] = end & 0xff; s[CAS_ENAD_OFFSET + 1] = (end >> 8) & 0xff; s[CAS_NAME_OFFSET] = 0; } break; case TFF_SFX: case TFF_LYNX: ++err; break; case TFF_TAPE: if (t64_find_next(tape, NULL, 1, ram + CAS_BUFFER_OFFSET) < 0) ++err; break; case TFF_DISK: ++err; sense = 0; break; default: fprintf(stderr, "Tape: Invalid fileformat.\n"); } /* switch */ } if (err) { s[CAS_TYPE_OFFSET] = CAS_TYPE_EOF; } #ifdef DEBUG_TAPE fprintf(stderr, "Tape: find next header (type %d).\n", s[CAS_TYPE_OFFSET]); printf(" BUF (b2/b3) %04X SAL ac/ad %02X%02X EAL ae/af %02X%02X\n\ \t\t STAL c1/c2 %02X%02X MEMUSS c3/c4 %02X%02X\n", CAS_BUFFER_OFFSET, ram[0xad], ram[0xac], ram[eal + 1], ram[eal], ram[stal + 1], ram[stal], ram[0xc4], ram[0xc3]); #endif mem_store(status, 0); /* Clear the STATUS word */ mem_store(verfl, 0); if (irqtmp) { mem_store(irqtmp, irqval & 0xff); mem_store(irqtmp + 1, (irqval >> 8) & 0xff); } /* Check if STOP has been pressed. FIXME: only works with C64. */ { int i, n = mem_read(nkeys); MOS6510_REGS_SET_CARRY(&maincpu_regs, 0); for (i = 0; i < n; i++) { if (mem_read(kbdbuf + i) == 0x3) { MOS6510_REGS_SET_CARRY(&maincpu_regs, 1); break; } } } MOS6510_REGS_SET_ZERO(&maincpu_regs, 1); } /* Write the Tape Header onto the Tape Buffer and pad the buffer with $20's. Then send it to tape along with a 10-second sync leader (short pulses). We only need to trap the Tape-Write-Block routine. For details on the Entry format see the description above. Always confirm the file to save onto. */ void writeheader(void) { BYTE *s; int sense = 0; #ifdef DEBUG_TAPE fprintf(stderr, "writeheader\n"); #endif if (tape->FileDs == NULL) { fprintf(stderr, "No Tape.\n"); } /* Write the filename record on tape */ s = ram + CAS_BUFFER_OFFSET; if (tape->FileDs != NULL) { ++sense; switch (tape->ImageFormat) { case TFF_CBM: /* Plain file - Write Load Address */ fwrite(s + CAS_STAD_OFFSET, 1, 2, tape->FileDs); /* Load Addr */ break; case TFF_P00: /* Write P00 image header */ write_pc64header(tape->FileDs, (char *) (s + CAS_NAME_OFFSET), 0); fwrite(s + CAS_STAD_OFFSET, 1, 2, tape->FileDs); /* Load Addr */ break; case TFF_INDEX: sense = 0; break; /*case FS: */ case TFF_SFX: case TFF_LYNX: case TFF_TAPE: fprintf(stderr, "Tape: No writing allowed on format specified.\n"); sense = 0; break; case TFF_DISK: sense = 0; break; default: fprintf(stderr, "Tape: Invalid fileformat.\n"); } /* switch */ } #ifdef DEBUG_TAPE fprintf(stderr, "Tape: write file header.\n"); printf(" BUF (b2/b3) %04X SAL ac/ad %02X%02X EAL ae/af %02X%02X\n\ \t\t STAL c1/c2 %02X%02X MEMUSS c3/c4 %02X%02X\n", CAS_BUFFER_OFFSET, ram[0xad], ram[0xac], ram[eal + 1], ram[eal], ram[stal + 1], ram[stal], ram[0xc4], ram[0xc3]); #endif if (irqtmp) { mem_store(irqtmp, irqval & 0xff); mem_store(irqtmp + 1, (irqval >> 8) & 0xff); } /* Carry flag sets BREAK error */ MOS6510_REGS_SET_CARRY(&maincpu_regs, 0); MOS6510_REGS_SET_ZERO(&maincpu_regs, 1); } /* This function is unreliable (open/close) and thus not used. It's easier to just check for any transitions on the Motor Control line. */ #if 0 void checkplay(void) { #ifdef DEBUG_TAPE fprintf(stderr, "Tape: check play on tape.\n"); #endif } #endif /* ------------------------------------------------------------------------- */ /* Cassette Data transfer trap. XR flags the function to be performed on IRQ: 08 Write tape 0a Write tape leader 0c Normal keyscan 0e Read tape Luckily enough, these offset values are valid for PETs, C64 and C128. */ void tapereceive(void) { int start, end, len; int st = 0; start = (mem_read(stal) | (mem_read(stal + 1) << 8)); /* C64: STAL */ end = (mem_read(eal) | (mem_read(eal + 1) << 8)); /* C64: EAL */ #ifdef DEBUG_TAPE fprintf(stderr, "TapeReceive: start %04X end %04X\n", start, end); printf(" BUF (b2/b3) %04X SAL ac/ad %02X%02X EAL ae/af %02X%02X\n\ \t\t STAL c1/c2 %02X%02X MEMUSS c3/c4 %02X%02X\n", CAS_BUFFER_OFFSET, ram[0xad], ram[0xac], ram[eal + 1], ram[eal], ram[stal + 1], ram[stal], ram[0xc4], ram[0xc3]); #endif switch (MOS6510_REGS_GET_X(&maincpu_regs)) { case 0x0a: /* Write Leader */ break; case 0x08: /* Write Block */ #ifdef DEBUG_TAPE fprintf(stderr, "Tape: send next block.\n"); #endif len = end - start; if (fwrite(ram + start, len, 1, tape->FileDs) == 1) { st |= 0x40; /* EOF */ } else { st |= 0xB4; /* All possible errors... */ fprintf(stderr, "Error: Tape write failed.\n"); } break; case 0x0e: /* Read Block */ #ifdef DEBUG_TAPE fprintf(stderr, "Tape: get next block.\n"); #endif len = end - start; if (fread(ram + start, len, 1, tape->FileDs) == 1) { st |= 0x40; /* EOF */ } else { st |= 0x10; fprintf(stderr, "Error: Unexpected end of tape. File may be truncated.\n"); } break; default: fprintf(stderr, "Tape: unknown command.\n"); } /* switch */ /* * Set registers and flags like the ML routine does * Set up serial success / data */ if (irqtmp) { mem_store(irqtmp, irqval & 0xff); mem_store(irqtmp + 1, (irqval >> 8) & 0xff); } SET_ST(st); /* EOF and possible errors */ MOS6510_REGS_SET_CARRY(&maincpu_regs, 0); MOS6510_REGS_SET_INTERRUPT(&maincpu_regs, 0); } /* ------------------------------------------------------------------------- */ /* Find the next applicable file on a *.T64 tape. On success, filename and both start and end addresses are copied to the cassette input buffer. */ static int t64_find_next(TAPE *tape, char *pattern, int type, BYTE *cbuf) { char asciiname[20], newname[20]; BYTE *dirp; long loc; if (tape->entry >= tape->entries) { return -1; } while (1) { if (tape->entry < tape->entries - 1) { tape->entry++; } else { tape->entry = 0; } dirp = tape->directory + TAPE_DIR_SIZE * tape->entry; if (!type && *dirp == 0) { /* found empty slot */ return 0; } if (type && *dirp == 1) { /* found a file */ /* Get filename and remove trailing spaces */ memccpy(newname, &dirp[16], 0xa0, 16); newname[16] = 0; { char *cf = &newname[15]; while (*cf == 0x20) *cf-- = 0; } strcpy(asciiname, newname); petconvstring(asciiname, 1); /* to ascii */ /* Check filename match against search pattern if one is given */ if (pattern && *pattern) { if (!compare_filename(asciiname, pattern)) { continue; /* No match, skip the file */ } } /* pattern */ cbuf[CAS_TYPE_OFFSET] = CAS_TYPE_PRG; memcpy(cbuf + CAS_STAD_OFFSET, dirp + 2, 4); memcpy(cbuf + CAS_NAME_OFFSET, dirp + 16, 16); loc = (dirp[8] | (dirp[9] << 8) | (dirp[10] << 16) | (dirp[11] << 24)); fseek(tape->FileDs, loc, 0); break; /* Found a match */ } /* if dirp */ } return 0; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.