ftp.nice.ch/pub/next/tools/emulators/vice.0.15.0.NeXT.sd.tgz#/vice-0.15.0/src/tapeunit.c

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.