diff --git a/Makefile b/Makefile index ed0988b..218b3c4 100755 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ CXXFLAGS=-Wall -I/usr/include/SDL2 -I .. -I . -I apple -I nix -I sdl -I/usr/loca TSRC=cpu.cpp util/testharness.cpp -COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o LRingBuffer.o globals.o apple/parallelcard.o apple/fx80.o lcg.o apple/hd32.o images.o apple/appleui.o vmram.o bios.o apple/noslotclock.o +COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o LRingBuffer.o globals.o apple/parallelcard.o apple/fx80.o lcg.o apple/hd32.o images.o apple/appleui.o vmram.o bios.o apple/noslotclock.o apple/woz.o apple/crc32.o apple/woz-serializer.o FBOBJS=linuxfb/linux-speaker.o linuxfb/fb-display.o linuxfb/linux-keyboard.o linuxfb/fb-paddles.o nix/nix-filemanager.o linuxfb/aiie.o linuxfb/linux-printer.o nix/nix-clock.o nix/nix-prefs.o diff --git a/apple/crc32.c b/apple/crc32.c new file mode 100644 index 0000000..e0471db --- /dev/null +++ b/apple/crc32.c @@ -0,0 +1,90 @@ +#include "crc32.h" +#include + +#if 1 +// Prepopulated CRC table for the CRC32 polynomial + +static uint32_t preload32[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +void preload_crc() +{ +} +#else +// Dynamically rebuild the CRC table at runtime + +#define poly32 (0xEDB88320L) // CRC32 polynomial +static uint32_t preload32[256]; // Precomputed CRC table for the CRC32 polynomial + +void preload_crc() +{ + int i, j; + uint32_t data; + uint32_t temp1, temp2; + + for (i=0; i<256; i++) { + data=0; + temp2=i; + for (j=0; j<8; j++) { + temp1=(data^temp2)&1; + data = (data>>1) ^ (temp1*poly32); + temp2>>=1; + } + preload32[i] = data; + } + +} +#endif + +uint32_t compute_crc_32(unsigned char *buffer, unsigned long length) +{ + uint32_t ret = ~0U; + + for (unsigned long i=0; i>8) ^ preload32[(ret ^ buffer[i])&0xFF]; + + return ret ^ ~0U; +} diff --git a/apple/crc32.h b/apple/crc32.h new file mode 100644 index 0000000..d763429 --- /dev/null +++ b/apple/crc32.h @@ -0,0 +1,12 @@ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void preload_crc(); +uint32_t compute_crc_32(unsigned char *buffer, unsigned long length); + +#ifdef __cplusplus +}; +#endif diff --git a/apple/diskii.cpp b/apple/diskii.cpp index e55c1cf..febc3b1 100644 --- a/apple/diskii.cpp +++ b/apple/diskii.cpp @@ -21,34 +21,33 @@ DiskII::DiskII(AppleMMU *mmu) { - this->trackBuffer = new LRingBuffer(NIBTRACKSIZE); - this->mmu = mmu; curPhase[0] = curPhase[1] = 0; curHalfTrack[0] = curHalfTrack[1] = 0; - - trackToFlush = -1; + curWozTrack[0] = curWozTrack[1] = 0xFF; writeMode = false; writeProt = false; // FIXME: expose an interface to this readWriteLatch = 0x00; + sequencer = 0; + dataRegister = 0; + lastDiskRead[0] = lastDiskRead[1] = 0; - disk[0] = disk[1] = -1; + disk[0] = disk[1] = NULL; indicatorIsOn[0] = indicatorIsOn[1] = 0; selectedDisk = 0; - diskType[0] = diskType[1] = dosDisk; } DiskII::~DiskII() { - delete this->trackBuffer; this->trackBuffer = NULL; } bool DiskII::Serialize(int8_t fd) { - /* Make sure to flush anything to disk first */ - checkFlush(curHalfTrack[selectedDisk]>>1); + return false; + + // FIXME: all the new variables are missing g_filemanager->writeByte(fd, DISKIIMAGIC); @@ -62,19 +61,21 @@ bool DiskII::Serialize(int8_t fd) g_filemanager->writeByte(fd, writeMode); g_filemanager->writeByte(fd, writeProt); - // Don't save disk[0,1]; save their file names & cursors - g_filemanager->SerializeFile(fd, disk[0]); - g_filemanager->SerializeFile(fd, disk[1]); + for (int i=0; i<2; i++) { + if (disk[i]) { + g_filemanager->writeByte(fd, 1); + } else { + g_filemanager->writeByte(fd, 0); + } + if (!disk[i]->Serialize(fd)) + return false; + } g_filemanager->writeByte(fd, indicatorIsOn[0]); g_filemanager->writeByte(fd, indicatorIsOn[1]); - g_filemanager->writeByte(fd, diskType[0]); - g_filemanager->writeByte(fd, diskType[1]); g_filemanager->writeByte(fd, selectedDisk); - trackBuffer->Serialize(fd); - g_filemanager->writeByte(fd, DISKIIMAGIC); return true; @@ -82,8 +83,8 @@ bool DiskII::Serialize(int8_t fd) bool DiskII::Deserialize(int8_t fd) { - /* Make sure to flush anything to disk first */ - checkFlush(curHalfTrack[selectedDisk]>>1); + return false; + // FIXME: all the new variables are missing if (g_filemanager->readByte(fd) != DISKIIMAGIC) { return false; @@ -99,22 +100,23 @@ bool DiskII::Deserialize(int8_t fd) writeMode = g_filemanager->readByte(fd); writeProt = g_filemanager->readByte(fd); - disk[0] = g_filemanager->DeserializeFile(fd); - disk[1] = g_filemanager->DeserializeFile(fd); + for (int i=0; i<2; i++) { + if (disk[i]) + delete disk[i]; + if (g_filemanager->readByte(fd) == 1) { + disk[i] = new WozSerializer(); + if (!disk[i]->Deserialize(fd)) + return false; + } else { + disk[i] = NULL; + } + } indicatorIsOn[0] = g_filemanager->readByte(fd); indicatorIsOn[1] = g_filemanager->readByte(fd); - diskType[0] = g_filemanager->readByte(fd); - diskType[1] = g_filemanager->readByte(fd); - selectedDisk = g_filemanager->readByte(fd); - trackBuffer->Deserialize(fd); - - // Reset the dirty caches and whatnot - trackToFlush = -1; - if (g_filemanager->readByte(fd) != DISKIIMAGIC) { return false; } @@ -127,8 +129,6 @@ void DiskII::Reset() curPhase[0] = curPhase[1] = 0; curHalfTrack[0] = curHalfTrack[1] = 0; - trackToFlush = -1; - writeMode = false; writeProt = false; // FIXME: expose an interface to this readWriteLatch = 0x00; @@ -137,17 +137,12 @@ void DiskII::Reset() ejectDisk(1); } -// FIXME: why does this need an argument? -void DiskII::checkFlush(int8_t track) -{ - if (trackToFlush != -1) { - flushTrack(trackToFlush, selectedDisk); - trackToFlush = -1; - } -} - uint8_t DiskII::readSwitches(uint8_t s) { +#if 0 + g_cpu->realtime(); /* cause the CPU to stop processing its outer + * loop b/c the disk might need attention immediately */ +#endif switch (s) { case 0x00: // change stepper motor phase break; @@ -173,11 +168,13 @@ uint8_t DiskII::readSwitches(uint8_t s) case 0x08: // drive off indicatorIsOn[selectedDisk] = 99; g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, false); // FIXME: delay a bit? Queue for later drawing? - checkFlush(curHalfTrack[selectedDisk]>>1); break; case 0x09: // drive on indicatorIsOn[selectedDisk] = 100; g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, true); // FIXME: delay a bit? Queue for later drawing? + + // Start the given disk drive spinning + lastDiskRead[selectedDisk] = g_cpu->cycles; break; case 0x0A: // select drive 1 @@ -189,6 +186,8 @@ uint8_t DiskII::readSwitches(uint8_t s) case 0x0C: // shift one read or write byte readWriteLatch = readOrWriteByte(); + if (readWriteLatch & 0x80) + sequencer = 0; break; case 0x0D: // load data register (latch) @@ -213,7 +212,7 @@ uint8_t DiskII::readSwitches(uint8_t s) // FIXME: improve the spin-down here. We need a CPU cycle callback // for some period of time instead of this silly decrement counter. if (!indicatorIsOn[selectedDisk]) { - // printf("Unexpected read while disk isn't on?\n"); + printf("Unexpected read while disk isn't on?\n"); indicatorIsOn[selectedDisk] = 100; g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, true); // FIXME: queue for later drawing? } @@ -221,6 +220,9 @@ uint8_t DiskII::readSwitches(uint8_t s) // slowly spin it down... if (--indicatorIsOn[selectedDisk] == 0) { g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, false); // FIXME: queue for later drawing? + + // Stop the given disk drive spinning + lastDiskRead[selectedDisk] = 0; // FIXME: magic value. We need a tristate for this. } } @@ -267,10 +269,12 @@ void DiskII::writeSwitches(uint8_t s, uint8_t v) break; case 0x0C: // shift one read or write byte - readOrWriteByte(); + if (readOrWriteByte() & 0x80) + sequencer = 0; break; case 0x0D: // drive write + // FIXME break; case 0x0E: // set read mode @@ -331,7 +335,7 @@ void DiskII::setPhase(uint8_t phase) curHalfTrack[selectedDisk] += _phase_delta[(prevPhase * 4) + phase]; curPhase[selectedDisk] = phase; - + // Cap at 35 tracks (a normal disk size). Some drives let you go farther, // and we could support that by increasing this limit - but the images // would be different too, so there would be more work to abstract out... @@ -345,20 +349,12 @@ void DiskII::setPhase(uint8_t phase) // recalibrate! This is where the fun noise goes DaDaDaDaDaDaDaDaDa } - /* - printf("phase %d => %d; curHalfTrack %d => %d\n", - prevPhase, curPhase[selectedDisk], - prevHalfTrack, curHalfTrack[selectedDisk]); - */ - - if (curHalfTrack[selectedDisk]>>1 != prevHalfTrack>>1) { + if (curHalfTrack[selectedDisk] != prevHalfTrack) { // We're changing track - flush the old track back to disk - checkFlush(prevHalfTrack>>1); + printf("track change\n"); - // Prime the cache by reading the current track - if (disk[selectedDisk] != -1) { - readDiskTrack(selectedDisk, curHalfTrack[selectedDisk]>>1); - } + curWozTrack[selectedDisk] = disk[selectedDisk]->trackNumberForQuarterTrack(curHalfTrack[selectedDisk]*2); + printf("New half: %d; track: %d\n", curHalfTrack[selectedDisk]*2, curWozTrack[selectedDisk]); } } @@ -399,36 +395,22 @@ static bool _endsWithI(const char *s1, const char *s2) void DiskII::insertDisk(int8_t driveNum, const char *filename, bool drawIt) { ejectDisk(driveNum); - disk[driveNum] = g_filemanager->openFile(filename); + + disk[driveNum] = new WozSerializer(); + disk[driveNum]->readFile(filename); // FIXME error checking + + curWozTrack[driveNum] = disk[driveNum]->trackNumberForQuarterTrack(curHalfTrack[driveNum]*2); + printf("Cur track: %d\n", curWozTrack[driveNum]); + if (drawIt) g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, false); - - if (_endsWithI(filename, ".nib")) { - diskType[driveNum] = nibDisk; - } else if (_endsWithI(filename, ".po")) { - diskType[driveNum] = prodosDisk; - } else { - diskType[driveNum] = dosDisk; -#ifndef TEENSYDUINO - // debugging: make a nib copy of the image to play with - // convertDskToNib("/tmp/debug.nib"); -#endif - } - - if (driveNum == selectedDisk) { - readDiskTrack(selectedDisk, curHalfTrack[selectedDisk]>>1); - } } void DiskII::ejectDisk(int8_t driveNum) { - if (disk[driveNum] != -1) { - if (selectedDisk == driveNum) { - checkFlush(0); // FIXME: bogus argument - trackBuffer->clear(); - } - g_filemanager->closeFile(disk[driveNum]); - disk[driveNum] = -1; + if (disk[driveNum]) { + delete disk[driveNum]; + disk[driveNum] = NULL; g_ui->drawOnOffUIElement(UIeDisk1_state + driveNum, true); } } @@ -438,125 +420,66 @@ void DiskII::select(int8_t which) if (which != 0 && which != 1) return; - if (which != selectedDisk) { - indicatorIsOn[selectedDisk] = 0; - g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, false); // FIXME: queue for later drawing? + printf("Select disk %d\n", which); - checkFlush(curHalfTrack[selectedDisk]>>1); + if (which != selectedDisk) { + indicatorIsOn[selectedDisk] = 100; // spindown time (fixme) + g_ui->drawOnOffUIElement(UIeDisk1_activity + selectedDisk, false); // FIXME: queue for later drawing? // set the selected disk drive selectedDisk = which; - - // Preread the current track - if (disk[selectedDisk] != -1) { - readDiskTrack(selectedDisk, curHalfTrack[selectedDisk]>>1); - } } + + // Update the current woz track for the given disk drive + curWozTrack[selectedDisk] = + disk[selectedDisk]->trackNumberForQuarterTrack(curHalfTrack[selectedDisk]*2); + printf("Cur Woz track is %d\n", curWozTrack[selectedDisk]); + } uint8_t DiskII::readOrWriteByte() { - if (disk[selectedDisk] == -1) { - return GAP; + if (!disk[selectedDisk]) { + printf("reading from uninserted disk\n"); + return 0xFF; } + // FIXME: not handling writes at all at the moment *** if (writeMode && !writeProt) { - - if (!trackBuffer->hasData()) { - // Error: writing to empty track buffer? That's a raw write w/o - // knowing where we are on the disk. Dangerous, at very least; - // I'm not sure what the best action would be here. For the - // moment, just refuse to write it. - - g_display->debugMsg("DII: unguarded write"); - return GAP; - } - - trackToFlush = curHalfTrack[selectedDisk]>>1; - // It's possible that a badly behaving OS could try to write more - // data than we have buffer to handle. Don't let it. We should - // only need something like 500 bytes, at worst. In the typical - // case, we're talking about something like - // - // ~5 bytes of GAP - // 3 bytes of sector prolog - // 2 bytes of volume - // 2 bytes of track - // 2 bytes of sector - // 2 bytes of checksum - // 2 bytes of epilog - // ~5 bytes of GAP - // 3 bytes of data prolog - // 342 bytes of GRP-encoded (6:2) data - // 1 byte of checksum - // 3 bytes of epilog - // 1 byte of GAP - // == 373 bytes - // - // ... so if we get up to the full 1024 we've allocated, there's - // something suspicious happening. - - if (readWriteLatch < 0x96) { - // Can't write a de-nibblized byte... - g_display->debugMsg("DII: bad write"); - return 0; - } - - trackBuffer->replaceByte(readWriteLatch); - return 0; } - // return 0x00 every other byte. Helps the logic sequencer stay in sync. - // Otherwise we wind up waiting long periods of time for it to sync up, - // presumably because we're overrunning it (returning data faster than - // the actual drive would be able to)? - static bool whitespace = false; - if (whitespace) { - whitespace = false; - return 0x00; + uint32_t curCycles = g_cpu->cycles; + bool updateCycles = false; + + if (lastDiskRead[selectedDisk] == 0) { + // assume it's a first-read-after-spinup; return the first valid data + sequencer = disk[selectedDisk]->nextDiskByte(curWozTrack[selectedDisk]); + updateCycles = true; + goto done; } - whitespace = !whitespace; + // Otherwise we figure out how many cycles we missed since the last + // disk read, and pop the right number of bits off the woz track + uint32_t missedCycles; + missedCycles = curCycles - lastDiskRead[selectedDisk]; - uint8_t ret = trackBuffer->peekNext(); - return ret; -} - -void DiskII::readDiskTrack(int8_t diskWeAreUsing, int8_t trackWeAreReading) -{ - checkFlush(trackWeAreReading); // FIXME: bogus argument - - trackBuffer->clear(); - trackBuffer->setPeekCursor(0); - - if (diskType[diskWeAreUsing] == nibDisk) { - // Read one nibblized sector at a time and jam it in trackBuf - // directly. We don't read the whole track at once only because - // of RAM constraints on the Teensy. There's no reason we - // couldn't, though, if RAM weren't at a premium. - - for (int i=0; i<16; i++) { - g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16 + i, true); - if (!g_filemanager->readBlock(disk[diskWeAreUsing], rawTrackBuffer, true)) { - // FIXME: error handling? - g_display->debugMsg("DII: FM nib read failure"); - return; - } - trackBuffer->addBytes(rawTrackBuffer, 416); - } - } else { - // It's a .dsk / .po disk image. Read the whole track in to - // rawTrackBuffer and nibblize it. - g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16, false); - if (!g_filemanager->readTrack(disk[diskWeAreUsing], rawTrackBuffer, false)) { - // FIXME: error handling? - g_display->debugMsg("DII: FM block read failure"); - return; - } - - nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[diskWeAreUsing], curHalfTrack[selectedDisk]>>1); + missedCycles >>= 2; + if (missedCycles) + updateCycles = true; + while (missedCycles) { + sequencer <<= 1; + sequencer |= disk[selectedDisk]->nextDiskBit(curWozTrack[selectedDisk]); + missedCycles--; } + + done: + if (updateCycles) { + // We only update the lastDiskRead counter if the number of passed + // cycles indicates that we did some sort of work... + lastDiskRead[selectedDisk] = curCycles; + } + return sequencer; } void DiskII::fillDiskBuffer() @@ -565,8 +488,7 @@ void DiskII::fillDiskBuffer() const char *DiskII::DiskName(int8_t num) { - if (disk[num] != -1) - return g_filemanager->fileName(disk[num]); + // *** return ""; } @@ -592,38 +514,5 @@ void DiskII::flushTrack(int8_t track, int8_t sel) return; } - if (!trackBuffer->hasData()) { - // Dunno what happened - we're writing but haven't initialized the sector buffer? - g_display->debugMsg("DII: uninit'd write"); - return; - } - - if (diskType[sel] == nibDisk) { - // Write the whole track out exactly as we've got it. Hopefully - // someone has re-calcuated appropriate checksums on it... - g_display->debugMsg("DII: Not writing Nib image"); - return; - } - - nibErr e = denibblizeTrack(trackBuffer, rawTrackBuffer, diskType[sel], curHalfTrack[selectedDisk]>>1); - switch (e) { - case errorShortTrack: - g_display->debugMsg("DII: short track"); - trackBuffer->clear(); - return; - - case errorMissingSectors: - // The nibblized track doesn't contain all possible sectors - so it's broken. Drop the write. - g_display->debugMsg("DII: missing sectors"); - trackBuffer->clear(); - break; - - case errorNone: - break; - } - - // ok, write the track! - g_filemanager->seekBlock(disk[sel], track * 16); - g_filemanager->writeTrack(disk[sel], rawTrackBuffer); + // *** } - diff --git a/apple/diskii.h b/apple/diskii.h index 1570b30..44c4889 100644 --- a/apple/diskii.h +++ b/apple/diskii.h @@ -8,6 +8,8 @@ #include #endif +#include "woz-serializer.h" + #include "filemanager.h" #include "applemmu.h" #include "slot.h" @@ -44,28 +46,24 @@ class DiskII : public Slot { void select(int8_t which); // 0 or 1 for drives 1 and 2, respectively uint8_t readOrWriteByte(); - void checkFlush(int8_t track); - void readDiskTrack(int8_t diskWeAreUsing, int8_t trackWeAreReading); - #ifndef TEENSYDUINO void convertDskToNib(const char *outFN); #endif private: volatile int8_t curHalfTrack[2]; + volatile uint8_t curWozTrack[2]; volatile int8_t curPhase[2]; uint8_t readWriteLatch; - LRingBuffer *trackBuffer; // nibblized data - uint8_t rawTrackBuffer[4096]; // not nibblized data + uint8_t sequencer, dataRegister; // diskII logic state sequencer vars + WozSerializer *disk[2]; + uint32_t lastDiskRead[2]; bool writeMode; bool writeProt; AppleMMU *mmu; - int8_t disk[2]; volatile uint8_t indicatorIsOn[2]; - uint8_t diskType[2]; - volatile int8_t trackToFlush; // -1 when there's none volatile int8_t selectedDisk; }; diff --git a/apple/disktypes.h b/apple/disktypes.h new file mode 100644 index 0000000..21b3c30 --- /dev/null +++ b/apple/disktypes.h @@ -0,0 +1,12 @@ +#ifndef __DISKTYPES_H +#define __DISKTYPES_H + +enum { + T_AUTO = 0, + T_WOZ = 1, + T_NIB = 2, + T_DSK = 3, + T_PO = 4 +}; + +#endif diff --git a/apple/nibutil.cpp b/apple/nibutil.cpp index 865ca68..8c51499 100644 --- a/apple/nibutil.cpp +++ b/apple/nibutil.cpp @@ -1,143 +1,236 @@ #include "nibutil.h" -#ifdef TEENSYDUINO -#include -#else #include #include #include #include #include -#endif - -// Long gaps are more "correct" in the sense that they're -// nib-disk-like; but they mean the VM has to chew on a lot of disk -// gaps to find the real data, which takes a noticeable amount of -// time. With this off, we present a minimum number of gaps (that -// hopefully aren't too short for the ROM to be able to write -// correctly) -// #define LONGGAPS +#include +#include "disktypes.h" +// Default disk volume identifier #define DISK_VOLUME 254 +// 4-and-4 encoding handlers +#define nib1(a) (((a & 0xAA) >> 1) | 0xAA) +#define nib2(b) (((b & 0x55) ) | 0xAA) +#define denib(a, b) ((((a) & ~0xAA) << 1) | ((b) & ~0xAA)) + +// In 6-and-2 encoding, there are 86 (0x56) 6-bit values +#define SIXBIT_SPAN 0x56 + +typedef struct _bitPtr { + uint16_t idx; + uint8_t bitIdx; +} bitPtr; + +#define INCIDX(p) { p->bitIdx >>= 1; if (!p->bitIdx) {p->bitIdx = 0x80; p->idx++;} } + +const static uint8_t _trans[64] = {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}; + +const static uint8_t _detrans[0x80] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20, + 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34, + 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C, + 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78, + 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84, + 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC, + 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, + 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0, + 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC }; + // dos 3.3 to physical sector conversion -const static uint8_t dephys[16] = { - 0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04, +const static uint8_t dephys[16] = { + 0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04, 0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f }; // Prodos to physical sector conversion -const uint8_t deProdosPhys[] = { +const uint8_t deProdosPhys[] = { 0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b, 0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f }; -const static uint8_t _trans[64] = {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}; - -const static uint8_t _detrans[0x80] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20, - 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34, - 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C, - 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78, - 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84, - 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC, - 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8, - 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0, - 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC }; - -void nibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, - uint8_t diskType, int8_t track) +uint8_t de44(uint8_t nibs[2]) { - int checksum; + return denib(nibs[0], nibs[1]); +} - for (uint8_t sector=0; sector<16; sector++) { +static void _packBit(uint8_t *output, bitPtr *ptr, uint8_t isOn) +{ + if (isOn) + output[ptr->idx] |= ptr->bitIdx; + INCIDX(ptr); +} - for (uint8_t i=0; -#ifdef LONGGAPS - i < (sector==0 ? 0x63 : 0x13); -#else - i < 8; -#endif - i++) { - trackBuffer->addByte(GAP); - } +static void _packGap(uint8_t *output, bitPtr *ptr) +{ + for (int i=0; i<8; i++) + _packBit(output, ptr, 1); + _packBit(output, ptr, 0); + _packBit(output, ptr, 0); +} - trackBuffer->addByte(0xD5); // prolog - trackBuffer->addByte(0xAA); - trackBuffer->addByte(0x96); - - trackBuffer->addByte(nib1(DISK_VOLUME)); - trackBuffer->addByte(nib2(DISK_VOLUME)); - - trackBuffer->addByte(nib1(track)); - trackBuffer->addByte(nib2(track)); - - trackBuffer->addByte(nib1(sector)); - trackBuffer->addByte(nib2(sector)); - - checksum = DISK_VOLUME ^ track ^ sector; - trackBuffer->addByte(nib1(checksum)); - trackBuffer->addByte(nib2(checksum)); - - trackBuffer->addByte(0xDE); // epilog - trackBuffer->addByte(0xAA); - trackBuffer->addByte(0xEB); // Not strictly necessary, but the DiskII controller does it, so we will too. - - // The DiskII controller puts out 5 GAP bytes here. - for (uint8_t i=0; i<5; i++) { - trackBuffer->addByte(GAP); - } - - trackBuffer->addByte(0xD5); // data prolog - trackBuffer->addByte(0xAA); - trackBuffer->addByte(0xAD); - - uint8_t physicalSector = (diskType == prodosDisk ? deProdosPhys[sector] : dephys[sector]); - encodeData(trackBuffer, &rawTrackBuffer[physicalSector * 256]); - - trackBuffer->addByte(0xDE); // data epilog - trackBuffer->addByte(0xAA); - trackBuffer->addByte(0xEB); - -#ifdef LONGGAPS - trackBuffer->addByte(GAP); -#endif +static void _packByte(uint8_t *output, bitPtr *ptr, uint8_t v) +{ + for (int i=0; i<8; i++) { + _packBit(output, ptr, v & (1 << (7-i))); } } -#define SIXBIT_SPAN 0x56 // 86 bytes +// Take 256 bytes of input and turn it in to 343 bytes of nibblized output +static void _encodeData(uint8_t *outputBuffer, bitPtr *ptr, const uint8_t input[256]) +{ + int ptr2 = 0; + int ptr6 = 0x56; + static int nibbles[0x156]; + + memset(nibbles, 0, sizeof(nibbles)); + + int idx2 = 0x55; + for (int idx6 = 0x101; idx6 >= 0; idx6--) { + int val6 = input[idx6 & 0xFF]; + int val2 = nibbles[ptr2 + idx2]; + + val2 = (val2 << 1) | (val6 & 1); + val6 >>= 1; + val2 = (val2 << 1) | (val6 & 1); + val6 >>= 1; + + // There are 2 "extra" bytes of 2-bit data that we add in here. + if (ptr6 + idx6 < 0x156) { + nibbles[ptr6 + idx6] = val6; + } + if (ptr2 + idx2 < 0x156) { + nibbles[ptr2 + idx2] = val2; + } + + if (--idx2 < 0) { + idx2 = 0x55; + } + } + // mask out the "extra" 2-bit data above. Note that the Apple decoders + // don't care about the extra bits, so taking these back out isn't + // operationally important. + nibbles[0x54] &= 0x0F; + nibbles[0x55] &= 0x0F; + + int lastv = 0; + for (int idx = 0; idx < 0x156; idx++) { + int val = nibbles[idx]; + _packByte(outputBuffer, ptr, _trans[lastv ^ val]); + lastv = val; + } + _packByte(outputBuffer, ptr, _trans[lastv]); +} + +static uint8_t _whichBit(uint8_t bitIdx) +{ + switch (bitIdx) { + case 0x80: + return 0; + case 0x40: + return 1; + case 0x20: + return 2; + case 0x10: + return 3; + case 0x08: + return 4; + case 0x04: + return 5; + case 0x02: + return 6; + case 0x01: + return 7; + default: + return 0; // not used + } + /* NOTREACHED */ +} + +// rawTrackBuffer is input (dsk/po format); outputBuffer is encoded +// nibbles (416*16 bytes). Returns the number of bits actually +// encoded. +uint32_t nibblizeTrack(uint8_t outputBuffer[NIBTRACKSIZE], const uint8_t rawTrackBuffer[256*16], + uint8_t diskType, int8_t track) +{ + int checksum; + bitPtr ptr = { 0, 0x80 }; + + for (uint8_t sector=0; sector<16; sector++) { + + for (uint8_t i=0; i<16; i++) { + _packGap(outputBuffer, &ptr); + } + + _packByte(outputBuffer, &ptr, 0xD5); // prolog + _packByte(outputBuffer, &ptr, 0xAA); + _packByte(outputBuffer, &ptr, 0x96); + + _packByte(outputBuffer, &ptr, nib1(DISK_VOLUME)); + _packByte(outputBuffer, &ptr, nib2(DISK_VOLUME)); + + _packByte(outputBuffer, &ptr, nib1(track)); + _packByte(outputBuffer, &ptr, nib2(track)); + + _packByte(outputBuffer, &ptr, nib1(sector)); + _packByte(outputBuffer, &ptr, nib2(sector)); + + checksum = DISK_VOLUME ^ track ^ sector; + _packByte(outputBuffer, &ptr, nib1(checksum)); + _packByte(outputBuffer, &ptr, nib2(checksum)); + + _packByte(outputBuffer, &ptr, 0xDE); // epilog + _packByte(outputBuffer, &ptr, 0xAA); + _packByte(outputBuffer, &ptr, 0xEB); + + for (uint8_t i=0; i<5; i++) { + _packGap(outputBuffer, &ptr); + } + + _packByte(outputBuffer, &ptr, 0xD5); // data prolog + _packByte(outputBuffer, &ptr, 0xAA); + _packByte(outputBuffer, &ptr, 0xAD); + + uint8_t physicalSector = (diskType == T_PO ? deProdosPhys[sector] : dephys[sector]); + _encodeData(outputBuffer, &ptr, &rawTrackBuffer[physicalSector * 256]); + + _packByte(outputBuffer, &ptr, 0xDE); // data epilog + _packByte(outputBuffer, &ptr, 0xAA); + _packByte(outputBuffer, &ptr, 0xEB); + + for (uint8_t i=0; i<16; i++) { + _packGap(outputBuffer, &ptr); + } + } + + return (ptr.idx*8 + _whichBit(ptr.bitIdx)); +} // Pop the next 343 bytes off of trackBuffer, which should be 342 // 6:2-bit GCR encoded values, which we decode back in to 256 8-byte // output values; and one checksum byte. // // Return true if we've successfully consumed 343 bytes from -// trackBuf. This reads from the circular buffer trackBuffer, so if -// there's not enough data there, the results are somewhat -// unpredictable. -bool decodeData(LRingBuffer *trackBuffer, uint16_t startAt, uint8_t *output) +// trackBuf. +static bool _decodeData(const uint8_t trackBuffer[343], uint8_t output[256]) { - // Basic check that there's enough buffer data in trackBuffer. Note - // that we're not checking it against startAt; we could be wrapping - // around. - if (trackBuffer->count() < 343) - return false; - static uint8_t workbuf[342]; for (int i=0; i<342; i++) { - uint8_t in = trackBuffer->peek(startAt++) & 0x7F; // strip high bit + uint8_t in = *(trackBuffer++) & 0x7F; // strip high bit workbuf[i] = _detrans[in]; } @@ -148,13 +241,12 @@ bool decodeData(LRingBuffer *trackBuffer, uint16_t startAt, uint8_t *output) prev = workbuf[i]; } - // Put the checksum on the track - only necessary if we're about to - // write the nibblized version of the track back out - /* uint16_t cursor = trackBuffer->Cursor(); - trackBuffer->setPeekCursor(startAt++); - trackBuffer->replaceByte(prev); // 'prev' holds the checksum - trackBuffer->setPeekCursor(cursor); // put it back where we found it - */ +#if 0 + if (prev != trackBuffer[342]) { + printf("ERROR: checksum of sector is incorrect [0x%X v 0x%X]\n", prev, trackBuffer[342]); + return false; + } +#endif // Start with all of the bytes with 6 bits of data for (uint16_t i=0; i<256; i++) { @@ -173,95 +265,46 @@ bool decodeData(LRingBuffer *trackBuffer, uint16_t startAt, uint8_t *output) output[2*SIXBIT_SPAN + i] |= ((thisbyte & 0x80) >> 7) | ((thisbyte & 0x40) >> 5); } } - // FIXME: check or update the checksum? return true; } -void encodeData(LRingBuffer *trackBuffer, uint8_t *data) -{ - int16_t i; - int ptr2 = 0; - int ptr6 = 0x56; - static int nibbles[0x156]; - - for (i=0; i<0x156; i++) { - nibbles[i] = 0; - } - - int idx2 = 0x55; - for (int idx6 = 0x101; idx6 >= 0; idx6--) { - int val6 = data[idx6 & 0xFF]; - int val2 = nibbles[ptr2 + idx2]; - - val2 = (val2 << 1) | (val6 & 1); - val6 >>= 1; - val2 = (val2 << 1) | (val6 & 1); - val6 >>= 1; - - // There are 2 "extra" bytes of 2-bit data that we ignore here. - if (ptr6 + idx6 < 0x156) { - nibbles[ptr6 + idx6] = val6; - } - if (ptr2 + idx2 < 0x156) { - nibbles[ptr2 + idx2] = val2; - } - - if (--idx2 < 0) { - idx2 = 0x55; - } - } - - int lastv = 0; - for (int idx = 0; idx < 0x156; idx++) { - int val = nibbles[idx]; - trackBuffer->addByte(_trans[lastv ^ val]); - lastv = val; - } - trackBuffer->addByte(_trans[lastv]); -} - -nibErr denibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, +// trackBuffer is input NIB data; rawTrackBuffer is output DSK/PO data +nibErr denibblizeTrack(const uint8_t input[NIBTRACKSIZE], uint8_t rawTrackBuffer[256*16], uint8_t diskType, int8_t track) { - // We can't tell exactly what the length should be, b/c there might - // be varying numbers of GAP bytes. But we can tell, generally, that - // this is the minimum acceptable length that might hold all the - // track data. - if (trackBuffer->count() < 16*MINNIBSECTORSIZE) { - return errorShortTrack; - } - // bitmask of the sectors that we've found while decoding. We should // find all 16. uint16_t sectorsUpdated = 0; // loop through the data twice, so we make sure we read anything // that crosses the end/start boundary - // FIXME: if this approach works, we probably want 1/16th extra, not 2* - for (uint16_t i=0; i<2*trackBuffer->count(); ) { + uint16_t startOfSector; + for (uint16_t i=0; i<2*416*16; i++) { // Find the prolog - while (trackBuffer->peek(i++) != 0xD5) - ; - if (trackBuffer->peek(i++) != 0xAA) { + if (input[i % NIBTRACKSIZE] != 0xD5) continue; - } - if (trackBuffer->peek(i++) != 0x96) { + startOfSector = i; + i++; + if (input[i % NIBTRACKSIZE] != 0xAA) continue; - } + i++; + if (input[i % NIBTRACKSIZE] != 0x96) + continue; + i++; // And now we should be in the header section - uint8_t volumeID = denib(trackBuffer->peek(i), - trackBuffer->peek(i+1)); + uint8_t volumeID = denib(input[i % NIBTRACKSIZE], + input[(i+1) % NIBTRACKSIZE]); i += 2; - uint8_t trackID = denib(trackBuffer->peek(i), - trackBuffer->peek(i+1)); + uint8_t trackID = denib(input[i % NIBTRACKSIZE], + input[(i+1) % NIBTRACKSIZE]); i += 2; - uint8_t sectorNum = denib(trackBuffer->peek(i), - trackBuffer->peek(i+1)); + uint8_t sectorNum = denib(input[i % NIBTRACKSIZE], + input[(i+1) % NIBTRACKSIZE]); i += 2; - uint8_t headerChecksum = denib(trackBuffer->peek(i), - trackBuffer->peek(i+1)); + uint8_t headerChecksum = denib(input[i % NIBTRACKSIZE], + input[(i+1) % NIBTRACKSIZE]); i += 2; if (headerChecksum != (volumeID ^ trackID ^ sectorNum)) { @@ -269,41 +312,51 @@ nibErr denibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, } // check for the epilog - if (trackBuffer->peek(i++) != 0xDE) { + if (input[i % NIBTRACKSIZE] != 0xDE) { continue; } - if (trackBuffer->peek(i++) != 0xAA) { + i++; + if (input[i % NIBTRACKSIZE] != 0xAA) { continue; } + i++; // Skip to the data prolog - while (trackBuffer->peek(i++) != 0xD5) - ; - if (trackBuffer->peek(i++) != 0xAA) { - continue; + while (input[i % NIBTRACKSIZE] != 0xD5) { + i++; } - if (trackBuffer->peek(i++) != 0xAD) { + i++; + if (input[i % NIBTRACKSIZE] != 0xAA) continue; - } + i++; + if (input[i % NIBTRACKSIZE] != 0xAD) + continue; + i++; + // Decode the data in to a temporary buffer: we don't want to overwrite // something valid with partial data uint8_t output[256]; - if (!decodeData(trackBuffer, i, output)) { + // create a new nibData (in case it wraps around our track data) + uint8_t nibData[343]; + for (int j=0; j<343; j++) { + nibData[j] = input[(i+j)%NIBTRACKSIZE]; + } + if (!_decodeData(nibData, output)) { continue; } i += 343; // Check the data epilog - if (trackBuffer->peek(i++) != 0xDE) { + if (input[i % NIBTRACKSIZE] != 0xDE) continue; - } - if (trackBuffer->peek(i++) != 0xAA) { + i++; + if (input[i % NIBTRACKSIZE] != 0xAA) continue; - } - if (trackBuffer->peek(i++) != 0xEB) { + i++; + if (input[i % NIBTRACKSIZE] != 0xEB) continue; - } + i++; // We've got a whole block! Put it in the rawTrackBuffer and mark // the bit for it in sectorsUpdated. @@ -311,12 +364,15 @@ nibErr denibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, // FIXME: if trackID != curTrack, that's an error? uint8_t targetSector; - if (diskType == prodosDisk) { + if (diskType == T_PO) { targetSector = deProdosPhys[sectorNum]; } else { targetSector = dephys[sectorNum]; } + if (targetSector > 16) + return errorBadData; + memcpy(&rawTrackBuffer[targetSector * 256], output, 256); diff --git a/apple/nibutil.h b/apple/nibutil.h index 5c7107b..3ea916d 100644 --- a/apple/nibutil.h +++ b/apple/nibutil.h @@ -1,44 +1,54 @@ -#ifdef TEENSYDUINO -#include -#else +#ifndef __NIBUTIL_H +#define __NIBUTIL_H + #include #include #include #include #include +#include + +#ifdef __cplusplus +extern "C" { #endif -#include "LRingBuffer.h" - #define NIBTRACKSIZE 0x1A00 -// Minimum viable nibblized sector size. With GAP bytes, could be much longer. -#define MINNIBSECTORSIZE (343 + 13 + 3) +#define NIBSECTORSIZE 416 -#define nib1(a) (((a & 0xAA) >> 1) | 0xAA) -#define nib2(b) (((b & 0x55) ) | 0xAA) -#define denib(a, b) ((((a) & ~0xAA) << 1) | ((b) & ~0xAA)) +typedef struct _nibSector { + uint8_t gap1[48]; -#define GAP 0xFF + uint8_t sectorProlog[3]; + uint8_t volume44[2]; + uint8_t track44[2]; + uint8_t sector44[2]; + uint8_t checksum44[2]; + uint8_t sectorEpilog[3]; -enum { - dosDisk = 0, - prodosDisk = 1, - nibDisk = 2 -}; + uint8_t gap2[5]; + + uint8_t dataProlog[3]; + uint8_t data62[342]; + uint8_t checksum; + uint8_t dataEpilog[3]; +} nibSector; enum nibErr { errorNone = 0, - errorShortTrack = 1, - errorMissingSectors = 2 + errorMissingSectors = 1, + errorBadData = 2 }; -void nibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, - uint8_t diskType, int8_t track); - -nibErr denibblizeTrack(LRingBuffer *trackBuffer, uint8_t *rawTrackBuffer, +uint32_t nibblizeTrack(uint8_t outputBuffer[NIBTRACKSIZE], const uint8_t rawTrackBuffer[256*16], uint8_t diskType, int8_t track); +nibErr denibblizeTrack(const uint8_t input[NIBTRACKSIZE], uint8_t rawTrackBuffer[256*16], + uint8_t diskType, int8_t track); -bool decodeData(LRingBuffer *trackBuffer, uint16_t startAt, uint8_t *output); -void encodeData(LRingBuffer *trackBuffer, uint8_t *data); +uint8_t de44(uint8_t nibs[2]); +#ifdef __cplusplus +}; +#endif + +#endif diff --git a/apple/version.h b/apple/version.h new file mode 100644 index 0000000..5b9af74 --- /dev/null +++ b/apple/version.h @@ -0,0 +1,6 @@ +#ifndef __VERSION_H +#define __VERSION_H + +#define VERSION_STRING "Wozzle v1.0+dev" + +#endif diff --git a/apple/woz-serializer.cpp b/apple/woz-serializer.cpp new file mode 100644 index 0000000..becfa23 --- /dev/null +++ b/apple/woz-serializer.cpp @@ -0,0 +1,14 @@ +#include "woz-serializer.h" + +bool WozSerializer::Serialize(int8_t fd) +{ + // *** + return false; +} + +bool WozSerializer::Deserialize(int8_t fd) +{ + // *** + return false; +} + diff --git a/apple/woz-serializer.h b/apple/woz-serializer.h new file mode 100644 index 0000000..379c426 --- /dev/null +++ b/apple/woz-serializer.h @@ -0,0 +1,12 @@ +#ifndef __WOZ_SERIALIZER_H +#define __WOZ_SERIALIZER_H + +#include "woz.h" +class WozSerializer: public virtual Woz { + public: + bool Serialize(int8_t fd); + bool Deserialize(int8_t fd); +}; + + +#endif diff --git a/apple/woz.cpp b/apple/woz.cpp new file mode 100644 index 0000000..901c0cc --- /dev/null +++ b/apple/woz.cpp @@ -0,0 +1,1088 @@ +#include "woz.h" +#include +#include "crc32.h" +#include "nibutil.h" +#include "version.h" + +// Block number we start packing data bits after (Woz 2.0 images) +#define STARTBLOCK 3 + +#define PREP_SECTION(f, t) { \ + uint32_t type = t; \ + if (!write32(f, type)) \ + return false; \ + if (!write32(f, 0)) \ + return false; \ + curpos = ftello(f); \ + } + +#define END_SECTION(f) { \ + long endpos = ftello(f); \ + fseeko(f, curpos-4, SEEK_SET); \ + uint32_t chunksize = endpos - curpos; \ + if (!write32(f, chunksize)) \ + return false; \ + fseeko(f, 0, SEEK_END); \ + } + +Woz::Woz() +{ + trackPointer = 0; + trackBitIdx = 0x80; + trackLoopCounter = 0; + metaData = NULL; + + memset(&quarterTrackMap, 255, sizeof(quarterTrackMap)); + memset(&di, 0, sizeof(diskInfo)); + memset(&tracks, 0, sizeof(tracks)); +} + +Woz::~Woz() +{ + // FIXME: free all the stuff +} + + +uint8_t Woz::getNextWozBit(uint8_t track) +{ + if (trackBitIdx == 0x80) { + // need another byte out of the track stream + trackByte = tracks[track].trackData[trackPointer++]; + if (trackPointer >= tracks[track].bitCount / 8) { + trackPointer = 0; + trackLoopCounter++; + } + } + + uint8_t ret = (trackByte & trackBitIdx) ? 1 : 0; + + trackBitIdx >>= 1; + if (!trackBitIdx) { + trackBitIdx = 0x80; + } + + return ret; +} + +uint8_t Woz::fakeBit() +{ + // 30% should be 1s + return 0; +} + +uint8_t Woz::nextDiskBit(uint8_t track) +{ + if (track == 0xFF) + return fakeBit(); + + static uint8_t head_window = 0; + head_window <<= 1; + head_window |= getNextWozBit(track); + if ((head_window & 0x0f) != 0x00) { + return (head_window & 0x02) >> 1; + } else { + return fakeBit(); + } +} + +uint8_t Woz::nextDiskByte(uint8_t track) +{ + uint8_t d = 0; + while ((d & 0x80) == 0) { + d <<= 1; + d |= nextDiskBit(track); + } + return d; +} + +static bool write8(FILE *f, uint8_t v) +{ + if (fwrite(&v, 1, 1, f) != 1) + return false; + return true; +} + +static bool write16(FILE *f, uint16_t v) +{ + if (!write8(f, v & 0xFF)) + return false; + v >>= 8; + if (!write8(f, v & 0xFF)) + return false; + return true; +} + +static bool write32(FILE *f, uint32_t v) +{ + for (int i=0; i<4; i++) { + if (!write8(f, v&0xFF)) + return false; + v >>= 8; + } + return true; +} + +static bool read8(FILE *f, uint8_t *toWhere) +{ + uint8_t r; + if (fread(&r, 1, 1, f) != 1) + return false; + *toWhere = r; + + return true; +} + +static bool read16(FILE *f, uint16_t *toWhere) +{ + uint16_t ret = 0; + for (int i=0; i<2; i++) { + uint8_t r; + if (!read8(f, &r)) { + return false; + } + ret >>= 8; + ret |= (r<<8); + } + + *toWhere = ret; + + return true; +} + +static bool read32(FILE *f, uint32_t *toWhere) +{ + uint32_t ret = 0; + for (int i=0; i<4; i++) { + uint8_t r; + if (!read8(f, &r)) { + return false; + } + ret >>= 8; + ret |= (r<<24); + } + + *toWhere = ret; + + return true; +} + +bool Woz::writeFile(uint8_t version, const char *filename) +{ + FILE *f = NULL; + bool retval = false; + uint32_t tmp32; // scratch 32-bit value + off_t crcPos, endPos; + off_t curpos; // used in macros to dynamically tell what size the chunks are + uint32_t crcDataSize; + uint8_t *crcData = NULL; + + + if (version > 2 || !version) { + fprintf(stderr, "ERROR: version must be 1 or 2\n"); + goto done; + } + + f = fopen(filename, "w+"); + if (!f) { + perror("ERROR: Unable to open output file"); + goto done; + } + + // header + if (version == 1) { + tmp32 = 0x315A4F57; + } else { + tmp32 = 0x325A4F57; + } + if (!write32(f, tmp32)) { + fprintf(stderr, "ERROR: failed to write\n"); + goto done; + } + tmp32 = 0x0A0D0AFF; + if (!write32(f, tmp32)) { + fprintf(stderr, "ERROR: failed to write\n"); + goto done; + } + + // We'll come back and write the checksum later + crcPos = ftello(f); + tmp32 = 0; + if (!write32(f, tmp32)) { + fprintf(stderr, "ERROR: failed to write\n"); + goto done; + } + + PREP_SECTION(f, 0x4F464E49); // 'INFO' + if (!writeInfoChunk(version, f)) { + fprintf(stderr, "ERROR: failed to write INFO chunk\n"); + goto done; + } + END_SECTION(f); + + PREP_SECTION(f, 0x50414D54); // 'TMAP' + if (!writeTMAPChunk(version, f)) { + fprintf(stderr, "ERROR: failed to write TMAP chunk\n"); + goto done; + } + END_SECTION(f); + + PREP_SECTION(f, 0x534B5254); // 'TRKS' + if (!writeTRKSChunk(version, f)) { + fprintf(stderr, "ERROR: failed to write TRKS chunk\n"); + goto done; + } + END_SECTION(f); + + // Write the metadata if we have any + if (metaData) { + PREP_SECTION(f, 0x4154454D); // 'META' + if (fwrite(metaData, 1, strlen(metaData), f) != strlen(metaData)) { + fprintf(stderr, "ERROR: failed to write META chunk\n"); + goto done; + } + END_SECTION(f); + } + + // FIXME: missing the WRIT chunk, if it exists + + // Fix up the checksum + endPos = ftello(f); + crcDataSize = endPos-crcPos-4; + crcData = (uint8_t *)malloc(crcDataSize); + if (!crcData) { + fprintf(stderr, "ERROR: failed to malloc crc data chunk\n"); + goto done; + } + + // Read the data in for checksumming + if (fseeko(f, crcPos+4, SEEK_SET)) { + fprintf(stderr, "ERROR: failed to fseek to crcPos+4 (0x%llX)\n", crcPos+4); + goto done; + } + + tmp32 = fread(crcData, 1, crcDataSize, f); + if (tmp32 != crcDataSize) { + fprintf(stderr, "ERROR: failed to read in data for checksum [read %d, wanted %d]\n", tmp32, crcDataSize); + goto done; + } + + tmp32 = compute_crc_32(crcData, crcDataSize); + // Write it back out + fseeko(f, crcPos, SEEK_SET); + if (!write32(f, tmp32)) { + fprintf(stderr, "ERROR: failed to write CRC\n"); + goto done; + } + + retval = true; + + done: + if (crcData) + free(crcData); + if (f) + fclose(f); + return retval; +} + +void Woz::_initInfo() +{ + di.version = 2; + di.diskType = 1; + di.writeProtected = 0; + di.synchronized = 0; + di.cleaned = 0; + sprintf(di.creator, "%.32s", VERSION_STRING); + di.diskSides = 1; + di.bootSectorFormat = 0; + di.optimalBitTiming = 32; + di.compatHardware = 0; + di.requiredRam = 0; + di.largestTrack = 13; + + // reset all the track data + for (int i=0; i<160; i++) { + memset(&tracks[i], 0, sizeof(trackInfo)); + } + // Construct a default quarter-track mapping + for (int i=0; i<140; i++) { + if ((i+1)/4 < 35) { + quarterTrackMap[i] = ((i-2) % 4 == 0) ? 0xFF : ((i+1)/4); + } else { + quarterTrackMap[i] = 0xFF; + } + } +} + +bool Woz::readDskFile(const char *filename, uint8_t subtype) +{ + bool retval = false; + + FILE *f = fopen(filename, "r"); + if (!f) { + perror("Unable to open input file"); + goto done; + } + + _initInfo(); + + // Now read in the 35 tracks of data from the DSK file and convert them to NIB + uint8_t sectorData[256*16]; + for (int track=0; track<35; track++) { + uint32_t bytesRead = fread(sectorData, 1, 256*16, f); + if (bytesRead != 256*16) { + fprintf(stderr, "Failed to read DSK data; got %d bytes, wanted %d\n", bytesRead, 256); + goto done; + } + + tracks[track].trackData = (uint8_t *)calloc(NIBTRACKSIZE, 1); + if (!tracks[track].trackData) { + fprintf(stderr, "Failed to malloc track data\n"); + goto done; + } + tracks[track].startingBlock = STARTBLOCK + 13*track; + tracks[track].blockCount = 13; + uint32_t sizeInBits = nibblizeTrack(tracks[track].trackData, sectorData, subtype, track); + tracks[track].bitCount = sizeInBits; // ... reality. + } + + retval = true; + + done: + if (f) + fclose(f); + return retval; +} + +bool Woz::readNibFile(const char *filename) +{ + FILE *f = fopen(filename, "r"); + if (!f) { + perror("Unable to open input file"); + return false; + } + + _initInfo(); + + // Now read in the 35 tracks of data from the nib file + nibSector nibData[16]; + for (int track=0; track<35; track++) { + uint32_t bytesRead = fread(nibData, 1, NIBTRACKSIZE, f); + if (bytesRead != NIBTRACKSIZE) { + printf("Failed to read NIB data; got %d bytes, wanted %d\n", bytesRead, NIBTRACKSIZE); + return false; + } + + tracks[track].trackData = (uint8_t *)calloc(NIBTRACKSIZE, 1); + if (!tracks[track].trackData) { + printf("Failed to malloc track data\n"); + return false; + } + + memcpy(tracks[track].trackData, nibData, NIBTRACKSIZE); + tracks[track].startingBlock = STARTBLOCK + 13*track; + tracks[track].blockCount = 13; + tracks[track].bitCount = NIBTRACKSIZE*8; + } + fclose(f); + + return true; +} + +bool Woz::readWozFile(const char *filename) +{ + FILE *f = fopen(filename, "r"); + if (!f) { + perror("Unable to open input file"); + return false; + } + + // Header + uint32_t h; + read32(f, &h); + if (h == 0x325A4F57 || h == 0x315A4F57) { + printf("WOZ%c disk image\n", (h & 0xFF000000)>>24); + } else { + printf("Unknown disk image type; can't continue\n"); + fclose(f); + return false; + } + + uint32_t tmp; + if (!read32(f, &tmp)) { + printf("Read failure\n"); + fclose(f); + return false; + } + if (tmp != 0x0A0D0AFF) { + printf("WOZ header failure; exiting\n"); + fclose(f); + return false; + } + uint32_t crc32; + read32(f, &crc32); + // printf("Disk crc32 should be 0x%X\n", crc32); + // FIXME: check CRC. Remember that 0x00 means "don't check CRC" + + uint32_t fpos = 12; + uint8_t haveData = 0; + +#define cINFO 1 +#define cTMAP 2 +#define cTRKS 4 + + while (1) { + if (fseeko(f, fpos, SEEK_SET)) { + break; + } + + uint32_t chunkType; + if (!read32(f, &chunkType)) { + break; + } + uint32_t chunkDataSize; + read32(f, &chunkDataSize); + + bool isOk; + + switch (chunkType) { + case 0x4F464E49: // 'INFO' + isOk = parseInfoChunk(f, chunkDataSize); + haveData |= cINFO; + break; + case 0x50414D54: // 'TMAP' + isOk = parseTMAPChunk(f, chunkDataSize); + haveData |= cTMAP; + break; + case 0x534B5254: // 'TRKS' + isOk = parseTRKSChunk(f, chunkDataSize); + haveData |= cTRKS; + break; + case 0x4154454D: // 'META' + isOk = parseMetaChunk(f, chunkDataSize); + break; + default: + printf("Unknown chunk type 0x%X\n", chunkType); + fclose(f); + return false; + break; + } + + if (!isOk) { + printf("Chunk parsing [0x%X] failed; exiting\n", chunkType); + fclose(f); + return false; + } + + fpos += chunkDataSize + 8; // 8 bytes for the ChunkID and the ChunkSize + } + + if (haveData != 0x07) { + printf("ERROR: missing one or more critical sections\n"); + return false; + } + + for (int i=0; i<35; i++) { + if (!readQuarterTrackData(f, i*4)) { + printf("Failed to read QTD for track %d\n", i); + fclose(f); + return false; + } + } + + fclose(f); + printf("File read successful\n"); + return true; +} + +bool Woz::readFile(const char *filename, uint8_t forceType) +{ + if (forceType == T_AUTO) { + // Try to determine type from the file extension + const char *p = strrchr(filename, '.'); + if (!p) { + printf("Unable to determine file type of '%s'\n", filename); + return false; + } + if (strcasecmp(p, ".woz") == 0) { + forceType = T_WOZ; + } else if (strcasecmp(p, ".dsk") == 0 || + strcasecmp(p, ".do") == 0) { + forceType = T_DSK; + } else if (strcasecmp(p, ".po") == 0) { + forceType = T_PO; + } else if (strcasecmp(p, ".nib") == 0) { + forceType = T_NIB; + } else { + printf("Unable to determine file type of '%s'\n", filename); + return false; + } + } + + switch (forceType) { + case T_WOZ: + printf("reading woz file %s\n", filename); + return readWozFile(filename); + case T_DSK: + case T_PO: + printf("reading DSK file %s\n", filename); + return readDskFile(filename, forceType); + case T_NIB: + printf("reading NIB file %s\n", filename); + return readNibFile(filename); + default: + printf("Unknown disk type; unable to read\n"); + return false; + } +} + +bool Woz::parseTRKSChunk(FILE *f, uint32_t chunkSize) +{ + if (di.version == 2) { + for (int i=0; i<160; i++) { + if (!read16(f, &tracks[i].startingBlock)) + return false; + if (!read16(f, &tracks[i].blockCount)) + return false; + if (!read32(f, &tracks[i].bitCount)) + return false; + tracks[i].startingByte = 0; // v1-specific + } + return true; + } + + // V1 parsing + uint32_t ptr = 0; + uint8_t trackNumber = 0; + while (ptr < chunkSize) { + tracks[trackNumber].startingByte = trackNumber * 6656 + 256; + tracks[trackNumber].startingBlock = 0; // v2-specific + tracks[trackNumber].blockCount = 13; + fseeko(f, (trackNumber * 6656 + 256) + 6648, SEEK_SET); + uint16_t numBits; + if (!read16(f, &numBits)) { + return false; + } + tracks[trackNumber].bitCount = numBits; + ptr += 6656; + trackNumber++; + } + + return true; +} + +bool Woz::parseTMAPChunk(FILE *f, uint32_t chunkSize) +{ + if (chunkSize != 0xa0) { + printf("TMAP chunk is the wrong size; aborting\n"); + return false; + } + + for (int i=0; i<40*4; i++) { + if (!read8(f, (uint8_t *)&quarterTrackMap[i])) + return false; + chunkSize--; + } + + return true; +} + +// return true if successful +bool Woz::parseInfoChunk(FILE *f, uint32_t chunkSize) +{ + if (chunkSize != 60) { + printf("INFO chunk size is not 60; aborting\n"); + return false; + } + + if (!read8(f, &di.version)) + return false; + if (di.version > 2) { + printf("Incorrect version header; aborting\n"); + return false; + } + + if (!read8(f, &di.diskType)) + return false; + if (di.diskType != 1) { + printf("Not a 5.25\" disk image; aborting\n"); + return false; + } + + if (!read8(f, &di.writeProtected)) + return false; + + if (!read8(f, &di.synchronized)) + return false; + + if (!read8(f, &di.cleaned)) + return false; + + di.creator[32] = 0; + for (int i=0; i<32; i++) { + if (!read8(f, (uint8_t *)&di.creator[i])) + return false; + } + + if (di.version >= 2) { + if (!read8(f, &di.diskSides)) + return false; + if (!read8(f, &di.bootSectorFormat)) + return false; + if (!read8(f, &di.optimalBitTiming)) + return false; + if (!read16(f, &di.compatHardware)) + return false; + if (!read16(f, &di.requiredRam)) + return false; + if (!read16(f, &di.largestTrack)) + return false; + } else { + di.diskSides = 0; + di.bootSectorFormat = 0; + di.compatHardware = 0; + di.requiredRam = 0; + di.largestTrack = 13; // 13 * 512 bytes = 6656. All tracks are + // padded to 6646 bytes in the v1 image. + di.optimalBitTiming = 32; // "standard" disk bit timing for a 5.25" disk (4us per bit) + } + + return true; +} + +bool Woz::parseMetaChunk(FILE *f, uint32_t chunkSize) +{ + metaData = (char *)calloc(chunkSize+1, 1); + if (!metaData) + return false; + + if (fread(metaData, 1, chunkSize, f) != chunkSize) + return false; + + metaData[chunkSize] = 0; + + return true; +} + +bool Woz::readQuarterTrackData(FILE *f, uint8_t quartertrack) +{ + uint8_t targetImageTrack = quarterTrackMap[quartertrack]; + if (targetImageTrack == 0xFF) { + // It's a tween-track with no reliable data. + return true; + } + + uint16_t bitsStartBlock = tracks[targetImageTrack].startingBlock; + + // if (tracks[targetImageTrack].trackData) + // free(tracks[targetImageTrack].trackData); + + // Allocate a new buffer for this track + uint32_t count = tracks[targetImageTrack].blockCount * 512; + if (di.version == 1) count = (tracks[targetImageTrack].bitCount / 8) + ((tracks[targetImageTrack].bitCount % 8) ? 1 : 0); + tracks[targetImageTrack].trackData = (uint8_t *)calloc(count, 1); + if (!tracks[targetImageTrack].trackData) { + perror("Failed to alloc buf to read track magnetic data"); + return false; + } + + if (di.version == 1) { + if (fseeko(f, tracks[targetImageTrack].startingByte, SEEK_SET)) { + perror("Failed to seek to start of block"); + return false; + } + } else { + if (fseeko(f, bitsStartBlock*512, SEEK_SET)) { + perror("Failed to seek to start of block"); + return false; + } + } + uint32_t didRead = fread(tracks[targetImageTrack].trackData, 1, count, f); + if (didRead != count) { + printf("Failed to read all track data for track [read %d, wanted %d]\n", didRead, count); + return false; + } + + return true; +} + + +bool Woz::readSectorData(uint8_t track, uint8_t sector, nibSector *sectorData) +{ + // Find the sector header for this sector... + uint32_t ptr = 0; + + memset(sectorData->gap1, 0xFF, sizeof(sectorData->gap1)); + memset(sectorData->gap2, 0xFF, sizeof(sectorData->gap1)); + + // Allow two loops through the track data looking for the sector prolog + uint32_t endCount = tracks[track].blockCount*512*2; + if (di.version == 1) endCount = 2*6646; + while (ptr < endCount) { + sectorData->sectorProlog[0] = sectorData->sectorProlog[1]; + sectorData->sectorProlog[1] = sectorData->sectorProlog[2]; + sectorData->sectorProlog[2] = nextDiskByte(track); + ptr++; + + if (sectorData->sectorProlog[0] == 0xd5 && + sectorData->sectorProlog[1] == 0xaa && + sectorData->sectorProlog[2] == 0x96) { + // Found *a* sector header. See if it's ours. + sectorData->volume44[0] = nextDiskByte(track); + sectorData->volume44[1] = nextDiskByte(track); + sectorData->track44[0] = nextDiskByte(track); + sectorData->track44[1] = nextDiskByte(track); + sectorData->sector44[0] = nextDiskByte(track); + sectorData->sector44[1] = nextDiskByte(track); + sectorData->checksum44[0] = nextDiskByte(track); + sectorData->checksum44[1] = nextDiskByte(track); + sectorData->sectorEpilog[0] = nextDiskByte(track); + sectorData->sectorEpilog[1] = nextDiskByte(track); + sectorData->sectorEpilog[2] = nextDiskByte(track); + + if (sectorData->sectorEpilog[0] == 0xde && + sectorData->sectorEpilog[1] == 0xaa && + sectorData->sectorEpilog[2] == 0xeb) { + // Header is integral. See if it's our sector: + uint8_t sectorNum = de44(sectorData->sector44); + if (sectorNum != sector) { + continue; + } + // It's our sector - find the data chunk and read it + while (ptr < tracks[track].blockCount*512*2) { + sectorData->dataProlog[0] = sectorData->dataProlog[1]; + sectorData->dataProlog[1] = sectorData->dataProlog[2]; + sectorData->dataProlog[2] = nextDiskByte(track); + ptr++; + + if (sectorData->dataProlog[0] == 0xd5 && + sectorData->dataProlog[1] == 0xaa && + sectorData->dataProlog[2] == 0xad) { + // Found the data; copy it in + for (int i=0; i<342; i++) { + sectorData->data62[i] = nextDiskByte(track); + } + sectorData->checksum = nextDiskByte(track); + sectorData->dataEpilog[0] = nextDiskByte(track); + sectorData->dataEpilog[1] = nextDiskByte(track); + sectorData->dataEpilog[2] = nextDiskByte(track); + if (sectorData->dataEpilog[0] != 0xde || + sectorData->dataEpilog[1] != 0xaa || + sectorData->dataEpilog[2] != 0xeb) { + continue; + } + // Have an integral hunk of data, with epilog - return it + return true; + } + } + } + } + } + + return false; +} + +bool Woz::writeInfoChunk(uint8_t version, FILE *f) +{ + if (!write8(f, version) || + !write8(f, di.diskType) || + !write8(f, di.writeProtected) || + !write8(f, di.synchronized) || + !write8(f, di.cleaned)) + return false; + + for (int i=0; i<32; i++) { + if (!write8(f, di.creator[i])) + return false; + } + + if (version >= 2) { + // If we read a Wozv1, this will be set to 0. Set it to 1. + if (di.diskSides == 0) + di.diskSides = 1; + + if ( !write8(f, di.diskSides) || + !write8(f, di.bootSectorFormat) || + !write8(f, di.optimalBitTiming) || + !write16(f, di.compatHardware) || + !write16(f, di.requiredRam) || + !write16(f, di.largestTrack)) + return false; + } + + // Padding + for (int i=0; i<((version==1)?23:14); i++) { + if (!write8(f, 0)) + return false; + } + return true; +} + +bool Woz::writeTMAPChunk(uint8_t version, FILE *f) +{ + for (int i=0; i<40*4; i++) { + if (!write8(f, quarterTrackMap[i])) + return false; + } + + return true; +} + +bool Woz::writeTRKSChunk(uint8_t version, FILE *f) +{ + if (version == 1) { + printf("V1 write is not implemented\n"); + return false; + } + + // Reconstruct all of the starting blocks/blockCounts for each + // track. The bitCount should be correct. + uint8_t numTracksPacked = 0; + for (int i=0; i<160; i++) { + if (tracks[i].trackData) { + // For any tracks that have data, put it somewhere in the destination file + tracks[i].startingBlock = STARTBLOCK + 13*(numTracksPacked++); + // assume tracks[track].bitCount is correct, and recalculate the block size of this track + uint32_t bytes = (tracks[i].bitCount / 8) + ((tracks[i].bitCount % 8) ? 1 : 0); + uint32_t blocks = (bytes / 512) + ((bytes % 512) ? 1 : 0); + tracks[i].blockCount = blocks; + } else { + tracks[i].startingBlock = 0; + tracks[i].blockCount = 0; + tracks[i].bitCount = 0; + } + + if (!write16(f, tracks[i].startingBlock)) + return false; + if (!write16(f, tracks[i].blockCount)) + return false; + if (!write32(f, tracks[i].bitCount)) + return false; + } + + // All the track data + for (int i=0; i<160; i++) { + if (tracks[i].startingBlock && + tracks[i].blockCount) { + fseeko(f, tracks[i].startingBlock * 512, SEEK_SET); + uint32_t writeSize = (tracks[i].bitCount / 8) + ((tracks[i].bitCount % 8) ? 1 : 0); + if (fwrite(tracks[i].trackData, 1, writeSize, f) != writeSize) + return false; + uint8_t c = 0; + while (writeSize < tracks[i].blockCount * 512) { + if (fwrite(&c, 1, 1, f) != 1) + return false; + writeSize++; + } + } + } + return true; +} + +bool Woz::decodeWozTrackToNib(uint8_t track, nibSector sectorData[16]) +{ + for (int sector=0; sector<16; sector++) { + if (!readSectorData(track, sector, (nibSector *)(§orData[sector]))) { + return false; + } + } + + return true; +} + +bool Woz::decodeWozTrackToDsk(uint8_t track, uint8_t subtype, uint8_t sectorData[256*16]) +{ + // First read it to a NIB; then convert the NIB to a DSK. + nibSector nibData[16]; + if (!decodeWozTrackToNib(track, nibData)) + return false; + + if (denibblizeTrack((const uint8_t *)nibData, sectorData, subtype, track) != errorNone) + return false; + + return true; +} + +void Woz::dumpInfo() +{ + printf("WOZ image version %d\n", di.version); + printf("Disk type: %s\n", di.diskType == 1 ? "5.25\"" : "3.5\""); + printf("Write protected: %s\n", di.writeProtected ? "yes" : "no"); + printf("Synchronized: %s\n", di.synchronized ? "yes" : "no"); + printf("Cleaned: %s\n", di.cleaned ? "yes" : "no"); + printf("Creator: %s\n", di.creator); + printf("Disk sides: %d\n", di.diskSides); + printf("Boot sector format: "); + switch (di.bootSectorFormat) { + case 0: + default: + printf("unknown\n"); + break; + case 1: + printf("16 sector\n"); + break; + case 2: + printf("13 sector\n"); + break; + case 3: + printf("Both 16 and 13 sector\n"); + break; + } + printf("Optimal bit timing: %d ns\n", di.optimalBitTiming * 125); + printf("Hardware compatability flags: 0x%X\n", di.compatHardware); + printf("Required RAM: %d K\n", di.requiredRam); + printf("Largest track: %d bytes\n", di.largestTrack * 512); + printf("\n"); + + if (metaData) { + printf("Metadata:\n"); + + char *token, *string, *tofree; + tofree = string = strdup(metaData); + char *parts[25]; + memset(parts, 0, sizeof(parts)); + int idx = 0; + while ((token = strsep(&string, "\n")) != NULL) { + if (idx >= sizeof(parts)) { + printf("ERROR: too many metadata keys\n"); + return; + } + parts[idx++] = strdup(token); + } + free(tofree); + + for (int idx2=0; idx2= 0) { + free(parts[idx]); + } + printf("\n"); + } + + printf("Quarter-track map:\n"); + for (int i=0; i<140; i+=4) { + printf("%2d %3d => %3d %3d => %3d %3d => %3d %3d => %3d\n", + i/4, + i, quarterTrackMap[i], + i+1, quarterTrackMap[i+1], + i+2, quarterTrackMap[i+2], + i+3, quarterTrackMap[i+3]); + } + + for (int i=0; i<40; i++) { + printf("Track %d:\n", i); + if (di.version == 1) { + printf(" Starts at byte %d\n", tracks[i].startingByte); + } else { + printf(" Starts at block %d\n", tracks[i].startingBlock); + } + printf(" Number of blocks: %d\n", tracks[i].blockCount); + printf(" Number of bits: %d\n", tracks[i].bitCount); + if (tracks[i].bitCount && tracks[i].trackData) { +#if 1 + // Raw track dump + printf(" Raw track data:\n"); + for (int k=0; k<(tracks[i].bitCount/8)+((tracks[i].bitCount%8)?1:0); k+=16) { + printf(" 0x%.4X :", k); + for (int j=0; j<16; j++) { + if (k+j < (tracks[i].bitCount/8)+((tracks[i].bitCount%8)?1:0)) { + printf(" %.2X", tracks[i].trackData[k+j]); + } + } + printf("\n"); + } + +#else + // Sector parsing & dump +#if 1 + // Look at the sectors in numerical order + // FIXME: 13-sector support + nibSector sectorData; + for (int sector=0; sector<16; sector++) { + if (readSectorData(i, sector, §orData)) { + printf(" Volume ID: %d\n", de44(sectorData.volume44)); + printf(" Track ID: %d\n", de44(sectorData.track44)); + uint8_t sector = de44(sectorData.sector44); + printf(" Sector: %d\n", sector); + printf(" Cksum: %d\n", de44(sectorData.checksum44)); + + printf(" Sector Data:\n"); + for (int k=0; k<342; k+=16) { + printf(" 0x%.4X :", k); + for (int j=0; j<16; j++) { + if (k+j < 342) { + printf(" %.2X", sectorData.data62[k+j]); + } + } + printf("\n"); + } + } + } +#else + // Look at the sectors found in order on the track + trackBitIdx = 0x80; trackPointer = 0; trackLoopCounter = 0; + uint16_t sectorsFound = 0; + do { + if (nextDiskByte(i) == 0xD5 && + nextDiskByte(i) == 0xAA && + nextDiskByte(i) == 0x96) { + printf(" Volume ID: %d\n", denib(nextDiskByte(i), nextDiskByte(i))); + printf(" Track ID: %d\n", denib(nextDiskByte(i), nextDiskByte(i))); + uint8_t sector = denib(nextDiskByte(i), nextDiskByte(i)); + printf(" Sector: %d\n", sector); + sectorsFound |= (1 << sector); + printf(" Cksum: %d\n", denib(nextDiskByte(i), nextDiskByte(i))); + + nextDiskByte(i); // skip epilog + nextDiskByte(i); + nextDiskByte(i); + // look for data prolog d5 aa ad + while (nextDiskByte(i) != 0xD5 && trackLoopCounter < 2) + ; + if (trackLoopCounter < 2) { + // Hope that's it and skip two bytes + nextDiskByte(i); + nextDiskByte(i); + // Dump the 6-and-2 data + printf(" Sector Data:\n"); + for (int k=0; k<342; k+=16) { + printf(" 0x%.4X :", k); + for (int j=0; j<16; j++) { + if (k+j < 342) { + printf(" %.2X", nextDiskByte(i)); + } + } + printf("\n"); + } + } + } + + } while (sectorsFound != 0xFFFF && trackLoopCounter < 2); +#endif +#endif + } + } +} + +bool Woz::isSynchronized() +{ + return di.synchronized; +} + +uint8_t Woz::trackNumberForQuarterTrack(uint16_t qt) +{ + return quarterTrackMap[qt]; +} + + diff --git a/apple/woz.h b/apple/woz.h new file mode 100644 index 0000000..9520fdd --- /dev/null +++ b/apple/woz.h @@ -0,0 +1,89 @@ +#ifndef __WOZ_H +#define __WOZ_H +#include +#include +#include +#include +#include +#include "nibutil.h" +#include "disktypes.h" + +typedef struct _diskInfo { + uint8_t version; // Woz format version # + uint8_t diskType; // 1 = 5.25"; 2 = 3.5" + uint8_t writeProtected; // 1 = true + uint8_t synchronized; // were tracks written with cross-track sync? + uint8_t cleaned; // were MC3470 "fake bits" removed? + char creator[33]; // 32 chars of creator string and one terminator + uint8_t diskSides; // always 1 for a 5.25" disk + uint8_t bootSectorFormat; // 0=Unknown; 1=16-sector; 2=13-sector; 3=both + uint8_t optimalBitTiming; // 125-nS increments; standard == 32 (4uS) + uint16_t compatHardware; // 0=unknown compatability + uint16_t requiredRam; // value in K. 0 = unknown + uint16_t largestTrack; // # of 512-byte blocks used for largest track +} diskInfo; + +typedef struct _trackInfo { + uint16_t startingBlock; // v2 + uint32_t startingByte; // v1 + uint16_t blockCount; + uint32_t bitCount; + uint8_t *trackData; +} trackInfo; + +class Woz { + public: + Woz(); + ~Woz(); + + bool readFile(const char *filename, uint8_t forceType = T_AUTO); + bool writeFile(uint8_t version, const char *filename); + + uint8_t getNextWozBit(uint8_t track); + uint8_t nextDiskBit(uint8_t track); + uint8_t nextDiskByte(uint8_t track); + + bool decodeWozTrackToNib(uint8_t track, nibSector sectorData[16]); + bool decodeWozTrackToDsk(uint8_t track, uint8_t subtype, uint8_t sectorData[256*16]); + + void dumpInfo(); + + bool isSynchronized(); + + uint8_t trackNumberForQuarterTrack(uint16_t qt); + + private: + bool readWozFile(const char *filename); + bool readDskFile(const char *filename, uint8_t subtype); + bool readNibFile(const char *filename); + + uint8_t fakeBit(); + + bool parseTRKSChunk(FILE *f, uint32_t chunkSize); + bool parseTMAPChunk(FILE *f, uint32_t chunkSize); + bool parseInfoChunk(FILE *f, uint32_t chunkSize); + bool parseMetaChunk(FILE *f, uint32_t chunkSize); + + bool writeInfoChunk(uint8_t version, FILE *f); + bool writeTMAPChunk(uint8_t version, FILE *f); + bool writeTRKSChunk(uint8_t version, FILE *f); + + bool readQuarterTrackData(FILE *f, uint8_t quartertrack); + bool readSectorData(uint8_t track, uint8_t sector, nibSector *sectorData); + + void _initInfo(); + + protected: + uint8_t quarterTrackMap[40*4]; + diskInfo di; + trackInfo tracks[160]; + + // cursor for track enumeration + uint32_t trackPointer; + uint8_t trackByte; + uint8_t trackBitIdx; + uint8_t trackLoopCounter; + char *metaData; +}; + +#endif