mirror of
https://github.com/buserror/mii_emu.git
synced 2024-11-22 01:30:51 +00:00
New DiskII Driver. Woz here we come
Complete replacement from the previous version, see readme for details... Short story is that theres support for WOZ 1/2 read/write disks, as now emulate the low level LSS state machine. Signed-off-by: Michel Pollet <buserror@gmail.com>
This commit is contained in:
parent
59beeb2f68
commit
66937f3858
@ -6,201 +6,120 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
// Shamelesly lifted from periph/disk2.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#define _GNU_SOURCE // for asprintf
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii.h"
|
||||
#include "mii_bank.h"
|
||||
#include "mii_disk2.h"
|
||||
#include "mii_rom_disk2.h"
|
||||
#include "mii_disk_format.h"
|
||||
#include "mii_woz.h"
|
||||
#include "mii_floppy.h"
|
||||
|
||||
//#define DISK_DEBUG
|
||||
#ifdef DISK_DEBUG
|
||||
# define D2DBG(...) do { \
|
||||
if (c->pr_count == 1) \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} while(0)
|
||||
#else
|
||||
# define D2DBG(...)
|
||||
#endif
|
||||
enum {
|
||||
// bits used to address the LSS ROM using lss_mode
|
||||
WRITE_BIT = 0,
|
||||
LOAD_BIT = 1,
|
||||
QA_BIT = 2,
|
||||
RP_BIT = 3,
|
||||
};
|
||||
|
||||
typedef struct mii_card_disk2_t {
|
||||
uint32_t timer;
|
||||
bool motor_on;
|
||||
bool drive_two;
|
||||
bool write_mode;
|
||||
union {
|
||||
struct {
|
||||
DiskFormatDesc disk1;
|
||||
DiskFormatDesc disk2;
|
||||
};
|
||||
DiskFormatDesc disk[2];
|
||||
};
|
||||
uint8_t data_register; // only used for write
|
||||
bool steppers[4];
|
||||
int cog1;
|
||||
int cog2;
|
||||
// int pr_count; // debug print
|
||||
mii_dd_t drive[2];
|
||||
mii_floppy_t floppy[2];
|
||||
uint8_t selected;
|
||||
|
||||
uint8_t timer_off;
|
||||
uint8_t timer_lss;
|
||||
|
||||
uint8_t write_register;
|
||||
uint8_t head : 4; // bits are shifted in there
|
||||
uint8_t clock : 3; // LSS clock cycles, read a bit when 0
|
||||
uint8_t lss_state : 4, // Sequence state
|
||||
lss_mode : 4; // WRITE/LOAD/SHIFT/QA/RP etc
|
||||
uint8_t lss_prev_state; // for write bit
|
||||
uint8_t lss_skip;
|
||||
uint8_t data_register;
|
||||
} mii_card_disk2_t;
|
||||
|
||||
//static const size_t dsk_disksz = 143360;
|
||||
|
||||
int disk2_debug = 0;
|
||||
static void
|
||||
_mii_disk2_lss_tick(
|
||||
mii_card_disk2_t *c );
|
||||
|
||||
static DiskFormatDesc *
|
||||
active_disk_obj(
|
||||
mii_card_disk2_t *c)
|
||||
// debug, used for mish, only supports one card tho (yet)
|
||||
static mii_card_disk2_t *_mish_d2 = NULL;
|
||||
|
||||
/*
|
||||
* This timer is used to turn off the motor after a second
|
||||
*/
|
||||
static uint64_t
|
||||
_mii_floppy_motor_off_cb(
|
||||
mii_t * mii,
|
||||
void * param )
|
||||
{
|
||||
return c->drive_two ? &c->disk2 : &c->disk1;
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool
|
||||
drive_spinning(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->motor_on;
|
||||
}
|
||||
|
||||
int
|
||||
active_disk(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->drive_two ? 2 : 1;
|
||||
}
|
||||
int
|
||||
eject_disk(
|
||||
mii_card_disk2_t *c,
|
||||
int drive)
|
||||
{
|
||||
if (c->motor_on && active_disk(c) == drive) {
|
||||
return -1;
|
||||
}
|
||||
init(c); // to make sure
|
||||
|
||||
if (drive == 1) {
|
||||
c->disk1.eject(&c->disk1);
|
||||
c->disk1 = disk_format_load(NULL);
|
||||
} else if (drive == 2) {
|
||||
c->disk2.eject(&c->disk2);
|
||||
c->disk2 = disk_format_load(NULL);
|
||||
}
|
||||
mii_card_disk2_t *c = param;
|
||||
mii_floppy_t *f = &c->floppy[c->selected];
|
||||
printf("%s drive %d off\n", __func__, c->selected);
|
||||
if (c->drive[c->selected].file && f->tracks_dirty)
|
||||
mii_floppy_update_tracks(f, c->drive[c->selected].file);
|
||||
f->motor = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
insert_disk(
|
||||
/*
|
||||
* This (tries) to be called every cycle, it never happends in practice,
|
||||
* as all the instructions can use multiple cycles by CPU runs.
|
||||
* But I can retreive the overshoot cycles and call the LSS as many times
|
||||
* as needed to 'catch up'
|
||||
*/
|
||||
static uint64_t
|
||||
_mii_floppy_lss_cb(
|
||||
mii_t * mii,
|
||||
void * param )
|
||||
{
|
||||
mii_card_disk2_t *c = param;
|
||||
mii_floppy_t *f = &c->floppy[c->selected];
|
||||
if (!f->motor)
|
||||
return 0; // stop timer, motor is now off
|
||||
// delta is ALWAYS negative, or zero here
|
||||
int32_t delta = mii_timer_get(mii, c->timer_lss);
|
||||
uint64_t ret = -delta + 1;
|
||||
do {
|
||||
_mii_disk2_lss_tick(c);
|
||||
_mii_disk2_lss_tick(c);
|
||||
} while (delta++ < 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
_mii_disk2_switch_track(
|
||||
mii_t *mii,
|
||||
mii_card_disk2_t *c,
|
||||
int drive,
|
||||
const char *path)
|
||||
int delta)
|
||||
{
|
||||
int err = eject_disk(c, drive);
|
||||
if (err != 0) return err;
|
||||
mii_floppy_t *f = &c->floppy[c->selected];
|
||||
int qtrack = f->qtrack + delta;
|
||||
if (qtrack < 0) qtrack = 0;
|
||||
if (qtrack >= 35 * 4) qtrack = (35 * 4) -1;
|
||||
|
||||
// Make sure we're inserted to slot 6
|
||||
// XXX should check for error/distinguish if we're already in that slot
|
||||
(void) periph_slot_reg(6, &disk2card);
|
||||
if (qtrack == f->qtrack)
|
||||
return f->qtrack;
|
||||
|
||||
if (err) {
|
||||
return -1; // XXX should be a distinguishable err code
|
||||
}
|
||||
if (drive == 1) {
|
||||
c->disk1 = disk_format_load(path);
|
||||
} else if (drive == 2) {
|
||||
c->disk2 = disk_format_load(path);
|
||||
}
|
||||
uint8_t track_id = f->track_id[f->qtrack];
|
||||
if (track_id != MII_FLOPPY_RANDOM_TRACK_ID)
|
||||
printf("NEW TRACK D%d: %d\n", c->selected, track_id);
|
||||
uint8_t track_id_new = f->track_id[qtrack];
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// NOTE: cog "left" and "right" refers only to the number line,
|
||||
// and not the physical cog or track head movement.
|
||||
static bool
|
||||
cogleft(
|
||||
mii_card_disk2_t *c,
|
||||
int *cog)
|
||||
{
|
||||
int cl = ((*cog) + 3) % 4; // position to the "left" of cog
|
||||
return (!c->steppers[(*cog)] && c->steppers[cl]);
|
||||
}
|
||||
|
||||
// NOTE: cog "left" and "right" refers only to the number line,
|
||||
// and not the physical cog or track head movement.
|
||||
static bool
|
||||
cogright(
|
||||
mii_card_disk2_t *c,
|
||||
int *cog)
|
||||
{
|
||||
int cr = ((*cog) + 1) % 4; // position to the "right" of cog
|
||||
return (!c->steppers[(*cog)] && c->steppers[cr]);
|
||||
}
|
||||
|
||||
static int *
|
||||
active_cog(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
return c->drive_two? &c->cog2 : &c->cog1;
|
||||
}
|
||||
|
||||
static void
|
||||
adjust_track(
|
||||
mii_card_disk2_t *c)
|
||||
{
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
int *cog = active_cog(c);
|
||||
D2DBG("halftrack: ");
|
||||
if (cogleft(c, cog)) {
|
||||
*cog = ((*cog) + 3) % 4;
|
||||
if (disk->halftrack > 0) --disk->halftrack;
|
||||
D2DBG("dec to %d", disk->halftrack);
|
||||
} else if (cogright(c, cog)) {
|
||||
*cog = ((*cog) + 1) % 4;
|
||||
if (disk->halftrack < 69) ++disk->halftrack;
|
||||
D2DBG("inc to %d", disk->halftrack);
|
||||
} else {
|
||||
D2DBG("no change (%d)", disk->halftrack);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
stepper_motor(
|
||||
mii_card_disk2_t *c,
|
||||
uint8_t psw)
|
||||
{
|
||||
uint8_t phase = psw >> 1;
|
||||
bool onoff = (psw & 1) != 0;
|
||||
|
||||
D2DBG("phase %d %s ", (int)phase, onoff? "on, " : "off,");
|
||||
c->steppers[phase] = onoff;
|
||||
adjust_track(c);
|
||||
}
|
||||
|
||||
static inline uint8_t encode4x4(mii_card_disk2_t *c, uint8_t orig)
|
||||
{
|
||||
return (orig | 0xAA);
|
||||
}
|
||||
|
||||
static void turn_off_motor(mii_card_disk2_t *c)
|
||||
{
|
||||
c->motor_on = false;
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
disk->spin(disk, false);
|
||||
// event_fire_disk_active(0);
|
||||
/* adapt the bit position from one track to the others, from WOZ specs */
|
||||
uint32_t track_size = f->tracks[track_id].bit_count;
|
||||
uint32_t new_size = f->tracks[track_id_new].bit_count;
|
||||
uint32_t new_pos = f->bit_position * new_size / track_size;
|
||||
f->bit_position = new_pos;
|
||||
f->qtrack = qtrack;
|
||||
return f->qtrack;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -209,30 +128,34 @@ _mii_disk2_init(
|
||||
struct mii_slot_t *slot )
|
||||
{
|
||||
mii_card_disk2_t *c = calloc(1, sizeof(*c));
|
||||
|
||||
slot->drv_priv = c;
|
||||
c->disk1 = disk_format_load(NULL);
|
||||
c->disk2 = disk_format_load(NULL);
|
||||
|
||||
printf("%s loading in slot %d\n", __func__, slot->id + 1);
|
||||
uint16_t addr = 0xc100 + (slot->id * 0x100);
|
||||
mii_bank_write(
|
||||
&mii->bank[MII_BANK_CARD_ROM],
|
||||
addr, mii_rom_disk2, 256);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
_mii_disk2_run(
|
||||
mii_t * mii,
|
||||
struct mii_slot_t *slot)
|
||||
{
|
||||
mii_card_disk2_t *c = slot->drv_priv;
|
||||
if (c->timer && c->timer <= mii->video.frame_count) {
|
||||
printf("%s turning off motor\n", __func__);
|
||||
c->timer = 0;
|
||||
turn_off_motor(c);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
mii_dd_t *dd = &c->drive[i];
|
||||
dd->slot_id = slot->id + 1;
|
||||
dd->drive = i + 1;
|
||||
dd->slot = slot;
|
||||
asprintf((char **)&dd->name, "Disk ][ S:%d D:%d",
|
||||
dd->slot_id, dd->drive);
|
||||
mii_floppy_init(&c->floppy[i]);
|
||||
c->floppy[i].id = i;
|
||||
}
|
||||
mii_dd_register_drives(&mii->dd, c->drive, 2);
|
||||
|
||||
c->timer_off = mii_timer_register(mii,
|
||||
_mii_floppy_motor_off_cb, c, 0,
|
||||
"Disk ][ motor off");
|
||||
c->timer_lss = mii_timer_register(mii,
|
||||
_mii_floppy_lss_cb, c, 0,
|
||||
"Disk ][ LSS");
|
||||
_mish_d2 = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
@ -241,99 +164,64 @@ _mii_disk2_access(
|
||||
uint16_t addr, uint8_t byte, bool write)
|
||||
{
|
||||
mii_card_disk2_t *c = slot->drv_priv;
|
||||
mii_floppy_t * f = &c->floppy[c->selected];
|
||||
uint8_t ret = 0;
|
||||
|
||||
if (write) {
|
||||
// printf("WRITE PC:%04x %4.4x: %2.2x\n", mii->cpu.PC, addr, byte);
|
||||
c->write_register = byte;
|
||||
}
|
||||
int psw = addr & 0x0F;
|
||||
|
||||
if (c->timer)
|
||||
c->timer = mii->video.frame_count + 60;
|
||||
uint8_t last = -1;
|
||||
if (disk2_debug && last != psw + (write? 0x10 : 0))
|
||||
printf("disk sw $%02X, PC = $%04X\n", psw, mii->cpu.PC);
|
||||
last = psw + (write? 0x10 : 0);
|
||||
if (write)
|
||||
c->data_register = byte; // ANY write sets data register
|
||||
if (psw < 8) {
|
||||
stepper_motor(c, psw);
|
||||
} else switch (psw) {
|
||||
case 0x08:
|
||||
if (c->motor_on) {
|
||||
c->timer = mii->video.frame_count + 60;
|
||||
//frame_timer(60, turn_off_motor);
|
||||
int p = psw >> 1, on = psw & 1;
|
||||
switch (psw) {
|
||||
case 0x00 ... 0x07: {
|
||||
if (on) {
|
||||
if ((f->stepper + 3) % 4 == p)
|
||||
_mii_disk2_switch_track(mii, c, -2);
|
||||
else if ((f->stepper + 1) % 4 == p)
|
||||
_mii_disk2_switch_track(mii, c, 2);
|
||||
f->stepper = p;
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
case 0x08:
|
||||
case 0x09: {
|
||||
// frame_timer_cancel(turn_off_motor);
|
||||
c->timer = 0;
|
||||
c->motor_on = true;
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
disk->spin(disk, true);
|
||||
if (disk2_debug)
|
||||
printf("%s turning on motor %d\n", __func__, c->drive_two);
|
||||
// event_fire_disk_active(drive_two? 2 : 1);
|
||||
// motor on/off
|
||||
if (on) {
|
||||
mii_timer_set(mii, c->timer_off, 0);
|
||||
mii_timer_set(mii, c->timer_lss, 1);
|
||||
f->motor = 1;
|
||||
} else {
|
||||
if (!mii_timer_get(mii, c->timer_off)) {
|
||||
mii_timer_set(mii, c->timer_off, 1000000); // one second
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 0x0A:
|
||||
if (c->motor_on && c->drive_two) {
|
||||
c->disk2.spin(&c->disk2, false);
|
||||
c->disk1.spin(&c->disk1, true);
|
||||
}
|
||||
c->drive_two = false;
|
||||
if (c->motor_on) {
|
||||
// event_fire_disk_active(1);
|
||||
}
|
||||
break;
|
||||
case 0x0B:
|
||||
if (c->motor_on && !c->drive_two) {
|
||||
c->disk1.spin(&c->disk1, false);
|
||||
c->disk2.spin(&c->disk2, true);
|
||||
}
|
||||
c->drive_two = true;
|
||||
if (c->motor_on) {
|
||||
// event_fire_disk_active(2);
|
||||
}
|
||||
break;
|
||||
case 0x0C: {
|
||||
DiskFormatDesc *disk = active_disk_obj(c);
|
||||
if (!c->motor_on) {
|
||||
// do nothing
|
||||
} else if (c->write_mode) {
|
||||
// XXX ignores timing
|
||||
disk->write_byte(disk, c->data_register);
|
||||
c->data_register = 0; // "shifted out".
|
||||
} else {
|
||||
// XXX any even-numbered switch can be used
|
||||
// to read a byte. But for now we do so only
|
||||
// through the sanctioned switch for that purpose.
|
||||
ret = c->data_register = disk->read_byte(disk);
|
||||
// printf("read byte %02X\n", ret);
|
||||
case 0x0B: {
|
||||
if (on != c->selected) {
|
||||
c->selected = on;
|
||||
printf("SELECTED DRIVE: %d\n", c->selected);
|
||||
c->floppy[on].motor = f->motor;
|
||||
f->motor = 0;
|
||||
}
|
||||
} break;
|
||||
case 0x0C:
|
||||
case 0x0D:
|
||||
#if 0
|
||||
if (!motor_on || drive_two) {
|
||||
// do nothing
|
||||
} else if (write_mode) {
|
||||
data_register = (val == -1? 0: val);
|
||||
} else {
|
||||
// XXX should return whether disk is write-protected...
|
||||
}
|
||||
#endif
|
||||
c->lss_mode = (c->lss_mode & ~(1 << LOAD_BIT)) | (!!on << LOAD_BIT);
|
||||
break;
|
||||
case 0x0E:
|
||||
c->write_mode = false;
|
||||
if (disk2_debug)
|
||||
printf("%s write mode off\n", __func__);
|
||||
break;
|
||||
case 0x0F:
|
||||
c->write_mode = true;
|
||||
if (disk2_debug)
|
||||
printf("%s write mode on\n", __func__);
|
||||
c->lss_mode = (c->lss_mode & ~(1 << WRITE_BIT)) | (!!on << WRITE_BIT);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
D2DBG("\n");
|
||||
/*
|
||||
* Here we run one LSS cycle ahead of 'schedule', just because it allows
|
||||
* the write protect bit to be loaded if needed, it *has* to run before
|
||||
* we return this value, so we marked a skip and run it here.
|
||||
*/
|
||||
_mii_disk2_lss_tick(c);
|
||||
c->lss_skip++;
|
||||
ret = on ? byte : c->data_register;
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -353,14 +241,15 @@ _mii_disk2_command(
|
||||
break;
|
||||
case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + 2 - 1:
|
||||
int drive = cmd - MII_SLOT_DRIVE_LOAD;
|
||||
if (c->disk[drive].privdat) {
|
||||
c->disk[drive].eject(&c->disk[drive]);
|
||||
}
|
||||
const char *filename = param;
|
||||
printf("%s: drive %d loading %s\n", __func__, drive,
|
||||
filename);
|
||||
c->disk[drive] = disk_format_load(
|
||||
filename && *filename ? filename : NULL);
|
||||
mii_dd_file_t *file = NULL;
|
||||
if (filename && *filename) {
|
||||
file = mii_dd_file_load(&mii->dd, filename, O_RDWR);
|
||||
if (!file)
|
||||
return -1;
|
||||
}
|
||||
mii_dd_drive_load(&c->drive[drive], file);
|
||||
mii_floppy_load(&c->floppy[drive], file);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
@ -371,7 +260,231 @@ static mii_slot_drv_t _driver = {
|
||||
.desc = "Apple Disk ][",
|
||||
.init = _mii_disk2_init,
|
||||
.access = _mii_disk2_access,
|
||||
.run = _mii_disk2_run,
|
||||
.command = _mii_disk2_command,
|
||||
};
|
||||
MI_DRIVER_REGISTER(_driver);
|
||||
|
||||
|
||||
/* Sather Infamous Table, pretty much verbatim
|
||||
WRITE
|
||||
-----------READ PULSE--------- -—------NO READ PULSE--------
|
||||
----SHIFT----- -----LOAD----- ----SHIFT----- -----LOAD-----
|
||||
SEQ --QA'- --QA-- --QA'- --QA-- --QA'- --QA-- --QA'- --QA--
|
||||
0- 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP 18-NOP
|
||||
1- 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP 28-NOP
|
||||
2- 39-SL0 39-SL0 3B-LD 3B-LD 39-SL0 39-SL0 3B-LD 3B-LD
|
||||
3- 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP 48-NOP
|
||||
4- 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP 58-NOP
|
||||
5- 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP 68-NOP
|
||||
6- 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP 78-NOP
|
||||
7- 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP
|
||||
8- 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP 98-NOP
|
||||
9- A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP A8-NOP
|
||||
A- B9-SL0 B9-SL0 BB-LD BB-LD B9-SL0 B9-SL0 BB-LD BB-LD
|
||||
B- C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP C8-NOP
|
||||
C- D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP D8-NOP
|
||||
D- E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP E8-NOP
|
||||
E- F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP F8-NOP
|
||||
F- 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP 88-NOP 08-NOP
|
||||
READ
|
||||
------------SHIFT------------ -------------LOAD------------
|
||||
-----QA'------ ------QA------ -----QA'------ -----QA------
|
||||
--RP-- -NORP- --RP-- -NORP- --RP-- -NORP- --RP-- -NORP-
|
||||
0- 18-NOP 18-NOP 18-NOP 18-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
1- 2D-SL1 2D-SL1 38-NOP 38-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
2- D8-NOP 38-NOP 08-NOP 28-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
3- D8-NOP 48-NOP 48-NOP 48-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
4- D8-NOP 58-NOP D8-NOP 58-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
5- D8-NOP 68-NOP D8-NOP 68-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
6- D8-NOP 78-NOP D8-NOP 78-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
7- D8-NOP 88-NOP D8-NOP 88-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
8- D8-NOP 98-NOP D8-NOP 98-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
9- D8-NOP 29-SL0 D8-NOP A8-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
A- CD-SL1 BD-SL1 D8-NOP B8-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
B- D9-SL0 59-SL0 D8-NOP C8-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
C- D9-SL0 D9-SL0 D8-NOP A0-CLR 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
D- D8-NOP 08-NOP E8-NOP E8-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
E- FD-SL1 FD-SL1 F8-NOP F8-NOP 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
F- DD-SL1 4D-SL1 E0-CLR E0-CLR 0A-SR 0A-SR 0A-SR 0A-SR
|
||||
*/
|
||||
enum {
|
||||
WRITE = (1 << WRITE_BIT),
|
||||
LOAD = (1 << LOAD_BIT),
|
||||
QA1 = (1 << QA_BIT),
|
||||
RP1 = (1 << RP_BIT),
|
||||
/* This keeps the transposed table more readable, as it looks like the book */
|
||||
READ = 0, SHIFT = 0, QA0 = 0, RP0 = 0,
|
||||
};
|
||||
// And this is the same Sather table (Figure 9.11, page 9-20) but transposed
|
||||
static const uint8_t lss_rom16s[16][16] = {
|
||||
[WRITE|RP0|SHIFT|QA0]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x88 },
|
||||
[WRITE|RP0|SHIFT|QA1]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x08 },
|
||||
[WRITE|RP0|LOAD|QA0]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x88 },
|
||||
[WRITE|RP0|LOAD|QA1]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x08 },
|
||||
[WRITE|RP1|SHIFT|QA0]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x88 },
|
||||
[WRITE|RP1|SHIFT|QA1]={ 0x18,0x28,0x39,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB9,0xC8,0xD8,0xE8,0xF8,0x08 },
|
||||
[WRITE|RP1|LOAD|QA0]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x08,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x88 },
|
||||
[WRITE|RP1|LOAD|QA1]={ 0x18,0x28,0x3B,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xBB,0xC8,0xD8,0xE8,0xF8,0x08 },
|
||||
[READ|SHIFT|QA0|RP1]={ 0x18,0x2D,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xCD,0xD9,0xD9,0xD8,0xFD,0xDD },
|
||||
[READ|SHIFT|QA0|RP0]={ 0x18,0x2D,0x38,0x48,0x58,0x68,0x78,0x88,0x98,0x29,0xBD,0x59,0xD9,0x08,0xFD,0x4D },
|
||||
[READ|SHIFT|QA1|RP1]={ 0x18,0x38,0x08,0x48,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xD8,0xE8,0xF8,0xE0 },
|
||||
[READ|SHIFT|QA1|RP0]={ 0x18,0x38,0x28,0x48,0x58,0x68,0x78,0x88,0x98,0xA8,0xB8,0xC8,0xA0,0xE8,0xF8,0xE0 },
|
||||
[READ|LOAD|QA0|RP1]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
|
||||
[READ|LOAD|QA0|RP0]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
|
||||
[READ|LOAD|QA1|RP1]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
|
||||
[READ|LOAD|QA1|RP0]={ 0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A,0x0A },
|
||||
};
|
||||
|
||||
static void
|
||||
_mii_disk2_lss_tick(
|
||||
mii_card_disk2_t *c )
|
||||
{
|
||||
if (c->lss_skip) {
|
||||
c->lss_skip--;
|
||||
return;
|
||||
}
|
||||
mii_floppy_t *f = &c->floppy[c->selected];
|
||||
|
||||
c->lss_mode = (c->lss_mode & ~(1 << QA_BIT)) |
|
||||
(!!(c->data_register & 0x80) << QA_BIT);
|
||||
if (c->clock++ == 0) { // clock is clipped to 3 bits
|
||||
uint8_t track_id = f->track_id[f->qtrack];
|
||||
|
||||
uint32_t byte_index = f->bit_position >> 3;
|
||||
uint8_t bit_index = 7 - (f->bit_position & 7);
|
||||
if (!(c->lss_mode & (1 << WRITE_BIT))) {
|
||||
uint8_t bit = f->tracks[track_id].data[byte_index];
|
||||
bit = (bit >> bit_index) & 1;
|
||||
c->head = (c->head << 1) | bit;
|
||||
// see WOZ spec for how we do this here
|
||||
if ((c->head & 0xf) == 0) {
|
||||
bit = f->tracks[MII_FLOPPY_RANDOM_TRACK_ID].data[byte_index];
|
||||
bit = (bit >> bit_index) & 1;
|
||||
} else {
|
||||
bit = (c->head >> 1) & 1;
|
||||
}
|
||||
c->lss_mode = (c->lss_mode & ~(1 << RP_BIT)) | (bit << RP_BIT);
|
||||
}
|
||||
if ((c->lss_mode & (1 << WRITE_BIT))) {
|
||||
uint8_t msb = c->data_register >> 7;
|
||||
|
||||
if (!f->tracks[track_id].dirty) {
|
||||
printf("DIRTY TRACK %2d \n", track_id);
|
||||
f->tracks[track_id].dirty = 1;
|
||||
f->tracks_dirty = 1;
|
||||
}
|
||||
f->tracks[track_id].data[byte_index] &= ~(1 << bit_index);
|
||||
f->tracks[track_id].data[byte_index] |= (msb << bit_index);
|
||||
}
|
||||
f->bit_position = (f->bit_position + 1) % f->tracks[track_id].bit_count;
|
||||
}
|
||||
const uint8_t *rom = lss_rom16s[c->lss_mode];
|
||||
uint8_t cmd = rom[c->lss_state];
|
||||
uint8_t next = cmd >> 4;
|
||||
uint8_t action = cmd & 0xF;
|
||||
|
||||
if (action & 0b1000) { // Table 9.3 in Sather's book
|
||||
switch (action & 0b0011) {
|
||||
case 1: // SL0/1
|
||||
c->data_register <<= 1;
|
||||
c->data_register |= !!(action & 0b0100);
|
||||
break;
|
||||
case 2: // SR
|
||||
c->data_register = (c->data_register >> 1) |
|
||||
(!!f->write_protected << 7);
|
||||
break;
|
||||
case 3: // LD
|
||||
c->data_register = c->write_register;
|
||||
break;
|
||||
}
|
||||
} else { // CLR
|
||||
c->data_register = 0;
|
||||
}
|
||||
c->lss_state = next;
|
||||
// read pulse only last one cycle..
|
||||
c->lss_mode &= ~(1 << RP_BIT);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
_mii_mish_d2(
|
||||
void * param,
|
||||
int argc,
|
||||
const char * argv[])
|
||||
{
|
||||
// mii_t * mii = param;
|
||||
if (!_mish_d2) {
|
||||
printf("No Disk ][ card installed\n");
|
||||
return;
|
||||
}
|
||||
static int sel = 0;
|
||||
if (!argv[1] || !strcmp(argv[1], "list")) {
|
||||
mii_card_disk2_t *c = _mish_d2;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
mii_floppy_t *f = &c->floppy[i];
|
||||
printf("Drive %d %s\n", f->id, f->write_protected ? "WP" : "RW");
|
||||
printf("\tMotor: %3s qtrack:%d Bit %6d\n",
|
||||
f->motor ? "ON" : "OFF", f->qtrack, f->bit_position);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "sel")) {
|
||||
if (argv[2]) {
|
||||
sel = atoi(argv[2]);
|
||||
}
|
||||
printf("Selected drive: %d\n", sel);
|
||||
return;
|
||||
}
|
||||
if (!strcmp(argv[1], "wp")) {
|
||||
if (argv[2]) {
|
||||
int wp = atoi(argv[2]);
|
||||
mii_card_disk2_t *c = _mish_d2;
|
||||
mii_floppy_t *f = &c->floppy[sel];
|
||||
f->write_protected = wp;
|
||||
}
|
||||
printf("Drive %d Write protected: %d\n", sel,
|
||||
_mish_d2->floppy[sel].write_protected);
|
||||
return;
|
||||
}
|
||||
// dump a track, specify track number and number of bytes
|
||||
if (!strcmp(argv[1], "track")) {
|
||||
if (argv[2]) {
|
||||
int track = atoi(argv[2]);
|
||||
int count = 256;
|
||||
if (argv[3])
|
||||
count = atoi(argv[3]);
|
||||
mii_card_disk2_t *c = _mish_d2;
|
||||
mii_floppy_t *f = &c->floppy[sel];
|
||||
uint8_t *data = f->tracks[track].data;
|
||||
|
||||
for (int i = 0; i < count; i += 8) {
|
||||
uint8_t *line = data + i;
|
||||
#if 0
|
||||
for (int bi = 0; bi < 8; bi++) {
|
||||
uint8_t b = line[bi];
|
||||
for (int bbi = 0; bbi < 8; bbi++) {
|
||||
printf("%c", (b & 0x80) ? '1' : '0');
|
||||
b <<= 1;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
#endif
|
||||
for (int bi = 0; bi < 8; bi++)
|
||||
printf("%8x", line[bi]);
|
||||
printf("\n");
|
||||
}
|
||||
} else {
|
||||
printf("track <track 0-36> [count]\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#include "mish.h"
|
||||
|
||||
MISH_CMD_NAMES(d2, "d2");
|
||||
MISH_CMD_HELP(d2,
|
||||
"d2: disk ][ internals",
|
||||
" <default>: dump status"
|
||||
);
|
||||
MII_MISH(d2, _mii_mish_d2);
|
||||
|
472
src/format/dsk.c
472
src/format/dsk.c
@ -1,472 +0,0 @@
|
||||
// format/dsk.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/mman.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
#define NIBBLE_SECTOR_SIZE 416
|
||||
#define NIBBLE_TRACK_SIZE 6656
|
||||
#define DSK_SECTOR_SIZE 256
|
||||
#define MAX_SECTORS 16
|
||||
#define VOLUME_NUMBER 254
|
||||
#define DSK_TRACK_SIZE (DSK_SECTOR_SIZE * MAX_SECTORS)
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
struct dskprivdat {
|
||||
const char *path;
|
||||
byte *realbuf;
|
||||
byte *buf;
|
||||
const byte *secmap;
|
||||
int bytenum;
|
||||
uint64_t dirty_tracks;
|
||||
};
|
||||
static const struct dskprivdat datinit = { 0 };
|
||||
|
||||
static const size_t nib_disksz = 232960;
|
||||
static const size_t dsk_disksz = 143360;
|
||||
|
||||
// DOS 3.3 Physical sector order (index is physical sector,
|
||||
// value is DOS sector)
|
||||
const byte DO[] = {
|
||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
|
||||
0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
||||
};
|
||||
|
||||
// ProDOS Physical sector order (index is physical sector,
|
||||
// value is ProDOS sector).
|
||||
const byte PO[] = {
|
||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb,
|
||||
0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf
|
||||
};
|
||||
|
||||
const byte TRANS62[] = {
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
|
||||
};
|
||||
|
||||
static const int DETRANS62[] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, 0x00, 0x01,
|
||||
-1, -1, 0x02, 0x03, -1, 0x04, 0x05, 0x06,
|
||||
-1, -1, -1, -1, -1, -1, 0x07, 0x08,
|
||||
-1, -1, -1, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
||||
-1, -1, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
|
||||
-1, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, 0x1B, -1, 0x1C, 0x1D, 0x1E,
|
||||
-1, -1, -1, 0x1F, -1, -1, 0x20, 0x21,
|
||||
-1, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
-1, -1, -1, -1, -1, 0x29, 0x2A, 0x2B,
|
||||
-1, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32,
|
||||
-1, -1, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
-1, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
|
||||
};
|
||||
|
||||
static void realign_tracks(DiskFormatDesc *desc)
|
||||
{
|
||||
/*
|
||||
When we unpack a .dsk into nibblized form, it's
|
||||
automatically aligned neatly within our track-storage
|
||||
boundaries within the disk buffer, because that's how
|
||||
we laid 'em out.
|
||||
|
||||
When a sector is written or updated, it will
|
||||
tend to stay aligned, because the program had to
|
||||
find the existing sector to write to, to keep things
|
||||
in order.
|
||||
|
||||
However, in the event of reformatting, there are no
|
||||
guarantees about the track being aligned conveniently.
|
||||
The formatting program can start at any old position
|
||||
and just start writing, because no existing data is going
|
||||
to be preserved.
|
||||
|
||||
To deal with this, we could either:
|
||||
1) Be prepared to loop back around our track's buffer,
|
||||
mid-sector
|
||||
2) Re-align the track at our convenience, to start
|
||||
somewhere that we know for sure can't be the middle of
|
||||
sector data.
|
||||
|
||||
I've opted for option (2). It would be a bad option if
|
||||
we're reading and writing a .nib file, because we'd be
|
||||
unnecessarily altering the file's structure (in cases
|
||||
where it had NOT been reformatted), but it's perfectly
|
||||
fine when we'll be discarding the nibblized format anyway.
|
||||
|
||||
We handle this by seeking forward to the first sector-field
|
||||
start boundary (D5 AA 96) that we can find, and make that
|
||||
the new "start" of our track. It doesn't matter if it's a
|
||||
false-start that doesn't start a real sector-field, because one
|
||||
thing we know for *sure* is that first D5 can't be found
|
||||
in the middle of a legitimate header or data field.
|
||||
*/
|
||||
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
byte *buf = dat->buf;
|
||||
byte *secbuf = malloc(NIBBLE_TRACK_SIZE);
|
||||
for (int t=0; t != NUM_TRACKS; ++t) {
|
||||
byte *tstart = buf + (t * NIBBLE_TRACK_SIZE);
|
||||
byte *tend = buf + ((t + 1) * NIBBLE_TRACK_SIZE);
|
||||
byte *talign;
|
||||
for (talign = tstart; talign <= (tend - 3); ++talign) {
|
||||
if (talign[0] == 0xD5 && talign[1] == 0xAA && talign[2] == 0x96) {
|
||||
if (talign == tstart) {
|
||||
// Nothing to do, already aligned.
|
||||
} else {
|
||||
size_t rollsz = talign - tstart;
|
||||
memcpy(secbuf, tstart, rollsz);
|
||||
memmove(tstart, talign, tend-talign);
|
||||
memcpy(tend - rollsz, secbuf, rollsz);
|
||||
}
|
||||
break; // handle next track
|
||||
}
|
||||
}
|
||||
}
|
||||
free(secbuf);
|
||||
}
|
||||
|
||||
static void implodeDo(DiskFormatDesc *desc)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
|
||||
realign_tracks(desc);
|
||||
|
||||
const byte *rd = dat->buf; // nibble buf
|
||||
const byte *end = dat->buf + nib_disksz;
|
||||
|
||||
bool warned = false;
|
||||
|
||||
for (;;) {
|
||||
// Scan forward for a sector header
|
||||
const int sector_hdr_sz = 11; // counts prologue, but not epilogue
|
||||
for (;;) {
|
||||
// This is the only place we're "allowed" to end processing.
|
||||
if (rd >= (end - sector_hdr_sz)) goto done;
|
||||
if (rd[0] == 0xD5 && rd[1] == 0xAA && rd[2] == 0x96) break;
|
||||
++rd;
|
||||
}
|
||||
|
||||
header:
|
||||
rd += 3;
|
||||
int v = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
int t = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
int s = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
int checkSum = ((rd[0] << 1) | 0x1) & rd[1];
|
||||
rd += 2;
|
||||
|
||||
if (checkSum != (v ^ t ^ s)) {
|
||||
WARN("Sector header checksum failed, t=%d s=%d"
|
||||
" at nibblized byte %zu.\n", t, s, rd - dat->buf);
|
||||
WARN("Probable disk corruption for %s\n", dat->path);
|
||||
}
|
||||
|
||||
byte truet = (rd - dat->buf)/NIBBLE_TRACK_SIZE;
|
||||
if (t != truet) {
|
||||
WARN("Sector header lying about track number"
|
||||
" at nibblized pos %zu\n", (size_t)(rd - dat->buf));
|
||||
WARN(" (says %d but we're on track %d). Skipping sector.\n",
|
||||
(int)t, (int)truet);
|
||||
continue;
|
||||
}
|
||||
if (s >= MAX_SECTORS) {
|
||||
WARN("Sector header sec# too high (%d; max is %d).\n",
|
||||
(int)s, (int)MAX_SECTORS);
|
||||
WARN(" Skipping sector.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// const int data_field_sz = 0x15A; //counts prologue, not epilogue
|
||||
for (;;) {
|
||||
if (rd >= (end - 0x15A)) goto bail;
|
||||
if (rd[0] == 0xD5 && rd[1] == 0xAA) {
|
||||
if (rd[2] == 0x96) goto header;
|
||||
if (rd[2] == 0xAD) break;
|
||||
}
|
||||
++rd;
|
||||
}
|
||||
|
||||
rd += 3;
|
||||
|
||||
// Translate sector data field
|
||||
{
|
||||
int s_ = dat->secmap[s];
|
||||
byte *data = dat->realbuf + (t * DSK_TRACK_SIZE)
|
||||
+ (s_ * DSK_SECTOR_SIZE);
|
||||
byte data2[0x56];
|
||||
byte last = 0;
|
||||
int val;
|
||||
|
||||
if (0) {
|
||||
WARN("Translating track %d, phys sec %d (image sec %d)\n",
|
||||
t, s, s_);
|
||||
WARN(" starting at dsk pos %zu\n", (size_t)(data - dat->realbuf));
|
||||
}
|
||||
|
||||
for (int j = 0x55; j >= 0; --j) {
|
||||
val = DETRANS62[*rd - 0x80];
|
||||
if (val == -1 && !warned) {
|
||||
warned = true;
|
||||
WARN("Untranslatable nibble at (nibblized) pos %zu,"
|
||||
" disk %s.\n", (size_t)(rd - dat->buf), dat->path);
|
||||
if (rd <= end - 4) {
|
||||
WARN("%02X %02X %02X %02X [%02X] %02X %02X %02X\n",
|
||||
rd[-4], rd[-3], rd[-2], rd[-1],
|
||||
rd[0], rd[1], rd[2], rd[3]);
|
||||
}
|
||||
WARN("CORRUPT DISK SAVE TO %s\n", dat->path);
|
||||
}
|
||||
val ^= last;
|
||||
data2[j] = val;
|
||||
last = val;
|
||||
++rd;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 0x100; ++j) {
|
||||
val = DETRANS62[*rd - 0x80];
|
||||
if (val == -1 && !warned) {
|
||||
warned = true;
|
||||
WARN("Untranslatable nibble at t=%d, phys s=%d,"
|
||||
" disk %s.\n", t, s, dat->path);
|
||||
WARN("CORRUPT DISK SAVE TO %s\n", dat->path);
|
||||
}
|
||||
val ^= last;
|
||||
data[j] = val;
|
||||
last = val;
|
||||
++rd;
|
||||
}
|
||||
|
||||
int checkSum = DETRANS62[*rd++ - 0x80];
|
||||
if (checkSum != -1) checkSum ^= last;
|
||||
if (checkSum != 0) {
|
||||
WARN("Bad sector data checksum at t=%d, phys s=%d,"
|
||||
" disk %s.\n", t, s, dat->path);
|
||||
}
|
||||
|
||||
for (int k = 0, j = 0x55; k < 0x100; ++k) {
|
||||
data[k] <<= 1;
|
||||
if ((data2[j] & 0x1) != 0) {
|
||||
data[k] |= 0x01;
|
||||
}
|
||||
data2[j] >>= 1;
|
||||
|
||||
data[k] <<= 1;
|
||||
if ((data2[j] & 0x01) != 0) {
|
||||
data[k] |= 0x01;
|
||||
}
|
||||
data2[j] >>= 0x01;
|
||||
|
||||
if (--j < 0) j = 0x55;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bail:
|
||||
WARN("Error translating to dsk: ended mid-sector!\n");
|
||||
WARN("Probable disk corruption for %s\n", dat->path);
|
||||
done:
|
||||
return;
|
||||
}
|
||||
|
||||
static void spin(DiskFormatDesc *desc, bool b)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
if (!b && dat->dirty_tracks != 0) {
|
||||
implodeDo(desc);
|
||||
// For now, sync the entire disk
|
||||
errno = 0;
|
||||
int err = msync(dat->realbuf, dsk_disksz, MS_SYNC);
|
||||
if (err < 0) {
|
||||
DIE(1,"Couldn't sync to disk file %s: %s\n",
|
||||
dat->path, strerror(errno));
|
||||
}
|
||||
dat->dirty_tracks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static byte read_byte(DiskFormatDesc *desc)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
byte val = dat->buf[pos];
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
return val;
|
||||
}
|
||||
|
||||
static void write_byte(DiskFormatDesc *desc, byte val)
|
||||
{
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
if ((val & 0x80) == 0) {
|
||||
// D2DBG("dodged write $%02X", val);
|
||||
return; // must have high bit
|
||||
}
|
||||
dat->dirty_tracks |= 1 << (desc->halftrack/2);
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
|
||||
//D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos);
|
||||
|
||||
dat->buf[pos] = val;
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
}
|
||||
|
||||
static void eject(DiskFormatDesc *desc)
|
||||
{
|
||||
// free dat->path and dat, and unmap disk image
|
||||
struct dskprivdat *dat = desc->privdat;
|
||||
(void) munmap(dat->buf, dsk_disksz);
|
||||
free((void*)dat->path);
|
||||
free(dat);
|
||||
}
|
||||
|
||||
// This function is derived from Scullin Steel Co.'s apple2js code
|
||||
// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140
|
||||
static void explodeSector(byte vol, byte track, byte sector,
|
||||
byte **nibSec, const byte *data)
|
||||
{
|
||||
byte *wr = *nibSec;
|
||||
unsigned int gap;
|
||||
|
||||
// Gap 1/3 (40/0x28 bytes)
|
||||
|
||||
if (sector == 0) // Gap 1
|
||||
gap = 0x80;
|
||||
else { // Gap 3
|
||||
gap = track == 0? 0x28 : 0x26;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i != gap; ++i) {
|
||||
*wr++ = 0xFF;
|
||||
}
|
||||
|
||||
// Address Field
|
||||
const byte checksum = vol ^ track ^ sector;
|
||||
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96
|
||||
*wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA;
|
||||
*wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA;
|
||||
*wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA;
|
||||
*wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA;
|
||||
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
|
||||
|
||||
// Gap 2 (5 bytes)
|
||||
for (int i = 0; i != 5; ++i) {
|
||||
*wr++ = 0xFF;
|
||||
}
|
||||
|
||||
// Data Field
|
||||
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD
|
||||
|
||||
byte *nibbles = wr;
|
||||
const unsigned ptr2 = 0;
|
||||
const unsigned ptr6 = 0x56;
|
||||
|
||||
for (int i = 0; i != 0x156; ++i) {
|
||||
nibbles[i] = 0;
|
||||
}
|
||||
|
||||
int i2 = 0x55;
|
||||
for (int i6 = 0x101; i6 >= 0; --i6) {
|
||||
byte val6 = data[i6 % 0x100];
|
||||
byte val2 = nibbles[ptr2 + i2];
|
||||
|
||||
val2 = (val2 << 1) | (val6 & 1);
|
||||
val6 >>= 1;
|
||||
val2 = (val2 << 1) | (val6 & 1);
|
||||
val6 >>= 1;
|
||||
|
||||
nibbles[ptr6 + i6] = val6;
|
||||
nibbles[ptr2 + i2] = val2;
|
||||
|
||||
if (--i2 < 0)
|
||||
i2 = 0x55;
|
||||
}
|
||||
|
||||
byte last = 0;
|
||||
for (int i = 0; i != 0x156; ++i) {
|
||||
const byte val = nibbles[i];
|
||||
nibbles[i] = TRANS62[last ^ val];
|
||||
last = val;
|
||||
}
|
||||
wr += 0x156; // advance write-pointer
|
||||
*wr++ = TRANS62[last];
|
||||
|
||||
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
|
||||
|
||||
// Gap 3
|
||||
*wr++ = 0xFF;
|
||||
|
||||
*nibSec = wr;
|
||||
}
|
||||
|
||||
static void explodeDsk(byte *nibbleBuf, byte *dskBuf, const byte *secmap)
|
||||
{
|
||||
for (int t = 0; t < NUM_TRACKS; ++t) {
|
||||
byte *writePtr = nibbleBuf;
|
||||
for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) {
|
||||
const byte dos_sector = secmap[phys_sector];
|
||||
const size_t off = ((MAX_SECTORS * t + dos_sector)
|
||||
* DSK_SECTOR_SIZE);
|
||||
explodeSector(VOLUME_NUMBER, t, phys_sector,
|
||||
&writePtr, &dskBuf[off]);
|
||||
}
|
||||
assert(writePtr - nibbleBuf <= NIBBLE_TRACK_SIZE);
|
||||
for (; writePtr != (nibbleBuf + NIBBLE_TRACK_SIZE); ++writePtr) {
|
||||
*writePtr = 0xFF;
|
||||
}
|
||||
nibbleBuf += NIBBLE_TRACK_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
DiskFormatDesc dsk_insert(const char *path, byte *buf, size_t sz)
|
||||
{
|
||||
if (sz != dsk_disksz) {
|
||||
DIE(0,"Wrong disk image size for %s:\n", path);
|
||||
DIE(1," Expected %zu, got %zu.\n", dsk_disksz, sz);
|
||||
}
|
||||
|
||||
struct dskprivdat *dat = malloc(sizeof *dat);
|
||||
*dat = datinit;
|
||||
dat->realbuf = buf;
|
||||
dat->path = strdup(path);
|
||||
dat->buf = calloc(1, nib_disksz);
|
||||
|
||||
const char *ext = rindex(path, '.');
|
||||
ext = ext ? ext+1 : "";
|
||||
if (!strcasecmp(ext, "PO")) {
|
||||
INFO("Opening %s as PO.\n", dat->path);
|
||||
dat->secmap = PO;
|
||||
} else {
|
||||
INFO("Opening %s as DO.\n", dat->path);
|
||||
dat->secmap = DO;
|
||||
}
|
||||
explodeDsk(dat->buf, dat->realbuf, dat->secmap);
|
||||
|
||||
return (DiskFormatDesc){
|
||||
.privdat = dat,
|
||||
.spin = spin,
|
||||
.read_byte = read_byte,
|
||||
.write_byte = write_byte,
|
||||
.eject = eject,
|
||||
};
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
// format/empty.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
void spin(DiskFormatDesc *d, bool b)
|
||||
{
|
||||
}
|
||||
|
||||
byte read_byte(DiskFormatDesc *d)
|
||||
{
|
||||
// A real disk can never send a low byte.
|
||||
// But an empty disk must never send a legitimate byte.
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void write_byte(DiskFormatDesc *d, byte b)
|
||||
{
|
||||
}
|
||||
|
||||
void eject(DiskFormatDesc *d)
|
||||
{
|
||||
}
|
||||
|
||||
DiskFormatDesc empty_disk_desc = {
|
||||
.spin = spin,
|
||||
.read_byte = read_byte,
|
||||
.write_byte = write_byte,
|
||||
.eject = eject,
|
||||
};
|
@ -141,10 +141,21 @@ mii_dd_file_load(
|
||||
{
|
||||
if (!flags)
|
||||
flags = O_RDONLY;
|
||||
if (flags & O_WRONLY)
|
||||
flags |= O_RDWR;
|
||||
int err;
|
||||
int fd = open(pathname, flags);
|
||||
if (fd < 0) {
|
||||
perror(pathname);
|
||||
printf("%s: %s: Retrying Read only\n", __func__, pathname);
|
||||
if (flags & (O_RDWR | O_WRONLY)) {
|
||||
flags &= ~(O_RDWR | O_WRONLY);
|
||||
flags |= O_RDONLY;
|
||||
fd = open(pathname, flags, 0666);
|
||||
}
|
||||
}
|
||||
if (fd < 0) {
|
||||
printf("%s: %s: Failed to open: %s\n",
|
||||
__func__, pathname, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
struct stat st;
|
||||
@ -174,10 +185,22 @@ mii_dd_file_load(
|
||||
res->dd = NULL;
|
||||
res->next = dd->file;
|
||||
dd->file = res;
|
||||
res->read_only = (flags & O_RDWR) == 0;
|
||||
char *suffix = strrchr(pathname, '.');
|
||||
if (suffix && !strcasecmp(suffix, ".2mg")) {
|
||||
res->format = MII_DD_FILE_2MG;
|
||||
res->map += 64;
|
||||
if (suffix) {
|
||||
if (!strcasecmp(suffix, ".dsk")) {
|
||||
res->format = MII_DD_FILE_DSK;
|
||||
} else if (!strcasecmp(suffix, ".po") || !strcasecmp(suffix, ".hdv")) {
|
||||
res->format = MII_DD_FILE_PO;
|
||||
} else if (!strcasecmp(suffix, ".nib")) {
|
||||
res->format = MII_DD_FILE_NIB;
|
||||
} else if (!strcasecmp(suffix, ".woz")) {
|
||||
res->format = MII_DD_FILE_WOZ;
|
||||
} else if (!strcasecmp(suffix, ".2mg")) {
|
||||
res->format = MII_DD_FILE_2MG;
|
||||
res->map += 64;
|
||||
}
|
||||
printf("%s: suffix %s, format %d\n", __func__, suffix, res->format);
|
||||
}
|
||||
return res;
|
||||
bail:
|
||||
|
@ -16,7 +16,11 @@ enum {
|
||||
// MII_DD_FILE_OVERLAY = 1,
|
||||
MII_DD_FILE_RAM,
|
||||
MII_DD_FILE_ROM,
|
||||
MII_DD_FILE_PO,
|
||||
MII_DD_FILE_2MG = 5,
|
||||
MII_DD_FILE_DSK,
|
||||
MII_DD_FILE_NIB,
|
||||
MII_DD_FILE_WOZ
|
||||
};
|
||||
|
||||
// a disk image file (or chunck of ram, if ramdisk is used)
|
||||
@ -24,6 +28,7 @@ typedef struct mii_dd_file_t {
|
||||
struct mii_dd_file_t *next;
|
||||
char * pathname;
|
||||
uint8_t format;
|
||||
uint8_t read_only;
|
||||
uint8_t * start; // start of the file
|
||||
uint8_t * map; // start of the blocks
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
/*
|
||||
* mii_disk_format.c
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
static const size_t nib_disksz = 232960;
|
||||
static const size_t dsk_disksz = 143360;
|
||||
|
||||
extern DiskFormatDesc nib_insert(const char*, byte *, size_t);
|
||||
extern DiskFormatDesc dsk_insert(const char *, byte *, size_t);
|
||||
extern DiskFormatDesc empty_disk_desc;
|
||||
|
||||
int mmapfile(const char *fname, byte **buf, size_t *sz, int flags);
|
||||
|
||||
DiskFormatDesc disk_format_load(const char *path)
|
||||
{
|
||||
if (path == NULL) {
|
||||
return empty_disk_desc;
|
||||
}
|
||||
byte *buf;
|
||||
size_t sz;
|
||||
int err = mmapfile(path, &buf, &sz, O_RDWR);
|
||||
if (buf == NULL) {
|
||||
DIE(1,"Couldn't load/mmap disk %s: %s\n",
|
||||
path, strerror(err));
|
||||
}
|
||||
printf("%s loaded %s dz = %d\n", __func__, path, (int)sz);
|
||||
if (sz == nib_disksz) {
|
||||
return nib_insert(path, buf, sz);
|
||||
} else if (sz == dsk_disksz) {
|
||||
return dsk_insert(path, buf, sz);
|
||||
} else {
|
||||
DIE(2,"Unrecognized disk format for %s.\n", path);
|
||||
}
|
||||
}
|
||||
|
||||
int mmapfile(const char *fname, byte **buf, size_t *sz, int flags)
|
||||
{
|
||||
int err;
|
||||
int fd;
|
||||
|
||||
*buf = NULL;
|
||||
|
||||
errno = 0;
|
||||
fd = open(fname, flags);
|
||||
if (fd < 0) {
|
||||
return errno;
|
||||
}
|
||||
|
||||
struct stat st;
|
||||
errno = 0;
|
||||
err = fstat(fd, &st);
|
||||
if (err < 0) {
|
||||
goto bail;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
int protect = PROT_READ;
|
||||
int mflags = MAP_PRIVATE;
|
||||
if (flags & O_RDWR || flags & O_WRONLY) {
|
||||
protect |= PROT_WRITE;
|
||||
mflags = MAP_SHARED;
|
||||
}
|
||||
*buf = mmap(NULL, st.st_size, protect, mflags, fd, 0);
|
||||
if (*buf == NULL) {
|
||||
err = errno;
|
||||
goto bail;
|
||||
}
|
||||
close(fd); // safe to close now.
|
||||
|
||||
*sz = st.st_size;
|
||||
return 0;
|
||||
bail:
|
||||
close(fd);
|
||||
return err;
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* mii_disk_format.h
|
||||
*
|
||||
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/********** FORMATS **********/
|
||||
#define NUM_TRACKS 35
|
||||
#define SECTOR_SIZE 256
|
||||
typedef struct DiskFormatDesc DiskFormatDesc;
|
||||
typedef struct DiskFormatDesc {
|
||||
void *privdat;
|
||||
bool writeprot;
|
||||
unsigned int halftrack;
|
||||
void (*spin)(DiskFormatDesc *, bool);
|
||||
uint8_t (*read_byte)(DiskFormatDesc *);
|
||||
void (*write_byte)(DiskFormatDesc *, uint8_t);
|
||||
void (*eject)(DiskFormatDesc *);
|
||||
} DiskFormatDesc;
|
||||
|
||||
DiskFormatDesc disk_format_load(const char *path);
|
||||
|
||||
#define WARN(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define INFO(...) fprintf(stderr, __VA_ARGS__)
|
||||
#define DIE(code, ...) do { \
|
||||
WARN(__VA_ARGS__); \
|
||||
if (code) exit(code); \
|
||||
} while(0)
|
470
src/format/mii_floppy.c
Normal file
470
src/format/mii_floppy.c
Normal file
@ -0,0 +1,470 @@
|
||||
/*
|
||||
* mii_floppy.c
|
||||
*
|
||||
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <libgen.h>
|
||||
|
||||
#include "mii_floppy.h"
|
||||
#include "mii_woz.h"
|
||||
|
||||
void
|
||||
mii_floppy_init(
|
||||
mii_floppy_t *f)
|
||||
{
|
||||
f->motor = 0;
|
||||
f->stepper = 0;
|
||||
f->qtrack = 15; // just to see something at seek time
|
||||
f->bit_position = 0;
|
||||
f->write_protected &= ~MII_FLOPPY_WP_MANUAL;// keep the manual WP bit
|
||||
/* this will look like this; ie half tracks are 'random'
|
||||
0: 0 1: 0 2:35 3: 1
|
||||
4: 1 5: 1 6:35 7: 2
|
||||
8: 2 9: 2 10:35 11: 3
|
||||
*/
|
||||
for (int i = 0; i < (int)sizeof(f->track_id); i++)
|
||||
f->track_id[i] = ((i + 1) % 4) == 3 ?
|
||||
MII_FLOPPY_RANDOM_TRACK_ID : ((i + 2) / 4);
|
||||
/* generate a buffer with about 30% one bits */
|
||||
uint8_t *random = f->tracks[MII_FLOPPY_RANDOM_TRACK_ID].data;
|
||||
memset(random, 0, 256);
|
||||
uint32_t bits = 256 * 8;
|
||||
uint32_t ones = bits * 0.3; // 30% ones
|
||||
// set 'ones' random bits in that random track
|
||||
while (ones) {
|
||||
uint32_t bit = rand() % bits;
|
||||
if (random[bit >> 3] & (1 << (bit & 7)))
|
||||
continue;
|
||||
random[bit >> 3] |= (1 << (bit & 7));
|
||||
ones--;
|
||||
}
|
||||
// we also fill up the last (ramdom) track with copies of the first
|
||||
// 256 uint8_ts of itself, to make it look like a real track
|
||||
int rbi = 0;
|
||||
for (int i = 0; i < 36; i++) {
|
||||
f->tracks[i].dirty = 0;
|
||||
f->tracks[i].bit_count = MII_FLOPPY_DEFAULT_TRACK_SIZE * 8;
|
||||
// fill the whole array up to the end..
|
||||
for (int bi = 0; bi < (int)sizeof(f->tracks[i].data); bi++)
|
||||
f->tracks[i].data[bi] = random[rbi++ % 256];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mii_track_write_bits(
|
||||
mii_floppy_track_t * dst,
|
||||
uint8_t bits,
|
||||
uint8_t count )
|
||||
{
|
||||
while (count--) {
|
||||
uint32_t uint8_t_index = dst->bit_count >> 3;
|
||||
uint8_t bit_index = 7 - (dst->bit_count & 7);
|
||||
dst->data[uint8_t_index] &= ~(1 << bit_index);
|
||||
dst->data[uint8_t_index] |= (!!(bits >> 7) << bit_index);
|
||||
dst->bit_count++;
|
||||
bits <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NIB isn't ideal to use with our bitstream, as it's lacking the sync
|
||||
* bits. It was made to use in something like our previous emulator that
|
||||
* was just iterating uint8_ts.
|
||||
* Anyway, We can recreate the proper bitstream by finding sectors headers,
|
||||
* filling up a few 'correct' 10 bits sync uint8_ts, then plonk said sector
|
||||
* as is.
|
||||
*/
|
||||
|
||||
static uint8_t _de44(uint8_t a, uint8_t b) {
|
||||
return ((a & 0x55) << 1) | (b & 0x55);
|
||||
}
|
||||
|
||||
static void
|
||||
mii_nib_rebit_track(
|
||||
uint8_t *track,
|
||||
mii_floppy_track_t * dst)
|
||||
{
|
||||
dst->bit_count = 0;
|
||||
uint32_t window = 0;
|
||||
int srci = 0;
|
||||
int seccount = 0;
|
||||
int state = 0; // look for address field
|
||||
do {
|
||||
window = (window << 8) | track[srci++];
|
||||
switch (state) {
|
||||
case 0: {
|
||||
if (window != 0xffd5aa96)
|
||||
break;
|
||||
for (int i = 0; i < (seccount == 0 ? 40 : 20); i++)
|
||||
mii_track_write_bits(dst, 0xff, 10);
|
||||
uint8_t * h = track + srci - 4; // incs first 0xff
|
||||
int tid = _de44(h[6], h[7]);
|
||||
int sid = _de44(h[8], h[9]);
|
||||
printf("Track %2d sector %2d\n", tid, sid);
|
||||
memcpy(dst->data + (dst->bit_count >> 3), track + srci - 4, 15);
|
||||
dst->bit_count += 15 * 8;
|
||||
srci += 11;
|
||||
state = 1;
|
||||
} break;
|
||||
case 1: {
|
||||
if (window != 0xffd5aaad)
|
||||
break;
|
||||
for (int i = 0; i < 4; i++)
|
||||
mii_track_write_bits(dst, 0xff, 10);
|
||||
uint8_t *h = track + srci - 4;
|
||||
memcpy(dst->data + (dst->bit_count >> 3), h, 4 + 342 + 4);
|
||||
dst->bit_count += (4 + 342 + 4) * 8;
|
||||
srci += 4 + 342;
|
||||
seccount++;
|
||||
state = 0;
|
||||
} break;
|
||||
}
|
||||
} while (srci < 6656);
|
||||
}
|
||||
|
||||
static int
|
||||
mii_floppy_load_nib(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
const char *filename = basename(file->pathname);
|
||||
printf("%s: loading NIB %s\n", __func__, filename);
|
||||
for (int i = 0; i < 35; i++) {
|
||||
uint8_t *track = file->map + (i * 6656);
|
||||
mii_nib_rebit_track(track, &f->tracks[i]);
|
||||
if (f->tracks[i].bit_count < 100) {
|
||||
printf("%s: %s: Invalid track %d has zero bits!\n", __func__,
|
||||
filename, i);
|
||||
return -1;
|
||||
}
|
||||
// printf("Track %d converted to %d bits\n", i, f->tracks[i].bit_count);
|
||||
f->tracks[i].dirty = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mii_floppy_write_track_woz(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file,
|
||||
int track_id )
|
||||
{
|
||||
mii_woz_header_t *header = (mii_woz_header_t *)file->map;
|
||||
|
||||
int version = !strncmp((char*)header, "WOZ", 3);
|
||||
if (!version) {
|
||||
printf("%s: not a WOZ file %4.4s\n", __func__, (char*)&header->magic_le);
|
||||
return 0;
|
||||
}
|
||||
version += !strncmp((char*)header, "WOZ2", 4);
|
||||
|
||||
mii_woz_tmap_t *tmap = NULL;
|
||||
if (version == 1) {
|
||||
mii_woz1_info_t *info = (mii_woz1_info_t *)(header + 1);
|
||||
tmap = (mii_woz_tmap_t *)((uint8_t *)info +
|
||||
le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
mii_woz1_trks_t *trks = (mii_woz1_trks_t *)((uint8_t *)tmap +
|
||||
le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
|
||||
trks->track[track_id].bit_count_le = htole32(f->tracks[track_id].bit_count);
|
||||
uint32_t byte_count = (le32toh(trks->track[track_id].bit_count_le) + 7) >> 3;
|
||||
memcpy(trks->track[track_id].bits, f->tracks[track_id].data, byte_count);
|
||||
trks->track[track_id].byte_count_le = htole16(byte_count);
|
||||
} else {
|
||||
mii_woz2_info_t *info = (mii_woz2_info_t *)(header + 1);
|
||||
tmap = (mii_woz_tmap_t *)((uint8_t *)info +
|
||||
le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
mii_woz2_trks_t *trks = (mii_woz2_trks_t *)((uint8_t *)tmap +
|
||||
le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
|
||||
uint8_t *track = file->map +
|
||||
(le16toh(trks->track[track_id].start_block_le) << 9);
|
||||
|
||||
trks->track[track_id].bit_count_le = htole32(f->tracks[track_id].bit_count);
|
||||
uint32_t byte_count = (le32toh(trks->track[track_id].bit_count_le) + 7) >> 3;
|
||||
memcpy(track, f->tracks[track_id].data, byte_count);
|
||||
}
|
||||
f->tracks[track_id].dirty = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
mii_floppy_load_woz(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
const char *filename = basename(file->pathname);
|
||||
printf("%s: loading WOZ %s\n", __func__, filename);
|
||||
mii_woz_header_t *header = (mii_woz_header_t *)file->map;
|
||||
|
||||
int version = !strncmp((char*)header, "WOZ", 3);
|
||||
if (!version) {
|
||||
printf("%s: not a WOZ file %4.4s\n", __func__, (char*)&header->magic_le);
|
||||
return 0;
|
||||
}
|
||||
version += !strncmp((char*)header, "WOZ2", 4);
|
||||
mii_woz_tmap_t *tmap = NULL;
|
||||
if (version == 1) {
|
||||
mii_woz1_info_t *info = (mii_woz1_info_t *)(header + 1);
|
||||
tmap = (mii_woz_tmap_t *)((uint8_t *)info +
|
||||
le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
mii_woz1_trks_t *trks = (mii_woz1_trks_t *)((uint8_t *)tmap +
|
||||
le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
#if 1
|
||||
printf("WOZ: version %d, type %d\n",
|
||||
info->version, info->disk_type );
|
||||
printf("WOZ: creator '%s'\n", info->creator);
|
||||
printf("WOZ: track map %4.4s size %d\n",
|
||||
(char*)&tmap->chunk.id_le,
|
||||
le32toh(tmap->chunk.size_le));
|
||||
printf("WOZ: Track chunk %4.4s size %d\n",
|
||||
(char*)&trks->chunk.id_le, le32toh(trks->chunk.size_le));
|
||||
#endif
|
||||
for (int i = 0; i < 35; i++) {
|
||||
uint8_t *track = trks->track[i].bits;
|
||||
memcpy(f->tracks[i].data, track, le16toh(trks->track[i].byte_count_le));
|
||||
f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le);
|
||||
f->tracks[i].dirty = 0;
|
||||
}
|
||||
} else {
|
||||
mii_woz2_info_t *info = (mii_woz2_info_t *)(header + 1);
|
||||
tmap = (mii_woz_tmap_t *)((uint8_t *)info +
|
||||
le32toh(info->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
mii_woz2_trks_t *trks = (mii_woz2_trks_t *)((uint8_t *)tmap +
|
||||
le32toh(tmap->chunk.size_le) + sizeof(mii_woz_chunk_t));
|
||||
#if 1
|
||||
printf("WOZ: version %d, type %d, sides %d, largest track %d\n",
|
||||
info->version, info->disk_type, info->sides,
|
||||
le16toh(info->largest_track_le) * 512);
|
||||
printf("WOZ: creator '%s'\n", info->creator);
|
||||
printf("WOZ: track map %4.4s size %d\n",
|
||||
(char*)&tmap->chunk.id_le,
|
||||
le32toh(tmap->chunk.size_le));
|
||||
printf("WOZ: Track chunk %4.4s size %d\n",
|
||||
(char*)&trks->chunk.id_le, le32toh(trks->chunk.size_le));
|
||||
#endif
|
||||
for (int i = 0; i < 35; i++) {
|
||||
uint8_t *track = file->map +
|
||||
(le16toh(trks->track[i].start_block_le) << 9);
|
||||
uint32_t byte_count = (le32toh(trks->track[i].bit_count_le) + 7) >> 3;
|
||||
memcpy(f->tracks[i].data, track, byte_count);
|
||||
f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le);
|
||||
f->tracks[i].dirty = 0;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
// copy the track map from the file to the floppy
|
||||
for (int ti = 0; ti < (int)sizeof(f->track_id); ti++) {
|
||||
f->track_id[ti] = tmap->track_id[ti] == 0xff ?
|
||||
MII_FLOPPY_RANDOM_TRACK_ID : tmap->track_id[ti];
|
||||
if (f->tracks[f->track_id[ti]].bit_count == 0) {
|
||||
printf("%s Invalid qtrack %d (points to track %d) has zero bits!\n",
|
||||
__func__, ti, f->track_id[ti]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return version;
|
||||
}
|
||||
|
||||
#define NIBBLE_SECTOR_SIZE 416
|
||||
#define NIBBLE_TRACK_SIZE 6656
|
||||
#define DSK_SECTOR_SIZE 256
|
||||
#define MAX_SECTORS 16
|
||||
#define VOLUME_NUMBER 254
|
||||
#define DSK_TRACK_SIZE (DSK_SECTOR_SIZE * MAX_SECTORS)
|
||||
|
||||
//static const size_t nib_disksz = 232960;
|
||||
//static const size_t dsk_disksz = 143360;
|
||||
|
||||
// DOS 3.3 Physical sector order (index is physical sector,
|
||||
// value is DOS sector)
|
||||
static const uint8_t DO[] = {
|
||||
0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4,
|
||||
0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF
|
||||
};
|
||||
// ProDOS Physical sector order (index is physical sector,
|
||||
// value is ProDOS sector).
|
||||
static const uint8_t PO[] = {
|
||||
0x0, 0x8, 0x1, 0x9, 0x2, 0xa, 0x3, 0xb,
|
||||
0x4, 0xc, 0x5, 0xd, 0x6, 0xe, 0x7, 0xf
|
||||
};
|
||||
static const uint8_t TRANS62[] = {
|
||||
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
|
||||
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
|
||||
};
|
||||
|
||||
// This function is derived from Scullin Steel Co.'s apple2js code
|
||||
// https://github.com/whscullin/apple2js/blob/e280c3d/js/formats/format_utils.ts#L140
|
||||
|
||||
/* Further recycled for MII .DSK decoding
|
||||
* We use this function to convert the sector from byte to nibble (8 bits), then
|
||||
* we pass that track to the mii_nib_rebit_track() to add 10 bit headers and
|
||||
* such. It could be done in one pass, but really, it's easier to reuse it as is.
|
||||
*/
|
||||
static void
|
||||
mii_floppy_nibblize_sector(
|
||||
uint8_t vol, uint8_t track, uint8_t sector,
|
||||
uint8_t **nibSec, const uint8_t *data)
|
||||
{
|
||||
uint8_t *wr = *nibSec;
|
||||
unsigned int gap;
|
||||
|
||||
// Gap 1/3 (40/0x28 uint8_ts)
|
||||
if (sector == 0) // Gap 1
|
||||
gap = 0x80;
|
||||
else { // Gap 3
|
||||
gap = track == 0? 0x28 : 0x26;
|
||||
}
|
||||
for (uint8_t i = 0; i != gap; ++i)
|
||||
*wr++ = 0xFF;
|
||||
// Address Field
|
||||
const uint8_t checksum = vol ^ track ^ sector;
|
||||
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0x96; // Address Prolog D5 AA 96
|
||||
*wr++ = (vol >> 1) | 0xAA; *wr++ = vol | 0xAA;
|
||||
*wr++ = (track >> 1) | 0xAA; *wr++ = track | 0xAA;
|
||||
*wr++ = (sector >> 1) | 0xAA; *wr++ = sector | 0xAA;
|
||||
*wr++ = (checksum >> 1) | 0xAA; *wr++ = checksum | 0xAA;
|
||||
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
|
||||
// Gap 2 (5 uint8_ts)
|
||||
for (int i = 0; i != 5; ++i)
|
||||
*wr++ = 0xFF;
|
||||
// Data Field
|
||||
*wr++ = 0xD5; *wr++ = 0xAA; *wr++ = 0xAD; // Data Prolog D5 AA AD
|
||||
|
||||
uint8_t *nibbles = wr;
|
||||
const unsigned ptr2 = 0;
|
||||
const unsigned ptr6 = 0x56;
|
||||
|
||||
for (int i = 0; i != 0x156; ++i)
|
||||
nibbles[i] = 0;
|
||||
|
||||
int i2 = 0x55;
|
||||
for (int i6 = 0x101; i6 >= 0; --i6) {
|
||||
uint8_t val6 = data[i6 % 0x100];
|
||||
uint8_t val2 = nibbles[ptr2 + i2];
|
||||
val2 = (val2 << 1) | (val6 & 1); val6 >>= 1;
|
||||
val2 = (val2 << 1) | (val6 & 1); val6 >>= 1;
|
||||
nibbles[ptr6 + i6] = val6;
|
||||
nibbles[ptr2 + i2] = val2;
|
||||
if (--i2 < 0)
|
||||
i2 = 0x55;
|
||||
}
|
||||
uint8_t last = 0;
|
||||
for (int i = 0; i != 0x156; ++i) {
|
||||
const uint8_t val = nibbles[i];
|
||||
nibbles[i] = TRANS62[last ^ val];
|
||||
last = val;
|
||||
}
|
||||
wr += 0x156; // advance write-pointer
|
||||
*wr++ = TRANS62[last];
|
||||
*wr++ = 0xDE; *wr++ = 0xAA; *wr++ = 0xEB; // Epilogue DE AA EB
|
||||
// Gap 3
|
||||
*wr++ = 0xFF;
|
||||
*nibSec = wr;
|
||||
}
|
||||
|
||||
static int
|
||||
mii_floppy_load_dsk(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
uint8_t *nibbleBuf = malloc(NIBBLE_TRACK_SIZE);
|
||||
const char *filename = basename(file->pathname);
|
||||
|
||||
const char *ext = rindex(filename, '.');
|
||||
ext = ext ? ext+1 : "";
|
||||
const uint8_t * secmap = DO;
|
||||
if (!strcasecmp(ext, "PO")) {
|
||||
printf("%s Opening %s as PO.\n", __func__, filename);
|
||||
secmap = PO;
|
||||
} else {
|
||||
printf("%s Opening %s as DO.\n", __func__, filename);
|
||||
}
|
||||
for (int i = 0; i < 35; ++i) {
|
||||
memset(nibbleBuf, 0xff, NIBBLE_TRACK_SIZE);
|
||||
uint8_t *writePtr = nibbleBuf;
|
||||
for (int phys_sector = 0; phys_sector < MAX_SECTORS; ++phys_sector) {
|
||||
const uint8_t dos_sector = secmap[phys_sector];
|
||||
uint32_t off = ((MAX_SECTORS * i + dos_sector) * DSK_SECTOR_SIZE);
|
||||
uint8_t *track = file->map + off;
|
||||
mii_floppy_nibblize_sector(VOLUME_NUMBER, i, phys_sector,
|
||||
&writePtr, track);
|
||||
}
|
||||
mii_nib_rebit_track(nibbleBuf, &f->tracks[i]);
|
||||
}
|
||||
free(nibbleBuf);
|
||||
// DSK is read only
|
||||
f->write_protected |= MII_FLOPPY_WP_RO_FORMAT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mii_floppy_update_tracks(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
if (f->write_protected & MII_FLOPPY_WP_RO_FORMAT)
|
||||
return -1;
|
||||
if (f->write_protected & MII_FLOPPY_WP_RO_FILE)
|
||||
return -1;
|
||||
if (!f->tracks_dirty)
|
||||
return 0;
|
||||
for (int i = 0; i < 35; i++) {
|
||||
if (!f->tracks[i].dirty)
|
||||
continue;
|
||||
printf("%s: track %d is dirty, saving\n", __func__, i);
|
||||
switch (file->format) {
|
||||
case MII_DD_FILE_NIB:
|
||||
break;
|
||||
case MII_DD_FILE_WOZ:
|
||||
mii_floppy_write_track_woz(f, file, i);
|
||||
printf("%s: WOZ track %d updated\n", __func__, i);
|
||||
break;
|
||||
}
|
||||
f->tracks[i].dirty = 0;
|
||||
}
|
||||
f->tracks_dirty = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mii_floppy_load(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file )
|
||||
{
|
||||
if (!file)
|
||||
return -1;
|
||||
int res = -1;
|
||||
switch (file->format) {
|
||||
case MII_DD_FILE_NIB:
|
||||
res = mii_floppy_load_nib(f, file);
|
||||
break;
|
||||
case MII_DD_FILE_WOZ:
|
||||
res = mii_floppy_load_woz(f, file);
|
||||
break;
|
||||
case MII_DD_FILE_DSK:
|
||||
res = mii_floppy_load_dsk(f, file);
|
||||
break;
|
||||
default:
|
||||
printf("%s: unsupported format %d\n", __func__, file->format);
|
||||
}
|
||||
// update write protection in case file is opened read only
|
||||
if (file->read_only)
|
||||
f->write_protected |= MII_FLOPPY_WP_RO_FILE;
|
||||
else
|
||||
f->write_protected &= ~MII_FLOPPY_WP_RO_FILE;
|
||||
return res;
|
||||
}
|
58
src/format/mii_floppy.h
Normal file
58
src/format/mii_floppy.h
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* mii_floppy.h
|
||||
*
|
||||
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mii_dd.h"
|
||||
|
||||
// for NIB and others. can be bigger on .WOZ
|
||||
#define MII_FLOPPY_DEFAULT_TRACK_SIZE 6656
|
||||
// track containing random bits
|
||||
#define MII_FLOPPY_RANDOM_TRACK_ID 35
|
||||
|
||||
enum {
|
||||
MII_FLOPPY_WP_MANUAL = (1 << 0), // write protect by the user
|
||||
MII_FLOPPY_WP_RO_FILE = (1 << 1), // file is read only
|
||||
MII_FLOPPY_WP_RO_FORMAT = (1 << 2), // File format doesn't do writes
|
||||
};
|
||||
|
||||
typedef struct mii_floppy_track_t {
|
||||
uint8_t dirty : 1; // track has been written to
|
||||
uint32_t bit_count;
|
||||
uint8_t data[6680]; // max suggested by WOZ spec
|
||||
} mii_floppy_track_t;
|
||||
|
||||
typedef struct mii_floppy_t {
|
||||
uint8_t write_protected : 3, id : 2;
|
||||
uint8_t motor; // motor is on
|
||||
uint8_t stepper; // last step we did...
|
||||
uint8_t qtrack; // quarter track we are on
|
||||
uint32_t bit_position;
|
||||
uint8_t tracks_dirty; // needs saving
|
||||
uint8_t track_id[35 * 4];
|
||||
mii_floppy_track_t tracks[36];
|
||||
} mii_floppy_t;
|
||||
|
||||
/*
|
||||
* Initialize a floppy structure with random data. It is not formatted, just
|
||||
* ready to use for loading a disk image, or formatting as a 'virgin' disk.
|
||||
*/
|
||||
void
|
||||
mii_floppy_init(
|
||||
mii_floppy_t *f);
|
||||
|
||||
int
|
||||
mii_floppy_load(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file );
|
||||
|
||||
int
|
||||
mii_floppy_update_tracks(
|
||||
mii_floppy_t *f,
|
||||
mii_dd_file_t *file );
|
94
src/format/mii_woz.h
Normal file
94
src/format/mii_woz.h
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* mii_woz.h
|
||||
*
|
||||
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*
|
||||
* Woz format parser. 'le' fields are little-endian in the file.
|
||||
* Use the appropriate macros like le32toh() to convert to host-endian as needed.
|
||||
*/
|
||||
typedef struct mii_woz_header_t {
|
||||
uint32_t magic_le; // 'WOZ2'
|
||||
uint8_t padding[4];
|
||||
uint32_t crc_le;
|
||||
} __attribute__((packed)) mii_woz_header_t;
|
||||
|
||||
typedef struct mii_woz_chunk_t {
|
||||
uint32_t id_le;
|
||||
uint32_t size_le;
|
||||
} __attribute__((packed)) mii_woz_chunk_t;
|
||||
|
||||
// offset 12 in the file, 'version' at offset 20
|
||||
typedef struct mii_woz2_info_t {
|
||||
mii_woz_chunk_t chunk; // 'INFO'
|
||||
uint8_t version; // version 3
|
||||
uint8_t disk_type; // 1=5.25" 2=3.5"
|
||||
uint8_t write_protected;
|
||||
uint8_t sync; // 1=cross track sync was used when imaged
|
||||
uint8_t cleaned; // 1=MC3470 fake bits have been cleaned
|
||||
uint8_t creator[32];
|
||||
uint8_t sides; // 1 or 2 (3.5")
|
||||
uint8_t boot_format; // boot sector format 1:16,2:13,3:both
|
||||
uint8_t optimal_bit_timing;
|
||||
uint16_t compatible_hardware_le;
|
||||
uint16_t required_ram_le; // apple II ram required (48, 64 etc)
|
||||
uint16_t largest_track_le; // in units of 512 bytes
|
||||
uint16_t flux_block_le;
|
||||
uint16_t flux_largest_track_le;
|
||||
} __attribute__((packed)) mii_woz2_info_t;
|
||||
|
||||
// offset 80 in the file -- same for WOZ1 and WOZ2
|
||||
typedef struct mii_woz_tmap_t {
|
||||
mii_woz_chunk_t chunk; // 'TMAP'
|
||||
// 140 bytes for 35*4 tracks; or 160 bytes for 80 tracks*2 sides
|
||||
uint8_t track_id[160]; // 'TRKS' id for each quarter track
|
||||
} __attribute__((packed)) mii_woz_tmap_t;
|
||||
|
||||
// offset 248 in the file
|
||||
typedef struct mii_woz2_trks_t {
|
||||
mii_woz_chunk_t chunk; // 'TRKS'
|
||||
// offset 256 in the file
|
||||
struct {
|
||||
/* First block of BITS data. This value is relative to the start
|
||||
of the file, so the first possible starting block is 3.
|
||||
Multiply this value by 512 (x << 9) to get the starting byte
|
||||
of the BITS data. */
|
||||
uint16_t start_block_le; // starting block number
|
||||
uint16_t block_count_le; // number of 512-byte blocks
|
||||
uint32_t bit_count_le; // number of bits in the track
|
||||
} track[160];
|
||||
uint8_t bits[]; // the actual bits
|
||||
} __attribute__((packed)) mii_woz2_trks_t;
|
||||
|
||||
/* Same info, tmap and trks for WOZ 1 files */
|
||||
typedef struct mii_woz1_info_t {
|
||||
mii_woz_chunk_t chunk; // 'INFO'
|
||||
uint8_t version; // version 1
|
||||
uint8_t disk_type; // 1=5.25" 2=3.5"
|
||||
uint8_t write_protected;
|
||||
uint8_t sync; // 1=cross track sync was used when imaged
|
||||
uint8_t cleaned; // 1=MC3470 fake bits have been cleaned
|
||||
uint8_t creator[32];
|
||||
} __attribute__((packed)) mii_woz1_info_t;
|
||||
|
||||
// offset 248 in the file
|
||||
typedef struct mii_woz1_trks_t {
|
||||
mii_woz_chunk_t chunk; // 'TRKS'
|
||||
// offset 256 in the file
|
||||
struct {
|
||||
uint8_t bits[6646];
|
||||
uint16_t byte_count_le; // size in bytes
|
||||
uint16_t bit_count_le;
|
||||
uint16_t splice_point_le;
|
||||
uint8_t splice_nibble;
|
||||
uint8_t splice_bit_count;
|
||||
uint16_t reserved;
|
||||
} track[35];
|
||||
} __attribute__((packed)) mii_woz1_trks_t;
|
100
src/format/nib.c
100
src/format/nib.c
@ -1,100 +0,0 @@
|
||||
// format/nib.c
|
||||
//
|
||||
// Copyright (c) 2023 Micah John Cowan.
|
||||
// This code is licensed under the MIT license.
|
||||
// See the accompanying LICENSE file for details.
|
||||
|
||||
#include "mii_disk_format.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define byte uint8_t
|
||||
|
||||
#define NIBBLE_TRACK_SIZE 6656
|
||||
#define NIBBLE_SECTOR_SIZE 416
|
||||
#define MAX_SECTORS 16
|
||||
|
||||
struct nibprivdat {
|
||||
const char *path;
|
||||
byte *buf;
|
||||
int bytenum;
|
||||
uint64_t dirty_tracks;
|
||||
};
|
||||
static const struct nibprivdat datinit = { 0 };
|
||||
|
||||
static const size_t nib_disksz = 232960;
|
||||
|
||||
static void spin(DiskFormatDesc *desc, bool b)
|
||||
{
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
if (!b && dat->dirty_tracks != 0) {
|
||||
// For now, sync the entire disk
|
||||
errno = 0;
|
||||
int err = msync(dat->buf, nib_disksz, MS_SYNC);
|
||||
if (err < 0) {
|
||||
DIE(1,"Couldn't sync to disk file %s: %s\n",
|
||||
dat->path, strerror(errno));
|
||||
}
|
||||
dat->dirty_tracks = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static byte read_byte(DiskFormatDesc *desc)
|
||||
{
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
byte val = dat->buf[pos];
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
return val;
|
||||
}
|
||||
|
||||
static void write_byte(DiskFormatDesc *desc, byte val)
|
||||
{
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
if ((val & 0x80) == 0) {
|
||||
// D2DBG("dodged write $%02X", val);
|
||||
return; // must have high bit
|
||||
}
|
||||
dat->dirty_tracks |= 1 << (desc->halftrack/2);
|
||||
size_t pos = (desc->halftrack/2) * NIBBLE_TRACK_SIZE;
|
||||
pos += (dat->bytenum % NIBBLE_TRACK_SIZE);
|
||||
|
||||
//D2DBG("write byte $%02X at pos $%04zX", (unsigned int)val, pos);
|
||||
|
||||
dat->buf[pos] = val;
|
||||
dat->bytenum = (dat->bytenum + 1) % NIBBLE_TRACK_SIZE;
|
||||
}
|
||||
|
||||
static void eject(DiskFormatDesc *desc)
|
||||
{
|
||||
// free dat->path and dat, and unmap disk image
|
||||
struct nibprivdat *dat = desc->privdat;
|
||||
(void) munmap(dat->buf, nib_disksz);
|
||||
free((void*)dat->path);
|
||||
free(dat);
|
||||
}
|
||||
|
||||
DiskFormatDesc nib_insert(const char *path, byte *buf, size_t sz)
|
||||
{
|
||||
if (sz != nib_disksz) {
|
||||
DIE(0,"Wrong disk image size for %s:\n", path);
|
||||
DIE(1," Expected %zu, got %zu.\n", nib_disksz, sz);
|
||||
}
|
||||
|
||||
struct nibprivdat *dat = malloc(sizeof *dat);
|
||||
*dat = datinit;
|
||||
dat->buf = buf;
|
||||
dat->path = strdup(path);
|
||||
|
||||
return (DiskFormatDesc){
|
||||
.privdat = dat,
|
||||
.spin = spin,
|
||||
.read_byte = read_byte,
|
||||
.write_byte = write_byte,
|
||||
.eject = eject,
|
||||
};
|
||||
}
|
@ -142,7 +142,7 @@ _mii_2dsk_action_cb(
|
||||
"Select DSK file to load",
|
||||
m->drive_kind == MII_2DSK_SMARTPORT ?
|
||||
"\\.(po|hdv|2mg)$" :
|
||||
"\\.(dsk)$",
|
||||
"\\.(woz|nib|dsk)$",
|
||||
getenv("HOME"),
|
||||
MUI_STDF_FLAG_REGEXP);
|
||||
mui_window_set_action(w, _mii_2dsk_stdfile_cb, m);
|
||||
|
Loading…
Reference in New Issue
Block a user