initial support for WOZ 2.0 disk image file specification

This commit is contained in:
Christopher Mosher 2018-12-23 16:44:30 -05:00
parent b531bc96f5
commit 117d4af8ef
5 changed files with 218 additions and 176 deletions

View File

@ -43,23 +43,23 @@ static void inst(std::uint8_t seq, std::uint8_t inst) {
if (cmd & 8u) { if (cmd & 8u) {
switch (cmd & 3u) { switch (cmd & 3u) {
case 3: case 3:
printf("=="); printf("LDR");
break; break;
case 2: case 2:
printf("W>"); printf("SRP");
break; break;
case 1: case 1:
if ((cmd & 4u) >> 2) { if ((cmd & 4u) >> 2) {
printf("<1"); printf("SL1");
} else { } else {
printf("<0"); printf("SL0");
} }
break; break;
default: default:
printf("\x1b[0m--"); printf("\x1b[0m...");
} }
} else { } else {
printf("00"); printf("CLR");
} }
printf("\x1b[0m"); printf("\x1b[0m");
@ -95,7 +95,7 @@ static void showua2seq(std::uint8_t lssrom[], std::uint8_t seq) {
inst(s,lssrom[seq|0x6]); inst(s,lssrom[seq|0x6]);
printf("\n"); printf("\n");
if (s == 7) { if (s == 7) {
printf(" +---------------------+---------------------+---------------------+--------------------\n"); printf(" +-------------------------+-------------------------+-------------------------+------------------------\n");
} }
} }
@ -217,15 +217,15 @@ LSS::LSS(bool use13SectorDos32LSS):
setseq(lss13rom,0x23u,0x30u); setseq(lss13rom,0x23u,0x30u);
setseq(lss13rom,0x33u,0xD0u); setseq(lss13rom,0x33u,0xD0u);
// if (use13Sector) { if (use13Sector) {
// for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) { for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) {
// showua2seq(lss13rom,seq); showua2seq(lss13rom,seq);
// } }
// } else { } else {
// for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) { for (unsigned int seq = 0; seq < 0x100u; seq += 0x10u) {
// showua2seq(lssrom,seq); showua2seq(lssrom,seq);
// } }
// } }
} }
LSS::~LSS() { LSS::~LSS() {

View File

@ -32,6 +32,8 @@
#include <iostream> #include <iostream>
#include <iomanip> #include <iomanip>
static int run(const std::string& config_file) { static int run(const std::string& config_file) {
GUI gui; GUI gui;
@ -47,59 +49,6 @@ static int run(const std::string& config_file) {
//std::uint8_t dataBus;
//std::uint8_t lssrom[0x100];
//std::uint8_t lss13rom[0x100];
//std::uint8_t wp(0);
//std::uint8_t q7(0);
//std::uint8_t q6(0);
//std::uint8_t dreg(0);
//std::uint8_t seq(0x20);
//std::uint8_t cmd;
//std::uint8_t adr;
//std::uint8_t cprint(0);
//void debugLog(const std::uint8_t x) {
// if (++cprint == 128 || x==0xD5u) {
// cprint = 0;
// printf("\n");
// }
// printf("%02x", x);
//}
//void lss(const std::uint8_t cmd) {
// if (cmd & 8) {
// switch (cmd & 3) {
// case 3: dreg = dataBus; break;
// case 2: dreg >>= 1; dreg |= (wp << 7); break;
// case 1: dreg <<= 1; dreg |= ((cmd & 4) >> 2); break;
// }
// } else {
// debugLog(dreg);
// dreg = 0;
// }
//}
//void doTrack(std::uint8_t *ptrk, const std::uint16_t ctrk) {
// std::uint8_t *pend(ptrk+ctrk);
// while (ptrk < pend) {
// for (std::uint8_t m(0x80); m; m >>= 1) {
// std::uint8_t pls = (*ptrk & m) ? 1 : 0;
// for (int i(0); i < 8; ++i) {
// adr = q7<<3 | q6<<2 | (dreg>>7)<<1 | pls;
// pls = 0;
// cmd = lssrom[seq|adr];
// seq = cmd & 0xF0;
// lss(cmd);
// }
// }
// ++ptrk;
// }
//}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
#endif #endif

View File

@ -22,11 +22,11 @@
class StepperMotor { class StepperMotor {
private: private:
enum { QTRACKS = 141 }; enum { QTRACKS = 160 };
// quarter track: 0=t0, 1=t0.25, 2=t0.5, 3=t0.75, 4=t1, ... 140=t35.00 ... 159=t39.75
// quarter track: 0=t0, 1=t0.25, 2=t0.5, 3=t0.75, 4=t1, ... 140=t35.00 // (see TMAP in WOZ2 file format spec)
// (see TMAP in WOZ file format spec)
std::int16_t quarterTrack; std::int16_t quarterTrack;
signed char pos; // 0 - 7 signed char pos; // 0 - 7
unsigned char mags; unsigned char mags;

View File

@ -24,7 +24,27 @@
#include <fstream> #include <fstream>
#include <cmath> #include <cmath>
WozFile::WozFile() : lastQuarterTrack(0) { #define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte) \
(byte & 0x80 ? '1' : '0'), \
(byte & 0x40 ? '1' : '0'), \
(byte & 0x20 ? '1' : '0'), \
(byte & 0x10 ? '1' : '0'), \
(byte & 0x08 ? '1' : '0'), \
(byte & 0x04 ? '1' : '0'), \
(byte & 0x02 ? '1' : '0'), \
(byte & 0x01 ? '1' : '0')
struct trk_t {
std::uint16_t blockFirst;
std::uint16_t blockCount;
std::uint32_t bitCount;
};
WozFile::WozFile() : tmap(0) {
for (int i(0); i < C_QTRACK; ++i) {
this->trk[i] = 0;
}
unload(); unload();
} }
@ -41,13 +61,13 @@ bool WozFile::load(const std::string& filePath) {
unload(); unload();
} }
std::uint32_t woz1; std::uint32_t woz2;
in.read((char*)&woz1, sizeof(woz1)); in.read((char*)&woz2, sizeof(woz2));
if (woz1 != 0x315A4F57u) { if (woz2 != 0x325A4F57u) {
printf("WOZ1 magic bytes missing."); printf("WOZ2 magic bytes missing.");
throw "WOZ1 magic bytes missing"; throw "WOZ2 magic bytes missing";
} }
printf("WOZ1 magic bytes present\n"); printf("WOZ2 magic bytes present\n");
std::uint32_t sanity; std::uint32_t sanity;
in.read((char*)&sanity, sizeof(sanity)); in.read((char*)&sanity, sizeof(sanity));
@ -72,82 +92,111 @@ bool WozFile::load(const std::string& filePath) {
std::uint8_t* buf = new std::uint8_t[chunk_size]; std::uint8_t* buf = new std::uint8_t[chunk_size];
in.read((char*)buf, chunk_size); in.read((char*)buf, chunk_size);
printf("INFO version %d\n", *buf); printf("INFO version %d\n", *buf);
if (*buf != 2) {
printf("File is not WOZ2 version.\n");
throw "File is not WOZ2 version";
}
five_25 = (buf[1]==1); five_25 = (buf[1]==1);
printf("Disk type: %s\n", five_25 ? "5.25" : buf[1]==2 ? "3.5" : "?"); printf("Disk type: %s\n", five_25 ? "5.25" : buf[1]==2 ? "3.5" : "?");
if (!five_25) {
printf("Only 5 1/4\" disk images are supported.\n");
throw "Only 5 1/4\" disk images are supported";
}
this->writable = !(buf[2]==1); this->writable = !(buf[2]==1);
printf("Write protected?: %s\n", this->writable ? "No" : "Yes"); printf("Write protected?: %s\n", this->writable ? "No" : "Yes");
printf("Imaged with cross-track sync?: %s\n", buf[3]==1 ? "Yes" : "No"); this->sync = buf[3]==1;
printf("MC3470 fake bits removed?: %s\n", buf[4]==1 ? "Yes" : "No"); printf("Imaged with cross-track sync?: %s\n", this->sync ? "Yes" : "No");
this->cleaned = buf[4]==1;
printf("MC3470 fake bits removed?: %s\n", this->cleaned ? "Yes" : "No");
this->creator = std::string((char*)buf+5, 32);
printf("Creator: \"%.32s\"\n", buf+5); printf("Creator: \"%.32s\"\n", buf+5);
this->timing = buf[39];
delete[] buf; delete[] buf;
} }
break; break;
case 0x50414D54: { // TMAP case 0x50414D54: { // TMAP
std::uint8_t* buf = new std::uint8_t[chunk_size]; this->tmap = new std::uint8_t[chunk_size];
in.read((char*)buf, chunk_size); in.read((char*)this->tmap, chunk_size);
if (!five_25) {
printf("Can only handle 5.25 floppy disk images.\n"); this->initalQtrack = 0;
throw "Can only handle 5.25 floppy disk images."; while (this->initalQtrack < chunk_size && this->tmap[this->initalQtrack] == 0xFFu) {
} else { ++this->initalQtrack;
printf("\x1b[31;47m-------------------------------------------\x1b[0m\n");
std::uint8_t i(0);
for (std::uint16_t t(0); t <= 3500; t += 25) {
if (buf[i] == 0xFFu) {
printf("\x1b[31;47m");
}
if (t % 100) {
printf("TMAP track 0x%02X +.%02d: TRKS track index 0x%02X", t/100, t%100, buf[i++]);
} else {
printf("TMAP track 0x%02X : TRKS track index 0x%02X", t/100, buf[i++]);
}
printf("\x1b[0m\n");
}
printf("\x1b[31;47m-------------------------------------------\x1b[0m\n");
for (std::uint8_t qt(0); qt <= 140; ++qt) {
this->tmap[qt] = buf[qt];
}
} }
delete[] buf; if (this->initalQtrack == chunk_size) {
this->initalQtrack = 0xFFu;
printf("Could not find any initial track (%02X).\n", this->initalQtrack);
}
this->finalQtrack = chunk_size-1;
while (this->finalQtrack != 0xFFu && this->tmap[this->finalQtrack] == 0xFFu) {
--this->finalQtrack;
}
if (this->finalQtrack == 0xFFu) {
printf("Could not find any final track (%02X).\n", this->finalQtrack);
}
printf("\x1b[31;47m-------------------------------------------\x1b[0m\n");
for (std::uint8_t qt(0); qt < chunk_size; ++qt) {
const std::uint16_t t(qt*25);
if (tmap[qt] == 0xFFu) {
printf("\x1b[31;47m");
}
if (t % 100) {
printf("TMAP track 0x%02X +.%02d: TRKS track index 0x%02X", t/100, t%100, tmap[qt]);
} else {
printf("TMAP track 0x%02X : TRKS track index 0x%02X", t/100, tmap[qt]);
}
printf("\x1b[0m");
if (qt == this->initalQtrack && qt == this->finalQtrack) {
printf(" <-- lone track");
} else if (qt == this->initalQtrack) {
printf(" <-- inital track");
} else if (qt == this->finalQtrack) {
printf(" <-- final track");
}
printf("\n");
}
printf("\x1b[31;47m-------------------------------------------\x1b[0m\n");
} }
break; break;
case 0x534B5254: { // TRKS case 0x534B5254: { // TRKS
if (chunk_size < C_QTRACK*8) {
throw "TRKS chunk doesn't have 160 track entries";
}
std::uint8_t* buf = new std::uint8_t[chunk_size]; std::uint8_t* buf = new std::uint8_t[chunk_size];
in.read((char*)buf, chunk_size); in.read((char*)buf, chunk_size);
if (chunk_size % 6656) { std::uint8_t* te = buf;
printf("chunk size is not an even multiple of 6656."); for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) {
} struct trk_t ts;
this->c_trks = chunk_size / 6656; ts.blockFirst = *((std::uint16_t*)te)-3;
printf("Count of tracks: 0x%02X\n", this->c_trks); te += 2;
if (this->c_trks > 141) { ts.blockCount = *((std::uint16_t*)te);
printf("Error: cannot handle more than 141 tracks."); te += 2;
throw "Error: cannot handle more than 141 tracks"; ts.bitCount = *((std::uint32_t*)te);
} te += 4;
for (std::uint8_t t(0); t < this->c_trks; ++t) { if (ts.blockCount) {
printf("track 0x%02X:\n", t); printf("TRK index %02X: start byte in BITS %08x; %08x bytes; %08x bits ", qt, ts.blockFirst<<9, ts.blockCount<<9, ts.bitCount);
std::uint16_t usedBytes = *(std::uint16_t*)&buf[t*6656+6646+0]; this->trk_bits[qt] = ts.bitCount;
printf(" used bytes: 0x%0X\n", usedBytes); this->trk[qt] = new std::uint8_t[ts.blockCount<<9];
this->trk_bits[t] = *(std::uint16_t*)&buf[t*6656+6646+2]; memcpy(this->trk[qt], buf+C_QTRACK*8+(ts.blockFirst<<9), ts.blockCount<<9);
printf(" count of bits: 0x%0X\n", this->trk_bits[t]); printf("("
std::uint16_t spliceBit = *(std::uint16_t*)&buf[t*6656+6646+4]; BYTE_TO_BINARY_PATTERN
if (spliceBit == 0xFFFFu) { BYTE_TO_BINARY_PATTERN
printf(" no splice information exists\n"); BYTE_TO_BINARY_PATTERN
} else { BYTE_TO_BINARY_PATTERN
printf(" bit after splice point: 0x%0X\n", spliceBit); BYTE_TO_BINARY_PATTERN
} BYTE_TO_BINARY_PATTERN
std::uint8_t spliceNib = *(std::uint8_t*)&buf[t*6656+6646+6]; BYTE_TO_BINARY_PATTERN
printf(" Nibble value to use for splice: 0x%0X\n", spliceNib); BYTE_TO_BINARY_PATTERN
std::uint8_t cSpliceBit = *(std::uint8_t*)&buf[t*6656+6646+7]; "...)\n",
printf(" Bit count of splice nibble: 0x%0X\n", cSpliceBit); BYTE_TO_BINARY(this->trk[qt][0]),
BYTE_TO_BINARY(this->trk[qt][1]),
std::uint8_t *base = (std::uint8_t*)&buf[t*6656+0]; BYTE_TO_BINARY(this->trk[qt][2]),
std::uint8_t *pd = base; BYTE_TO_BINARY(this->trk[qt][3]),
printf(" beginning of data: 0x: "); BYTE_TO_BINARY(this->trk[qt][4]),
for (int cd(0); cd < 32; ++cd) { BYTE_TO_BINARY(this->trk[qt][5]),
printf("%02X", *pd++); BYTE_TO_BINARY(this->trk[qt][6]),
} BYTE_TO_BINARY(this->trk[qt][7]));
printf("\n");
for (int i(0); i < 6646; ++i) {
this->trks[t][i] = *base++;
} }
} }
delete[] buf; delete[] buf;
@ -155,9 +204,9 @@ bool WozFile::load(const std::string& filePath) {
break; break;
case 0x4154454D: { // META case 0x4154454D: { // META
std::uint8_t* buf = new std::uint8_t[chunk_size]; std::uint8_t* buf = new std::uint8_t[chunk_size];
in.read((char*)buf, chunk_size); in.read(reinterpret_cast<char*>(buf), chunk_size);
std::uint32_t i(0); std::uint32_t i(0);
char* pc((char*)buf); char* pc(reinterpret_cast<char*>(buf));
while (i++ < chunk_size) { while (i++ < chunk_size) {
if (*pc == '\t') { if (*pc == '\t') {
printf(": "); printf(": ");
@ -170,6 +219,7 @@ bool WozFile::load(const std::string& filePath) {
} }
break; break;
default: { // unknown type of chunk; safely skip past it and ignore it default: { // unknown type of chunk; safely skip past it and ignore it
// TODO save all unknown chunks and write out during save (at end of file)
in.seekg(chunk_size, in.cur); in.seekg(chunk_size, in.cur);
} }
break; break;
@ -189,6 +239,22 @@ bool WozFile::load(const std::string& filePath) {
this->loaded = true; this->loaded = true;
this->modified = false; this->modified = false;
// TODO if the file is not write protected, then "fill out" our tracks:
// 1. create new TRKS entries and full copies of duplicate TMAP pointers
// 2. create new TRKS entries (and fill with zeroes) for FF entries in TMAP. (with length of avg of adjacent tracks?)
//
// ...
// 6.50: FF x 08 - 00 00 00
// 6.75: 02 \ 02 - d5 aa 96
// 7.00: 02 - d5 aa 96 03 - d5 aa 96
// 7.25: 02 / 04 - d5 aa 96
// 7.50: FF x 09 - 00 00 00
// 7.75: 05 \ 05 - d5 aa 96
// 8.00: 05 - d5 aa 96 06 - d5 aa 96
// 8.25: 05 / 07 - d5 aa 96
// 8.50: FF x 0A - 00 00 00
// ...
return true; return true;
} }
@ -221,9 +287,18 @@ void WozFile::unload() {
this->loaded = false; this->loaded = false;
this->filePath = ""; this->filePath = "";
this->modified = false; this->modified = false;
if (this->tmap) {
delete [] this->tmap;
this->tmap = 0;
}
for (int i(0); i < C_QTRACK; ++i) {
if (this->trk[i]) {
delete [] this->trk[i];
this->trk[i] = 0;
}
}
} }
static std::uint8_t bc(std::uint8_t bit) { static std::uint8_t bc(std::uint8_t bit) {
switch (bit) { switch (bit) {
case 0x80u: return 0u; case 0x80u: return 0u;
@ -266,6 +341,11 @@ void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) {
return; // there's no disk to rotate return; // there's no disk to rotate
} }
if (C_QTRACK <= currentQuarterTrack) {
printf("attempt to move to illegal track.\n");
return;
}
// Move to next bit // Move to next bit
this->bit >>= 1; this->bit >>= 1;
@ -288,11 +368,11 @@ void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) {
// previous, based on each track's length (tracks can be of // previous, based on each track's length (tracks can be of
// different lengths in the WOZ image). // different lengths in the WOZ image).
if (currentQuarterTrack != this->lastQuarterTrack) { if (currentQuarterTrack != this->lastQuarterTrack) {
double oldLen = this->trk_bits[this->tmap[this->lastQuarterTrack]]; const double oldLen = this->trk_bits[this->tmap[this->lastQuarterTrack]];
double newLen = this->trk_bits[this->tmap[currentQuarterTrack]]; const double newLen = this->trk_bits[this->tmap[currentQuarterTrack]];
double ratio = newLen/oldLen; const double ratio = newLen/oldLen;
if (!(fabs(1-ratio) < 0.0001f)) { if (!(fabs(1-ratio) < 0.0001)) {
std::uint16_t newBit = (this->byt*8+bc(this->bit)) * ratio; const std::uint16_t newBit = static_cast<std::uint16_t>(round((this->byt*8+bc(this->bit)) * ratio));
this->byt = newBit / 8; this->byt = newBit / 8;
this->bit = cb(newBit % 8); this->bit = cb(newBit % 8);
} }
@ -306,7 +386,7 @@ void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) {
this->byt = 0; this->byt = 0;
this->bit = 0x80u; this->bit = 0x80u;
} }
} }
@ -321,12 +401,7 @@ bool WozFile::getBit(std::uint8_t currentQuarterTrack) {
return false; // empty track return false; // empty track
} }
if (this->c_trks <= this->tmap[currentQuarterTrack]) { // shouldn't happen return this->trk[this->tmap[currentQuarterTrack]][this->byt] & this->bit;
printf("INVALID quarterTrack %d mapped to TRKS index %d (count of tracks: %d)\n", currentQuarterTrack, this->tmap[currentQuarterTrack], this->c_trks);
return false;
}
return this->trks[this->tmap[currentQuarterTrack]][this->byt] & this->bit;
} }
void WozFile::setBit(std::uint8_t currentQuarterTrack, bool on) { void WozFile::setBit(std::uint8_t currentQuarterTrack, bool on) {
@ -338,19 +413,11 @@ void WozFile::setBit(std::uint8_t currentQuarterTrack, bool on) {
return; // write-protected return; // write-protected
} }
if (this->c_trks <= this->tmap[currentQuarterTrack]) {
return; // TODO track doesn't exist: create a new one
}
if (this->tmap[currentQuarterTrack] == 0xFFu) {
// track does not exist, create new one
}
// TODO extend track length if needed????
if (on) { if (on) {
this->trks[this->tmap[currentQuarterTrack]][this->byt] |= this->bit; this->trk[this->tmap[currentQuarterTrack]][this->byt] |= this->bit;
} else { } else {
this->trks[this->tmap[currentQuarterTrack]][this->byt] &= ~this->bit; this->trk[this->tmap[currentQuarterTrack]][this->byt] &= ~this->bit;
} }
// TODO: also write preceding and following quarter tracks (at relative position, and if they exist)
} }

View File

@ -23,14 +23,25 @@
#include <string> #include <string>
#include <cstdint> #include <cstdint>
static const std::uint8_t C_QTRACK(160);
/** /**
* @brief WOZ file (floppy disk image) * @brief WOZ file (floppy disk image)
* Represents a floppy disk. We only handle 5.25" disks. * Represents a floppy disk. We only handle 5.25" disks.
* The disk has 141 quarter-track possible positions, but * A standard disk has 141 quarter-track possible positions
* typically will have 35 tracks. * (0.00, 0.25, 0.5, 0.75, 1.00, ..., 33.5, 33.75, 34.00),
* but will typically have 35 tracks (0.00, 1.00, ..., 34.00).
*
* Non-standard disks can have more tracks. Especially, modified DOSes
* exists that can easily handle one additional track. Also, the WOZ2
* specification allows for more tracks.
*
* Here we handle what the WOZ2 specification can handle, that is,
* 160 quarter-track positions, tracks 0.00 through 39.75.
*
* The floppy disk "knows" it's rotational position. * The floppy disk "knows" it's rotational position.
* *
* Note, the floppy has no notion of the current track; * Note, the floppy disk has no concept of a "current" track;
* rather, that information is known by the stepper motor and arm. * rather, that information is known by the stepper motor and arm.
*/ */
class WozFile { class WozFile {
@ -45,17 +56,24 @@ class WozFile {
// indicates that the floppy was write-protected. // indicates that the floppy was write-protected.
bool writable; bool writable;
std::uint8_t tmap[141]; // quarter-tracks from T0 through T35, values are indexes into trks // map of quarter-tracks from T00.00 through T39.75, values are indexes into trk
std::uint8_t c_trks; // count of actual tracks: std::uint8_t* tmap;
std::uint8_t trks[141][6646]; // 141 is theoretical max; will always be less // first actual quarter-track (e.g., 0 for normal 35-track disk), or 0xFF if no tracks
std::uint16_t trk_bits[141]; // count of bits in each track std::uint8_t initalQtrack;
// last actual quarter-track (e.g., 136 for normal 35-track disk), or 0xFF if no tracks
std::uint8_t finalQtrack;
// array of track buffers (null if no track data)
std::uint8_t* trk[C_QTRACK];
// count of bits in each track
std::uint32_t trk_bits[C_QTRACK];
// bit and byt together represent the rotational position // bit and byt together represent the rotational position
// of the floppy disk. // of the floppy disk.
// bit is a mask indicating current bit within current byte (byt). // bit is a mask indicating current bit within current byte (byt).
// WOZ file bits are packed into bytes starting at bit 7 // WOZ file bits are packed into bytes starting at bit 7
// through bit 0 of the first byte, then bits 7-0 of the // through bit 0 of the first byte, then bits 7-0 of the
// next byte, etc., from bits 0 through trk_bits-1 // next byte, etc., from bits 0 through trk_bits[qt]-1
std::uint8_t bit; std::uint8_t bit;
std::uint16_t byt; std::uint16_t byt;
@ -65,6 +83,14 @@ class WozFile {
// in the WOZ file spec. // in the WOZ file spec.
std::uint8_t lastQuarterTrack; std::uint8_t lastQuarterTrack;
// optimal timing, in "one-eighth microseconds per bit" units
// standard is 32 ( = 4 microseconds per bit)
std::uint8_t timing;
bool sync;
bool cleaned;
std::string creator;
void checkForWriteProtection(); void checkForWriteProtection();
public: public: