From 117d4af8ef8fe372832cb3ef421a0f5e4c893114 Mon Sep 17 00:00:00 2001 From: Christopher Mosher Date: Sun, 23 Dec 2018 16:44:30 -0500 Subject: [PATCH] initial support for WOZ 2.0 disk image file specification --- src/lss.cpp | 32 +++--- src/main.cpp | 55 +--------- src/steppermotor.h | 8 +- src/wozfile.cpp | 257 ++++++++++++++++++++++++++++----------------- src/wozfile.h | 42 ++++++-- 5 files changed, 218 insertions(+), 176 deletions(-) diff --git a/src/lss.cpp b/src/lss.cpp index 9eefddd..39a6bd0 100644 --- a/src/lss.cpp +++ b/src/lss.cpp @@ -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() { diff --git a/src/main.cpp b/src/main.cpp index e81a8bc..beadb39 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,8 @@ #include #include + + 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 diff --git a/src/steppermotor.h b/src/steppermotor.h index d892945..11b7d51 100644 --- a/src/steppermotor.h +++ b/src/steppermotor.h @@ -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; diff --git a/src/wozfile.cpp b/src/wozfile.cpp index 70c97ba..bab5d56 100644 --- a/src/wozfile.cpp +++ b/src/wozfile.cpp @@ -24,7 +24,27 @@ #include #include -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(buf), chunk_size); std::uint32_t i(0); - char* pc((char*)buf); + char* pc(reinterpret_cast(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(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) } diff --git a/src/wozfile.h b/src/wozfile.h index ae2a8bb..679e41e 100644 --- a/src/wozfile.h +++ b/src/wozfile.h @@ -23,14 +23,25 @@ #include #include +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: