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);
152 bool FloppyDrive::SaveImageAs(const char * filename, uint8 driveNum/*= 0*/)
154 //WARNING: Buffer overflow possibility
155 #warning "Buffer overflow possible--!!! FIX !!!"
156 strcpy(imageName[driveNum], filename);
157 return SaveImage(driveNum);
160 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
162 if (disk[driveNum] != NULL)
163 delete disk[driveNum];
165 disk[driveNum] = new uint8[143360];
166 diskSize[driveNum] = 143360;
167 memset(disk[driveNum], 0x00, 143360);
168 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
169 diskType[driveNum] = DT_DOS33;
170 strcpy(imageName[driveNum], "newblank.dsk");
171 SpawnMessage("New blank image inserted in drive %u...", driveNum);
174 void FloppyDrive::SwapImages(void)
176 uint8 nybblizedImageTmp[232960];
177 char imageNameTmp[MAX_PATH];
179 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
180 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
181 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
183 memcpy(imageNameTmp, imageName[0], MAX_PATH);
184 memcpy(imageName[0], imageName[1], MAX_PATH);
185 memcpy(imageName[1], imageNameTmp, MAX_PATH);
187 uint8 * diskTmp = disk[0];
191 uint32 diskSizeTmp = diskSize[0];
192 diskSize[0] = diskSize[1];
193 diskSize[1] = diskSizeTmp;
195 uint8 diskTypeTmp = diskType[0];
196 diskType[0] = diskType[1];
197 diskType[1] = diskTypeTmp;
199 uint8 imageDirtyTmp = imageDirty[0];
200 imageDirty[0] = imageDirty[1];
201 imageDirty[1] = imageDirtyTmp;
202 SpawnMessage("Drive 0: %s...", imageName[0]);
205 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
207 diskType[driveNum] = DT_UNKNOWN;
209 if (diskSize[driveNum] == 232960)
211 diskType[driveNum] = DT_NYBBLE;
212 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
214 else if (diskSize[driveNum] == 143360)
216 const char * ext = strrchr(filename, '.');
220 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
222 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
223 //[DONE, see below why we don't need it]
224 if (strcasecmp(ext, ".po") == 0)
225 diskType[driveNum] = DT_PRODOS;
226 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
228 diskType[driveNum] = DT_DOS33;
229 //WriteLog("Detected DOS 3.3 disk!\n");
231 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
232 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
233 WRT to the disk image itself.
234 // This could really be a ProDOS order disk with a .dsk extension, so let's see...
235 char fingerprint[3][4] = {
236 { 0x04, 0x00, 0x00, 0x00 }, // @ $500
237 { 0x03, 0x00, 0x05, 0x00 }, // @ $700
238 { 0x02, 0x00, 0x04, 0x00 } }; // @ $900
240 if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
241 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
242 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
243 diskType[driveNum] = DT_PRODOS;
247 // Actually, it just might matter WRT to nybblyzing/denybblyzing
248 // Here, we check for BT3
250 //diskType[driveNum] = DT_PRODOS;
252 NybblizeImage(driveNum);
255 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
257 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
258 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
259 "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
262 void FloppyDrive::NybblizeImage(uint8 driveNum)
264 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
265 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
266 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
267 // This is now correct, BTW
268 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
269 // (not incl. 64 byte track marker)
272 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
273 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
274 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 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 };
279 uint8 diskbyte[0x40] = {
280 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
281 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
282 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
283 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
284 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
285 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
286 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
287 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
289 uint8 * img = nybblizedImage[driveNum];
290 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
292 for(uint8 trk=0; trk<35; trk++)
294 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
297 for(uint8 sector=0; sector<16; sector++)
299 memcpy(img, header, 21); // Set up the sector header
301 img[5] = ((trk >> 1) & 0x55) | 0xAA;
302 img[6] = (trk & 0x55) | 0xAA;
303 img[7] = ((sector >> 1) & 0x55) | 0xAA;
304 img[8] = (sector & 0x55) | 0xAA;
305 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
306 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
309 uint8 * bytes = disk[driveNum];
311 if (diskType[driveNum] == DT_DOS33)
312 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
313 else if (diskType[driveNum] == DT_PRODOS)
314 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
316 bytes += (sector * 256) + (trk * 256 * 16);
318 // Convert the 256 8-bit bytes into 342 6-bit bytes.
320 for(uint16 i=0; i<0x56; i++)
322 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
323 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
324 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
325 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
326 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
327 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
332 memcpy(img + 0x56, bytes, 256);
334 // XOR the data block with itself, offset by one byte,
335 // creating a 343rd byte which is used as a cheksum.
339 for(uint16 i=342; i>0; i--)
340 img[i] = img[i] ^ img[i - 1];
342 // Using a lookup table, convert the 6-bit bytes into disk bytes.
344 for(uint16 i=0; i<343; i++)
345 //#define TEST_NYBBLIZATION
346 #ifdef TEST_NYBBLIZATION
348 WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img[i] >> 2]);
350 img[i] = diskbyte[img[i] >> 2];
351 #ifdef TEST_NYBBLIZATION
352 //WriteLog(" img[i] = %02X\n", img[i]);
357 // Done with the nybblization, now for the epilogue...
359 memcpy(img, footer, 48);
365 void FloppyDrive::DenybblizeImage(uint8 driveNum)
367 uint8 decodeNybble[0x80] = {
368 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
369 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
370 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
371 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
372 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
373 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
374 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
375 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
376 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
377 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
378 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
379 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
380 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
381 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
382 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
383 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
386 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
388 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
389 driveNum, disk[driveNum], diskSize[driveNum]);
393 uint8 * srcImg = nybblizedImage[driveNum];
394 uint8 * dstImg = disk[driveNum];
395 uint8 buffer[345]; // 2 extra bytes for the unpack routine below...
397 for(uint8 trk=0; trk<35; trk++)
399 uint8 * trackBase = srcImg + (trk * 6656);
401 for(uint8 sector=0; sector<16; sector++)
403 uint16 sectorStart = (uint16)-1;
405 for(uint16 i=0; i<6656; i++)
407 if (trackBase[i] == header[0]
408 && trackBase[(i + 1) % 6656] == header[1]
409 && trackBase[(i + 2) % 6656] == header[2]
410 && trackBase[(i + 3) % 6656] == header[3]
411 && trackBase[(i + 4) % 6656] == header[4])
413 //Could also check the track # at +5,6...
414 uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
415 | (trackBase[(i + 8) % 6656] & 0x55);
417 if (foundSector == sector)
419 sectorStart = (i + 21) % 6656;
426 if (sectorStart == (uint16)-1)
428 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
433 // Using a lookup table, convert the disk bytes into 6-bit bytes.
435 for(uint16 i=0; i<343; i++)
436 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
438 // XOR the data block with itself, offset by one byte.
440 for(uint16 i=1; i<342; i++)
441 buffer[i] = buffer[i] ^ buffer[i - 1];
443 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
445 for(uint16 i=0; i<0x56; i++)
447 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
448 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
449 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
452 uint8 * bytes = dstImg;
454 if (diskType[driveNum] == DT_DOS33)
455 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
456 else if (diskType[driveNum] == DT_PRODOS)
457 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
459 bytes += (sector * 256) + (trk * 256 * 16);//*/
461 memcpy(bytes, buffer + 0x56, 256);
466 const char * FloppyDrive::GetImageName(uint8 driveNum/*= 0*/)
468 // Set up a zero-length string for return value
473 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
477 // Now we attempt to strip out extraneous paths/extensions to get just the filename
478 const char * startOfFile = strrchr(imageName[driveNum], '/');
479 const char * startOfExt = strrchr(imageName[driveNum], '.');
481 // If there isn't a path, assume we're starting at the beginning
482 if (startOfFile == NULL)
483 startOfFile = &imageName[driveNum][0];
487 // If there isn't an extension, assume it's at the terminating NULL
488 if (startOfExt == NULL)
489 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
491 // Now copy the filename (may copy nothing!)
494 for(const char * i=startOfFile; i<startOfExt; i++)
503 // Memory mapped I/O functions
506 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
507 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
508 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
509 are in a different sequence. The NIB format is a nybblized format: a more direct representation
510 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
511 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
512 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
513 other unusual encodings.
516 void FloppyDrive::ControlStepper(uint8 addr)
520 What I can gather here:
521 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
522 bit 0 is the "do something" bit.
526 uint8 newPhase = (addr >> 1) & 0x03;
527 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
529 if (((phase + 1) & 0x03) == newPhase)
530 phase += (phase < 79 ? 1 : 0);
532 if (((phase - 1) & 0x03) == newPhase)
533 phase -= (phase > 0 ? 1 : 0);
537 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
540 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
541 SpawnMessage("Stepping to track %u...", track);
544 // return something if read mode...
547 void FloppyDrive::ControlMotor(uint8 addr)
553 void FloppyDrive::DriveEnable(uint8 addr)
559 uint8 FloppyDrive::ReadWrite(void)
561 SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
562 (ioMode == IO_MODE_READ ? "Read" : "Write"),
563 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
566 I think what happens here is that once a track is read its nybblized form
567 is fed through here, one byte at a time--which means for DO disks, we have
568 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
571 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
573 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
574 imageDirty[activeDrive] = true;
577 uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
578 currentPos = (currentPos + 1) % 6656;
580 //WriteLog("FL: diskByte=%02X, currentPos=%u\n", diskByte, currentPos);
584 uint8 FloppyDrive::GetLatchValue(void)
590 void FloppyDrive::SetLatchValue(uint8 value)
596 void FloppyDrive::SetReadMode(void)
599 ioMode = IO_MODE_READ;
602 void FloppyDrive::SetWriteMode(void)
605 ioMode = IO_MODE_WRITE;
609 PRODOS 8 MLI ERROR CODES
612 $01: Bad system call number
613 $04: Bad system call parameter count
614 $25: Interrupt table full
616 $28: No device connected
617 $2B: Disk write protected
619 $40: Invalid pathname
620 $42: Maximum number of files open
621 $43: Invalid reference number
622 $44: Directory not found
623 $45: Volume not found
625 $47: Duplicate filename
627 $49: Volume directory full
628 $4A: Incompatible file format, also a ProDOS directory
629 $4B: Unsupported storage_type
630 $4C: End of file encountered
631 $4D: Position out of range
632 $4E: File access error, also file locked
634 $51: Directory structure damaged
635 $52: Not a ProDOS volume
636 $53: Invalid system call parameter
637 $55: Volume Control Block table full
638 $56: Bad buffer address
639 $57: Duplicate volume
640 $5A: File structure damaged