2 // Apple 2 floppy disk support
5 // (c) 2005-2018 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"
25 #include "video.h" // For message spawning... Though there's probably a
26 // better approach than this!
30 enum { IO_MODE_READ, IO_MODE_WRITE };
32 // FloppyDrive class variable initialization
34 uint8_t FloppyDrive::doSector[16] = {
35 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
36 uint8_t FloppyDrive::poSector[16] = {
37 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
38 uint8_t FloppyDrive::wozHeader[9] = "WOZ1\xFF\x0A\x0D\x0A";
39 uint8_t FloppyDrive::wozHeader2[9] = "WOZ2\xFF\x0A\x0D\x0A";
40 uint8_t FloppyDrive::standardTMAP[141] = {
41 0, 0, 0xFF, 1, 1, 1, 0xFF, 2, 2, 2, 0xFF, 3, 3, 3, 0xFF, 4, 4, 4, 0xFF,
42 5, 5, 5, 0xFF, 6, 6, 6, 0xFF, 7, 7, 7, 0xFF, 8, 8, 8, 0xFF, 9, 9, 9, 0xFF,
43 10, 10, 10, 0xFF, 11, 11, 11, 0xFF, 12, 12, 12, 0xFF, 13, 13, 13, 0xFF,
44 14, 14, 14, 0xFF, 15, 15, 15, 0xFF, 16, 16, 16, 0xFF, 17, 17, 17, 0xFF,
45 18, 18, 18, 0xFF, 19, 19, 19, 0xFF, 20, 20, 20, 0xFF, 21, 21, 21, 0xFF,
46 22, 22, 22, 0xFF, 23, 23, 23, 0xFF, 24, 24, 24, 0xFF, 25, 25, 25, 0xFF,
47 26, 26, 26, 0xFF, 27, 27, 27, 0xFF, 28, 28, 28, 0xFF, 29, 29, 29, 0xFF,
48 30, 30, 30, 0xFF, 31, 31, 31, 0xFF, 32, 32, 32, 0xFF, 33, 33, 33, 0xFF,
49 34, 34, 34, 0xFF, 0xFF, 0xFF
51 uint8_t FloppyDrive::bitMask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
52 uint8_t FloppyDrive::sequencerROM[256] = {
53 0x18, 0x18, 0x18, 0x18, 0x0A, 0x0A, 0x0A, 0x0A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
54 0x2D, 0x38, 0x2D, 0x38, 0x0A, 0x0A, 0x0A, 0x0A, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
55 0x38, 0x28, 0xD8, 0x08, 0x0A, 0x0A, 0x0A, 0x0A, 0x39, 0x39, 0x39, 0x39, 0x3B, 0x3B, 0x3B, 0x3B,
56 0x48, 0x48, 0xD8, 0x48, 0x0A, 0x0A, 0x0A, 0x0A, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48,
57 0x58, 0x58, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58,
58 0x68, 0x68, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68,
59 0x78, 0x78, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78,
60 0x88, 0x88, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88,
61 0x98, 0x98, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
62 0x29, 0xA8, 0xD8, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8, 0xA8,
63 0xBD, 0xB8, 0xCD, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xB9, 0xB9, 0xB9, 0xB9, 0xBB, 0xBB, 0xBB, 0xBB,
64 0x59, 0xC8, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8, 0xC8,
65 0xD9, 0xA0, 0xD9, 0xD8, 0x0A, 0x0A, 0x0A, 0x0A, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
66 0x08, 0xE8, 0xD8, 0xE8, 0x0A, 0x0A, 0x0A, 0x0A, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8, 0xE8,
67 0xFD, 0xF8, 0xFD, 0xF8, 0x0A, 0x0A, 0x0A, 0x0A, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8,
68 0x4D, 0xE0, 0xDD, 0xE0, 0x0A, 0x0A, 0x0A, 0x0A, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08, 0x88, 0x08
71 char FloppyDrive::nameBuf[MAX_PATH];
74 // Static in-line functions, for clarity & speed, mostly for reading values out
75 // of the WOZ struct, which stores its data in LE; some for swapping variables
76 static inline uint16_t Uint16LE(uint16_t v)
78 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
79 return ((v & 0xFF) << 8) | ((v & 0xFF00) >> 8);
86 static inline uint32_t Uint32LE(uint32_t v)
88 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
89 return ((v & 0xFF) << 24) | ((v & 0xFF00) << 8)
90 | ((v & 0xFF0000) >> 8) | ((v & 0xFF000000) >> 24);
97 static inline void Swap(uint8_t & a, uint8_t & b)
105 static inline void Swap(uint32_t & a, uint32_t & b)
113 static inline void Swap(bool & a, bool & b)
121 static inline void Swap(uint8_t * & a, uint8_t * & b)
129 static inline void Swap(WOZ * & a, WOZ * & b)
137 // FloppyDrive class implementation...
139 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), ioHappened(false)
141 phase[0] = phase[1] = 0;
142 headPos[0] = headPos[1] = 0;
143 trackLength[0] = trackLength[1] = 51200;
144 disk[0] = disk[1] = NULL;
145 woz[0] = woz[1] = NULL;
146 diskSize[0] = diskSize[1] = 0;
147 diskType[0] = diskType[1] = DT_EMPTY;
148 imageDirty[0] = imageDirty[1] = false;
149 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
153 FloppyDrive::~FloppyDrive()
163 bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
165 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
169 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
173 // Zero out filename, in case it doesn't load
174 imageName[driveNum][0] = 0;
176 FILE * fp = fopen(filename, "rb");
180 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
185 delete[] disk[driveNum];
187 fseek(fp, 0, SEEK_END);
188 diskSize[driveNum] = ftell(fp);
189 fseek(fp, 0, SEEK_SET);
190 disk[driveNum] = new uint8_t[diskSize[driveNum]];
191 woz[driveNum] = (WOZ *)disk[driveNum];
192 fread(disk[driveNum], 1, diskSize[driveNum], fp);
195 //printf("Read disk image: %u bytes.\n", diskSize);
196 DetectImageType(filename, driveNum);
197 strcpy(imageName[driveNum], filename);
199 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
205 bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
207 // comment out for now...
209 // Various sanity checks...
212 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
216 if (!imageDirty[driveNum])
218 WriteLog("FLOPPY: No need to save unchanged image...\n");
222 if (imageName[driveNum][0] == 0)
224 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
228 // Finally, write the damn image
229 FILE * fp = fopen(imageName[driveNum], "wb");
233 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
237 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
240 WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]);
244 char * ext = strrchr(imageName[driveNum], '.');
246 if ((ext != NULL) && (diskType[driveNum] != DT_WOZ))
247 memcpy(ext, ".woz", 4);
249 return SaveWOZ(driveNum);
254 bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
256 strncpy(imageName[driveNum], filename, MAX_PATH);
257 // Ensure a NULL terminated string here, as strncpy() won't terminate the
258 // string if the source length is >= MAX_PATH
259 imageName[driveNum][MAX_PATH - 1] = 0;
260 return SaveImage(driveNum);
264 void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
266 if (disk[driveNum] != NULL)
267 delete disk[driveNum];
270 diskType[driveNum] = DT_WOZ;
271 strcpy(imageName[driveNum], "newblank.woz");
272 SpawnMessage("New blank image inserted in drive %u...", driveNum);
276 void FloppyDrive::SwapImages(void)
279 WriteLog("SwapImages BEFORE:\n");
280 WriteLog("\tdisk[0]=%X, disk[1]=%X\n", disk[0], disk[1]);
281 WriteLog("\twoz[0]=%X, woz[1]=%X\n", woz[0], woz[1]);
282 WriteLog("\tdiskSize[0]=%X, diskSize[1]=%X\n", diskSize[0], diskSize[1]);
283 WriteLog("\tdiskType[0]=%X, diskType[1]=%X\n", diskType[0], diskType[1]);
284 WriteLog("\timageDirty[0]=%X, imageDirty[1]=%X\n", imageDirty[0], imageDirty[1]);
285 WriteLog("\tphase[0]=%X, phase[1]=%X\n", phase[0], phase[1]);
286 WriteLog("\theadPos[0]=%X, headPos[1]=%X\n", headPos[0], headPos[1]);
287 WriteLog("\tcurrentPos[0]=%X, currentPos[1]=%X\n", currentPos[0], currentPos[1]);
289 char imageNameTmp[MAX_PATH];
291 memcpy(imageNameTmp, imageName[0], MAX_PATH);
292 memcpy(imageName[0], imageName[1], MAX_PATH);
293 memcpy(imageName[1], imageNameTmp, MAX_PATH);
295 Swap(disk[0], disk[1]);
296 Swap(woz[0], woz[1]);
297 Swap(diskSize[0], diskSize[1]);
298 Swap(diskType[0], diskType[1]);
299 Swap(imageDirty[0], imageDirty[1]);
301 Swap(phase[0], phase[1]);
302 Swap(headPos[0], headPos[1]);
303 Swap(currentPos[0], currentPos[1]);
304 SpawnMessage("Drive 0: %s...", imageName[0]);
306 WriteLog("SwapImages AFTER:\n");
307 WriteLog("\tdisk[0]=%X, disk[1]=%X\n", disk[0], disk[1]);
308 WriteLog("\twoz[0]=%X, woz[1]=%X\n", woz[0], woz[1]);
309 WriteLog("\tdiskSize[0]=%X, diskSize[1]=%X\n", diskSize[0], diskSize[1]);
310 WriteLog("\tdiskType[0]=%X, diskType[1]=%X\n", diskType[0], diskType[1]);
311 WriteLog("\timageDirty[0]=%X, imageDirty[1]=%X\n", imageDirty[0], imageDirty[1]);
312 WriteLog("\tphase[0]=%X, phase[1]=%X\n", phase[0], phase[1]);
313 WriteLog("\theadPos[0]=%X, headPos[1]=%X\n", headPos[0], headPos[1]);
314 WriteLog("\tcurrentPos[0]=%X, currentPos[1]=%X\n", currentPos[0], currentPos[1]);
320 Need to add some type of error checking here, so we can report back on bad images, etc.
322 void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
324 diskType[driveNum] = DFT_UNKNOWN;
326 if (memcmp(disk[driveNum], wozHeader, 8) == 0)
328 diskType[driveNum] = DT_WOZ;
329 /*bool r =*/ CheckWOZ(disk[driveNum], diskSize[driveNum], driveNum);
331 else if (diskSize[driveNum] == 143360)
333 const char * ext = strrchr(filename, '.');
338 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
340 if (strcasecmp(ext, ".po") == 0)
341 diskType[driveNum] = DT_PRODOS;
342 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
344 // We assume this, but check for a PRODOS fingerprint. Trust, but
346 diskType[driveNum] = DT_DOS33;
348 uint8_t fingerprint[4][4] = {
349 { 0x00, 0x00, 0x03, 0x00 }, // @ $400
350 { 0x02, 0x00, 0x04, 0x00 }, // @ $600
351 { 0x03, 0x00, 0x05, 0x00 }, // @ $800
352 { 0x04, 0x00, 0x00, 0x00 } // @ $A00
355 bool foundProdos = true;
357 for(uint32_t i=0; i<4; i++)
359 for(uint32_t j=0; j<4; j++)
361 if (disk[driveNum][0x400 + (i * 0x200) + j] != fingerprint[i][j])
370 diskType[driveNum] = DT_PRODOS;
373 // Actually, it just might matter WRT to nybblyzing/denybblyzing
374 // (and, it does... :-P)
375 WOZifyImage(driveNum);
377 else if (diskSize[driveNum] == 143488)
379 diskType[driveNum] = DT_DOS33_HDR;
380 WOZifyImage(driveNum);
383 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
384 // No, we don't nybblize anymore. But we should tell the user that the loading failed with a return value
386 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_DOS33 ?
387 "DOS 3.3 image" : (diskType[driveNum] == DT_DOS33_HDR ?
388 "DOS 3.3 image (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : (diskType[driveNum] == DT_WOZ ? "WOZ image" : "unknown")))));
393 // Write a bitstream (source left justified to bit 7) to destination buffer.
394 // Writes 'bits' number of bits to 'dest', starting at bit position 'dstPtr',
395 // updating 'dstPtr' for the caller.
397 void FloppyDrive::WriteBits(uint8_t * dest, uint8_t * src, uint16_t bits, uint16_t * dstPtr)
399 for(uint16_t i=0; i<bits; i++)
401 // Get the destination location's bitmask
402 uint8_t dstMask = bitMask[*dstPtr % 8];
404 // Set the bit to one if there's a corresponding one in the source
405 // data, otherwise set it to zero
406 if (src[i / 8] & bitMask[i % 8])
407 dest[*dstPtr / 8] |= dstMask;
409 dest[*dstPtr / 8] &= ~dstMask;
416 void FloppyDrive::WOZifyImage(uint8_t driveNum)
418 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
419 // (not incl. 64 byte track marker)
420 // let's try 394 per sector... & see what happens
421 // let's go back to what we had, and see what happens :-)
422 // [still need to expand them back to what they were]
424 uint8_t ff10[2] = { 0xFF, 0x00 };
425 uint8_t addressHeader[14] = {
426 0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
427 0x00, 0x00, 0x00, 0xDE, 0xAA, 0xEB };
428 uint8_t sectorHeader[3] = { 0xD5, 0xAA, 0xAD };
429 uint8_t footer[3] = { 0xDE, 0xAA, 0xEB };
430 uint8_t diskbyte[0x40] = {
431 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
432 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
433 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
434 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
435 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
436 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
437 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
438 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
441 // memcpy(tmpDisk, disk[driveNum], diskSize[driveNum]);
442 // delete[] disk[driveNum];
443 uint8_t * tmpDisk = disk[driveNum];
444 disk[driveNum] = NULL;//new uint8_t[diskSize[driveNum]];
446 // Set up track index...
447 // memcpy(woz[driveNum]->tmap, standardTMAP, 141);
450 // Upconvert data from DSK & friends format to WOZ tracks :-)
451 for(uint8_t trk=0; trk<35; trk++)
453 uint16_t dstBitPtr = 0;
454 uint8_t * img = woz[driveNum]->track[trk].bits;
456 // memset(img, 0, 6646);
458 // Write self-sync header bytes (16, should it be 64? Dunno.)
459 for(int i=0; i<64; i++)
460 WriteBits(img, ff10, 10, &dstBitPtr);
462 // Write out the following sectors
463 for(uint8_t sector=0; sector<16; sector++)
465 // Set up the sector address header
466 addressHeader[5] = ((trk >> 1) & 0x55) | 0xAA;
467 addressHeader[6] = (trk & 0x55) | 0xAA;
468 addressHeader[7] = ((sector >> 1) & 0x55) | 0xAA;
469 addressHeader[8] = (sector & 0x55) | 0xAA;
470 addressHeader[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
471 addressHeader[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
473 WriteBits(img, addressHeader, 14 * 8, &dstBitPtr);
475 // Write 5 self-sync bytes for actual sector header
476 for(int i=0; i<5; i++)
477 WriteBits(img, ff10, 10, &dstBitPtr);
479 // Write sector header (D5 AA AD)
480 WriteBits(img, sectorHeader, 3 * 8, &dstBitPtr);
481 // uint8_t * bytes = disk[driveNum];
482 uint8_t * bytes = tmpDisk;
484 //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 !!!
485 // Figure out location of sector data in disk image
486 if (diskType[driveNum] == DT_DOS33)
487 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
488 else if (diskType[driveNum] == DT_DOS33_HDR)
489 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
490 else if (diskType[driveNum] == DT_PRODOS)
491 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
493 bytes += (sector * 256) + (trk * 256 * 16);
495 // Convert the 256 8-bit bytes into 342 6-bit bytes.
496 for(uint16_t i=0; i<0x56; i++)
498 tmpNib[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
499 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
500 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
501 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
502 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
503 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
506 tmpNib[0x54] &= 0x3F;
507 tmpNib[0x55] &= 0x3F;
508 memcpy(tmpNib + 0x56, bytes, 256);
510 // XOR the data block with itself, offset by one byte, creating a
511 // 343rd byte which is used as a checksum.
514 for(uint16_t i=342; i>0; i--)
515 tmpNib[i] = tmpNib[i] ^ tmpNib[i - 1];
517 // Using a lookup table, convert the 6-bit bytes into disk bytes.
518 for(uint16_t i=0; i<343; i++)
519 tmpNib[i] = diskbyte[tmpNib[i] >> 2];
521 WriteBits(img, tmpNib, 343 * 8, &dstBitPtr);
523 // Done with the nybblization, now add the epilogue...
524 WriteBits(img, footer, 3 * 8, &dstBitPtr);
526 // (Should the footer be 30 or 48? would be 45 FF10s here for 48)
527 for(int i=0; i<27; i++)
528 WriteBits(img, ff10, 10, &dstBitPtr);
531 // Set the proper bit/byte lengths in the WOZ for this track
532 woz[driveNum]->track[trk].bitCount = Uint16LE(dstBitPtr);
533 woz[driveNum]->track[trk].byteCount = Uint16LE((dstBitPtr + 7) / 8);
540 const char * FloppyDrive::ImageName(uint8_t driveNum/*= 0*/)
542 // Set up a zero-length string for return value
547 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
551 // Now we attempt to strip out extraneous paths/extensions to get just the filename
552 const char * startOfFile = strrchr(imageName[driveNum], '/');
553 const char * startOfExt = strrchr(imageName[driveNum], '.');
555 // If there isn't a path, assume we're starting at the beginning
556 if (startOfFile == NULL)
557 startOfFile = &imageName[driveNum][0];
561 // If there isn't an extension, assume it's at the terminating NULL
562 if (startOfExt == NULL)
563 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
565 // Now copy the filename (may copy nothing!)
568 for(const char * i=startOfFile; i<startOfExt; i++)
577 void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
580 if (diskType[driveNum] == DT_EMPTY)
583 // Probably want to save a dirty image... ;-)
584 if (SaveImage(driveNum))
585 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
588 delete[] disk[driveNum];
590 disk[driveNum] = NULL;
591 woz[driveNum] = NULL;
592 diskSize[driveNum] = 0;
593 diskType[driveNum] = DT_EMPTY;
594 imageDirty[driveNum] = false;
595 imageName[driveNum][0] = 0; // Zero out filenames
599 bool FloppyDrive::IsEmpty(uint8_t driveNum/*= 0*/)
603 WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum);
607 return (diskType[driveNum] == DT_EMPTY ? true : false);
611 bool FloppyDrive::IsWriteProtected(uint8_t driveNum/*= 0*/)
615 WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum);
619 return (bool)woz[driveNum]->writeProtected;
623 void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
627 WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum);
631 woz[driveNum]->writeProtected = (uint8_t)state;
635 int FloppyDrive::DriveLightStatus(uint8_t driveNum/*= 0*/)
637 int retval = DLS_OFF;
639 if (activeDrive != driveNum)
643 retval = (ioMode == IO_MODE_READ ? DLS_READ : DLS_WRITE);
650 void FloppyDrive::SaveState(FILE * file)
652 // Internal state vars
653 fputc(motorOn, file);
654 fputc(activeDrive, file);
656 fputc(dataRegister, file);
657 fputc((ioHappened ? 1 : 0), file);
662 WriteLong(file, diskSize[0]);
663 WriteLong(file, diskType[0]);
664 fputc(phase[0], file);
665 fputc(headPos[0], file);
666 WriteLong(file, currentPos[0]);
667 fputc((imageDirty[0] ? 1 : 0), file);
668 fwrite(disk[0], 1, diskSize[0], file);
669 fwrite(imageName[0], 1, MAX_PATH, file);
677 WriteLong(file, diskSize[1]);
678 WriteLong(file, diskType[1]);
679 fputc(phase[1], file);
680 fputc(headPos[1], file);
681 WriteLong(file, currentPos[1]);
682 fputc((imageDirty[1] ? 1 : 0), file);
683 fwrite(disk[1], 1, diskSize[1], file);
684 fwrite(imageName[1], 1, MAX_PATH, file);
691 void FloppyDrive::LoadState(FILE * file)
693 // Eject images if they're loaded
697 // Read internal state variables
698 motorOn = fgetc(file);
699 activeDrive = fgetc(file);
700 ioMode = fgetc(file);
701 dataRegister = fgetc(file);
702 ioHappened = (fgetc(file) == 1 ? true : false);
704 diskSize[0] = ReadLong(file);
708 disk[0] = new uint8_t[diskSize[0]];
709 diskType[0] = (uint8_t)ReadLong(file);
710 phase[0] = fgetc(file);
711 headPos[0] = fgetc(file);
712 currentPos[0] = ReadLong(file);
713 imageDirty[0] = (fgetc(file) == 1 ? true : false);
714 fread(disk[0], 1, diskSize[0], file);
715 fread(imageName[0], 1, MAX_PATH, file);
716 woz[0] = (WOZ *)disk[0];
719 diskSize[1] = ReadLong(file);
723 disk[1] = new uint8_t[diskSize[1]];
724 diskType[1] = (uint8_t)ReadLong(file);
725 phase[1] = fgetc(file);
726 headPos[1] = fgetc(file);
727 currentPos[1] = ReadLong(file);
728 imageDirty[1] = (fgetc(file) == 1 ? true : false);
729 fread(disk[1], 1, diskSize[1], file);
730 fread(imageName[1], 1, MAX_PATH, file);
731 woz[1] = (WOZ *)disk[1];
736 uint32_t FloppyDrive::ReadLong(FILE * file)
740 for(int i=0; i<4; i++)
741 r = (r << 8) | fgetc(file);
747 void FloppyDrive::WriteLong(FILE * file, uint32_t l)
749 for(int i=0; i<4; i++)
751 fputc((l >> 24) & 0xFF, file);
757 void FloppyDrive::WriteLongLE(FILE * file, uint32_t l)
759 for(int i=0; i<4; i++)
761 fputc(l & 0xFF, file);
767 void FloppyDrive::WriteWordLE(FILE * file, uint16_t w)
769 fputc(w & 0xFF, file);
770 fputc((w >> 8) & 0xFF, file);
774 void FloppyDrive::WriteZeroes(FILE * file, uint32_t num)
776 for(uint32_t i=0; i<num; i++)
781 // Memory mapped I/O functions + Logic State Sequencer
784 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35
785 tracks of 16 sectors of 256 bytes each, making 143,360 bytes in total. The PO
786 format is exactly the same size as DSK and is also organized as 35 sequential
787 tracks, but the sectors within each track are in a different sequence. The NIB
788 format is a nybblized format: a more direct representation of the disk's data
789 as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
790 6656 bytes each, for a total size of 232,960 bytes. Although this format is
791 much larger, it is also more versatile and can represent the older 13-sector
792 disks, many copy-protected disks, and other unusual encodings.
794 N.B.: Though the NIB format is *closer* to the representation of the disk's
795 data, it's not *quite* 100% as there can be zero bits lurking in the
796 interstices of the bytes written to the disk. There's room for another
797 format that takes this into account (possibly even take phase 1 & 3
798 tracks into account as well).
800 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
802 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.
805 uint64_t stepperTime = 0;
806 bool seenReadSinceStep = false;
808 void FloppyDrive::ControlStepper(uint8_t addr)
814 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.
816 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.
818 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.
820 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.
823 // This is an array of stub positions crossed with solenoid energize
824 // patterns. The numbers represent how many quarter tracks the head will
825 // move given its current position and the pattern of energized solenoids.
826 // N.B.: Patterns for 11 & 13 haven't been filled in as I'm not sure how
827 // the stub would react to those patterns. :-/
828 int16_t step[16][8] = {
829 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [....]
830 { 0, -1, -2, 0, 0, 0, +2, +1 }, // [|...]
831 { +2, +1, 0, -1, -2, 0, 0, 0 }, // [.|..]
832 { +1, 0, -1, -2, -3, 0, +3, +2 }, // [||..]
833 { 0, 0, +2, +1, 0, -1, -2, 0 }, // [..|.]
834 { 0, -1, 0, +1, 0, -1, 0, +1 }, // [|.|.]
835 { +3, +2, +1, 0, -1, -2, -3, 0 }, // [.||.]
836 { +2, +1, 0, -1, -2, -3, 0, +3 }, // [|||.]
837 { -2, 0, 0, 0, +2, +1, 0, -1 }, // [...|]
838 { -1, -2, -3, 0, +3, +2, +1, 0 }, // [|..|]
839 { 0, +1, 0, -1, 0, +1, 0, -1 }, // [.|.|]
840 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [||.|] ???
841 { -3, 0, +3, +2, +1, 0, -1, -2 }, // [..||]
842 { 0, 0, 0, 0, 0, 0, 0, 0 }, // [|.||] ???
843 { 0, +3, +2, +1, 0, -1, -2, -3 }, // [.|||]
844 { -1, +2, +1, 0, -1, -2, +1, 0 } // [||||]
848 if (diskType[activeDrive] == DT_EMPTY)
851 // Convert phase solenoid number into a bit from 1 through 8 [1, 2, 4, 8]:
852 uint8_t phaseBit = 1 << ((addr >> 1) & 0x03);
854 // Set the state of the phase solenoid accessed using the phase bit
856 phase[activeDrive] |= phaseBit;
858 phase[activeDrive] &= ~phaseBit;
861 uint8_t oldHeadPos = headPos[activeDrive] & 0x07;
862 int16_t newStep = step[phase[activeDrive]][oldHeadPos];
863 int16_t newHeadPos = (int16_t)headPos[activeDrive] + newStep;
866 if ((newHeadPos >= 0) && (newHeadPos <= 140))
867 headPos[activeDrive] = (uint8_t)newHeadPos;
869 // See if the new phase solenoid is energized, & move the stepper/head
871 // N.B.: The head stub is located by bits 1 & 2 of the headPos variable
872 uint8_t oldHeadPos = headPos[activeDrive];
873 uint8_t nextUp = 1 << (((oldHeadPos >> 1) + 1) & 0x03);
874 uint8_t nextDown = 1 << (((oldHeadPos >> 1) - 1) & 0x03);
876 // We simulate cogging here by seeing if there's a valid up and/or down
877 // position to go to. If both are valid, the head goes nowhere.
878 if (phase[activeDrive] & nextUp)
879 headPos[activeDrive] += (headPos[activeDrive] < 140 ? 2 : 0);
881 if (phase[activeDrive] & nextDown)
882 headPos[activeDrive] -= (headPos[activeDrive] > 0 ? 2 : 0);
885 if (oldHeadPos != headPos[activeDrive])
887 uint8_t newTIdx = woz[activeDrive]->tmap[headPos[activeDrive]];
888 float newBitLen = (newTIdx == 0xFF ? 51200.0f
889 : Uint16LE(woz[activeDrive]->track[newTIdx].bitCount));
891 uint8_t oldTIdx = woz[activeDrive]->tmap[oldHeadPos];
892 float oldBitLen = (oldTIdx == 0xFF ? 51200.0f
893 : Uint16LE(woz[activeDrive]->track[oldTIdx].bitCount));
894 currentPos[activeDrive] = (uint32_t)((float)currentPos[activeDrive] * (newBitLen / oldBitLen));
896 trackLength[activeDrive] = (uint16_t)newBitLen;
897 SpawnMessage("Stepping to track %u...", headPos[activeDrive] >> 2);
900 // only check the time since the phase was first set ON
903 stepperTime = mainCPU.clock;
904 seenReadSinceStep = false;
906 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);
910 void FloppyDrive::ControlMotor(uint8_t addr)
918 driveOffTimeout = 2000000;
920 WriteLog("FLOPPY: Turning drive motor %s\n", (motorOn ? "ON" : "off"));
924 void FloppyDrive::DriveEnable(uint8_t addr)
928 WriteLog("FLOPPY: Selecting drive #%hhd\n", addr + 1);
933 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).
935 So it forms a matrix like so:
938 +-----------------------------------------------------------------------
939 $C08C |Enable READ sequencing |Data reg SHL every 8th clock while writing
940 +----------------------------+------------------------------------------
941 $C08D |Check write prot./init write|Data reg LOAD every 8th clk while writing
943 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.
948 void FloppyDrive::SetShiftLoadSwitch(uint8_t state)
955 void FloppyDrive::SetReadWriteSwitch(uint8_t state)
962 // MMIO: Reads from $C08x to $C0XX on even addresses
963 uint8_t FloppyDrive::DataRegister(void)
966 if (diskType[activeDrive] != DT_EMPTY)
968 uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]];
969 uint32_t bitLen = (tIdx == 0xFF ? 51200
970 : Uint16LE(woz[activeDrive]->track[tIdx].bitCount));
971 SpawnMessage("%u:Reading $%02X from track %u, sector %u...",
972 activeDrive, dataRegister, headPos[activeDrive] >> 2, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f));
973 ioMode = IO_MODE_READ;
976 if ((seenReadSinceStep == false) && (slSwitch == false) && (rwSwitch == false) && ((iorAddr & 0x0F) == 0x0C))
978 seenReadSinceStep = true;
979 WriteLog("%u:Reading $%02X from track %u, sector %u (delta since seek: %lu cycles) [%u]...\n",
980 activeDrive, dataRegister, headPos[activeDrive] >> 2, (uint32_t)(((float)currentPos[activeDrive] / (float)bitLen) * 16.0f), mainCPU.clock - stepperTime, mainCPU.clock & 0xFFFFFFFF);
988 // MMIO: Writes from $C08x to $C0XX on odd addresses
989 void FloppyDrive::DataRegister(uint8_t data)
992 ioMode = IO_MODE_WRITE;
998 OFF switches ON switches
999 Switch Addr Func Addr Func
1000 Q0 $C080 Phase 0 off $C081 Phase 0 on
1001 Q1 $C082 Phase 1 off $C083 Phase 1 on
1002 Q2 $C084 Phase 2 off $C085 Phase 2 on
1003 Q3 $C086 Phase 3 off $C087 Phase 3 on
1004 Q4 $C088 Drive off $C089 Drive on
1005 Q5 $C08A Select Drive 1 $C08B Select Drive 2
1006 Q6 $C08C Shift data register $C08D Load data register
1007 Q7 $C08E Read $C08F Write
1009 From "Beneath Apple ProDOS", description of combinations of $C0EC-EF
1011 $C08C, $C08E: Enable read sequencing
1012 $C08C, $C08F: Shift data register every four cycles while writing
1013 $C08D, $C08E: Check write protect and initialize sequencer for writing
1014 $C08D, $C08F: Load data register every four cycles while writing
1017 Sense Write Protect:
1019 LDX #SLOT Put slot number times 16 in X-register.
1021 LDA $C08E, X Sense write protect.
1022 BMI ERROR If high bit set, protected.
1027 PRODOS 8 MLI ERROR CODES
1030 $01: Bad system call number
1031 $04: Bad system call parameter count
1032 $25: Interrupt table full
1034 $28: No device connected
1035 $2B: Disk write protected
1037 $40: Invalid pathname
1038 $42: Maximum number of files open
1039 $43: Invalid reference number
1040 $44: Directory not found
1041 $45: Volume not found
1043 $47: Duplicate filename
1045 $49: Volume directory full
1046 $4A: Incompatible file format, also a ProDOS directory
1047 $4B: Unsupported storage_type
1048 $4C: End of file encountered
1049 $4D: Position out of range
1050 $4E: File access error, also file locked
1052 $51: Directory structure damaged
1053 $52: Not a ProDOS volume
1054 $53: Invalid system call parameter
1055 $55: Volume Control Block table full
1056 $56: Bad buffer address
1057 $57: Duplicate volume
1058 $5A: File structure damaged
1063 // This is used mainly to initialize blank disks and upconvert non-WOZ disks
1065 void FloppyDrive::InitWOZ(uint8_t driveNum/*= 0*/)
1068 if (disk[driveNum] != NULL)
1070 WriteLog("FLOPPY: Attempted to initialize non-NULL WOZ structure\n");
1074 diskSize[driveNum] = 256 + (35 * sizeof(WOZTrack));
1075 disk[driveNum] = new uint8_t[diskSize[driveNum]];
1076 woz[driveNum] = (WOZ *)disk[driveNum];
1078 // Zero out WOZ image in memory
1079 memset(woz[driveNum], 0, diskSize[driveNum]);
1081 // Set up header (leave CRC as 0 for now)
1082 memcpy(woz[driveNum]->magic, wozHeader, 8);
1085 memcpy(woz[driveNum]->infoTag, "INFO", 4);
1086 woz[driveNum]->infoSize = Uint32LE(60);
1087 woz[driveNum]->infoVersion = 1;
1088 woz[driveNum]->diskType = 1;
1089 woz[driveNum]->writeProtected = 0;
1090 woz[driveNum]->synchronized = 0;
1091 woz[driveNum]->cleaned = 1;
1092 memset(woz[driveNum]->creator, ' ', 32);
1093 memcpy(woz[driveNum]->creator, "Apple2 emulator v1.0.0", 22);
1096 memcpy(woz[driveNum]->tmapTag, "TMAP", 4);
1097 woz[driveNum]->tmapSize = Uint32LE(160);
1098 memcpy(woz[driveNum]->tmap, standardTMAP, 141);
1101 memcpy(woz[driveNum]->trksTag, "TRKS", 4);
1102 woz[driveNum]->trksSize = Uint32LE(35 * sizeof(WOZTrack));
1104 for(int i=0; i<35; i++)
1106 woz[driveNum]->track[i].bitCount = Uint16LE(51200);
1107 woz[driveNum]->track[i].byteCount = Uint16LE((51200 + 7) / 8);
1110 // META header (how to handle? prolly with a separate pointer)
1115 // Do basic sanity checks on the passed in contents (file loaded elsewhere).
1116 // Returns true if successful, false on failure.
1118 bool FloppyDrive::CheckWOZ(const uint8_t * wozData, uint32_t wozSize, uint8_t driveNum/*= 0*/)
1120 // Hey! This reference works!! :-D
1121 WOZ & woz1 = *((WOZ *)wozData);
1122 woz[driveNum] = (WOZ *)wozData;
1124 // Basic sanity checking
1125 if (wozData == NULL)
1127 WriteLog("FLOPPY: NULL pointer passed in to CheckWOZ()...\n");
1131 if (memcmp(woz1.magic, wozHeader, 8) != 0)
1133 WriteLog("FLOPPY: Invalid WOZ header in file\n");
1137 uint32_t crc = CRC32(&wozData[12], wozSize - 12);
1138 uint32_t wozCRC = Uint32LE(woz1.crc32);
1140 if ((wozCRC != 0) && (wozCRC != crc))
1142 WriteLog("FLOPPY: Corrupted data found in WOZ. CRC32: %08X, computed: %08X\n", wozCRC, crc);
1145 else if (wozCRC == 0)
1146 WriteLog("FLOPPY: Warning--WOZ file has no CRC...\n");
1149 WriteLog("Track map:\n");
1150 WriteLog(" 1 1 1 1 1 1 1 1\n");
1151 WriteLog("0.,.1.,.2.,.3.,.4.,.5.,.6.,.7.,.8.,.9.,.0.,.1.,.2.,.3.,.4.,.5.,.6.,.7.,.\n");
1152 WriteLog("------------------------------------------------------------------------\n");
1154 for(uint8_t j=0; j<2; j++)
1156 for(uint8_t i=0; i<72; i++)
1158 char buf[64] = "..";
1159 buf[0] = buf[1] = '.';
1161 if (woz1.tmap[i] != 0xFF)
1162 sprintf(buf, "%02d", woz1.tmap[i]);
1164 WriteLog("%c", buf[j]);
1170 WriteLog("\n1 1 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3\n");
1171 WriteLog("8.,.9.,.0.,.1.,.2.,.3.,.4.,.5.,.6.,.7.,.8.,.9.,.0.,.1.,.2.,.3.,.4.,.5\n");
1172 WriteLog("---------------------------------------------------------------------\n");
1174 for(uint8_t j=0; j<2; j++)
1176 for(uint8_t i=72; i<141; i++)
1178 char buf[64] = "..";
1180 if (woz1.tmap[i] != 0xFF)
1181 sprintf(buf, "%02d", woz1.tmap[i]);
1183 WriteLog("%c", buf[j]);
1191 uint8_t numTracks = woz1.trksSize / sizeof(WOZTrack);
1193 // N.B.: Need to check the track[] to have this tell the correct track... Right now, it doesn't
1194 for(uint8_t i=0; i<numTracks; i++)
1196 WriteLog("WOZ: Track %2.2f: %d bits (packed into %d bytes)\n", (float)i / 4.0f, woz1.track[i].bitCount, woz1.track[i].byteCount);
1200 WriteLog("FLOPPY: Well formed WOZ file found\n");
1205 bool FloppyDrive::SaveWOZ(uint8_t driveNum)
1207 // Various sanity checks...
1210 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
1214 if (diskType[driveNum] == DT_EMPTY)
1216 WriteLog("FLOPPY: No image in drive #%u to save\n", driveNum);
1220 if (!imageDirty[driveNum])
1222 WriteLog("FLOPPY: No need to save unchanged image in drive #%u...\n", driveNum);
1226 // Set up CRC32 before writing
1227 woz[driveNum]->crc32 = Uint32LE(CRC32(&disk[driveNum][12], diskSize[driveNum] - 12));
1229 // META header (skip for now) (actually, should be in the disk[] image already)
1231 // Finally, write the damn image
1232 FILE * fp = fopen(imageName[driveNum], "wb");
1236 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
1240 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
1243 WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]);
1249 // N.B.: The WOZ documentation says that the bitstream is normalized to 4µs.
1250 // Which means on the //e that you would have to run it at that clock
1251 // rate (instead of the //e clock rate 0.9799µs/cycle) to get the
1252 // simulated drive running at 300 RPM. So, instead of doing that, we're
1253 // just gonna run it at twice the clock rate of the base 6502 clock,
1254 // which will make the simulated drive run in the neighborhood of around
1255 // 306 RPM. Should be close enough to get away with it. :-) (And it
1256 // seems to run OK, for the most part.)
1259 static bool logSeq = false;
1261 // Logic State Sequencer & Data Register
1263 void FloppyDrive::RunSequencer(uint32_t cyclesToRun)
1265 static uint32_t prng = 1;
1268 if (diskType[activeDrive] == DT_EMPTY)
1270 else if (motorOn == false)
1272 if (driveOffTimeout == 0)
1278 // It's x2 because the sequencer clock runs twice as fast as the CPU clock.
1281 //extern bool dumpDis;
1282 //static bool tripwire = false;
1284 //static uint32_t lastPos = 0;
1287 WriteLog("DISKSEQ: Running for %d cycles [rw=%hhd, sl=%hhd, reg=%02X, bus=%02X]\n", cyclesToRun, rwSwitch, slSwitch, dataRegister, cpuDataBus);
1290 while (cyclesToRun-- > 0)
1292 pulseClock = (pulseClock + 1) & 0x07;
1294 if (pulseClock == 0)
1296 uint16_t bytePos = currentPos[activeDrive] / 8;
1297 uint8_t bitPos = currentPos[activeDrive] % 8;
1298 uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]];
1302 if (woz[activeDrive]->track[tIdx].bits[bytePos] & bitMask[bitPos])
1304 // According to Jim Sather (Understanding the Apple II),
1305 // the Read Pulse, when it happens, is 1µs long, which is 2
1306 // sequencer clock pulses long.
1313 currentPos[activeDrive] = (currentPos[activeDrive] + 1) % Uint16LE(woz[activeDrive]->track[tIdx].bitCount);
1316 currentPos[activeDrive] = (currentPos[activeDrive] + 1) % 51200;
1320 //this doesn't work reliably for some reason...
1321 //seems to work OK now...
1322 currentPos[activeDrive] = (currentPos[activeDrive] + 1) % trackLength[activeDrive];
1325 // If we hit more than 2 zero bits in a row, simulate the disk head
1326 // reader's Automatic Gain Control (AGC) turning itself up too high
1327 // by stuffing random bits in the bitstream. We also do this if
1328 // the current track is marked as unformatted.
1330 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.
1332 if ((zeroBitCount > 3) || (tIdx == 0xFF))
1336 // This PRNG is called the "Galois configuration".
1345 // Find and run the Sequencer's next state
1346 uint8_t nextState = (sequencerState & 0xF0) | (rwSwitch << 3)
1347 | (slSwitch << 2) | (readPulse ? 0x02 : 0)
1348 | ((dataRegister & 0x80) >> 7);
1350 WriteLog("[%02X:%02X]%s", sequencerState, nextState, (chop == 15 ? "\n" : ""));
1351 chop = (chop + 1) % 20;
1352 sequencerState = sequencerROM[nextState];
1354 switch (sequencerState & 0x0F)
1364 // CLR (clear data register)
1369 // NOP (no operation)
1372 // SL0 (shift left, 0 fill LSB)
1376 uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]];
1378 if (rwSwitch && (tIdx != 0xFF)
1379 && !woz[activeDrive]->writeProtected)
1381 imageDirty[activeDrive] = true;
1382 uint16_t bytePos = currentPos[activeDrive] / 8;
1383 uint8_t bitPos = currentPos[activeDrive] % 8;
1385 if (dataRegister & 0x80)
1386 // Fill in the one, if necessary
1387 woz[activeDrive]->track[tIdx].bits[bytePos] |= bitMask[bitPos];
1389 // Otherwise, punch in the zero
1390 woz[activeDrive]->track[tIdx].bits[bytePos] &= ~bitMask[bitPos];
1393 if (dumpDis || tripwire)
1396 WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0"));
1397 if (lastPos == currentPos[activeDrive])
1398 WriteLog("{STOMP}");
1399 else if ((lastPos + 1) != currentPos[activeDrive])
1401 lastPos = currentPos[activeDrive];
1409 // SR (shift right write protect bit)
1411 dataRegister |= (woz[activeDrive]->writeProtected ? 0x80 : 0x00);
1415 // LD (load data register from data bus)
1416 dataRegister = cpuDataBus;
1419 uint8_t tIdx = woz[activeDrive]->tmap[headPos[activeDrive]];
1421 if (rwSwitch && (tIdx != 0xFF)
1422 && !woz[activeDrive]->writeProtected)
1424 imageDirty[activeDrive] = true;
1425 uint16_t bytePos = currentPos[activeDrive] / 8;
1426 uint8_t bitPos = currentPos[activeDrive] % 8;
1427 woz[activeDrive]->track[tIdx].bits[bytePos] |= bitMask[bitPos];
1429 if (dumpDis || tripwire)
1432 WriteLog("[%s]", (dataRegister & 0x80 ? "1" : "0"));
1433 if (lastPos == currentPos[activeDrive])
1434 WriteLog("{STOMP}");
1435 else if ((lastPos + 1) != currentPos[activeDrive])
1437 lastPos = currentPos[activeDrive];
1444 // SL1 (shift left, 1 fill LSB)
1446 dataRegister |= 0x01;
1459 FloppyDrive floppyDrive[2];
1461 static uint8_t SlotIOR(uint16_t address)
1463 uint8_t state = address & 0x0F;
1475 floppyDrive[0].ControlStepper(state);
1479 floppyDrive[0].ControlMotor(state & 0x01);
1483 floppyDrive[0].DriveEnable(state & 0x01);
1487 floppyDrive[0].SetShiftLoadSwitch(state & 0x01);
1491 floppyDrive[0].SetReadWriteSwitch(state & 0x01);
1495 //temp, for debugging
1497 // Even addresses return the data register, odd (we suppose) returns a
1498 // floating bus read...
1499 return (address & 0x01 ? ReadFloatingBus(0) : floppyDrive[0].DataRegister());
1503 static void SlotIOW(uint16_t address, uint8_t byte)
1505 uint8_t state = address & 0x0F;
1517 floppyDrive[0].ControlStepper(state);
1521 floppyDrive[0].ControlMotor(state & 0x01);
1525 floppyDrive[0].DriveEnable(state & 0x01);
1529 floppyDrive[0].SetShiftLoadSwitch(state & 0x01);
1533 floppyDrive[0].SetReadWriteSwitch(state & 0x01);
1537 // Odd addresses write to the Data register, even addresses (we assume) go
1540 floppyDrive[0].DataRegister(byte);
1544 // This slot function doesn't need to differentiate between separate instances
1546 static uint8_t SlotROM(uint16_t address)
1548 return diskROM[address];
1552 void InstallFloppy(uint8_t slot)
1554 SlotData disk = { SlotIOR, SlotIOW, SlotROM, 0, 0, 0 };
1555 InstallSlotHandler(slot, &disk);