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

View File

@ -32,6 +32,8 @@
#include <iostream>
#include <iomanip>
static int run(const std::string& config_file) {
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
extern "C"
#endif

View File

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

View File

@ -24,7 +24,27 @@
#include <fstream>
#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();
}
@ -41,13 +61,13 @@ bool WozFile::load(const std::string& filePath) {
unload();
}
std::uint32_t woz1;
in.read((char*)&woz1, sizeof(woz1));
if (woz1 != 0x315A4F57u) {
printf("WOZ1 magic bytes missing.");
throw "WOZ1 magic bytes missing";
std::uint32_t woz2;
in.read((char*)&woz2, sizeof(woz2));
if (woz2 != 0x325A4F57u) {
printf("WOZ2 magic bytes missing.");
throw "WOZ2 magic bytes missing";
}
printf("WOZ1 magic bytes present\n");
printf("WOZ2 magic bytes present\n");
std::uint32_t 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];
in.read((char*)buf, chunk_size);
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);
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);
printf("Write protected?: %s\n", this->writable ? "No" : "Yes");
printf("Imaged with cross-track sync?: %s\n", buf[3]==1 ? "Yes" : "No");
printf("MC3470 fake bits removed?: %s\n", buf[4]==1 ? "Yes" : "No");
this->sync = buf[3]==1;
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);
this->timing = buf[39];
delete[] buf;
}
break;
case 0x50414D54: { // TMAP
std::uint8_t* buf = new std::uint8_t[chunk_size];
in.read((char*)buf, chunk_size);
if (!five_25) {
printf("Can only handle 5.25 floppy disk images.\n");
throw "Can only handle 5.25 floppy disk images.";
} else {
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];
}
this->tmap = new std::uint8_t[chunk_size];
in.read((char*)this->tmap, chunk_size);
this->initalQtrack = 0;
while (this->initalQtrack < chunk_size && this->tmap[this->initalQtrack] == 0xFFu) {
++this->initalQtrack;
}
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;
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];
in.read((char*)buf, chunk_size);
if (chunk_size % 6656) {
printf("chunk size is not an even multiple of 6656.");
}
this->c_trks = chunk_size / 6656;
printf("Count of tracks: 0x%02X\n", this->c_trks);
if (this->c_trks > 141) {
printf("Error: cannot handle more than 141 tracks.");
throw "Error: cannot handle more than 141 tracks";
}
for (std::uint8_t t(0); t < this->c_trks; ++t) {
printf("track 0x%02X:\n", t);
std::uint16_t usedBytes = *(std::uint16_t*)&buf[t*6656+6646+0];
printf(" used bytes: 0x%0X\n", usedBytes);
this->trk_bits[t] = *(std::uint16_t*)&buf[t*6656+6646+2];
printf(" count of bits: 0x%0X\n", this->trk_bits[t]);
std::uint16_t spliceBit = *(std::uint16_t*)&buf[t*6656+6646+4];
if (spliceBit == 0xFFFFu) {
printf(" no splice information exists\n");
} else {
printf(" bit after splice point: 0x%0X\n", spliceBit);
}
std::uint8_t spliceNib = *(std::uint8_t*)&buf[t*6656+6646+6];
printf(" Nibble value to use for splice: 0x%0X\n", spliceNib);
std::uint8_t cSpliceBit = *(std::uint8_t*)&buf[t*6656+6646+7];
printf(" Bit count of splice nibble: 0x%0X\n", cSpliceBit);
std::uint8_t *base = (std::uint8_t*)&buf[t*6656+0];
std::uint8_t *pd = base;
printf(" beginning of data: 0x: ");
for (int cd(0); cd < 32; ++cd) {
printf("%02X", *pd++);
}
printf("\n");
for (int i(0); i < 6646; ++i) {
this->trks[t][i] = *base++;
std::uint8_t* te = buf;
for (std::uint8_t qt(0); qt < C_QTRACK; ++qt) {
struct trk_t ts;
ts.blockFirst = *((std::uint16_t*)te)-3;
te += 2;
ts.blockCount = *((std::uint16_t*)te);
te += 2;
ts.bitCount = *((std::uint32_t*)te);
te += 4;
if (ts.blockCount) {
printf("TRK index %02X: start byte in BITS %08x; %08x bytes; %08x bits ", qt, ts.blockFirst<<9, ts.blockCount<<9, ts.bitCount);
this->trk_bits[qt] = ts.bitCount;
this->trk[qt] = new std::uint8_t[ts.blockCount<<9];
memcpy(this->trk[qt], buf+C_QTRACK*8+(ts.blockFirst<<9), ts.blockCount<<9);
printf("("
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
BYTE_TO_BINARY_PATTERN
"...)\n",
BYTE_TO_BINARY(this->trk[qt][0]),
BYTE_TO_BINARY(this->trk[qt][1]),
BYTE_TO_BINARY(this->trk[qt][2]),
BYTE_TO_BINARY(this->trk[qt][3]),
BYTE_TO_BINARY(this->trk[qt][4]),
BYTE_TO_BINARY(this->trk[qt][5]),
BYTE_TO_BINARY(this->trk[qt][6]),
BYTE_TO_BINARY(this->trk[qt][7]));
}
}
delete[] buf;
@ -155,9 +204,9 @@ bool WozFile::load(const std::string& filePath) {
break;
case 0x4154454D: { // META
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);
char* pc((char*)buf);
char* pc(reinterpret_cast<char*>(buf));
while (i++ < chunk_size) {
if (*pc == '\t') {
printf(": ");
@ -170,6 +219,7 @@ bool WozFile::load(const std::string& filePath) {
}
break;
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);
}
break;
@ -189,6 +239,22 @@ bool WozFile::load(const std::string& filePath) {
this->loaded = true;
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;
}
@ -221,9 +287,18 @@ void WozFile::unload() {
this->loaded = false;
this->filePath = "";
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) {
switch (bit) {
case 0x80u: return 0u;
@ -266,6 +341,11 @@ void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) {
return; // there's no disk to rotate
}
if (C_QTRACK <= currentQuarterTrack) {
printf("attempt to move to illegal track.\n");
return;
}
// Move to next bit
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
// different lengths in the WOZ image).
if (currentQuarterTrack != this->lastQuarterTrack) {
double oldLen = this->trk_bits[this->tmap[this->lastQuarterTrack]];
double newLen = this->trk_bits[this->tmap[currentQuarterTrack]];
double ratio = newLen/oldLen;
if (!(fabs(1-ratio) < 0.0001f)) {
std::uint16_t newBit = (this->byt*8+bc(this->bit)) * ratio;
const double oldLen = this->trk_bits[this->tmap[this->lastQuarterTrack]];
const double newLen = this->trk_bits[this->tmap[currentQuarterTrack]];
const double ratio = newLen/oldLen;
if (!(fabs(1-ratio) < 0.0001)) {
const std::uint16_t newBit = static_cast<std::uint16_t>(round((this->byt*8+bc(this->bit)) * ratio));
this->byt = newBit / 8;
this->bit = cb(newBit % 8);
}
@ -306,7 +386,7 @@ void WozFile::rotateOneBit(std::uint8_t currentQuarterTrack) {
this->byt = 0;
this->bit = 0x80u;
}
}
}
@ -321,12 +401,7 @@ bool WozFile::getBit(std::uint8_t currentQuarterTrack) {
return false; // empty track
}
if (this->c_trks <= this->tmap[currentQuarterTrack]) { // shouldn't happen
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;
return this->trk[this->tmap[currentQuarterTrack]][this->byt] & this->bit;
}
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
}
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) {
this->trks[this->tmap[currentQuarterTrack]][this->byt] |= this->bit;
this->trk[this->tmap[currentQuarterTrack]][this->byt] |= this->bit;
} 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 <cstdint>
static const std::uint8_t C_QTRACK(160);
/**
* @brief WOZ file (floppy disk image)
* Represents a floppy disk. We only handle 5.25" disks.
* The disk has 141 quarter-track possible positions, but
* typically will have 35 tracks.
* A standard disk has 141 quarter-track possible positions
* (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.
*
* 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.
*/
class WozFile {
@ -45,17 +56,24 @@ class WozFile {
// indicates that the floppy was write-protected.
bool writable;
std::uint8_t tmap[141]; // quarter-tracks from T0 through T35, values are indexes into trks
std::uint8_t c_trks; // count of actual tracks:
std::uint8_t trks[141][6646]; // 141 is theoretical max; will always be less
std::uint16_t trk_bits[141]; // count of bits in each track
// map of quarter-tracks from T00.00 through T39.75, values are indexes into trk
std::uint8_t* tmap;
// first actual quarter-track (e.g., 0 for normal 35-track disk), or 0xFF if no tracks
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
// of the floppy disk.
// bit is a mask indicating current bit within current byte (byt).
// WOZ file bits are packed into bytes starting at bit 7
// 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::uint16_t byt;
@ -65,6 +83,14 @@ class WozFile {
// in the WOZ file spec.
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();
public: