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

This is c1541.c in view mode; [Download] [Up]

/*
 * c1541.c - Stand-alone disk image maintenance program.
 *
 * Written by
 *  Teemu Rantanen   (tvr@cs.hut.fi)
 *  Jouko Valta      (jopi@zombie.oulu.fi)
 *  Gerhard Wesp     (gwesp@cosy.sbg.ac.at)
 *  Daniel Sladic    (sladic@eecg.toronto.edu)
 *  Ricardo Ferreira (storm@esoterica.pt)
 *  Andreas Boose    (boose@unixserv.rz.fh-hannover.de)
 *
 * Patches by
 *  Olaf Seibert     (rhialto@mbfys.kun.nl)
 *  Ettore Perazzoli (ettore@comm2000.it)
 *
 * Zipcode implementation based on `zip2disk' by
 *  Paul David Doherty (h0142kdd@rz.hu-berlin.de)
 *
 * 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.
 *
 */

#ifdef __hpux
#ifndef _POSIX_SOURCE
#define _POSIX_SOURCE
#endif
#endif

#include "vice.h"	/* for convenience */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <memory.h>
#include <assert.h>

#include "c1541.h"
#include "mshell.h"
#include "serial.h"
#include "drive.h"
#include "gcr.h"
#include "file.h"
#include "charsets.h"
#include "tape.h"
#include "utils.h"
#include "zipcode.h"

#define MAXARG		16
#define MAXVAL		0xffff

#define MAXDRIVE	1

#define C1541_VERSION_MAJOR	2
#define C1541_VERSION_MINOR	4

/* Global */

DRIVE *DriveData[4] = { NULL, NULL, NULL, NULL };

static int  DriveNum = 0;

static char *args[MAXARG+1];
static int  values[MAXARG+1];
static int  types[MAXARG+1];
static int  nargs;

char    newname[256];


/* Local functions */

static int  check_drive ( int dev, int mode );
static int  open_image ( int dev, char *name, int create );
static int  create_image ( int fd, int devtyp, int tracks, int errb, char *label );
static void usage( void );

static int  disk_attach ( void );
static int  disk_cdd ( void );
static int  disk_format ( void );
static int  disk_gcrformat ( void );
static int  disk_delete ( void );
static int  disk_list ( void );
static int  disk_validate ( void );
static int  disk_copy ( void );
static int  disk_rename ( void );
static int  disk_read ( void );
static int  disk_write ( void );
static int  disk_import ( void );
static int  disk_import_zipfile ( void );
static int  disk_unlynx ( void );
static int  disk_copy_tape ( void );
static int  disk_sectordump ( void );
static int  disk_extract ( void );
static int  disk_raw_command ( void );
static int  disk_info ( void );
static int  disk_help ( void );
static int  disk_quit ( void );
static int  disk_system ( void );

extern char sector_map[43]; /* Ugly: FIXME! */
extern int speed_map[42];

struct ms_table disk_cmds[] = {
    {"format", 1, 2, disk_format,
    "format  [imagename] 'diskname,id'\t(create X64 disk & format)\n"},
    {"gcrformat", 1, 2, disk_gcrformat,
    "gcrformat  imagename ['diskname,id']\t(create GCR disk & format)\n"},
    {"delete", 1, MAXARG, disk_delete,
    "delete  files\n"},
    {"list", 0, MAXARG, disk_list,
    "list   [pattern]\n"},
    {"validate", 0, 1, disk_validate,
    "validate\n"},
    {"copy", 2, MAXARG, disk_copy,
#ifdef COPY_TO_LEFT
    "copy    newname oldname [oldname2 ...]\n"},
#else
    "copy    oldname [oldname2 ...] newname\n"},
#endif
    {"rename", 2, 2, disk_rename, /* reintroduced 1996/04/08 EP */
#ifdef COPY_TO_LEFT
    "rename  newname oldname\n"},
#else
    "rename  oldname newname\n"},
#endif
    {"attach", 0, 2, disk_attach,
     "attach [drive] imagename\t\t(attaches floppy image for use)\n"},
    {"unit", 1, 1, disk_cdd,
     "unit\t  drive\t\t\t\t(changes default device)\n"},

    {"create", 2, 3, disk_import,
    "create  imagename dumpname ['descr']\t(create disk & import a raw diskdump)\n"},
    {"zcreate", 2, 3, disk_import_zipfile,
    "zcreate  imagename zipname ['descr']\t(create disk & import zipfile images)\n"},
    {"tape", 1, MAXARG, disk_copy_tape,
    "tape\t  tapeimage [files]\t\t(copy files from tape image)\n"},
    {"unlynx", 1, 1, disk_unlynx,
     "unlynx lynxname\t\t\t(unarchive an LNX file to disk image)\n"},
    {"read", 1, 2, disk_read,
    "read\t  1541name [FSname]\t\t(copy file from disk image)\n"},
    {"write", 1, 2, disk_write,
    "write\t  FSname [1541name]\t\t(write CBM or P00 file to disk image)\n"},

#if 0
    {"memory", 0, 2, disk_memdump,
    "memory [addr] [end|+len]\t\t(show memory in hex format)\n"},
    {"hunt", 3, MAXARG, disk_hunt,
    "hunt     start end|+len  bytes|'string'\n"},
#endif

    {"block", 0, 4, disk_sectordump,
    "block [drive:] [track] [sector] [disp] (show disk blocks in hex format)\n"},

    {"extract", 0, 0, disk_extract,
    "extract\t\t\t\t(extract all files to filesystem)\n"},
    {"info", 0, 1, disk_info,
    "info\t ['description']\t\t(show disk image version and info)\n"},

    {"@", 0, 1, disk_raw_command,
    "@\t[disk command]\t\t\t(command for channel 15)\n"},
    {"system", 1, 1, disk_system,
     "system command\t\t\t(execute a system command)\n"},
    {"help", 0, 1, disk_help,
    "help\t [topic]\t\t\t(Describe command)\n"},
    {"?", 0, 1, disk_help,
    "?\t [topic]\t\t\t(Same as help)\n"},
    {"quit", 0, 0, disk_quit,
    "quit\t\t\t\t\t(exits c1541)\n"},
    {"x", 0, 0, disk_quit,
    "x\t\t\t\t\t(exits c1541)\n"},
    {NULL, 0, 0, 0, NULL}
};



/*
 * Create Floppy Image
 */

static int  create_image (int fd, int devtype, int tracks, int errb, char *label)
{
    BYTE    header[HEADER_LENGTH];
    BYTE    block[256];
    int     blks;
    int     i;

    memset(header, 0, sizeof (header));
    memset(block, 0, sizeof (block));


    /* Check values */

    if (check_track_sector(get_diskformat (devtype), tracks, 1) < 0)
	exit (-1);

    blks = num_blocks (get_diskformat (devtype), tracks);


    header[HEADER_MAGIC_OFFSET + 0] = HEADER_MAGIC_1;
    header[HEADER_MAGIC_OFFSET + 1] = HEADER_MAGIC_2;
    header[HEADER_MAGIC_OFFSET + 2] = HEADER_MAGIC_3;
    header[HEADER_MAGIC_OFFSET + 3] = HEADER_MAGIC_4;

    header[HEADER_VERSION_OFFSET + 0] = HEADER_VERSION_MAJOR;
    header[HEADER_VERSION_OFFSET + 1] = HEADER_VERSION_MINOR;

    header[HEADER_FLAGS_OFFSET + 0] = devtype;
    header[HEADER_FLAGS_OFFSET + 1] = tracks;

    if (label)
	strncpy((char *)header + HEADER_LABEL_OFFSET, label, HEADER_LABEL_LEN);

    header[HEADER_LABEL_OFFSET + HEADER_LABEL_LEN] = 0;	  /* terminator */

    printf("writing header\n");
    if (write(fd, (char *)header, sizeof (header)) != sizeof (header)) {
	printf("cannot write header\n");
	exit(1);
    }

    printf("creating blocks\n");
    for (i = 0; i < blks; i++) {
	if (write(fd, (char *)block, sizeof (block)) != sizeof (block)) {
	    printf("cannot write block %d\n", i);
	    exit(1);
	}
    }


    if (errb) {
	printf("creating error data\n");
#if 0
	if (set_error_data(floppy, 5) < 0) {	/* clear and write */
	    printf("cannot write error data block.\n");
	    exit(1);
	}
#endif
    }

    return (0);
}


static int  set_label(int fd, char *label)
{
    int     siz = HEADER_LABEL_LEN +1;
    char    buf[HEADER_LABEL_LEN +2];


    memset(buf, 0, siz);

    if (label)
	strncpy(buf, label, HEADER_LABEL_LEN);

    lseek(fd, (off_t)HEADER_LABEL_OFFSET, SEEK_SET);

    if (write(fd, (char *)buf, siz) != siz) {
	return (FD_WRTERR);
    }
    return (FD_OK);
}


  /*
   * These 4 bytes are disk type flags (set upon create or format)
   * They contain: Device Type, Max Tracks, Side, and Error Flag.
   */

static int  set_disk_size(int fd, int tracks, int sides, int errblk)
{
    int     siz = HEADER_FLAGS_LEN;
    char    buf[HEADER_FLAGS_LEN +1];

    buf[0] = DEFAULT_DEVICE_TYPE;
    buf[1] = tracks;
    buf[2] = sides;
    buf[3] = errblk;

    lseek(fd, (off_t)HEADER_FLAGS_OFFSET, SEEK_SET);

    if (write(fd, (char *)buf, siz) != siz) {
	return (FD_WRTERR);
    }
    return (FD_OK);
}


/*
 * Open image or create a new one.
 * If the file exists, it must have valid header.
 */

static int  open_image(int dev, char *name, int create)
{
    DRIVE  *floppy;
    int     fd;


    if (dev < 0 || dev > MAXDRIVE)
	return (-1);  /* FD_BADDEV */


    floppy = DriveData[dev & 3];

    if (create) {
	if ((fd = open(name, O_RDWR | O_CREAT, 0666)) < 0) {
	    printf("could not create image %s\n", name);
	    return (-1);
	}


	/*
	 * Get default geometry
	 * make a new image file and format it
	 */

	create_image(fd, DEFAULT_DEVICE_TYPE, NUM_TRACKS_1541, 0, NULL);
	close(fd);
    }


    attach_floppy_image(floppy, name, 0);

    if (floppy->ActiveFd < 0)
	return (-1);

    return (0);
}


/*
 * This routine is used in order to get around standard serial bus
 * connection.
 * It checks privileges for the requested operation on the specified
 * disk unit. Return value < 0 means requested action is not allowed,
 * i.e. there is no disk in the drive, or that drive is not available.
 */


static int  check_drive(int dev, int flags)
{
    DRIVE *floppy;

    dev &= 7;
    if (dev < 0 || dev > 3)
	return (FD_BADDEV);

    floppy = DriveData[dev & 3];

    if (!floppy || (flags != CHK_NUM && floppy->ActiveFd < 0)) {
	return (FD_NOTREADY);
    }

    return (FD_OK);
}


/* ------------------------------------------------------------------------- */

static int  disk_attach(void)
{
    char    *name = NULL;
    int      dev  = DriveNum;

    switch (nargs) {
      case 3:
	dev  = values[1];
	name = args[2];
	break;
      case 2:
	name = args[1];
    }

    if (check_drive(dev, CHK_NUM) < 0)
	return (FD_BADDEV);

    attach_floppy_image(DriveData[dev & 3], name, 0);
    return (0);
}


static int  disk_cdd(void)
{
    int      dev  = values[1];
    if (check_drive(dev, CHK_NUM) < 0)
	return (FD_BADDEV);

    DriveNum = (dev & 3);
    return (0);
}

/*
 * Creates a raw GCR disk image.
 */

static int  disk_gcrformat (void)
{
    int fd, track, sector;
    BYTE gcr_header[12], id[2];
    char name[16], *idptr;
    DWORD gcr_track_p[MAX_TRACKS_1541 * 2];
    DWORD gcr_speed_p[MAX_TRACKS_1541 * 2];
    BYTE gcr_track[7930], rawdata[260];
    BYTE *gcrptr;

    if (nargs < 2)
	return 0;

    memset(name, 0xa0, 16);
    id[0] = id[1] = 0xa0;

    if (nargs > 2) {
	strcpy(newname, args[2]);
	petconvstring(newname, 1);
	idptr = memchr(newname, ',', strlen(newname));
	if (idptr == NULL) {
	    int i;
	    for (i = 0; i < 16 && newname[i] != '\0'; i++)
		name[i] = newname[i];
	} else {
	    int i;
	    *idptr = '\0';
	    for (i = 0; i < 16 && newname[i] != '\0'; i++)
		name[i] = newname[i];
	    strncpy(id, ++idptr, 2);
	    if (idptr[1] != '\0') {
		id[0] = idptr[1];
		if (idptr[2] != '\0')
		    id[1] = idptr[2];
	    }
	}
    }

    if ((fd = open(args[1], O_RDWR | O_CREAT, 0666)) < 0) {
	printf("could not create image %s\n", args[1]);
	return (FD_BADIMAGE);
    }

    strcpy(gcr_header, "GCR-1541");

    gcr_header[8] = 0;
    gcr_header[9] = MAX_TRACKS_1541 * 2;
    gcr_header[10] = 7928 % 256;
    gcr_header[11] = 7928 / 256;

    if (write(fd, (char *)gcr_header, sizeof(gcr_header))
        != sizeof(gcr_header)) {
	printf("Cannot write header.\n");
	close(fd);
	return 0;
    }

    for (track = 0; track < MAX_TRACKS_1541; track++) {
	gcr_track_p[track * 2] = 12 + MAX_TRACKS_1541 * 16 + track * 7930;
	gcr_track_p[track * 2 + 1] = 0;
	gcr_speed_p[track * 2] = speed_map[track];
	gcr_speed_p[track * 2 + 1] = 0;
    }

    if (write_dword(fd, gcr_track_p, sizeof(gcr_track_p)) < 0) {
	printf("Cannot write track header.\n");
	close(fd);
	return 0;
    }

    if (write_dword(fd, gcr_speed_p, sizeof(gcr_speed_p)) < 0) {
	printf("Cannot write speed header.\n");
	close(fd);
	return 0;
    }

    for (track = 0; track < MAX_TRACKS_1541; track++) {

	int raw_track_size[4] = { 6250, 6666, 7142, 7692 };

	memset(&gcr_track[2], 0xff, 7928);
	gcr_track[0] = raw_track_size[speed_map[track]] % 256;
	gcr_track[1] = raw_track_size[speed_map[track]] / 256;
	gcrptr = &gcr_track[2];

	for (sector = 0; sector < sector_map[track+1]; sector++) {
	    BYTE chksum;
	    int i;
	    memset(rawdata, 0, 260);
	    if (track == 17 && sector == 0) {
		BYTE *rdat = &rawdata[1];
		int s, t;
		memset(rdat + BAM_DISK_NAME, 0xa0, 27);
		rdat[0] = DSK_DIR_TRACK;
		rdat[1] = DSK_DIR_SECTOR;
		rdat[2] = 65;
		rdat[BAM_VERSION] = 50;
		rdat[BAM_VERSION + 1] = 65;
		memcpy(rdat + BAM_DISK_NAME, (BYTE *)name, 16);
		rdat[BAM_DISK_ID] = id[0];
		rdat[BAM_DISK_ID + 1] = id[1];
		for (t = 1; t <= 35; t++)
		    for (s = 0; s < sector_map[t]; s++)
			free_sector(rdat, t, s);
		allocate_sector(rdat, DSK_BAM_TRACK, 0);
		allocate_sector(rdat, DSK_BAM_TRACK, 1);
	    }
	    rawdata[0] = 7;
	    chksum = rawdata[1];
	    for (i = 1; i < 256; i++)
		chksum ^= rawdata[i + 1];
	    rawdata[257] = chksum;

	    convert_sector_to_GCR(rawdata, gcrptr, track+1, sector, id[0], id[1]);
	    gcrptr += 360;
	}

	if (write(fd, (char *)gcr_track, sizeof(gcr_track))
            != sizeof(gcr_track)) {
	    printf("Cannot write track data.\n");
	    close(fd);
	    return 0;
	}
    }

    close(fd);
    return 0;
}

/*
 * Simulate the 1541 DOS commands
 */

static int  disk_format (void)
{

    strcpy(newname, "N:");
    strcpy(newname + 2, ((nargs > 2) ? args[2] : args[1]) );

    if (!memchr(newname, ',', strlen(newname))) {
	printf("There must be ID on the name\n");
	return (0);
    }

    /* Open image or create a new one.
     * If the file exists, it must have valid header.
     * Note: Syntax check from command line is NOT passed
     * without the image name.
     */

    /*printf("DriveNum = %d\n", DriveNum);*/
    if (nargs > 2) {
	if (open_image(DriveNum, args[1], 1) < 0)
	    return (FD_BADIMAGE);
    }

    printf("formatting image\n");
    petconvstring(newname +2, 1);

    ip_execute(DriveData[DriveNum], (BYTE *)newname, strlen(newname));

    return(0);
}


static int  disk_delete (void)
{
    DRIVE *floppy;
    int   dev, err, i = 1;
    char *name, tmp[20];

    do {
	name = args[i];
	dev =  find_devno(DriveNum, name);

	if ((err = check_drive(dev, CHK_RDY)) < 0)
	    return (err);
	floppy = DriveData[dev & 3];

	petconvstring(name, 1);
	strcpy(tmp, "S:");
	strcat(tmp, name);

	ip_execute(floppy, (BYTE *)tmp, strlen(tmp));
    } while (++i < nargs);

    return(0);
}


static int  disk_validate (void)
{
    int   dev, err;

    dev =  (nargs > 1 ? values[1] : DriveNum);

    if ((err = check_drive(dev, CHK_RDY)) < 0)
	return (err);

    printf("validating\n");
    do_validate(DriveData[dev & 3]);

    return(0);
}


static int  disk_list (void)
{
    DRIVE *floppy;
    int   dev, err, i = 1;
    char *name, *listing;

    do {
	name = args[i];
	dev =  find_devno(DriveNum, name);

	if ((err = check_drive(dev, CHK_RDY)) < 0)
	    return (err);
	floppy = DriveData[dev & 3];

	if (name) {
	    if (*name && strchr (name, ':'))
		name = strchr (name, ':') +1;
	    petconvstring(name, 1);
	    listing = floppy_read_directory(floppy, name);
	}
	else
	    listing = floppy_read_directory(floppy, NULL);

	if (listing != NULL) {
	    printf("\n === Listing for drive %d ===\n\n%s", dev | 8,
		   listing);
	    free(listing);
	}
    } while (++i < nargs);

    return(0);
}


static int  disk_copy (void)
{
    DRIVE *floppy1, *floppy2;		/* may point to the same data */
    int    dev, err, i;
    char  *name, *dest;
    char   c;

#ifdef COPY_TO_LEFT
    /* copy    newname oldname [oldname2 ...] */
    dest = args[1];
#else
    /* copy    oldname [oldname2 ...] newname */
    dest = args[nargs -1];	/* EP */
#endif

    dev =  find_devno(DriveNum, dest);

    if ((err = check_drive(dev, CHK_RDY)) < 0)
	return (err);
    floppy2 = DriveData[dev & 3];

	if (strchr (dest, ':'))
	    dest = strchr (dest, ':') +1;

    strcpy((char *)newname, dest);
    petconvstring(newname, 1);

    if (open_1541(floppy2, newname, (int)strlen(newname), 1)) {
	printf("cannot open `%s' for writing on image.\n", dest);
	return (FD_WRTERR);
    }

#ifdef COPY_TO_LEFT
    for (i = 2; i < nargs; i++) {
#else
    for (i = 1; i < nargs -1; i++) {
#endif

	dev =  find_devno(DriveNum, args[i]);

	if ((err = check_drive(dev, CHK_RDY)) < 0)
	    return (err);
	floppy1 = DriveData[dev & 3];

	if (strchr (args[i], ':'))
	    name = strchr (args[i], ':') +1;
	else
	    name = args[i];

	strcpy((char *)newname, name);
	petconvstring(newname, 1);
	if (open_1541(floppy1, newname, strlen(newname), 0)) {
	    printf("u%d: cannot open `%s' on image.\n", dev, name);
	    close_1541(floppy2, 1);
	    return (FD_RDERR);
	}

	printf("copying %s\n", args[i]);

	while (!read_1541(floppy1, (BYTE *)&c, 0)) {
	    if (write_1541(floppy2, c, 1)) {
		printf("No space on image ?\n");
		break;
	    }
	}

	close_1541(floppy1, 0);
    } /* for */

    close_1541(floppy2, 1);
    return(0);
}


static int  disk_rename (void)
{
    DRIVE *floppy2;
    int    dev, err;
    char  *src, *dest;

#if defined (COPY_TO_LEFT)
    /* rename newname oldname */
    dest = args[1];
    src = args[nargs - 1];
#else
    /* rename oldname newname */
    src = args[1];
    dest = args[nargs - 1];
#endif

    dev =  find_devno(DriveNum, dest);

    if ((err = check_drive(dev, CHK_RDY)) < 0)
	return (err);
    floppy2 = DriveData[dev & 3];

    if (strchr (dest, ':'))
	dest = strchr (dest, ':') +1;

    printf("renaming `%s' to `%s'\n", src, dest);

    sprintf (newname, "R:%s=%s", dest, src);		/* Rename File */
    petconvstring(newname +2, 1);

    ip_execute(floppy2, (BYTE *)newname, strlen(newname));

    return(0);
}


/*
 * Additional Disk Maintenance commands
 */

static int  disk_read (void)
{
    DRIVE *floppy;
    FILE  *f;
    char  *name, *fsname;
    int    dev, err;
    char   c;


    if (strchr (args[1], ':'))
	name = strchr (args[1], ':') +1;
    else
	name = args[1];

    dev =  find_devno(DriveNum, args[1]);

    if ((err = check_drive(dev, CHK_RDY)) < 0)
	return (err);
	floppy = DriveData[dev & 3];

    strcpy((char *)newname, name);
    petconvstring(newname, 0);			/* To PETscii */

    if (open_1541(floppy, newname, strlen(newname), 0)) {
	printf("u%d: cannot open `%s' on image.\n", dev, name);
	return (FD_BADNAME);
    }


    /* Get real filename from the disk file.
     * slot must be defined by open_1541().
     */

    memcpy (newname, floppy->buffers[0].slot + SLOT_NAME_OFFSET, 16);
    if (nargs > 2)
	fsname = args[2];
    else {
	int l;

	fsname = newname;
	no_a0_pads((BYTE *)fsname, 16);
	l = strlen (fsname) - 1; /* EP */
	while (fsname[l] == ' ') {
	    fsname[l] = '\0';
	    l--;
	}

	petconvstring(fsname, 1);
    }


    if (*fsname == '-')
	f = stdout;
    else {
	if (!(f = fopen(fsname, WRITE))) {
	    close_1541(floppy, 0);
	    printf("cannot open %s for writing.\n", fsname);
	    return (FD_NOTWRT);
	}

	/* Write P00 header ? */

	if (nargs > 2 && is_pc64name(fsname) >= 0) {
	    printf("writing PC64 header.\n");
	    write_pc64header(f, newname, 0);
	}
    } /* stdout */


    fprintf(stderr, "reading file `%s' from image.\n", name);

    while (!read_1541(floppy, (BYTE *)&c, 0))
	fputc(c, f);

    fclose(f);
    close_1541(floppy, 0);

    return(0);
}


static int  disk_write (void)
{
    DRIVE *floppy;
    FILE  *f;
    int    dev, err, c;
    int    realtype, reclen = 0;
    char  *p, realname[20];


    if (!(f = fopen(args[1], READ))) {
	printf("cannot open %s for reading\n", args[1]);
	return (FD_NOTRD);
    }


    if (nargs > 2)
	p = args[2];
    else if ((p = strrchr(args[1], '/')) == NULL || !p++)
	p = args[1];

    if (!p || !*p)
	return (FD_BADNAME);

    strcpy((char *)newname, p);
    dev =  find_devno(DriveNum, newname);
    petconvstring(newname, 1);


    /* Check if P00 header and read the actual filename and type */

    if ((realtype = is_pc64name(args[1])) >= 0 &&
	read_pc64header(f, realname, &reclen) == FD_OK) {

	if (nargs < 3)
	    strcpy (newname, pc_get_cbmname (f, args[1]));
    }
    else
	rewind (f);		/* There is no P00 header */


    if ((err = check_drive(dev, CHK_RDY)) < 0)
	return (err);
	floppy = DriveData[dev & 3];


    if (open_1541(floppy, newname, strlen(newname), 1)) {
	printf("cannot open %s for writing on image.\n", newname);
	return (FD_WRTERR);
    }

    printf("writing file `%s' to image\n", newname);

    while (EOF != (c = fgetc(f))) {
	if (write_1541(floppy, c, 1)) {
	    printf("No space on image ?\n");
	    break;
	}
    }

    fclose(f);
    close_1541(floppy, 1);
    return(0);
}


typedef struct {
  char    ImageFormat;		/* 1541/71/81 */
  int     TracksSide;
  int     Sides;
  int     TotalBks;
} DiskFormats;

static DiskFormats  Legal_formats[] = {
  {DT_1541, 35, 0,  683 },
  {DT_1541, 40, 0,  768 },
/*{DT_1541, 42, 0,  785 }, */
  {DT_1571, 35, 1, 1366 },
  {DT_1581, 80, 0, 3200 },
  {DT_8050, 77, 0, 2088 },
  {DT_8250, 72, 1, 4166 },
  {  -1,     0, 0,    0 }
};

static int  disk_import_zipfile (void)
{
    DRIVE  *floppy = DriveData[DriveNum];
    DiskFormats  *format = Legal_formats;
    char    tmp[256];
    int     fsfd=-1, errblk = 0;
    int track, sector, count;
    char *fname;
    int    channel = 2;
    BYTE  str[20];
    static int  drive = 8;
    int		singlefilemode = 0;

    /*
     * Open image or create a new one.
     * If the file exists, it must have valid header.
     */
    if (open_image(DriveNum, args[1], 1) < 0) {
	return (FD_BADIMAGE);
    }

    fname = (char *) malloc (sizeof(char) * strlen(args[2]) + 3);
    /* ignore '[0-4]!' if found */
    if (args[2][0] >= '1' && args[2][0] <= '4' && args[2][1] == '!')
	strcpy(fname+2, args[2]+2);
    else
	strcpy(fname+2, args[2]);
    fname[0] = '0';
    fname[1] = '!';

    set_label(floppy->ActiveFd, "*** Truncated image."); /* Notify of errors */

    printf("copying blocks to image\n");
    lseek(floppy->ActiveFd, HEADER_LENGTH, SEEK_SET);

    /* Write out all the sectors */
    for(count=0;count<format->TotalBks;count++) {
       if (write(floppy->ActiveFd, tmp, 256) != 256) {
          printf("cannot write block %d of %s\n", count, args[2]);
          return (FD_WRTERR);
       }
    }

    if (open_1541(floppy, "#", 1, channel)) {
       printf("u%d: cannot open buffer #%d.\n", drive, channel);
       return (FD_RDERR);
    }

    for (track = 1; track <= 35; track++) {
	if (singlefilemode || track == 1) {
	    if (track == 1) {
		fsfd = open(fname + 2, O_RDONLY);
		if (fsfd >= 0) {
		    printf("reading zipfile on onefile -mode\n");
		    singlefilemode = 1;
		    lseek(fsfd, 4, SEEK_SET);
		}
	    } else if (track == 9 || track == 17 || track == 26) {
		lseek(fsfd, 2, SEEK_CUR);
	    }
	}
	if (!singlefilemode) {
	    switch (track) {
	    case 1:
	    case 9:
	    case 17:
	    case 26:
		fname[0]++;
		if (fsfd >= 0)
		    close(fsfd);
		if ((fsfd = open(fname, O_RDONLY)) < 0) {
		    printf("cannot open %s\n", fname);
		    perror (fname);
		    return (FD_NOTRD);
		}
		lseek(fsfd, (track == 1) ? 4 : 2, SEEK_SET);
		break;
	    }
	}

       for (count = 0; count < sector_map[track]; count++) {
          if ( (zipcode_read_sector(fsfd, track, &sector, floppy->buffers[channel].buffer)) != 0) {
	      close(fsfd);
	      return (FD_BADIMAGE);
          }

          /* Write one block */

          sprintf((char *)str, "B-W:%d 0 %d %d", channel, track, sector);
          if (ip_execute(floppy, (BYTE *)str, strlen((char *)str)) != 0) {
	      track = DSK_DIR_TRACK;
	      sector= 0;
	      close(fsfd);
	      return (FD_RDERR);
          }
       }
    }
    close_1541(floppy, channel);

    /* Update Format and Label information on Disk Header */

    lseek(floppy->ActiveFd, (off_t)HEADER_LABEL_OFFSET + 0, SEEK_SET);

    if (write(floppy->ActiveFd, &(format->ImageFormat), 1) != 1) {
	return (FD_WRTERR);
    }

    set_disk_size(floppy->ActiveFd, format->TracksSide, format->Sides, errblk);

    set_label(floppy->ActiveFd, (args[3] ? args[3] : NULL)); /* Fix the note */

    close(fsfd);

    ip_execute(floppy, (BYTE *)"I", 1);

    return(0);
}


static int  disk_import (void)
{
    DRIVE  *floppy = DriveData[DriveNum];
    DiskFormats  *format;
    char    tmp[256];
    int     fsfd, len, blk = 0, errblk = 0;


    /*
     * Open image or create a new one.
     * If the file exists, it must have valid header.
     */
    if (open_image(DriveNum, args[1], 1) < 0)
	return (FD_BADIMAGE);

    if ((fsfd = open(args[2], O_RDONLY)) < 0) {
	printf("cannot open %s\n", args[2]);
	perror (args[2]);
	return (FD_NOTRD);
    }

    set_label(floppy->ActiveFd, "*** Truncated image."); /* Notify of errors */


    /* First copy all available blocks and then check existence of
     * Error Data Block */

    printf("copying blocks to image\n");
    lseek(floppy->ActiveFd, HEADER_LENGTH, SEEK_SET);

    while ((len = read(fsfd, tmp, 256)) == 256) {
	if (++blk > MAX_BLOCKS_ANY) {
	    printf("\nNice try.\n");
	    break;
	}
	if (write(floppy->ActiveFd, tmp, 256) != 256) {
	    printf("cannot write block %d of %s\n", blk, args[2]);
	    return (FD_WRTERR);
	}
    }


    /* Now recognize the format and verify block count on it. */

    if (blk <  NUM_BLOCKS_1541) {
	    printf("cannot read block %d of %s\n", blk, args[2]);
	    return (FD_NOTRD);
	}

    for (format = Legal_formats; format->ImageFormat >= 0; ++format) {
	if (blk == format->TotalBks) {
	    errblk = 0;
	    break;
	}
	if (blk == (format->TotalBks + (format->TotalBks >> 8)) ) {
	    errblk = 1;
	    break;
	}
    }

    if (format->ImageFormat < 0)
	return (FD_BADIMAGE);


    /* Check and write the last (short) sector of error bytes */

    if (len) {
	if (len != (format->TotalBks % 256)) {
	    printf("cannot read block %d of %s\n", blk, args[2]);
	    return (FD_NOTRD);
	}
	if (write(floppy->ActiveFd, tmp, len) != len) {
	    printf("cannot write block %d of %s\n", blk, args[2]);
	    return (FD_WRTERR);
	}
    }


    /* Update Format and Label information on Disk Header */

    lseek(floppy->ActiveFd, (off_t)HEADER_LABEL_OFFSET + 0, SEEK_SET);

    if (write(floppy->ActiveFd, &(format->ImageFormat), 1) != 1) {
	return (FD_WRTERR);
    }

    set_disk_size(floppy->ActiveFd, format->TracksSide, format->Sides, errblk);

    set_label(floppy->ActiveFd, (args[3] ? args[3] : NULL)); /* Fix the note */

    close(fsfd);

    ip_execute(floppy, (BYTE *)"I", 1);

    return(0);
}


/*
 * Copy files from *.t64 tape image. An already formatted
 * X64 disk image is required
 */

#define TAPE_HDR_SIZE 64
#define TAPE_DIR_SIZE 32

static int  disk_copy_tape (void)
{
    DRIVE *floppy = DriveData[DriveNum];
    FILE *f;
    BYTE *dirp, *tapebuf = NULL;
    char  asciiname[20], *p;
    int   maxentries = 0, ccount = 0;
    int   c, i, len, loc;


    if (!(f = fopen(args[1], READ))) {
	printf("cannot open %s for reading\n", args[1]);
	return (FD_NOTRD);
    }

    if ((maxentries = check_t64_header (f)) <= 0)
      return (maxentries);


    /* Read the tape directory entries into memory */

    tapebuf = (BYTE *)malloc(TAPE_DIR_SIZE * maxentries);
    assert(tapebuf);


    fread(tapebuf, TAPE_DIR_SIZE, maxentries, f);
    dirp = tapebuf;

    while (maxentries--) {
	if (*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 [files] list */

	    /* if (nargs > 4) { */
	    if (nargs > 2) {
		for (i = 2; i < nargs &&
		     !compare_filename(asciiname, args[i]); ++i);

		if (i >= nargs) {
		    dirp += TAPE_DIR_SIZE;	/* No match, skip the file */
		    continue;
		}
	    }
	    else {
		if (!strcmp(asciiname, "file")) {
		    /* Invalid filename, try the imagename instead. */
		    strncpy(asciiname, args[1], 16);
		    asciiname[16] = 0;

		    if ((p = strstr(asciiname, ".t64")) != NULL)
		      *p = 0;
		    strcpy(newname, asciiname);
		    petconvstring(newname, 0);		/* to petcii */
		}
	    }


	    printf("  \"%-16s\"\t%02x%02x to %02x%02x\n",
		   asciiname, dirp[3], dirp[2], dirp[5], dirp[4]);

	    loc = dirp[8] | (dirp[9]<<8);
	    len = (dirp[4] | (dirp[5]<<8)) - (dirp[2] | (dirp[3]<<8));


	    if (open_1541(floppy, newname, strlen(newname), 1)) {
		printf("Cannot open `%s' for writing on image.\n", asciiname);
		free(tapebuf);
		return (FD_WRTERR);
	    }


	    printf("Writing file to image. %d bytes\n", len);

	    /* PRG file */
	    write_1541(floppy, dirp[2], 1);
	    write_1541(floppy, dirp[3], 1);

	    fseek(f, loc, 0);
	    while ((len--) > 0 && (EOF != (c = fgetc(f))))
		if (write_1541(floppy, c, 1)) {
		    printf("No space on image ?\n");
		    close_1541(floppy, 1);
		    free(tapebuf);
		    return (FD_WRTERR);
		}

	    close_1541(floppy, 1);

	    if (len > 0)
		fprintf(stderr,
			"T64 Error: Unexpected end of tape. File may be truncated.\n");
	    ++ccount;
	} /* if dirp */
	dirp += TAPE_DIR_SIZE;
    }

    printf("\n%d files copied.\n", ccount);
    fclose(f);
    free(tapebuf);
    return(0);
}


/*
 * Execute selection of BLOCK commands
 */

static int  disk_sectordump(void)
{
    static int  drive = 8, track = DSK_DIR_TRACK, sector = DSK_DIR_SECTOR;
    DRIVE *floppy;
    BYTE  *buf, str[20];
    int    err, cnt;
    int    channel = 2, disp = 0;


  /* block [drive:] [track] [sector] [disp]   show disk blocks in hex form */

    if ((nargs > 1) && (types[1] != T_NUMBER)) {
	switch (nargs) {
	  case 5:
	    disp = values[4];
	  case 4:
	    sector = values[3];
	  case 3:
	    track = values[2];
	}
	drive = find_devno(drive, args[1]);
    }
    else
	switch (nargs) {
	  case 4:
	    disp = values[3];
	  case 3:
	    sector = values[2];
	  case 2:
	    track = values[1];
	}

    if ((err = check_drive(drive, CHK_RDY)) < 0)
	return (err);
    floppy = DriveData[drive & 3];

    if (check_track_sector(floppy->ImageFormat, track, sector) < 0) {
	sector = 0;
	track = DSK_DIR_TRACK;
	return FD_BAD_TS;
    }


    /* Read one block */

    if (open_1541(floppy, "#", 1, channel)) {
	printf("u%d: cannot open buffer #%d.\n", drive, channel);
	return (FD_RDERR);
    }
    sprintf((char *)str, "B-R:%d 0 %d %d", channel, track, sector);
    if (ip_execute(floppy, (BYTE *)str, strlen((char *)str)) != 0) {
	track = DSK_DIR_TRACK;
	sector= 0;
	return (FD_RDERR);
    }

    buf = floppy->buffers[channel].buffer;


  /* Show block */

    printf("<%2d: %2d %2d>\n", drive, track, sector);
    str[16] = 0;
    for (; disp < 256;) {
	printf("> %02X ", disp &255);
	for (cnt = 0; cnt < 16; cnt++, disp++) {
	    printf(" %02X", buf[disp &255]);
	    str[cnt] = (buf[disp &255] < ' ' ?
			'.' : p_toascii(buf[disp &255], 0));
	}
	printf("  ;%s\n", str);
    }

    /* Find next sector for the file being traced */
    if (buf[0] && buf[1]) {
	track  = buf[0];
	sector = buf[1];
    }
    else if (check_track_sector(floppy->ImageFormat, track, ++sector) < 0) {
	sector = 0;
	if (++track > floppy->NumTracks)
	    track = DSK_DIR_TRACK;
    }

    close_1541(floppy, channel);
    return (FD_OK);
}


/*
 * Extract all files (gwesp@cosy.sbg.ac.at)
 */

static int  disk_extract(void)
{
    int  drive = 8, track = DSK_DIR_TRACK, sector = DSK_DIR_SECTOR;
    DRIVE *floppy;
    BYTE  *buf, str[20];
    int    err;
    int    channel = 2;

    if ((err = check_drive(drive, CHK_RDY)) < 0)
	return (err);
    floppy = DriveData[drive & 3];

    if (open_1541(floppy, "#", 1, channel)) {
	printf("u%d: cannot open buffer #%d.\n", drive, channel);
	return (FD_RDERR);
    }

    while(1)
    {
	int i;
	/* Read one block */

	sprintf((char *)str, "B-R:%d 0 %d %d", channel, track, sector);
	if (ip_execute(floppy, (BYTE *)str, strlen((char *)str))) {
	    return (FD_RDERR);
	}

	buf = floppy->buffers[channel].buffer;

	for(i=0; i < 256; i+= 32) {
	    dirslot *entry=  (dirslot *) (buf + i);
	    if ((((entry->FileType & 7) == (FT_SEQ))
		 || ((entry->FileType & 7) == (FT_PRG))
		 || ((entry->FileType & 7) == (FT_USR))
		) && (entry->FileType & FT_CLOSED)) {
		int len;
		BYTE c;
		FILE *fd;
		BYTE name[17];
		BYTE *l= memccpy(name,entry->FileName,0xa0,16);

		if (l) {
		    *(l - 1)= 0;	/* terminate by 0 */
		    len= l - 1 - name;
		} else {
		    name[16]= 0;
		    len= 16;
		}

		petconvstring(name,1);
		printf("%s\n",name);

		unix_filename(name);	/* for now, convert '/' to '_' */

		if (open_1541(floppy,entry->FileName,len,0)) {
		    printf("u%d: cannot open `%s' on image.\n", drive, name);
		    continue;
		}

		if (!(fd= fopen(name,"wb"))) {
		    perror("Couldn't open unix file");
		    close_1541(floppy,0);
		    continue;
		}

		while (!read_1541(floppy,&c,0))
		    fputc(c,fd);

		close_1541(floppy,0);

		if (fclose(fd)) {
		    perror("fclose");
		    return FD_RDERR;
		}
	    }
	}
	if (buf[0] && buf[1]) {
	    track  = buf[0];
	    sector = buf[1];
	}
	else break;
    }

    close_1541(floppy, channel);
    return (FD_OK);
}


#define OPEN15	0

static int disk_raw_command(void)
{
    DRIVE *floppy = DriveData[DriveNum & 3];
#if OPEN15
    char c;
#endif

#if OPEN15
    if (open_1541(floppy, "", 0, 15)) {
	printf("Can't open channel 15\n");
	return (FD_NOTREADY);
    }
#endif

    /* Write to the command channel */
    if (nargs >= 2) {
#if OPEN15
        char *cp;
#endif

	strcpy((char *)newname, args[1]);
	petconvstring(newname, 1);
#if OPEN15
	for (cp = newname; *cp; cp++) {
	    if (write_1541(floppy, *cp, 15)) {
		printf("Error writing to channel 15\n");
		break;
	    }
	}
	flush_1541(floppy, 15);
#else
        ip_execute(floppy, (BYTE *)newname, strlen(newname));
#endif
    }

    /* Print the error now */
#if OPEN15
    while (!read_1541(floppy, (BYTE *)&c, 15)) {
	putchar(c);
    }

    close_1541(floppy, 15);
#else
    printf("%s\n", floppy->buffers[15].buffer);
#endif
    return FD_OK;
}


static int  disk_info (void)
{
    DRIVE *floppy;
    hdrinfo hdr;
    int     err;


    if ((err = check_drive(DriveNum, CHK_RDY)) < 0)
	return (err);

    floppy = DriveData[DriveNum & 3];
    if ((err = check_header(floppy -> ActiveFd, &hdr)) < 0)
	return (err);


    /* Check if there's a new description provided. */

    if (args[1]) {
	return (set_label(floppy -> ActiveFd, args[1]));
    }
    else {
	printf ("\nDescription: %s\n",
		(*hdr.description ? hdr.description : "None."));
	printf ("Drive Type : %d.\n", floppy->ImageFormat);  /* Compatible drive */
	printf ("Disk Format: %c.\n", hdr.format);
	printf ("Sides\t   : %d.\n", hdr.sides);
	printf ("Tracks\t   : %d.\n", hdr.tracks);
	printf ((hdr.errblk ? "Error Block present.\n" : "No Error Block.\n"));
	printf ("Write protect: %s.\n",  hdr.wprot ? "On" : "Off");
    }

    return(0);
}


static void usage()
{
    fprintf (stderr, "\nUsage: following commands are supported on c1541\n\
    -format image 'name,id'        Format image. Create if needed\n\
    -delete image files            Delete files from image\n\
    -list image                    List files on image\n\
    -validate image                Validate disk\n    %s\n\
    -create image dump ['descr']   Build image out of raw blockdump\n\
                                    (683 256-byte blocks, e.g. *.d64 file)\n\
    -zcreate image zipfile         Create image out of a ZIPcoded file\n\
    -unlynx image lynxfile         Extract contents of a LYNX file\n\
    -tape image tapeimage [files]  Transfer files from *.t64 image\n\
    -read image 1541name [FSname]  Read 1541name on image to file FSname\n\
    -extract image                 Extract all files on image\n\
    -write image FSname [1541name] Write bin or P00 file FSname to image\n\
    -block [drive:] [track] [sector] [disp] \n\
                                   Show disk blocks in hex format\n\
    -info image ['description']    Show image and 1541 version\n\
    [image list]                   Enter interactive mode\n\n\
\tA '-' in place of FSname stands for stdin/stdout.\n\n",
#ifdef COPY_TO_LEFT
	     "-copy image newname oldname [oldname2 ...]"
#else
	     "-copy image oldname [oldname2 ...] newname"
#endif
	     );

    exit(1);
}


/*
 * Help
 */

static int  disk_help(void)
{
    int     i,n;

    if (nargs == 2) {
	n = strlen(args[1]);
	for (i = 0; disk_cmds[i].command; i++)
	    if (!strncmp(disk_cmds[i].command, args[1], n)) {
		printf("\n  %s\n", disk_cmds[i].help_line);
		/* add help page here */
		break;
	    }
    }
    else {
	printf("\nFollowing commands are available:\n\n");
	    for (i = 0; disk_cmds[i].command; i++)
		printf("  %s", disk_cmds[i].help_line);
	printf("\n\tAll commands may be abbreviated\n");
    }
    return (0);
}


static int  disk_quit(void)
{
    return 1;
}


/* Lynx support added by Riccardo Ferreira (storm@esoterica.pt) --
   1998-02-07.  */
static int disk_unlynx(void)
{
    DRIVE *floppy;
    FILE *f, *f2;
    int err, dev, cnt=0;
    long dentries, lbsize, bsize, dirsize;
    BYTE val;
    char buff[256] = { 0 }, cname[20] = { 0 }, ftype;

    if (!(f=fopen(args[1], READ))) {
	printf("cannot open %s for reading\n", args[1]);
	return (FD_NOTRD);
    }

    /* Look for the 0, 0, 0 sign of the end of BASIC */
    while (1) {
	fread(&val, 1, 1, f);
	if (val == 0)
	    cnt++;
	else
	    cnt = 0;
	if (cnt == 3)
	    break;
    }

    /* Bypass the 1st return in the file */
    fgetc(f);

    /* Get the directory block size */
    cnt = 0;
    while (1) {
	fread(&val, 1, 1, f);
	if (val != 13)
	    buff[cnt++] = val;
	else
	    break;
    }
    buff[cnt] = 0;
    if (string_to_long(buff, NULL, 10, &dirsize) < 0
	|| dirsize <= 0) {
	printf("invalid LyNX file.\n");
	return (FD_RDERR);
    }

    /* Get the number of dir entries */
    cnt = 0;
    while (1) {
	fread(&val, 1, 1, f);
	if (val != 13)
	    buff[cnt++] = val;
	else
	    break;
    }
    buff[cnt] = 0;
    if (string_to_long(buff, NULL, 10, &dentries) < 0
	|| dentries <= 0) {
	printf("invalid LyNX file.\n");
	return (FD_RDERR);
    }

    /* Open the file for reading of the chained data */
    f2 = fopen(args[1], READ);
    fseek(f2, (dirsize*254), SEEK_SET);

    /* Loop */
    while (dentries != 0) {
	/* Read CBM filename */
	cnt = 0;
	while (1) {
	    fread(&val, 1, 1, f);
	    if (val != 13) cname[cnt++] = val; else break;
	}
	cname[cnt] = 0;

	/* Read the block size */
	cnt = 0;
	while (1) {
	    fread(&val, 1, 1, f);
	    if (val != 13) buff[cnt++] = val; else break;
	}
	buff[cnt] = 0;

	if (string_to_long(buff, NULL, 10, &bsize) < 0) {
	    printf("invalid LyNX file.\n");
	    return (FD_RDERR);
	}

	/* Get the file type (P[RG], S[EQ], R[EL], U[SR]) */
	ftype = fgetc(f);
	fgetc(f);

	/* FIXME: REL type files unsupported */
	if (ftype == 'R') {
	    printf("REL not supported.\n");
	    return (FD_RDERR);
	}

	/* FIXME: This is a temporary hack!  How can we persuade `open_1541()'
	   to write `DEL' files without breaking compatibility with CBM
	   DOS?  -- [EP] 98.04.17  */
	if (ftype == 'D')
	    ftype = 'P';

	/* Add the file type to the name */
	cnt = strlen(cname);
	cname[cnt++] = ',';
	cname[cnt++] = ftype;
	cname[cnt] = 0;

	/* Get the byte size of the last block +1 */
	cnt = 0;
	while (1) {
	    fread(&val, 1, 1, f);
	    if (val != 13)
		buff[cnt++] = val;
	    else
		break;
	}
	buff[cnt] = 0;

	if (string_to_long(buff, NULL, 10, &lbsize) < 0) {
	    printf("invalid LyNX file.\n");
	    return (FD_RDERR);
	}

	/* Calculate byte size of file */
	cnt = bsize*254;

	/* Get the device number */
	dev = find_devno(DriveNum, (char *)NULL);

	if ((err = check_drive(dev, CHK_RDY))<0)
	    return(err);

	floppy = DriveData[dev & 3];

	if (open_1541(floppy, cname, strlen(cname), 1)) {
	    printf("couldn't open '%s' for writing.\n", cname);
	    return (FD_WRTERR);
	}

	printf("writing file '%s' to image.\n", cname);

	while (cnt != 0) {
	    fread(&val, 1, 1, f2);
	    if (write_1541(floppy, val, 1)) {
		printf("no space on image ?\n");
		break;
	    }
	    cnt--;
	}
	close_1541(floppy, 1);

	/* Adjust for the last block */
	fread(buff, 1, 254 - lbsize, f2);
	dentries--;
    }
    fclose(f);
    fclose(f2);
    return(0);
}

/* `system' command added by Riccardo Ferreira (storm@esoterica.pt) --
   1998-02-07.  */
int disk_system(void)
{
	return system(args[1]);
}


/*
 * Disk Maintenance main program
 */

int     main(argc, argv)
     int     argc;
     char   *argv[];
{
    char   *progname, *line;
    int     flg = 0, i;


    progname = argv[0];
    nargs    = 0;

    initialize_1541(8, DT_DISK | DT_1541, NULL, NULL, NULL);
    initialize_1541(9, DT_DISK | DT_1541, NULL, NULL, NULL);


    /* Direct mode */

    if (argc > 1 && argv[1][0] == '-') {
	char *ImageName;

	if (argc < 3)
	    usage();

	args[0]  = argv[1] + 1;				/* Command */
	ImageName = argv[2];

	if (!strncmp(args[0], "f", 1) || !strncmp(args[0], "cr", 2) ||
	    !strncmp(args[0], "zcr", 3)) {
	    --argc;
	    ++argv;
	} else {
	    argc -= 2;
	    argv += 2;
	}

	for (nargs = 1; nargs < argc; nargs++) {	/* Parameters */
	    if (nargs >= MAXARG) {
		fprintf(stderr, "%s: Bailing out: Too many arguments.\n\n",
		    progname);
		usage();
	    }
	    args[nargs] = argv[nargs];
	    values[nargs] = strlen(args[nargs]);
	}

	if (strncmp(args[0], "f", 1) && strncmp(args[0], "cr", 2))
	    if (open_image(0, ImageName, 0) < 0) {
		printf ("Cannot open image\n");
		exit (1);
	    }

	printf("\n");
	if (eval_command(args[0], nargs, disk_cmds) < 0) {	/* any error */
	    printf(" I won't do that.\n");
	    usage();
	}
    }

    /* Mainloop: parse & execute */

    else {
	char buf[16];

	fprintf (stderr, "\nC1541 V %d.%02d  Image %d.%02d\n\n",
		 C1541_VERSION_MAJOR, C1541_VERSION_MINOR,
		 HEADER_VERSION_MAJOR, HEADER_VERSION_MINOR);


	while (--argc && ++argv && nargs <= MAXDRIVE) {		/* Images */
	    if (*argv[0])
		if (open_image(nargs, argv[0], 0) < 0) {	/* open each */
		    printf ("Invalid image\n");
		    exit (1);
		}
	    ++nargs;
	}


	while (!flg) {
	    sprintf (buf, "c1541 #%d> ", (DriveNum | 8) );
	    if ((line = read_line(buf, MODE_MON)) == NULL)
		break;

	    if (*line) {
		nargs = split_args(line, 0,
				   MAXARG, MAXVAL, args, values, types);

		switch ( eval_command(args[0], nargs, disk_cmds) ) {
		  case FD_EXIT:
		    ++flg;
		  case FD_OK:
		    break;

		  case FD_NOTREADY:
		    printf("Drive not ready.\n");
		    break;
		  case FD_CHANGED:
		    printf("Image file has changed on disk.\n");
		    break;
		  case FD_NOTRD:
		    printf("Cannot read file.\n");
		    break;
		  case FD_NOTWRT:
		    printf("Cannot write file.\n");
		    break;
		  case FD_WRTERR:
		    printf("Floppy write failed.\n");
		    break;
		  case FD_RDERR:
		    printf("Floppy read failed.\n");
		    break;
		  case FD_INCOMP:
		    printf("Incompatible DOS version.\n");
		    break;
		  case FD_BADIMAGE:
		    printf("Invalid image.\n");		/* Disk or tape */
		    break;
		  case FD_BADNAME:
		    printf("Invalid filename.\n");
		    break;
		  case FD_BADVAL:
		    printf("Illegal value.\n");
		    break;
		  case FD_BADDEV:
		    printf("Illegal device number.\n");
		    break;
		  case FD_BAD_TS:
		    printf("Inaccessible Track or Sector.\n");
		    break;

		  default:
		    printf(" Unknown command. Try 'help'\n");
		}
		printf("\n");

	    } /* line */
	}  /* while */
    } /* Mainloop */


    for (i = 0; i <= MAXDRIVE; i++) {
	if (DriveData[i])
	    detach_floppy_image(DriveData[i]);
    }

    printf("\n");
    return (0);
}

int attach_fsdevice(int device, char *var, char *name)
{
    DriveData[device & 3] = (DRIVE *)var;
    return 0;
}

int serial_attach_device(int device, char *var, char *name,
                         int (*getf)(void *, BYTE * , int),
                         int (*putf)(void *, BYTE , int),
                         int (*openf)(void *, char *, int , int),
                         int (*closef)(void *, int),
                         void (*flushf)(void *, int))
{
    DriveData[device & 3] = (DRIVE *)var;
    return 0;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.