2 // Apple 2 floppy disk support
5 // (c) 2005 Underground Software
7 // JLH = James L. 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
22 #include "applevideo.h" // For message spawning... Though there's probably a better approach than this!
24 //using namespace std;
28 enum { IO_MODE_READ, IO_MODE_WRITE };
30 // FloppyDrive class variable initialization
32 uint8 FloppyDrive::header[21] = {
33 0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
34 0x00, 0x00, 0x00, 0xDE, 0xAA, 0xFF, 0xFF, 0xFF,
35 0xFF, 0xFF, 0xD5, 0xAA, 0xAD };
36 uint8 FloppyDrive::doSector[16] = {
37 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
38 uint8 FloppyDrive::poSector[16] = {
39 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
40 char FloppyDrive::nameBuf[MAX_PATH];
42 // FloppyDrive class implementation...
44 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
46 disk[0] = disk[1] = NULL;
47 diskSize[0] = diskSize[1] = 0;
48 diskType[0] = diskType[1] = DT_UNKNOWN;
49 imageDirty[0] = imageDirty[1] = false;
50 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
53 FloppyDrive::~FloppyDrive()
62 bool FloppyDrive::LoadImage(const char * filename, uint8 driveNum/*= 0*/)
64 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
68 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
72 imageName[driveNum][0] = 0; // Zero out filename, in case it doesn't load
74 FILE * fp = fopen(filename, "rb");
78 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
83 delete[] disk[driveNum];
85 fseek(fp, 0, SEEK_END);
86 diskSize[driveNum] = ftell(fp);
87 fseek(fp, 0, SEEK_SET);
88 disk[driveNum] = new uint8[diskSize[driveNum]];
89 fread(disk[driveNum], 1, diskSize[driveNum], fp);
92 //printf("Read disk image: %u bytes.\n", diskSize);
93 DetectImageType(filename, driveNum);
94 strcpy(imageName[driveNum], filename);
97 WriteLog("FLOPPY: Opening image for drive #%u.\n", driveNum);
98 FILE * fp2 = fopen("bt-nybblized.nyb", "wb");
101 WriteLog("FLOPPY: Failed to open image file 'bt-nybblized.nyb' for writing...\n");
104 fwrite(nybblizedImage[driveNum], 1, 232960, fp2);
108 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
113 bool FloppyDrive::SaveImage(uint8 driveNum/*= 0*/)
117 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
121 if (!imageDirty[driveNum])
123 WriteLog("FLOPPY: No need to save unchanged image...\n");
127 if (imageName[driveNum][0] == 0)
129 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
133 if (diskType[driveNum] == DT_NYBBLE)
134 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
136 DenybblizeImage(driveNum);
138 FILE * fp = fopen(imageName[driveNum], "wb");
142 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
146 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
149 WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]);
154 bool FloppyDrive::SaveImageAs(const char * filename, uint8 driveNum/*= 0*/)
156 //WARNING: Buffer overflow possibility
157 #warning "Buffer overflow possible--!!! FIX !!!"
158 strcpy(imageName[driveNum], filename);
159 return SaveImage(driveNum);
162 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
164 if (disk[driveNum] != NULL)
165 delete disk[driveNum];
167 disk[driveNum] = new uint8[143360];
168 diskSize[driveNum] = 143360;
169 memset(disk[driveNum], 0x00, 143360);
170 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
171 diskType[driveNum] = DT_DOS33;
172 strcpy(imageName[driveNum], "newblank.dsk");
173 SpawnMessage("New blank image inserted in drive %u...", driveNum);
176 void FloppyDrive::SwapImages(void)
178 uint8 nybblizedImageTmp[232960];
179 char imageNameTmp[MAX_PATH];
181 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
182 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
183 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
185 memcpy(imageNameTmp, imageName[0], MAX_PATH);
186 memcpy(imageName[0], imageName[1], MAX_PATH);
187 memcpy(imageName[1], imageNameTmp, MAX_PATH);
189 uint8 * diskTmp = disk[0];
193 uint32 diskSizeTmp = diskSize[0];
194 diskSize[0] = diskSize[1];
195 diskSize[1] = diskSizeTmp;
197 uint8 diskTypeTmp = diskType[0];
198 diskType[0] = diskType[1];
199 diskType[1] = diskTypeTmp;
201 uint8 imageDirtyTmp = imageDirty[0];
202 imageDirty[0] = imageDirty[1];
203 imageDirty[1] = imageDirtyTmp;
204 SpawnMessage("Drive 0: %s...", imageName[0]);
207 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
209 diskType[driveNum] = DT_UNKNOWN;
211 if (diskSize[driveNum] == 232960)
213 diskType[driveNum] = DT_NYBBLE;
214 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
216 else if (diskSize[driveNum] == 143360)
218 const char * ext = strrchr(filename, '.');
222 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
224 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
225 //[DONE, see below why we don't need it]
226 if (strcasecmp(ext, ".po") == 0)
227 diskType[driveNum] = DT_PRODOS;
228 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
230 diskType[driveNum] = DT_DOS33;
231 //WriteLog("Detected DOS 3.3 disk!\n");
233 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
234 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
235 WRT to the disk image itself.
236 // This could really be a ProDOS order disk with a .dsk extension, so let's see...
237 char fingerprint[3][4] = {
238 { 0x04, 0x00, 0x00, 0x00 }, // @ $500
239 { 0x03, 0x00, 0x05, 0x00 }, // @ $700
240 { 0x02, 0x00, 0x04, 0x00 } }; // @ $900
242 if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
243 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
244 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
245 diskType[driveNum] = DT_PRODOS;
249 // Actually, it just might matter WRT to nybblyzing/denybblyzing
250 // Here, we check for BT3
252 //diskType[driveNum] = DT_PRODOS;
254 NybblizeImage(driveNum);
257 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
259 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
260 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
261 "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
264 void FloppyDrive::NybblizeImage(uint8 driveNum)
266 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
267 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
268 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
269 // This is now correct, BTW
270 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
271 // (not incl. 64 byte track marker)
274 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
275 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
276 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
277 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
278 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
279 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
281 uint8 diskbyte[0x40] = {
282 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
283 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
284 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
285 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
286 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
287 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
288 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
289 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
291 uint8 * img = nybblizedImage[driveNum];
292 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
294 for(uint8 trk=0; trk<35; trk++)
296 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
299 for(uint8 sector=0; sector<16; sector++)
301 memcpy(img, header, 21); // Set up the sector header
303 img[5] = ((trk >> 1) & 0x55) | 0xAA;
304 img[6] = (trk & 0x55) | 0xAA;
305 img[7] = ((sector >> 1) & 0x55) | 0xAA;
306 img[8] = (sector & 0x55) | 0xAA;
307 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
308 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
311 uint8 * bytes = disk[driveNum];
313 if (diskType[driveNum] == DT_DOS33)
314 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
315 else if (diskType[driveNum] == DT_PRODOS)
316 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
318 bytes += (sector * 256) + (trk * 256 * 16);
320 // Convert the 256 8-bit bytes into 342 6-bit bytes.
322 for(uint16 i=0; i<0x56; i++)
324 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
325 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
326 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
327 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
328 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
329 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
334 memcpy(img + 0x56, bytes, 256);
336 // XOR the data block with itself, offset by one byte,
337 // creating a 343rd byte which is used as a cheksum.
341 for(uint16 i=342; i>0; i--)
342 img[i] = img[i] ^ img[i - 1];
344 // Using a lookup table, convert the 6-bit bytes into disk bytes.
346 for(uint16 i=0; i<343; i++)
347 //#define TEST_NYBBLIZATION
348 #ifdef TEST_NYBBLIZATION
350 WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img[i] >> 2]);
352 img[i] = diskbyte[img[i] >> 2];
353 #ifdef TEST_NYBBLIZATION
354 //WriteLog(" img[i] = %02X\n", img[i]);
359 // Done with the nybblization, now for the epilogue...
361 memcpy(img, footer, 48);
367 void FloppyDrive::DenybblizeImage(uint8 driveNum)
369 uint8 decodeNybble[0x80] = {
370 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
371 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
372 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
373 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
374 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
375 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
376 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
377 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
378 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
379 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
380 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
381 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
382 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
383 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
384 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
385 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
388 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
390 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
391 driveNum, disk[driveNum], diskSize[driveNum]);
395 uint8 * srcImg = nybblizedImage[driveNum];
396 uint8 * dstImg = disk[driveNum];
397 uint8 buffer[345]; // 2 extra bytes for the unpack routine below...
399 for(uint8 trk=0; trk<35; trk++)
401 uint8 * trackBase = srcImg + (trk * 6656);
403 for(uint8 sector=0; sector<16; sector++)
405 uint16 sectorStart = (uint16)-1;
407 for(uint16 i=0; i<6656; i++)
409 if (trackBase[i] == header[0]
410 && trackBase[(i + 1) % 6656] == header[1]
411 && trackBase[(i + 2) % 6656] == header[2]
412 && trackBase[(i + 3) % 6656] == header[3]
413 && trackBase[(i + 4) % 6656] == header[4])
415 //Could also check the track # at +5,6...
416 uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
417 | (trackBase[(i + 8) % 6656] & 0x55);
419 if (foundSector == sector)
421 sectorStart = (i + 21) % 6656;
428 if (sectorStart == (uint16)-1)
430 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
435 // Using a lookup table, convert the disk bytes into 6-bit bytes.
437 for(uint16 i=0; i<343; i++)
438 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
440 // XOR the data block with itself, offset by one byte.
442 for(uint16 i=1; i<342; i++)
443 buffer[i] = buffer[i] ^ buffer[i - 1];
445 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
447 for(uint16 i=0; i<0x56; i++)
449 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
450 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
451 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
454 uint8 * bytes = dstImg;
456 if (diskType[driveNum] == DT_DOS33)
457 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
458 else if (diskType[driveNum] == DT_PRODOS)
459 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
461 bytes += (sector * 256) + (trk * 256 * 16);//*/
463 memcpy(bytes, buffer + 0x56, 256);
468 const char * FloppyDrive::GetImageName(uint8 driveNum/*= 0*/)
470 // Set up a zero-length string for return value
475 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
479 // Now we attempt to strip out extraneous paths/extensions to get just the filename
480 const char * startOfFile = strrchr(imageName[driveNum], '/');
481 const char * startOfExt = strrchr(imageName[driveNum], '.');
483 // If there isn't a path, assume we're starting at the beginning
484 if (startOfFile == NULL)
485 startOfFile = &imageName[driveNum][0];
489 // If there isn't an extension, assume it's at the terminating NULL
490 if (startOfExt == NULL)
491 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
493 // Now copy the filename (may copy nothing!)
496 for(const char * i=startOfFile; i<startOfExt; i++)
504 void FloppyDrive::EjectImage(uint8 driveNum/*= 0*/)
506 // Probably want to save a dirty image... ;-)
509 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
512 delete[] disk[driveNum];
514 disk[driveNum] = NULL;
515 diskSize[driveNum] = 0;
516 diskType[driveNum] = DT_UNKNOWN;
517 imageDirty[driveNum] = false;
518 imageName[driveNum][0] = 0; // Zero out filenames
519 memset(nybblizedImage[driveNum], 0xFF, 232960); // Doesn't matter if 00s or FFs...
523 bool FloppyDrive::DriveIsEmpty(uint8 driveNum/*= 0*/)
525 // This is kinda gay, but it works
526 return (imageName[driveNum][0] == 0 ? true : false);
530 // Memory mapped I/O functions
533 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
534 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
535 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
536 are in a different sequence. The NIB format is a nybblized format: a more direct representation
537 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
538 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
539 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
540 other unusual encodings.
543 void FloppyDrive::ControlStepper(uint8 addr)
547 What I can gather here:
548 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
549 bit 0 is the "do something" bit.
553 uint8 newPhase = (addr >> 1) & 0x03;
554 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
556 if (((phase + 1) & 0x03) == newPhase)
557 phase += (phase < 79 ? 1 : 0);
559 if (((phase - 1) & 0x03) == newPhase)
560 phase -= (phase > 0 ? 1 : 0);
564 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
567 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
568 SpawnMessage("Stepping to track %u...", track);
571 // return something if read mode...
574 void FloppyDrive::ControlMotor(uint8 addr)
580 void FloppyDrive::DriveEnable(uint8 addr)
586 uint8 FloppyDrive::ReadWrite(void)
588 SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
589 (ioMode == IO_MODE_READ ? "Read" : "Write"),
590 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
593 I think what happens here is that once a track is read its nybblized form
594 is fed through here, one byte at a time--which means for DO disks, we have
595 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
598 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
600 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
601 imageDirty[activeDrive] = true;
604 uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
605 currentPos = (currentPos + 1) % 6656;
607 //WriteLog("FL: diskByte=%02X, currentPos=%u\n", diskByte, currentPos);
611 uint8 FloppyDrive::GetLatchValue(void)
617 void FloppyDrive::SetLatchValue(uint8 value)
623 void FloppyDrive::SetReadMode(void)
626 ioMode = IO_MODE_READ;
629 void FloppyDrive::SetWriteMode(void)
632 ioMode = IO_MODE_WRITE;
636 PRODOS 8 MLI ERROR CODES
639 $01: Bad system call number
640 $04: Bad system call parameter count
641 $25: Interrupt table full
643 $28: No device connected
644 $2B: Disk write protected
646 $40: Invalid pathname
647 $42: Maximum number of files open
648 $43: Invalid reference number
649 $44: Directory not found
650 $45: Volume not found
652 $47: Duplicate filename
654 $49: Volume directory full
655 $4A: Incompatible file format, also a ProDOS directory
656 $4B: Unsupported storage_type
657 $4C: End of file encountered
658 $4D: Position out of range
659 $4E: File access error, also file locked
661 $51: Directory structure damaged
662 $52: Not a ProDOS volume
663 $53: Invalid system call parameter
664 $55: Volume Control Block table full
665 $56: Bad buffer address
666 $57: Duplicate volume
667 $5A: File structure damaged