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

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

/*
 * true1541.c - Hardware-level Commodore 1541 disk drive emulation.
 *
 * Written by
 *  Daniel Sladic (sladic@eecg.toronto.edu)
 *  Andreas Boose (boose@unixserv.rz.fh-hannover.de)
 *  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.
 *
 */

/* TODO:
	- more accurate emulation of disk rotation.
	- different speeds within one track.
	- support for .d64 images with attached error code.
	- support for GCR encoded image files.
	- check for byte ready *within* `BVC', `BVS' and `PHP'.
	- serial bus handling might be faster.  */

#define __1541__

#include "vice.h"

#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <math.h>

#include "true1541.h"
#include "gcr.h"
#include "interrupt.h"
#include "vmachine.h"
#include "serial.h"
#include "drive.h"
#include "warn.h"
#include "mem.h"
#include "resources.h"
#include "cmdline.h"
#include "memutils.h"
#include "viad.h"
#include "via.h"
#include "cia.h"
#include "utils.h"
#include "ui.h"

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

/* Flag: Is the true 1541 emulation turned on?  */
int true1541_enabled;

/* Flag: Do we emulate a SpeedDOS-compatible parallel cable?  */
int true1541_parallel_cable_enabled;

/* What extension policy?  (See `TRUE1541_EXTEND_*' in `true1541.h'.)  */
static int extend_image_policy;

/* What idling method?  (See `TRUE1541_IDLE_*' in `true1541.h'.)  */
static int idling_method;

/* Original ROM code is saved here.  */
static BYTE true1541_rom_idle_trap;

/* What sync factor between the CPU and the 1541?  If equal to
   `TRUE1541_SYNC_PAL', the same as PAL machines.  If equal to
   `TRUE1541_SYNC_NTSC', the same as NTSC machines.  The sync factor is
   calculated as

   65536 * clk_1541 / clk_[c64|vic20]

   where `clk_1541' is fixed to 1 MHz, while `clk_[c64|vic20]' depends on the
   video timing (PAL or NTSC).  The pre-calculated values for PAL and NTSC
   are in `pal_sync_factor' and `ntsc_sync_factor'.  */
static int sync_factor;

/* Name of the DOS ROM.  */
static char *dos_rom_name;

static int set_true1541_enabled(resource_value_t v)
{
    if ((int) v)
        true1541_enable();
    else
        true1541_disable();
    return 0;
}

static int set_true1541_parallel_cable_enabled(resource_value_t v)
{
    true1541_parallel_cable_enabled = (int) v;
    return 0;
}

static int set_extend_image_policy(resource_value_t v)
{
    switch ((int) v) {
      case TRUE1541_EXTEND_NEVER:
      case TRUE1541_EXTEND_ASK:
      case TRUE1541_EXTEND_ACCESS:
        extend_image_policy = (int) v;
        return 0;
      default:
        return -1;
    }
}

static int set_idling_method(resource_value_t v)
{
    /* FIXME: Maybe we should call `true1541_cpu_execute()' here?  */
    if ((int) v != TRUE1541_IDLE_SKIP_CYCLES
        && (int) v != TRUE1541_IDLE_TRAP_IDLE
        && (int) v != TRUE1541_IDLE_NO_IDLE)
        return -1;

    if (rom_loaded)
	true1541_rom[0xec9b - 0xc000] =
	    (idling_method != TRUE1541_IDLE_TRAP_IDLE)
	    ? 0x00 : true1541_rom_idle_trap;
    idling_method = (int) v;
    return 0;
}

static int set_sync_factor(resource_value_t v)
{
    switch ((int) v) {
      case TRUE1541_SYNC_PAL:
        sync_factor = (int) v;
        true1541_set_pal_sync_factor();
        break;
      case TRUE1541_SYNC_NTSC:
        sync_factor = (int) v;
        true1541_set_ntsc_sync_factor();
        break;
      default:
        if ((int) v > 0) {
            sync_factor = (int) v;
            true1541_set_sync_factor((int) v);
        } else {
            return -1;
        }
    }

    return 0;
}

static int set_dos_rom_name(resource_value_t v)
{
    const char *name = (const char *) v;

    if (dos_rom_name == NULL)
        dos_rom_name = stralloc(name);
    else {
        dos_rom_name = xrealloc(dos_rom_name, strlen(name) + 1);
        strcpy(dos_rom_name, name);
    }
    return 0;
}

static resource_t resources[] = {
    { "True1541", RES_INTEGER, (resource_value_t) 0,
      (resource_value_t *) &true1541_enabled, set_true1541_enabled },
    { "True1541ParallelCable", RES_INTEGER, (resource_value_t) 0,
      (resource_value_t *) &true1541_parallel_cable_enabled, set_true1541_parallel_cable_enabled },
    { "True1541ExtendImagePolicy", RES_INTEGER, (resource_value_t) TRUE1541_EXTEND_NEVER,
      (resource_value_t *) &extend_image_policy, set_extend_image_policy },
    { "True1541IdleMethod", RES_INTEGER, (resource_value_t) TRUE1541_IDLE_TRAP_IDLE,
      (resource_value_t *) &idling_method, set_idling_method },
    { "True1541SyncFactor", RES_INTEGER, (resource_value_t) TRUE1541_SYNC_PAL,
      (resource_value_t *) &sync_factor, set_sync_factor },
    { "DosName", RES_STRING, (resource_value_t) "dos1541",
      (resource_value_t *) &dos_rom_name, set_dos_rom_name },
    { NULL }
};

int true1541_init_resources(void)
{
    return resources_register(resources);
}

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

/* FIXME: Maybe DosName should not be changed by `-dosname'?  */

static cmdline_option_t cmdline_options[] = {
    { "-1541", SET_RESOURCE, 0, NULL, NULL, "True1541", (resource_value_t) 1,
      NULL, "Enable hardware-level 1541 emulation" },
    { "+1541", SET_RESOURCE, 0, NULL, NULL, "True1541", (resource_value_t) 0,
      NULL, "Disable hardware-level 1541 emulation" },
    { "-parallel", SET_RESOURCE, 0, NULL, NULL, "True1541ParallelCable",
      (resource_value_t) 1,
      NULL, "Enable SpeedDOS-compatible parallel cable" },
    { "+parallel", SET_RESOURCE, 0, NULL, NULL, "True1541ParallelCable",
      (resource_value_t) 0,
      NULL, "Disable SpeedDOS-compatible parallel cable" },
    { "-driveidle", SET_RESOURCE, 2, NULL, NULL, "True1541IdleMethod",
      NULL, "<method>",
      "Set 1541 idling method (0: no traps, 1: skip cycles, 2: trap idle)" },
    { "-drivesync", SET_RESOURCE, 1, NULL, NULL, "True1541SyncFactor",
      NULL, "<value>", "Set 1541 sync factor to <value>" },
    { "-paldrive", SET_RESOURCE, 0, NULL, NULL, "True1541SyncFactor",
      (resource_value_t) TRUE1541_SYNC_PAL,
      NULL, "Use PAL 1541 sync factor" },
    { "-ntscdrive", SET_RESOURCE, 0, NULL, NULL, "True1541SyncFactor",
      (resource_value_t) TRUE1541_SYNC_NTSC,
      NULL, "Use NTSC 1541 sync factor" },
    { "-dosname", SET_RESOURCE, 1, NULL, NULL, "DosName", NULL,
      "<name>", "Specify name of 1541 DOS ROM image name" },
    { NULL }
};

int true1541_init_cmdline_options(void)
{
    return cmdline_register_options(cmdline_options);
}

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

#define NUM_BYTES_SECTOR_GCR 360
#define NUM_MAX_BYTES_TRACK 7928

/* RAM/ROM.  */
BYTE true1541_rom[TRUE1541_ROM_SIZE];
BYTE true1541_ram[TRUE1541_RAM_SIZE];

/* LED status (zero = off).  */
int true1541_led_status;

/* Current half track on which the R/W head is positioned.  */
int true1541_current_half_track = 36;

/* If nonzero, the 1541 ROM has already been loaded.  */
static int rom_loaded = 0;

static int byte_ready = 1;

/* Pointer to the attached disk image.  */
static DRIVE *true1541_floppy;

/* Disk ID.  */
static BYTE diskID1, diskID2;

/* Map of the sector sizes.  */
extern char sector_map[43];

/* Speed zone of each track.  */
extern int speed_map[42];

/* Flag: does the current need to be written out to disk?  */
static int GCR_dirty_track = 0;

/* GCR value being written to the disk.  */
static BYTE GCR_write_value = 0x55;

/* Raw GCR image of the disk.  */
static BYTE GCR_data[MAX_TRACKS_1541 * NUM_MAX_BYTES_TRACK];

/* Speed zone image of the disk.  */
static BYTE GCR_speed_zone[MAX_TRACKS_1541 * NUM_MAX_BYTES_TRACK];

/* Pointer to the start of the GCR data for this track.  */
static BYTE *GCR_track_start_ptr = GCR_data;

/* Size of the GCR data for the current track.  */
static int GCR_current_track_size;

/* Size of the GCR data of each track.  */
static int GCR_track_size[MAX_TRACKS_1541];

/* Offset of the R/W head on the current track.  */
static int GCR_head_offset;

/* Speed (in bps) of the disk in the 4 disk areas.  */
static int rot_speed_bps[4] = { 250000, 266667, 285714, 307692 };

/* Number of bytes per track size.  */
static int raw_track_size[4] = { 6250, 6666, 7142, 7692 };

static int read_write_mode;
static int byte_ready_active;

/* If the user does not want to extend the disk image and `ask mode' is
   selected this flag gets cleared.  */
static int ask_extend_disk_image;

/* Tick when the disk image was attached.  */
static CLOCK attach_clk = (CLOCK)0;

/* Tick when the disk image was detached.  */
static CLOCK detach_clk = (CLOCK)0;

/* Clock speed of the PAL and NTSC versions of the connected computer.  */
static CLOCK pal_cycles_per_sec;
static CLOCK ntsc_cycles_per_sec;

/* Warnings.  */
enum true1541_warnings { WARN_GCRWRITE };
#define TRUE1541_NUM_WARNINGS (WARN_GCRWRITE + 1)
static warn_t *true1541_warn;

#define GCR_OFFSET(track, sector)  ((track - 1) * NUM_MAX_BYTES_TRACK \
				    + sector * NUM_BYTES_SECTOR_GCR)

static void GCR_data_writeback(void);
static void initialize_rotation(void);
static void true1541_extend_disk_image(void);

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

/* Disk image handling. */

static void read_image_d64(void)
{
    BYTE buffer[260], *ptr, chksum;
    int rc, i;
    int track, sector;

    if (!true1541_floppy)
	return;

    buffer[0] = 0x07;
    buffer[258] = buffer[259] = 0;

    /* Since the D64 format does not provide the actual track sizes or
       speed zones, we set them to standard values.  */
    for (track = 0; track < MAX_TRACKS_1541; track++) {
	GCR_track_size[track] = raw_track_size[speed_map[track]];
	memset(GCR_speed_zone, speed_map[track], NUM_MAX_BYTES_TRACK);
    }

    true1541_set_half_track(true1541_current_half_track);

    for (track = 1; track <= true1541_floppy->NumTracks; track++) {
	ptr = GCR_data + GCR_OFFSET(track, 0);

	/* Clear track to avoid read errors.  */
	memset(ptr, 0xff, NUM_MAX_BYTES_TRACK);

	for (sector = 0; sector < sector_map[track]; sector++) {
	    ptr = GCR_data + GCR_OFFSET(track, sector);

	    rc = floppy_read_block(true1541_floppy->ActiveFd,
				   true1541_floppy->ImageFormat,
				   buffer + 1, track, sector,
				   true1541_floppy->D64_Header);
	    if (rc < 0) {
		printf("1541: error reading T:%d S:%d from the disk image\n",
		       track, sector);
		/* FIXME: could be handled better. */
	    } else {
		chksum = buffer[1];
		for (i = 2; i < 257; i++)
		    chksum ^= buffer[i];
		buffer[257] = chksum;
		convert_sector_to_GCR(buffer, ptr, track, sector, diskID1, diskID2);
	    }
	}
    }
}

static int read_image_gcr(void)
{
    int track, track_len, zone_len, i, NumTracks;
    BYTE len[2], comp_speed[NUM_MAX_BYTES_TRACK / 4];
    BYTE *track_data, *zone_data;
    DWORD gcr_track_p[MAX_TRACKS_1541 * 2];
    DWORD gcr_speed_p[MAX_TRACKS_1541 * 2];
    off_t offset;

    NumTracks = true1541_floppy->NumTracks;

    lseek(true1541_floppy->ActiveFd, 12, SEEK_SET);
    if (read_dword(true1541_floppy->ActiveFd, gcr_track_p, NumTracks * 8) < 0) {
	fprintf(stderr, "1541: Could not read GCR disk image.\n");
	return 0;
    }

    lseek(true1541_floppy->ActiveFd, 12 + NumTracks * 8, SEEK_SET);
    if (read_dword(true1541_floppy->ActiveFd, gcr_speed_p, NumTracks * 8) < 0) {
	fprintf(stderr, "1541: Could not read GCR disk image.\n");
	return 0;
    }

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

	track_data = GCR_data + track * NUM_MAX_BYTES_TRACK;
	zone_data = GCR_speed_zone + track * NUM_MAX_BYTES_TRACK;
	memset(track_data, 0xff, NUM_MAX_BYTES_TRACK);
	memset(zone_data, 0x00, NUM_MAX_BYTES_TRACK / 4);
	GCR_track_size[track] = 6250;

	if (track <= NumTracks && gcr_track_p[track * 2] != 0) {

	    offset = gcr_track_p[track * 2];

	    lseek(true1541_floppy->ActiveFd, offset, SEEK_SET);
	    if (read(true1541_floppy->ActiveFd, (char *)len, 2) < 2) {
		fprintf(stderr, "1541: Could not read GCR disk image.\n");
		return 0;
	    }

	    track_len = len[0] + len[1] * 256;

	    if (track_len < 5000 || track_len > 7928) {
		fprintf(stderr, "1541: Track field length %i is not supported.\n", track_len);
		return 0;
	    }

	    GCR_track_size[track] = track_len;

	    lseek(true1541_floppy->ActiveFd, offset + 2, SEEK_SET);
	    if (read(true1541_floppy->ActiveFd, (char *)track_data, track_len)
			< track_len) {
		fprintf(stderr, "1541: Could not read GCR disk image.\n");
		return 0;
	    }

	    zone_len = (track_len + 3) / 4;

	    if (gcr_speed_p[track * 2] > 3) {

		offset = gcr_speed_p[track * 2];

		lseek(true1541_floppy->ActiveFd, offset, SEEK_SET);
		if (read(true1541_floppy->ActiveFd, (char *)comp_speed,
			zone_len) < zone_len) {
		    fprintf(stderr, "1541: Could not read GCR disk image.\n");
		    return 0;
		}

		for (i = 0; i < zone_len; i++) {
		    zone_data[i * 4] = comp_speed[i] & 3;
		    zone_data[i * 4 + 1] = (comp_speed[i] >> 2) & 3;
		    zone_data[i * 4 + 2] = (comp_speed[i] >> 4) & 3;
		    zone_data[i * 4 + 3] = (comp_speed[i] >> 6) & 3;
		}
	    } else {
		memset(zone_data, gcr_speed_p[track * 2], NUM_MAX_BYTES_TRACK);
	    }
	}
    }
    return 1;
}

static void write_track_gcr(int track)
{
    int gap, i, NumTracks;
    BYTE len[2];
    DWORD gcr_track_p[MAX_TRACKS_1541 * 2];
    DWORD gcr_speed_p[MAX_TRACKS_1541 * 2];
    off_t offset;

    NumTracks = true1541_floppy->NumTracks;

    lseek(true1541_floppy->ActiveFd, 12, SEEK_SET);
    if (read_dword(true1541_floppy->ActiveFd, gcr_track_p, NumTracks * 8) < 0) {
	fprintf(stderr, "1541: Could not read GCR disk image header.\n");
	return;
    }

    lseek(true1541_floppy->ActiveFd, 12 + NumTracks * 8, SEEK_SET);
    if (read_dword(true1541_floppy->ActiveFd, gcr_speed_p, NumTracks * 8) < 0) {
	fprintf(stderr, "1541: Could not read GCR disk image header.\n");
	return;
    }

    if (gcr_track_p[(track - 1) * 2] == 0) {
	offset = lseek(true1541_floppy->ActiveFd, 0, SEEK_END);
	if (offset < 0) {
	    fprintf(stderr, "1541: Could not extend GCR disk image.\n");
	    return;
	}
	gcr_track_p[(track - 1) * 2] = offset;
    }

    offset = gcr_track_p[(track - 1) * 2];

    len[0] = GCR_track_size[track - 1] % 256;
    len[1] = GCR_track_size[track - 1] / 256;

    if (lseek(true1541_floppy->ActiveFd, offset, SEEK_SET) < 0
        || write(true1541_floppy->ActiveFd, (char *)len, 2) < 0) {
	fprintf(stderr, "1541: Could not write GCR disk image.\n");
	return;
    }

    /* Clear gap between the end of the actual track and the start of
       the next track.  */
    gap = NUM_MAX_BYTES_TRACK - GCR_track_size[track - 1];
    if (gap > 0)
	memset(GCR_track_start_ptr + GCR_track_size[track - 1], 0, gap);

    if (lseek(true1541_floppy->ActiveFd, offset + 2, SEEK_SET) < 0
        || write(true1541_floppy->ActiveFd, (char *)GCR_track_start_ptr,
                 NUM_MAX_BYTES_TRACK) < 0) {
	fprintf(stderr, "1541: Could not write GCR disk image.\n");
	return;
    }

    for (i = 0; (GCR_speed_zone[(track - 1) * NUM_MAX_BYTES_TRACK]
	    == GCR_speed_zone[(track - 1) * NUM_MAX_BYTES_TRACK + i])
	    && i < NUM_MAX_BYTES_TRACK; i++);

    if (i < GCR_track_size[track - 1]) {
	/* This will change soon.  */
	fprintf(stderr,
                "1541: Saving different speed zones is not supported yet.\n");
	return;
    }

    if (gcr_speed_p[(track - 1) * 2] >= 4) {
	/* This will change soon.  */
	fprintf(stderr,
                "1541: Adding new speed zones is not supported yet.\n");
	return;
    }

    offset = 12 + NumTracks * 8 + (track - 1) * 8;
    if (lseek(true1541_floppy->ActiveFd, offset, SEEK_SET) < 0
        || write_dword(true1541_floppy->ActiveFd,
           &gcr_speed_p[(track - 1) * 2], 4) < 0) {
    fprintf(stderr, "1541: Could not write GCR disk image.\n");
    return;
    }

#if 0  /* We do not support writing different speeds yet.  */
    for (i = 0; i < (NUM_MAX_BYTES_TRACK / 4); i++)
    zone_len = (GCR_track_size[track - 1] + 3) / 4;
    zone_data = GCR_speed_zone + (track - 1) * NUM_MAX_BYTES_TRACK;

    if (gap > 0)
	memset(zone_data + GCR_track_size[track - 1], 0, gap);

    for (i = 0; i < (NUM_MAX_BYTES_TRACK / 4); i++)
	comp_speed[i] = (zone_data[i * 4]
                         | (zone_data[i * 4 + 1] << 2)
                         | (zone_data[i * 4 + 2] << 4)
                         | (zone_data[i * 4 + 3] << 6));

    if (lseek(true1541_floppy->ActiveFd, offset, SEEK_SET) < 0
        || write(true1541_floppy->ActiveFd, (char *)comp_speed,
                 NUM_MAX_BYTES_TRACK / 4) < 0) {
        fprintf(stderr, "1541: Could not write GCR disk image");
        return;
    }
#endif
}

static int setID(void)
{
    BYTE buffer[256];
    int rc;

    if (!true1541_floppy)
	return -1;

    rc = floppy_read_block(true1541_floppy->ActiveFd,
			   true1541_floppy->ImageFormat,
			   buffer, 18, 0, true1541_floppy->D64_Header);
    if (rc >= 0) {
	diskID1 = buffer[0xa2];
	diskID2 = buffer[0xa3];
    }

    return rc;
}

static BYTE *GCR_find_sector_header(int track, int sector)
{
    BYTE *offset = GCR_track_start_ptr;
    BYTE *GCR_track_end = GCR_track_start_ptr + GCR_current_track_size;
    char GCR_header[5], header_data[4];
    int i, sync_count = 0, wrap_over = 0;

    while ((offset < GCR_track_end) && !wrap_over) {
	while (*offset != 0xff)	{
	    offset++;
	    if (offset >= GCR_track_end)
		return NULL;
	}

	while (*offset == 0xff)	{
	    offset++;
	    if (offset == GCR_track_end) {
		offset = GCR_track_start_ptr;
		wrap_over = 1;
	    }
	    /* Check for killer tracks.  */
	    if((++sync_count) >= GCR_current_track_size)
		return NULL;
	}

	for (i=0; i < 5; i++) {
	    GCR_header[i] = *(offset++);
	    if (offset >= GCR_track_end) {
		offset = GCR_track_start_ptr;
		wrap_over = 1;
	    }
	}

	convert_GCR_to_4bytes(GCR_header, header_data);

	if (header_data[0] == 0x08) {
	    /* FIXME: Add some sanity checks here.  */
	    if (header_data[2] == sector && header_data[3] == track)
		return offset;
	}
    }
    return NULL;
}

static BYTE *GCR_find_sector_data(BYTE *offset)
{
    BYTE *GCR_track_end = GCR_track_start_ptr + GCR_current_track_size;
    int header = 0;

    while (*offset != 0xff) {
	offset++;
	if (offset >= GCR_track_end)
	    offset = GCR_track_start_ptr;
	header++;
	if (header >= 500)
	    return NULL;
    }

    while (*offset == 0xff) {
	offset++;
	if (offset == GCR_track_end)
	    offset = GCR_track_start_ptr;
    }
    return offset;
}

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

/* Initialize the hardware-level 1541 emulation (should be called at least once
   before anything else).  Return 0 on success, -1 on error.  */
int true1541_init(CLOCK pal_hz, CLOCK ntsc_hz)
{
    int track;

    pal_cycles_per_sec = pal_hz;
    ntsc_cycles_per_sec = ntsc_hz;

    if (rom_loaded)
	return 0;

    true1541_warn = warn_init("1541", TRUE1541_NUM_WARNINGS);

    /* Load the ROMs. */
    if (mem_load_sys_file(dos_rom_name, true1541_rom, TRUE1541_ROM_SIZE,
                          TRUE1541_ROM_SIZE) < 0) {
	fprintf(stderr,
		"1541: Warning: ROM image not loaded; hardware-level "
		"emulation is not available.\n");
	true1541_enabled = 0;
	return -1;
    }

    /* Calculate ROM checksum. */
    {
	unsigned long s;
	int i;

	for (i = 0, s = 0; i < TRUE1541_ROM_SIZE; i++)
	    s += true1541_rom[i];

	if (s != TRUE1541_ROM_CHECKSUM)
	    fprintf(stderr,
		    "1541: Warning: unknown ROM image.  Sum: %lu\n", s);
    }

    printf("1541: ROM loaded successfully.\n");
    rom_loaded = 1;

    /* Remove the ROM check.  */
    true1541_rom[0xeae4 - 0xc000] = 0xea;
    true1541_rom[0xeae5 - 0xc000] = 0xea;
    true1541_rom[0xeae8 - 0xc000] = 0xea;
    true1541_rom[0xeae9 - 0xc000] = 0xea;

    /* Trap the idle loop.  */
    true1541_rom_idle_trap = true1541_rom[0xec9b - 0xc000];
    if (idling_method == TRUE1541_IDLE_TRAP_IDLE)
	true1541_rom[0xec9b - 0xc000] = 0x00;

    for (track = 0; track < MAX_TRACKS_1541; track++)
	GCR_track_size[track] = raw_track_size[speed_map[track]];

    /* Position the R/W head on the directory track.  */
    true1541_set_half_track(36);

    initialize_rotation();

    true1541_cpu_init();

    /* Make sure the sync factor is acknowledged correctly.  */
    set_sync_factor((resource_value_t) sync_factor);

    /* Make sure the traps are moved as needed.  */
    if (true1541_enabled)
	true1541_enable();

    return 0;
}

/* Activate full 1541 emulation. */
int true1541_enable(void)
{
    /* This must come first, because this might be called before the true
       1541 initialization.  */
    true1541_enabled = 1;

    if (!rom_loaded)
        return -1;

    /* Always disable kernal traps. */
    serial_remove_traps();

    if (true1541_floppy != NULL)
        true1541_attach_floppy(true1541_floppy);

    true1541_cpu_wake_up();

    ui_toggle_drive_status(1);
    return 0;
}

/* Disable full 1541 emulation.  */
void true1541_disable(void)
{
    /* This must come first, because this might be called before the true
       1541 initialization.  */
    true1541_enabled = 0;

    if (rom_loaded)
        serial_install_traps();

    true1541_cpu_sleep();

    GCR_data_writeback();

    ui_toggle_drive_status(0);
}

void true1541_reset(void)
{
    true1541_cpu_reset();
    warn_reset(true1541_warn);
}

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

static int have_new_disk = 0;	/* used for disk change detection */

/* Attach a disk image to the true 1541 emulation. */
int true1541_attach_floppy(DRIVE *floppy)
{
    if (floppy->ImageFormat != 1541)
	return -1;

    true1541_floppy = floppy;
    have_new_disk = 1;
    attach_clk = true1541_clk;
    ask_extend_disk_image = 1;

    if (true1541_floppy->GCR_Header != 0) {
        if (!read_image_gcr())
            return -1;
    } else {
	if (setID() >= 0) {
	    read_image_d64();
	    return 0;
	} else {
	    return -1;
	}
    }
    return 0;
}

/* Detach a disk image from the true 1541 emulation. */
int true1541_detach_floppy(DRIVE *floppy)
{
    if (floppy != true1541_floppy) {
        /* Shouldn't happen.  */
        fprintf(stderr, "Whaaat?  Attempt for bogus true1541 detachment!\n");
        return -1;
    } else if (true1541_floppy != NULL) {
	GCR_data_writeback();
	detach_clk = true1541_clk;
	true1541_floppy = NULL;
	memset(GCR_data, 0, sizeof(GCR_data));
    }
    return 0;
}

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

static BYTE GCR_read;
static unsigned long bits_moved = 0, accum = 0;
static int finish_byte = 0, last_mode = 1;
static CLOCK rotation_last_clk = 0L;

#define ROTATION_TABLE_SIZE      0x1000
#define ACCUM_MAX                0x10000

struct _rotation_table {
    unsigned long bits;
    unsigned long accum;
};

struct _rotation_table rotation_table[4][ROTATION_TABLE_SIZE];
struct _rotation_table *rotation_table_ptr = rotation_table[0];

/* Initialization.  */
static void initialize_rotation(void)
{
    int i, j;

    for (i = 0; i < 4; i++) {
        int speed = rot_speed_bps[i];

        for (j = 0; j < ROTATION_TABLE_SIZE; j++) {
            double bits = (double)j * (double)speed / 1000000.0;

            rotation_table[i][j].bits = (unsigned long)bits;
            rotation_table[i][j].accum = ((bits - (unsigned long)bits)
                                          * ACCUM_MAX);
        }
    }

    bits_moved = accum = 0;
}

/* Set the `byte ready' bit.  */
inline void true1541_set_byte_ready(int val)
{
    byte_ready = val;
}

/* Rotate the disk according to the current value of `true1541_clk'.  If
   `mode_change' is non-zero, there has been a Read -> Write mode switch.  */
void true1541_rotate_disk(int mode_change)
{
    unsigned long new_bits;

    if (mode_change) {
	finish_byte = 1;
	return;
    }

    /* If the drive's motor is off or byte ready is disabled do nothing.  */
    if (byte_ready_active != 0x06)
	return;

    /* Calculate the number of bits that have passed under the R/W head since
       the last time.  */
    {
        CLOCK delta = true1541_clk - rotation_last_clk;

        new_bits = 0;
        while (delta > 0) {
            if (delta >= ROTATION_TABLE_SIZE) {
                struct _rotation_table *p = (rotation_table_ptr
                                             + ROTATION_TABLE_SIZE - 1);
                new_bits += p->bits;
                accum += p->accum;
                delta -= ROTATION_TABLE_SIZE - 1;
            } else {
                struct _rotation_table *p = rotation_table_ptr + delta;
                new_bits += p->bits;
                accum += p->accum;
                delta = 0;
            }
            if (accum >= ACCUM_MAX) {
                accum -= ACCUM_MAX;
                new_bits++;
            }
        }
    }

    if (bits_moved + new_bits >= 8) {

	bits_moved += new_bits;
	rotation_last_clk = true1541_clk;

	if (finish_byte) {
	    if (last_mode == 0) { /* write */
		GCR_dirty_track = 1;
		if (bits_moved >= 8) {
		    GCR_track_start_ptr[GCR_head_offset] = GCR_write_value;
		    GCR_head_offset = ((GCR_head_offset + 1) %
                                       GCR_current_track_size);
		    bits_moved -= 8;
		}
	    } else {		/* read */
		if (bits_moved >= 8) {
		    GCR_head_offset = ((GCR_head_offset + 1) %
                                       GCR_current_track_size);
		    bits_moved -= 8;
		    GCR_read = GCR_track_start_ptr[GCR_head_offset];
		}
	    }

	    finish_byte = 0;
	    last_mode = read_write_mode;
	}

	if (last_mode == 0) {	/* write */
	    GCR_dirty_track = 1;
	    while (bits_moved >= 8) {
		GCR_track_start_ptr[GCR_head_offset] = GCR_write_value;
		GCR_head_offset = ((GCR_head_offset + 1)
                                   % GCR_current_track_size);
		bits_moved -= 8;
	    }
	} else {		/* read */
	    GCR_head_offset = ((GCR_head_offset + bits_moved / 8)
			       % GCR_current_track_size);
	    bits_moved %= 8;
	    GCR_read = GCR_track_start_ptr[GCR_head_offset];
	}

	if (!true1541_sync_found())
	    true1541_set_byte_ready(1);
    } /* if (bits_moved + new_bits >= 8) */
}

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

/* This prevents the CLOCK counters `rotation_last_clk', `attach_clk'
   and `detach_clk' from overflowing.  */
void true1541_prevent_clk_overflow(CLOCK sub)
{
    sub = true1541_cpu_prevent_clk_overflow(sub);

    if (sub > 0) {
	true1541_rotate_disk(0);
	rotation_last_clk -= sub;
	if (attach_clk > (CLOCK) 0)
	    attach_clk -= sub;
	if (detach_clk > (CLOCK) 0)
	    detach_clk -= sub;
    }
}

/* Read a GCR byte from the disk. */
BYTE true1541_read_disk_byte(void)
{
    BYTE val;

    if (attach_clk != (CLOCK)0) {
        if (true1541_clk - attach_clk < TRUE1541_ATTACH_DELAY)
            return 0;
        attach_clk = (CLOCK)0;
    }

    true1541_rotate_disk(0);
    val = GCR_read;

    return val;
}

int true1541_byte_ready(void)
{
   if(byte_ready_active) {
       true1541_rotate_disk(0);
       return byte_ready;
   } else {
       return 0;
   }
}


/* Return non-zero if the Sync mark is found.  It is required to
   call true1541_rotate_disk() to update GCR_head_offset first.  */
int true1541_sync_found(void)
{
    BYTE val = GCR_track_start_ptr[GCR_head_offset];

    if (val != 0xff || last_mode == 0) {
        return 0;
    } else {
	int next_head_offset = (GCR_head_offset > 0
				? GCR_head_offset - 1
				: GCR_current_track_size - 1);

	if (GCR_track_start_ptr[next_head_offset] != 0xff)
	    return 0;

	/* As the current rotation code cannot cope with non byte aligned
	   writes, do not change `bits_moved'!  */
	/* bits_moved = 0; */
	return 1;
    }
}

/* Move the head to half track `num'.  */
void true1541_set_half_track(int num)
{
    if (num > 84)
	num = 84;
    else if (num < 2)
	num = 2;

    true1541_current_half_track = num;
    GCR_track_start_ptr = (GCR_data
			   + ((true1541_current_half_track / 2 - 1)
			      * NUM_MAX_BYTES_TRACK));

    GCR_current_track_size = GCR_track_size[true1541_current_half_track / 2
                                            - 1];
    GCR_head_offset = 0;
}

/* Increment the head position by `step' half-tracks. Valid values
   for `step' are `+1' and `-1'.  */
void true1541_move_head(int step)
{
    GCR_data_writeback();
    true1541_set_half_track(true1541_current_half_track + step);
    ui_display_drive_track((double)true1541_current_half_track / 2.0);
}

/* Write one GCR byte to the disk. */
void true1541_write_gcr(BYTE val)
{
    if (true1541_floppy == NULL)
	return;

    true1541_rotate_disk(0);
    GCR_write_value = val;
}

/* Return the write protect sense status. */
int true1541_write_protect_sense(void)
{
    /* Toggle the write protection bit if the disk was detached.  */
    if (detach_clk != (CLOCK)0) {
	if (true1541_clk - detach_clk < TRUE1541_DETACH_DELAY)
	    return 0;
	detach_clk = (CLOCK)0;
    }
    if ((attach_clk != (CLOCK)0) &&
	(true1541_clk - attach_clk < TRUE1541_ATTACH_DELAY))
	return 0;
    if (true1541_floppy == NULL) {
	/* No disk in drive, write protection is on. */
	return 1;
    } else if (have_new_disk) {
	/* Disk has changed, make sure the drive sees at least one change in
	   the write protect status. */
	have_new_disk = 0;
	return !true1541_floppy->ReadOnly;
    } else {
	return true1541_floppy->ReadOnly;
    }
}

static void GCR_data_writeback(void)
{
    int rc, extend, track, sector;
    BYTE buffer[260], *offset;

    track = true1541_current_half_track / 2;

    if (!GCR_dirty_track)
	return;

    if (true1541_floppy->GCR_Header != 0) {
	write_track_gcr(track);
	GCR_dirty_track = 0;
	return;
    }

    if (track > EXT_TRACKS_1541)
	return;

    if (track > true1541_floppy->NumTracks) {
	switch (extend_image_policy) {
	  case TRUE1541_EXTEND_NEVER:
	    ask_extend_disk_image = 1;
	    return;
	  case TRUE1541_EXTEND_ASK:
	    if (ask_extend_disk_image == 1) {
		extend = ui_extend_image_dialog();
		if (extend == 0) {
		    ask_extend_disk_image = 0;
		    return;
		} else {
		    true1541_extend_disk_image();
		}
	    } else {
		return;
	    }
	    break;
	  case TRUE1541_EXTEND_ACCESS:
	    ask_extend_disk_image = 1;
	    true1541_extend_disk_image();
	    break;
	}
    }

    GCR_dirty_track = 0;

    for (sector = 0; sector < sector_map[track]; sector++) {

	offset = GCR_find_sector_header(track, sector);
	if (offset == NULL)
	    fprintf(stderr,
                    "1541: Could not find header of T:%d S:%d.\n",
                    track, sector);
	else {

	    offset = GCR_find_sector_data(offset);
	    if (offset == NULL)
		fprintf(stderr,
		"1541: Could not find data sync of T:%d S:%d.\n",
		track, sector);
	    else {

		convert_GCR_to_sector(buffer, offset, GCR_track_start_ptr,
		    GCR_current_track_size);
		if (buffer[0] != 0x7)
		    fprintf(stderr,
			"1541: Could not find data block id of T:%d S:%d.\n",
			track, sector);
		else {
		    rc = floppy_write_block(true1541_floppy->ActiveFd,
                                true1541_floppy->ImageFormat,
                                buffer + 1, track, sector,
                                true1541_floppy->D64_Header);
		    if (rc < 0)
			fprintf(stderr,
			"1541: Could not update T:%d S:%d.\n", track, sector);
		}
	    }
	}
    }
}

void true1541_extend_disk_image(void)
{
    int rc, track, sector;
    BYTE buffer[256];

    true1541_floppy->NumTracks = EXT_TRACKS_1541;
    true1541_floppy->NumBlocks = EXT_BLOCKS_1541;
    memset(buffer, 0, 256);
    for (track = NUM_TRACKS_1541 + 1; track <= EXT_TRACKS_1541; track++) {
	for (sector = 0; sector < sector_map[track]; sector++) {
	    rc = floppy_write_block(true1541_floppy->ActiveFd,
                            true1541_floppy->ImageFormat,
                            buffer, track, sector,
                            true1541_floppy->D64_Header);
	if (rc < 0)
	    fprintf(stderr,
	    "1541: Could not update T:%d S:%d.\n", track, sector);
	}
    }
}

void true1541_update_zone_bits(int zone)
{
    rotation_table_ptr = rotation_table[zone];
}

void true1541_update_viad2_pcr(int pcrval)
{
    read_write_mode = pcrval & 0x20;
    byte_ready_active = (byte_ready_active & ~0x02) | (pcrval & 0x02);
}

void true1541_motor_control(int flag)
{
    byte_ready_active = (byte_ready_active & ~0x04) | (flag & 0x04);
}

BYTE true1541_read_viad2_prb(void)
{
    true1541_rotate_disk(0);
    return (true1541_sync_found() ? 0 : 0x80)
        | (true1541_write_protect_sense() ? 0 : 0x10);
}

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

/* Handle a ROM trap. */
int true1541_trap_handler(void)
{
    if (MOS6510_REGS_GET_PC(&true1541_cpu_regs) == 0xec9b) {
	/* Idle loop */
	MOS6510_REGS_SET_PC(&true1541_cpu_regs, 0xebff);
	if (idling_method == TRUE1541_IDLE_TRAP_IDLE)
	    true1541_clk = next_alarm_clk(&true1541_int_status);
    } else
	return 1;

    return 0;
}

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

/* Set the sync factor between the computer and the 1541.  */

void true1541_set_sync_factor(unsigned int factor)
{
    true1541_cpu_set_sync_factor(factor);
}

void true1541_set_pal_sync_factor(void)
{
    if (pal_cycles_per_sec != 0.0) {
        int sync_factor = (int) floor(65536.0
				      * (1000000.0 /
                                         ((double)pal_cycles_per_sec)));
        true1541_set_sync_factor(sync_factor);
    }
}

void true1541_set_ntsc_sync_factor(void)
{
    if (ntsc_cycles_per_sec != 0.0) {
        int sync_factor = (int) floor(65536.0 * (1000000.0 /
                                             ((double)ntsc_cycles_per_sec)));

        true1541_cpu_set_sync_factor(sync_factor);
    }
}

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

/* Update the status bar in the UI.  */
void true1541_update_ui_status(void)
{
    static int old_led_status = 0;
    static int old_half_track = 0;
    int my_led_status;

    if (!true1541_enabled) {
        if (old_led_status >= 0) {
            old_led_status = old_half_track = -1;
            ui_toggle_drive_status(0);
        }
        return;
    }

    /* Actually update the LED status only if the `trap idle' idling method
       is being used, as the LED status could be incorrect otherwise.  */
    if (idling_method != TRUE1541_IDLE_SKIP_CYCLES)
	my_led_status = true1541_led_status ? 1 : 0;
    else
	my_led_status = 0;

    if (my_led_status != old_led_status) {
        ui_display_drive_led(my_led_status);
	old_led_status = my_led_status;
    }

    if (true1541_current_half_track != old_half_track) {
	old_half_track = true1541_current_half_track;
	ui_display_drive_track((float) true1541_current_half_track / 2.0);
    }
}

/* This is called at every vsync.  */
void true1541_vsync_hook(void)
{
    true1541_update_ui_status();

    if (!true1541_enabled)
        return;

    if (idling_method != TRUE1541_IDLE_SKIP_CYCLES)
        true1541_cpu_execute();
}

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