2 // Apple 2 floppy disk support
5 // (c) 2005-2019 Underground Software
7 // JLH = James Hammons <jlhamm@acm.org>
10 // --- ---------- -----------------------------------------------------------
11 // JLH 12/03/2005 Created this file
12 // JLH 12/15/2005 Fixed nybblization functions to work properly
13 // JLH 12/27/2005 Added blank disk creation, fixed saving to work properly
16 #include "floppydrive.h"
23 #include "firmware/firmware.h"
26 #include "video.h" // For message spawning... Though there's probably a
27 // better approach than this!
31 enum { IO_MODE_READ, IO_MODE_WRITE };
33 // Misc. arrays (read only) that are needed
35 static const uint8_t bitMask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
36 static const uint8_t sequencerROM[256] = {
37 0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
38 0x2D, 0x38, 0x2D, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
39 0x38, 0x28, 0xD8, 0x08, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B,
40 0x48, 0x48, 0xD8, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
41 0x58, 0x58, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
42 0x68, 0x68, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68,
43 0x78, 0x78, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
44 0x88, 0x88, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88,
45 0x98, 0x98, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
46 0x29, 0xA8, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8,
47 0xBD, 0xB8, 0xCD, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB,
48 0x59, 0xC8, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8,
49 0xD9, 0xA0, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
50 0x08, 0xE8, 0xD8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8,
51 0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
52 0x4D, 0xE0, 0xDD, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08
55 static char nameBuf[MAX_PATH];
58 // Static in-line functions, for clarity & speed, for swapping variables
59 static inline void Swap(uint8_t & a, uint8_t & b)
67 static inline void Swap(uint32_t & a, uint32_t & b)
75 static inline void Swap(bool & a, bool & b)
83 static inline void Swap(uint8_t * & a, uint8_t * & b)
91 // FloppyDrive class implementation...
93 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), ioHappened(false), diskImageReady(false)
95 phase[0] = phase[1] = 0;
96 headPos[0] = headPos[1] = 0;
97 trackLength[0] = trackLength[1] = 51200;
98 disk[0] = disk[1] = NULL;
99 diskSize[0] = diskSize[1] = 0;
100 diskType[0] = diskType[1] = DT_EMPTY;
101 imageDirty[0] = imageDirty[1] = false;
102 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
106 FloppyDrive::~FloppyDrive()
116 bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
118 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
122 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
126 // Zero out filename, in case it doesn't load
127 imageName[driveNum][0] = 0;
128 //prolly should load EjectImage() first, so we don't have to dick around with crap
129 uint8_t * buffer = ReadFile(filename, &diskSize[driveNum]);
133 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
138 free(disk[driveNum]);
140 disk[driveNum] = buffer;
142 diskImageReady = false;
143 DetectImageType(filename, driveNum);
144 strcpy(imageName[driveNum], filename);
145 diskImageReady = true;
147 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
153 bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
155 // Various sanity checks...
158 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
162 if (diskType[driveNum] == DT_EMPTY)
164 WriteLog("FLOPPY: No image in drive #%u to save\n", driveNum);
168 if (!imageDirty[driveNum])
170 WriteLog("FLOPPY: No need to save unchanged image in drive #%u...\n", driveNum);
174 char * ext = strrchr(imageName[driveNum], '.');
176 if ((ext != NULL) && (diskType[driveNum] != DT_WOZ))
177 memcpy(ext, ".woz", 4);
179 return SaveWOZ(imageName[driveNum], (WOZ2 *)disk[driveNum], diskSize[driveNum]);
183 bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
185 strncpy(imageName[driveNum], filename, MAX_PATH);
186 // Ensure a NULL terminated string here, as strncpy() won't terminate the
187 // string if the source length is >= MAX_PATH
188 imageName[driveNum][MAX_PATH - 1] = 0;
189 return SaveImage(driveNum);
193 void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
195 if (disk[driveNum] != NULL)
196 free(disk[driveNum]);
198 disk[driveNum] = InitWOZ(&diskSize[driveNum]);
199 diskType[driveNum] = DT_WOZ;
200 strcpy(imageName[driveNum], "newblank.woz");
201 SpawnMessage("New blank image inserted in drive %u...", driveNum);
205 void FloppyDrive::SwapImages(void)
207 char imageNameTmp[MAX_PATH];
209 memcpy(imageNameTmp, imageName[0], MAX_PATH);
210 memcpy(imageName[0], imageName[1], MAX_PATH);
211 memcpy(imageName[1], imageNameTmp, MAX_PATH);
213 Swap(disk[0], disk[1]);
214 Swap(diskSize[0], diskSize[1]);
215 Swap(diskType[0], diskType[1]);
216 Swap(imageDirty[0], imageDirty[1]);
218 Swap(phase[0], phase[1]);
219 Swap(headPos[0], headPos[1]);
220 Swap(currentPos[0], currentPos[1]);
221 SpawnMessage("Drive 0: %s...", imageName[0]);
226 Need to add some type of error checking here, so we can report back on bad images, etc. (basically, it does by returning DFT_UNKNOWN, but we could do better)
228 void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
230 diskType[driveNum] = DFT_UNKNOWN;
232 uint8_t wozType = CheckWOZType(disk[driveNum], diskSize[driveNum]);
236 // Check WOZ integrity...
237 CheckWOZIntegrity(disk[driveNum], diskSize[driveNum]);
239 // If it's a WOZ type 1 file, upconvert it to type 2
243 uint8_t * buffer = UpconvertWOZ1ToWOZ2(disk[driveNum], diskSize[driveNum], &size);
245 free(disk[driveNum]);
246 disk[driveNum] = buffer;
247 diskSize[driveNum] = size;
248 WriteLog("FLOPPY: Upconverted WOZ type 1 to type 2...\n");
251 diskType[driveNum] = DT_WOZ;
253 else if (diskSize[driveNum] == 143360)
255 const char * ext = strrchr(filename, '.');
260 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
262 if (strcasecmp(ext, ".po") == 0)
263 diskType[driveNum] = DT_PRODOS;
264 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
266 // We assume this, but check for a PRODOS fingerprint. Trust, but
268 diskType[driveNum] = DT_DOS33;
270 uint8_t fingerprint[4][4] = {
271 { 0x00, 0x00, 0x03, 0x00 }, // @ $400
272 { 0x02, 0x00, 0x04, 0x00 }, // @ $600
273 { 0x03, 0x00, 0x05, 0x00 }, // @ $800
274 { 0x04, 0x00, 0x00, 0x00 } // @ $A00
277 bool foundProdos = true;
279 for(uint32_t i=0; i<4; i++)
281 for(uint32_t j=0; j<4; j++)
283 if (disk[driveNum][0x400 + (i * 0x200) + j] != fingerprint[i][j])
292 diskType[driveNum] = DT_PRODOS;
295 // Actually, it just might matter WRT to nybblyzing/denybblyzing
296 // (and, it does... :-P)
297 WOZifyImage(driveNum);
299 else if (diskSize[driveNum] == 143488)
301 diskType[driveNum] = DT_DOS33_HDR;
302 WOZifyImage(driveNum);
305 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
306 // No, we don't nybblize anymore. But we should tell the user that the loading failed with a return value
308 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_DOS33 ?
309 "DOS 3.3" : (diskType[driveNum] == DT_DOS33_HDR ?
310 "DOS 3.3 (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS" : (diskType[driveNum] == DT_WOZ ? "WOZ" : "unknown")))));
315 // Write a bitstream (source left justified to bit 7) to destination buffer.
316 // Writes 'bits' number of bits to 'dest', starting at bit position 'dstPtr',
317 // updating 'dstPtr' for the caller.
319 void FloppyDrive::WriteBits(uint8_t * dest, const uint8_t * src, uint16_t bits, uint16_t * dstPtr)
321 for(uint16_t i=0; i<bits; i++)
323 // Get the destination location's bitmask
324 uint8_t dstMask = bitMask[*dstPtr % 8];
326 // Set the bit to one if there's a corresponding one in the source
327 // data, otherwise set it to zero
328 if (src[i / 8] & bitMask[i % 8])
329 dest[*dstPtr / 8] |= dstMask;
331 dest[*dstPtr / 8] &= ~dstMask;
338 void FloppyDrive::WOZifyImage(uint8_t driveNum)
340 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
341 // (not incl. 64 byte track marker)
342 // let's try 394 per sector... & see what happens
343 // let's go back to what we had, and see what happens :-)
344 // [still need to expand them back to what they were]
346 const uint8_t ff10[2] = { 0xFF, 0x00 };
347 uint8_t addressHeader[14] = {
348 0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
349 0x00, 0x00, 0x00, 0xDE, 0xAA, 0xEB };
350 const uint8_t sectorHeader[3] = { 0xD5, 0xAA, 0xAD };
351 const uint8_t footer[3] = { 0xDE, 0xAA, 0xEB };
352 const uint8_t diskbyte[0x40] = {
353 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
354 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
355 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
356 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
357 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
358 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
359 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
360 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
361 const uint8_t doSector[16] = {
362 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
363 const uint8_t poSector[16] = {
364 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
367 // Save current image until we're done converting
368 uint8_t * tmpDisk = disk[driveNum];
370 // Set up track index...
371 disk[driveNum] = InitWOZ(&diskSize[driveNum]);
372 WOZ2 & woz = *((WOZ2 *)disk[driveNum]);
374 // Upconvert data from DSK & friends format to WOZ tracks :-)
375 for(uint8_t trk=0; trk<35; trk++)
377 uint16_t dstBitPtr = 0;
378 uint8_t * img = disk[driveNum] + (Uint16LE(woz.track[trk].startingBlock) * 512);
379 //printf("Converting track %u: startingBlock=%u, %u blocks, img=%X\n", trk, Uint16LE(woz.track[trk].startingBlock), Uint16LE(woz.track[trk].blockCount), img);
381 // Write self-sync header bytes (16, should it be 64? Dunno.)
382 for(int i=0; i<64; i++)
383 WriteBits(img, ff10, 10, &dstBitPtr);
385 // Write out the following sectors
386 for(uint8_t sector=0; sector<16; sector++)
388 // Set up the sector address header
389 addressHeader[5] = ((trk >> 1) & 0x55) | 0xAA;
390 addressHeader[6] = (trk & 0x55) | 0xAA;
391 addressHeader[7] = ((sector >> 1) & 0x55) | 0xAA;
392 addressHeader[8] = (sector & 0x55) | 0xAA;
393 addressHeader[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
394 addressHeader[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
396 WriteBits(img, addressHeader, 14 * 8, &dstBitPtr);
398 // Write 5 self-sync bytes for actual sector header
399 for(int i=0; i<5; i++)
400 WriteBits(img, ff10, 10, &dstBitPtr);
402 // Write sector header (D5 AA AD)
403 WriteBits(img, sectorHeader, 3 * 8, &dstBitPtr);
404 uint8_t * bytes = tmpDisk;
406 //Need to fix this so it writes the correct sector in the correct place *and* put the correct sector # into the header above as well. !!! FIX !!!
407 // Figure out location of sector data in disk image
408 if (diskType[driveNum] == DT_DOS33)
409 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
410 else if (diskType[driveNum] == DT_DOS33_HDR)
411 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
412 else if (diskType[driveNum] == DT_PRODOS)
413 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
415 bytes += (sector * 256) + (trk * 256 * 16);
417 // Convert the 256 8-bit bytes into 342 6-bit bytes.
418 for(uint16_t i=0; i<0x56; i++)
420 tmpNib[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
421 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
422 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
423 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
424 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
425 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
428 tmpNib[0x54] &= 0x3F;
429 tmpNib[0x55] &= 0x3F;
430 memcpy(tmpNib + 0x56, bytes, 256);
432 // XOR the data block with itself, offset by one byte, creating a
433 // 343rd byte which is used as a checksum.
436 for(uint16_t i=342; i>0; i--)
437 tmpNib[i] = tmpNib[i] ^ tmpNib[i - 1];
439 // Using a lookup table, convert the 6-bit bytes into disk bytes.
440 for(uint16_t i=0; i<343; i++)
441 tmpNib[i] = diskbyte[tmpNib[i] >> 2];
443 WriteBits(img, tmpNib, 343 * 8, &dstBitPtr);
445 // Done with the nybblization, now add the epilogue...
446 WriteBits(img, footer, 3 * 8, &dstBitPtr);
448 // (Should the footer be 30 or 48? would be 45 FF10s here for 48)
449 for(int i=0; i<27; i++)
450 WriteBits(img, ff10, 10, &dstBitPtr);
453 // Set the proper bit/byte lengths in the WOZ for this track
454 woz.track[trk].bitCount = Uint16LE(dstBitPtr);
457 // Finally, free the non-WOZ image now that we're done converting
462 const char * FloppyDrive::ImageName(uint8_t driveNum/*= 0*/)
464 // Set up a zero-length string for return value
469 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
473 // Now we attempt to strip out extraneous paths/extensions to get just the filename
474 const char * startOfFile = strrchr(imageName[driveNum], '/');
475 const char * startOfExt = strrchr(imageName[driveNum], '.');
477 // If there isn't a path, assume we're starting at the beginning
478 if (startOfFile == NULL)
479 startOfFile = &imageName[driveNum][0];
483 // If there isn't an extension, assume it's at the terminating NULL
484 if (startOfExt == NULL)
485 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
487 // Now copy the filename (may copy nothing!)
490 for(const char * i=startOfFile; i<startOfExt; i++)
499 void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
502 if (diskType[driveNum] == DT_EMPTY)
505 // Probably want to save a dirty image... ;-)
506 if (SaveImage(driveNum))
507 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
510 free(disk[driveNum]);
512 disk[driveNum] = NULL;
513 diskSize[driveNum] = 0;
514 diskType[driveNum] = DT_EMPTY;
515 imageDirty[driveNum] = false;
516 imageName[driveNum][0] = 0; // Zero out filenames
520 bool FloppyDrive::IsEmpty(uint8_t driveNum/*= 0*/)
524 WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum);
528 return (diskType[driveNum] == DT_EMPTY ? true : false);
532 bool FloppyDrive::IsWriteProtected(uint8_t driveNum/*= 0*/)
536 WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum);
540 WOZ2 & woz = *((WOZ2 *)disk[driveNum]);
541 return (bool)woz.writeProtected;
545 void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
549 WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum);
553 WOZ2 & woz = *((WOZ2 *)disk[driveNum]);
554 woz.writeProtected = (uint8_t)state;
558 int FloppyDrive::DriveLightStatus(uint8_t driveNum/*= 0*/)
560 int retval = DLS_OFF;
562 if (activeDrive != driveNum)
566 retval = (ioMode == IO_MODE_READ ? DLS_READ : DLS_WRITE);
573 void FloppyDrive::SaveState(FILE * file)
575 // Internal state vars
576 fputc(motorOn, file);
577 fputc(activeDrive, file);
579 fputc(dataRegister, file);
580 fputc((ioHappened ? 1 : 0), file);
585 WriteLong(file, diskSize[0]);
586 WriteLong(file, diskType[0]);
587 fputc(phase[0], file);
588 fputc(headPos[0], file);
589 WriteLong(file, currentPos[0]);
590 fputc((imageDirty[0] ? 1 : 0), file);
591 fwrite(disk[0], 1, diskSize[0], file);
592 fwrite(imageName[0], 1, MAX_PATH, file);
600 WriteLong(file, diskSize[1]);
601 WriteLong(file, diskType[1]);
602 fputc(phase[1], file);
603 fputc(headPos[1], file);
604 WriteLong(file, currentPos[1]);
605 fputc((imageDirty[1] ? 1 : 0), file);
606 fwrite(disk[1], 1, diskSize[1], file);
607 fwrite(imageName[1], 1, MAX_PATH, file);
614 void FloppyDrive::LoadState(FILE * file)
616 // Eject images if they're loaded
620 // Read internal state variables
621 motorOn = fgetc(file);
622 activeDrive = fgetc(file);
623 ioMode = fgetc(file);
624 dataRegister = fgetc(file);
625 ioHappened = (fgetc(file) == 1 ? true : false);
627 diskSize[0] = ReadLong(file);
631 disk[0] = new uint8_t[diskSize[0]];
632 diskType[0] = (uint8_t)ReadLong(file);
633 phase[0] = fgetc(file);
634 headPos[0] = fgetc(file);
635 currentPos[0] = ReadLong(file);
636 imageDirty[0] = (fgetc(file) == 1 ? true : false);
637 fread(disk[0], 1, diskSize[0], file);
638 fread(imageName[0], 1, MAX_PATH, file);
641 diskSize[1] = ReadLong(file);
645 disk[1] = new uint8_t[diskSize[1]];
646 diskType[1] = (uint8_t)ReadLong(file);
647 phase[1] = fgetc(file);
648 headPos[1] = fgetc(file);
649 currentPos[1] = ReadLong(file);
650 imageDirty[1] = (fgetc(file) == 1 ? true : false);
651 fread(disk[1], 1, diskSize[1], file);
652 fread(imageName[1], 1, MAX_PATH, file);
657 uint32_t FloppyDrive::ReadLong(FILE * file)
661 for(int i=0; i<4; i++)
662 r = (r << 8) | fgetc(file);
668 void FloppyDrive::WriteLong(FILE * file, uint32_t l)
670 for(int i=0; i<4; i++)
672 fputc((l >> 24) & 0xFF, file);
678 // Memory mapped I/O functions + Logic State Sequencer
681 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35
682 tracks of 16 sectors of 256 bytes each, making 143,360 bytes in total. The PO
683 format is exactly the same size as DSK and is also organized as 35 sequential
684 tracks, but the sectors within each track are in a different sequence. The NIB
685 format is a nybblized format: a more direct representation of the disk's data
686 as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
687 6656 bytes each, for a total size of 232,960 bytes. Although this format is
688 much larger, it is also more versatile and can represent the older 13-sector
689 disks, many copy-protected disks, and other unusual encodings.
691 N.B.: Though the NIB format is *closer* to the representation of the disk's
692 data, it's not *quite* 100% as there can be zero bits lurking in the
693 interstices of the bytes written to the disk. There's room for another
694 format that takes this into account (possibly even take phase 1 & 3
695 tracks into account as well).
697 As luck would have it, not long after I wrote that, I found out that some enterprising people have created it already--WOZ format. Which is now supported by apple2. :-D
699 According to Beneath Apple DOS, DOS checks the data register to see if it changes when spinning up a drive: "A sufficient delay should be provided to allow the motor time to come up to speed. Shugart recommends one second, but DOS is able to reduce this delay by watching the read latch until data starts to change." Which means, we can simulate an empty/off drive by leaving the data register alone.
702 uint64_t stepperTime = 0;
703 bool seenReadSinceStep = false;
705 void FloppyDrive::ControlStepper(uint8_t addr)
711 The stepper motor has 4 phase solenoids (numbered 0-3) which corresponds to bits 1-2 of the address. Bit 0 tells the phase solenoid to either energize (1) or de-energize (0). By energizing the phase solenoids in ascending order, the stepper motor moves the head from a low numbered track to a higher numbered track; conversely, by energizing the solenoids in descending order, the stepper motor moves the head from a high numbered track to a lower one. Given that this is a mechanical device, it takes a certain amount of time for the drum in the stepper motor to move from place to place--though pretty much all software written for the Disk II takes this into account.
713 Tracks can apparently go from 0 to 79, though typically only 0 to 69 are usuable. Further, because of the limitations of the read/write head of the drive, not every track can be written to, so typically (about 99.99% of the time in my guesstimation) only every *other* track is written to (phases 0 and 2); some disks exist that have tracks written on phase 1 or 3, but these tend to be the exception rather than the rule.
715 Taking into account the head slew time: ATM nothing seems to look at it, though it could be problematic as how we emulate it is different from how it actually works; namely, the emulator zaps the head to a new track instantly when the write to the phase happens while in the real thing, obviously this takes a non-zero amount of time. As such, none of the states where more than one phase solenoid is active at a time can be written so that they come on instantaneously; it would be fairly easy to write things that work on the real thing that don't on the emulator because of this. But most software (pretty much everything that I've ever seen) is pretty well behaved and this isn't an issue.
717 If it ever *does* become a problem, doing the physical modeling of the head moving at a real velocity shouldn't be that difficult to do.
720 // This is an array of stub positions crossed with solenoid energize
721 // patterns. The numbers represent how many quarter tracks the head will
722 // move given its current position and the pattern of energized solenoids.
723 // N.B.: Patterns for 11 & 13 haven't been filled in as I'm not sure how
724 // the stub(s) would react to those patterns. :-/
725 int16_t step[16][8] = {
726 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [....]
727 { 0, -1, -2, 0, 0, 0, +2, +1 }, // [|...]
728 { +2, +1, 0, -1, -2, 0, 0, 0 }, // [.|..]
729 { +1, 0, -1, -2, -3, 0, +3, +2 }, // [||..]
730 { 0, 0, +2, +1, 0, -1, -2, 0 }, // [..|.]
731 { 0, -1, 0, +1, 0, -1, 0, +1 }, // [|.|.]
732 { +3, +2, +1, 0, -1, -2, -3, 0 }, // [.||.]
733 { +2, +1, 0, -1, -2, -3, 0, +3 }, // [|||.]
734 { -2, 0, 0, 0, +2, +1, 0, -1 }, // [...|]
735 { -1, -2, -3, 0, +3, +2, +1, 0 }, // [|..|]
736 { 0, +1, 0, -1, 0, +1, 0, -1 }, // [.|.|]
737 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [||.|] ???
738 { -3, 0, +3, +2, +1, 0, -1, -2 }, // [..||]
739 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [|.||] ???
740 { 0, +3, +2, +1, 0, -1, -2, -3 }, // [.|||]
741 { -1, +2, +1, 0, -1, -2, +1, 0 } // [||||]
745 if (diskType[activeDrive] == DT_EMPTY)
748 // Convert phase solenoid number into a bit from 1 through 8 [1, 2, 4, 8]:
749 uint8_t phaseBit = 1 << ((addr >> 1) & 0x03);
751 // Set the state of the phase solenoid accessed using the phase bit
753 phase[activeDrive] |= phaseBit;
755 phase[activeDrive] &= ~phaseBit;
757 uint8_t oldHeadPos = headPos[activeDrive] & 0x07;
758 int16_t newStep = step[phase[activeDrive]][oldHeadPos];
759 int16_t newHeadPos = (int16_t)headPos[activeDrive] + newStep;
762 if ((newHeadPos >= 0) && (newHeadPos <= 140))
763 headPos[activeDrive] = (uint8_t)newHeadPos;
765 if (oldHeadPos != headPos[activeDrive])
767 WOZ2 & woz = *((WOZ2 *)disk[activeDrive]);
768 uint8_t newTIdx = woz.tmap[headPos[activeDrive]];
769 float newBitLen = (newTIdx == 0xFF
770 ? 51200.0f : Uint16LE(woz.track[newTIdx].bitCount));
772 uint8_t oldTIdx = woz.tmap[oldHeadPos];
773 float oldBitLen = (oldTIdx == 0xFF
774 ? 51200.0f : Uint16LE(woz.track[oldTIdx].bitCount));
775 currentPos[activeDrive] = (uint32_t)((float)currentPos[activeDrive] * (newBitLen / oldBitLen));
777 trackLength[activeDrive] = (uint16_t)newBitLen;
778 SpawnMessage("Stepping to track %u...", headPos[activeDrive] >> 2);
781 // only check the time since the phase was first set ON
784 stepperTime = mainCPU.clock;
785 seenReadSinceStep = false;
787 WriteLog("FLOPPY: Stepper phase %d set to %s [%c%c%c%c] (track=%2.2f) [%u]\n", (addr >> 1) & 0x03, (addr & 0x01 ? "ON " : "off"), (phase[activeDrive] & 0x08 ? '|' : '.'), (phase[activeDrive] & 0x04 ? '|' : '.'), (phase[activeDrive] & 0x02 ? '|' : '.'), (phase[activeDrive] & 0x01 ? '|' : '.'), (float)headPos[activeDrive] / 4.0f, mainCPU.clock & 0xFFFFFFFF);
791 void FloppyDrive::ControlMotor(uint8_t addr)
799 driveOffTimeout = 2000000;
801 WriteLog("FLOPPY: Turning drive motor %s\n", (motorOn ? "ON" : "off"));
805 void FloppyDrive::DriveEnable(uint8_t addr)
809 WriteLog("FLOPPY: Selecting drive #%hhd\n", addr + 1);
814 So for $C08C-F, we have two switches (Q6 & Q7) which combine to make four states ($C-D is off/on for Q6, $E-F is off/on for Q7).
816 So it forms a matrix like so:
819 +-----------------------------------------------------------------------
820 $C08C |Enable READ sequencing |Data reg SHL every 8th clock while writing
821 +----------------------------+------------------------------------------
822 $C08D |Check write prot./init write|Data reg LOAD every 8th clk while writing
824 Looks like reads from even addresses in $C080-F block transfer data from the sequencer to the MPU, does write from odd do the inverse (transfer from MPU to sequencer)? Looks like it.
829 void FloppyDrive::SetShiftLoadSwitch(uint8_t state)
836 void FloppyDrive::SetReadWriteSwitch(uint8_t state)
843 // MMIO: Reads from $C08x to $C0XX on even addresses
844 uint8_t FloppyDrive::DataRegister(void)
847 if (diskType[activeDrive] != DT_EMPTY)
849 WOZ2 & woz = *((WOZ2 *)disk[activeDrive]);
850 uint8_t tIdx = woz.tmap[headPos[activeDrive]];
851 uint32_t bitLen = (tIdx == 0xFF ? 51200
852 : Uint16LE(woz.track[tIdx].bitCount));
853 SpawnMessage("%u:Reading $%02X from track %u, sector %u...",
854 activeDrive, dataRegister, headPos[activeDrive] >> 2, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f));
855 ioMode = IO_MODE_READ;
858 if ((seenReadSinceStep == false) && (slSwitch == false) && (rwSwitch == false) && ((iorAddr & 0x0F) == 0x0C))
860 seenReadSinceStep = true;
861 WriteLog("%u:Reading $%02X from track %u, sector %u (delta since seek: %lu cycles) [%u]...\n",
862 activeDrive, dataRegister, headPos[activeDrive] >> 2, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f), mainCPU.clock - stepperTime, mainCPU.clock & 0xFFFFFFFF);
870 // MMIO: Writes from $C08x to $C0XX on odd addresses
871 void FloppyDrive::DataRegister(uint8_t data)
874 ioMode = IO_MODE_WRITE;
880 OFF switches ON switches
881 Switch Addr Func Addr Func
882 Q0 $C080 Phase 0 off $C081 Phase 0 on
883 Q1 $C082 Phase 1 off $C083 Phase 1 on
884 Q2 $C084 Phase 2 off $C085 Phase 2 on
885 Q3 $C086 Phase 3 off $C087 Phase 3 on
886 Q4 $C088 Drive off $C089 Drive on
887 Q5 $C08A Select Drive 1 $C08B Select Drive 2
888 Q6 $C08C Shift data register $C08D Load data register
889 Q7 $C08E Read $C08F Write
891 From "Beneath Apple ProDOS", description of combinations of $C0EC-EF
893 $C08C, $C08E: Enable read sequencing
894 $C08C, $C08F: Shift data register every four cycles while writing
895 $C08D, $C08E: Check write protect and initialize sequencer for writing
896 $C08D, $C08F: Load data register every four cycles while writing
901 LDX #SLOT Put slot number times 16 in X-register.
903 LDA $C08E, X Sense write protect.
904 BMI ERROR If high bit set, protected.
909 PRODOS 8 MLI ERROR CODES
912 $01: Bad system call number
913 $04: Bad system call parameter count
914 $25: Interrupt table full
916 $28: No device connected
917 $2B: Disk write protected
919 $40: Invalid pathname
920 $42: Maximum number of files open
921 $43: Invalid reference number
922 $44: Directory not found
923 $45: Volume not found
925 $47: Duplicate filename
927 $49: Volume directory full
928 $4A: Incompatible file format, also a ProDOS directory
929 $4B: Unsupported storage_type
930 $4C: End of file encountered
931 $4D: Position out of range
932 $4E: File access error, also file locked
934 $51: Directory structure damaged
935 $52: Not a ProDOS volume
936 $53: Invalid system call parameter
937 $55: Volume Control Block table full
938 $56: Bad buffer address
939 $57: Duplicate volume
940 $5A: File structure damaged
943 // N.B.: The WOZ documentation says that the bitstream is normalized to 4µs.
944 // Which means on the //e that you would have to run it at that clock
945 // rate (instead of the //e clock rate 0.9799µs/cycle) to get the
946 // simulated drive running at 300 RPM. So, instead of doing that, we're
947 // just gonna run it at twice the clock rate of the base 6502 clock,
948 // which will make the simulated drive run in the neighborhood of around
949 // 306 RPM. Should be close enough to get away with it. :-) (And it
950 // seems to run OK, for the most part.)
953 static bool logSeq = false;
955 // Logic State Sequencer & Data Register
957 void FloppyDrive::RunSequencer(uint32_t cyclesToRun)
959 static uint32_t prng = 1;
964 else if (diskType[activeDrive] == DT_EMPTY)
966 else if (motorOn == false)
968 if (driveOffTimeout == 0)
974 WOZ2 & woz = *((WOZ2 *)disk[activeDrive]);
975 uint8_t tIdx = woz.tmap[headPos[activeDrive]];
976 uint8_t * tdata = disk[activeDrive] + (Uint16LE(woz.track[tIdx].startingBlock) * 512);
978 // It's x2 because the sequencer clock runs twice as fast as the CPU clock.
981 //extern bool dumpDis;
982 //static bool tripwire = false;
984 //static uint32_t lastPos = 0;
987 WriteLog("DISKSEQ: Running for %d cycles [rw=%hhd, sl=%hhd, reg=%02X, bus=%02X]\n", cyclesToRun, rwSwitch, slSwitch, dataRegister, cpuDataBus);
990 while (cyclesToRun-- > 0)
992 // pulseClock = (pulseClock + 1) & 0x07;
993 pulseClock = (pulseClock + 1) % 8;
994 // 7 doesn't work... Is that 3.5µs? Seems to be. Which means to get a 0.25µs granularity here, we need to double the # of cycles to run...
995 // pulseClock = (pulseClock + 1) % 7;
999 uint16_t bytePos = currentPos[activeDrive] / 8;
1000 uint8_t bitPos = currentPos[activeDrive] % 8;
1004 if (tdata[bytePos] & bitMask[bitPos])
1006 // According to Jim Sather (Understanding the Apple II),
1007 // the Read Pulse, when it happens, is 1µs long, which is 2
1008 // sequencer clock pulses long.
1016 currentPos[activeDrive] = (currentPos[activeDrive] + 1) % trackLength[activeDrive];
1018 // If we hit more than 2 zero bits in a row, simulate the disk head
1019 // reader's Automatic Gain Control (AGC) turning itself up too high
1020 // by stuffing random bits in the bitstream. We also do this if
1021 // the current track is marked as unformatted.
1023 N.B.: Had to up this to 3 because Up N' Down had some weird sync bytes (FE10). May have to up it some more.
1025 if ((zeroBitCount > 3) || (tIdx == 0xFF))
1029 // This PRNG is called the "Galois configuration".
1038 // Find and run the Sequencer's next state
1039 uint8_t nextState = (sequencerState & 0xF0) | (rwSwitch << 3)
1040 | (slSwitch << 2) | (readPulse ? 0x02 : 0)
1041 | ((dataRegister & 0x80) >> 7);
1043 WriteLog("[%02X:%02X]%s", sequencerState, nextState, (chop == 15 ? "\n" : ""));
1044 chop = (chop + 1) % 20;
1045 sequencerState = sequencerROM[nextState];
1047 switch (sequencerState & 0x0F)
1057 // CLR (clear data register)
1062 // NOP (no operation)
1065 // SL0 (shift left, 0 fill LSB)
1069 if (rwSwitch && (tIdx != 0xFF)
1070 && !woz.writeProtected)
1072 imageDirty[activeDrive] = true;
1073 uint16_t bytePos = currentPos[activeDrive] / 8;
1074 uint8_t bitPos = currentPos[activeDrive] % 8;
1076 if (dataRegister & 0x80)
1077 // Fill in the one, if necessary
1078 tdata[bytePos] |= bitMask[bitPos];
1080 // Otherwise, punch in the zero
1081 tdata[bytePos] &= ~bitMask[bitPos];
1084 if (dumpDis || tripwire)
1087 WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0"));
1088 if (lastPos == currentPos[activeDrive])
1089 WriteLog("{STOMP}");
1090 else if ((lastPos + 1) != currentPos[activeDrive])
1092 lastPos = currentPos[activeDrive];
1100 // SR (shift right write protect bit)
1102 dataRegister |= (woz.writeProtected ? 0x80 : 0x00);
1106 // LD (load data register from data bus)
1107 dataRegister = cpuDataBus;
1110 if (rwSwitch && (tIdx != 0xFF) && !woz.writeProtected)
1112 imageDirty[activeDrive] = true;
1113 uint16_t bytePos = currentPos[activeDrive] / 8;
1114 uint8_t bitPos = currentPos[activeDrive] % 8;
1115 tdata[bytePos] |= bitMask[bitPos];
1117 if (dumpDis || tripwire)
1120 WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0"));
1121 if (lastPos == currentPos[activeDrive])
1122 WriteLog("{STOMP}");
1123 else if ((lastPos + 1) != currentPos[activeDrive])
1125 lastPos = currentPos[activeDrive];
1132 // SL1 (shift left, 1 fill LSB)
1134 dataRegister |= 0x01;
1147 FloppyDrive floppyDrive[2];
1149 static uint8_t SlotIOR(uint16_t address)
1151 uint8_t state = address & 0x0F;
1163 floppyDrive[0].ControlStepper(state);
1167 floppyDrive[0].ControlMotor(state & 0x01);
1171 floppyDrive[0].DriveEnable(state & 0x01);
1175 floppyDrive[0].SetShiftLoadSwitch(state & 0x01);
1179 floppyDrive[0].SetReadWriteSwitch(state & 0x01);
1183 //temp, for debugging
1185 // Even addresses return the data register, odd (we suppose) returns a
1186 // floating bus read...
1187 return (address & 0x01 ? ReadFloatingBus(0) : floppyDrive[0].DataRegister());
1191 static void SlotIOW(uint16_t address, uint8_t byte)
1193 uint8_t state = address & 0x0F;
1205 floppyDrive[0].ControlStepper(state);
1209 floppyDrive[0].ControlMotor(state & 0x01);
1213 floppyDrive[0].DriveEnable(state & 0x01);
1217 floppyDrive[0].SetShiftLoadSwitch(state & 0x01);
1221 floppyDrive[0].SetReadWriteSwitch(state & 0x01);
1225 // Odd addresses write to the Data register, even addresses (we assume) go
1228 floppyDrive[0].DataRegister(byte);
1232 // This slot function doesn't need to differentiate between separate instances
1234 static uint8_t SlotROM(uint16_t address)
1236 return diskROM[address];
1240 void InstallFloppy(uint8_t slot)
1242 SlotData disk = { SlotIOR, SlotIOW, SlotROM, 0, 0, 0 };
1243 InstallSlotHandler(slot, &disk);