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 };
41 // FloppyDrive class implementation...
43 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
45 disk[0] = disk[1] = NULL;
46 diskSize[0] = diskSize[1] = 0;
47 diskType[0] = diskType[1] = DT_UNKNOWN;
48 imageDirty[0] = imageDirty[1] = false;
49 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
52 FloppyDrive::~FloppyDrive()
61 bool FloppyDrive::LoadImage(const char * filename, uint8 driveNum/*= 0*/)
63 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
67 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
71 imageName[driveNum][0] = 0; // Zero out filename, in case it doesn't load
73 FILE * fp = fopen(filename, "rb");
77 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
82 delete[] disk[driveNum];
84 fseek(fp, 0, SEEK_END);
85 diskSize[driveNum] = ftell(fp);
86 fseek(fp, 0, SEEK_SET);
87 disk[driveNum] = new uint8[diskSize[driveNum]];
88 fread(disk[driveNum], 1, diskSize[driveNum], fp);
91 //printf("Read disk image: %u bytes.\n", diskSize);
92 DetectImageType(filename, driveNum);
93 strcpy(imageName[driveNum], filename);
96 WriteLog("FLOPPY: Opening image for drive #%u.\n", driveNum);
97 FILE * fp2 = fopen("bt-nybblized.nyb", "wb");
100 WriteLog("FLOPPY: Failed to open image file 'bt-nybblized.nyb' for writing...\n");
103 fwrite(nybblizedImage[driveNum], 1, 232960, fp2);
107 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
112 bool FloppyDrive::SaveImage(uint8 driveNum/*= 0*/)
116 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
120 if (!imageDirty[driveNum])
122 WriteLog("FLOPPY: No need to save unchanged image...\n");
126 if (imageName[driveNum][0] == 0)
128 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
132 if (diskType[driveNum] == DT_NYBBLE)
133 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
135 DenybblizeImage(driveNum);
137 FILE * fp = fopen(imageName[driveNum], "wb");
141 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
145 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
151 bool FloppyDrive::SaveImageAs(const char * filename, uint8 driveNum/*= 0*/)
153 //WARNING: Buffer overflow possibility
154 #warning "Buffer overflow possible--!!! FIX !!!"
155 strcpy(imageName[driveNum], filename);
156 return SaveImage(driveNum);
159 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
161 if (disk[driveNum] != NULL)
162 delete disk[driveNum];
164 disk[driveNum] = new uint8[143360];
165 diskSize[driveNum] = 143360;
166 memset(disk[driveNum], 0x00, 143360);
167 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
168 diskType[driveNum] = DT_DOS33;
169 strcpy(imageName[driveNum], "newblank.dsk");
170 SpawnMessage("New blank image inserted in drive %u...", driveNum);
173 void FloppyDrive::SwapImages(void)
175 uint8 nybblizedImageTmp[232960];
176 char imageNameTmp[MAX_PATH];
178 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
179 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
180 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
182 memcpy(imageNameTmp, imageName[0], MAX_PATH);
183 memcpy(imageName[0], imageName[1], MAX_PATH);
184 memcpy(imageName[1], imageNameTmp, MAX_PATH);
186 uint8 * diskTmp = disk[0];
190 uint32 diskSizeTmp = diskSize[0];
191 diskSize[0] = diskSize[1];
192 diskSize[1] = diskSizeTmp;
194 uint8 diskTypeTmp = diskType[0];
195 diskType[0] = diskType[1];
196 diskType[1] = diskTypeTmp;
198 uint8 imageDirtyTmp = imageDirty[0];
199 imageDirty[0] = imageDirty[1];
200 imageDirty[1] = imageDirtyTmp;
201 SpawnMessage("Drive 0: %s...", imageName[0]);
204 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
206 diskType[driveNum] = DT_UNKNOWN;
208 if (diskSize[driveNum] == 232960)
210 diskType[driveNum] = DT_NYBBLE;
211 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
213 else if (diskSize[driveNum] == 143360)
215 const char * ext = strrchr(filename, '.');
219 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
221 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
222 //[DONE, see below why we don't need it]
223 if (strcasecmp(ext, ".po") == 0)
224 diskType[driveNum] = DT_PRODOS;
225 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
227 diskType[driveNum] = DT_DOS33;
228 //WriteLog("Detected DOS 3.3 disk!\n");
230 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
231 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
232 WRT to the disk image itself.
233 // This could really be a ProDOS order disk with a .dsk extension, so let's see...
234 char fingerprint[3][4] = {
235 { 0x04, 0x00, 0x00, 0x00 }, // @ $500
236 { 0x03, 0x00, 0x05, 0x00 }, // @ $700
237 { 0x02, 0x00, 0x04, 0x00 } }; // @ $900
239 if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
240 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
241 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
242 diskType[driveNum] = DT_PRODOS;
246 // Actually, it just might matter WRT to nybblyzing/denybblyzing
247 // Here, we check for BT3
249 //diskType[driveNum] = DT_PRODOS;
251 NybblizeImage(driveNum);
254 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
256 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
257 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
258 "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
261 void FloppyDrive::NybblizeImage(uint8 driveNum)
263 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
264 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
265 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
266 // This is now correct, BTW
267 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
268 // (not incl. 64 byte track marker)
271 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
272 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 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 };
278 uint8 diskbyte[0x40] = {
279 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
280 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
281 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
282 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
283 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
284 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
285 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
286 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
288 uint8 * img = nybblizedImage[driveNum];
289 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
291 for(uint8 trk=0; trk<35; trk++)
293 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
296 for(uint8 sector=0; sector<16; sector++)
298 memcpy(img, header, 21); // Set up the sector header
300 img[5] = ((trk >> 1) & 0x55) | 0xAA;
301 img[6] = (trk & 0x55) | 0xAA;
302 img[7] = ((sector >> 1) & 0x55) | 0xAA;
303 img[8] = (sector & 0x55) | 0xAA;
304 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
305 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
308 uint8 * bytes = disk[driveNum];
310 if (diskType[driveNum] == DT_DOS33)
311 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
312 else if (diskType[driveNum] == DT_PRODOS)
313 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
315 bytes += (sector * 256) + (trk * 256 * 16);
317 // Convert the 256 8-bit bytes into 342 6-bit bytes.
319 for(uint16 i=0; i<0x56; i++)
321 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
322 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
323 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
324 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
325 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
326 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
331 memcpy(img + 0x56, bytes, 256);
333 // XOR the data block with itself, offset by one byte,
334 // creating a 343rd byte which is used as a cheksum.
338 for(uint16 i=342; i>0; i--)
339 img[i] = img[i] ^ img[i - 1];
341 // Using a lookup table, convert the 6-bit bytes into disk bytes.
343 for(uint16 i=0; i<343; i++)
344 //#define TEST_NYBBLIZATION
345 #ifdef TEST_NYBBLIZATION
347 WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img[i] >> 2]);
349 img[i] = diskbyte[img[i] >> 2];
350 #ifdef TEST_NYBBLIZATION
351 //WriteLog(" img[i] = %02X\n", img[i]);
356 // Done with the nybblization, now for the epilogue...
358 memcpy(img, footer, 48);
364 void FloppyDrive::DenybblizeImage(uint8 driveNum)
366 uint8 decodeNybble[0x80] = {
367 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
368 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
369 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
370 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
371 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
372 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
373 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
374 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
375 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
376 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
377 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
378 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
379 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
380 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
381 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
382 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
385 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
387 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
388 driveNum, disk[driveNum], diskSize[driveNum]);
392 uint8 * srcImg = nybblizedImage[driveNum];
393 uint8 * dstImg = disk[driveNum];
394 uint8 buffer[345]; // 2 extra bytes for the unpack routine below...
396 for(uint8 trk=0; trk<35; trk++)
398 uint8 * trackBase = srcImg + (trk * 6656);
400 for(uint8 sector=0; sector<16; sector++)
402 uint16 sectorStart = (uint16)-1;
404 for(uint16 i=0; i<6656; i++)
406 if (trackBase[i] == header[0]
407 && trackBase[(i + 1) % 6656] == header[1]
408 && trackBase[(i + 2) % 6656] == header[2]
409 && trackBase[(i + 3) % 6656] == header[3]
410 && trackBase[(i + 4) % 6656] == header[4])
412 //Could also check the track # at +5,6...
413 uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
414 | (trackBase[(i + 8) % 6656] & 0x55);
416 if (foundSector == sector)
418 sectorStart = (i + 21) % 6656;
425 if (sectorStart == (uint16)-1)
427 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
432 // Using a lookup table, convert the disk bytes into 6-bit bytes.
434 for(uint16 i=0; i<343; i++)
435 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
437 // XOR the data block with itself, offset by one byte.
439 for(uint16 i=1; i<342; i++)
440 buffer[i] = buffer[i] ^ buffer[i - 1];
442 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
444 for(uint16 i=0; i<0x56; i++)
446 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
447 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
448 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
451 uint8 * bytes = dstImg;
453 if (diskType[driveNum] == DT_DOS33)
454 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
455 else if (diskType[driveNum] == DT_PRODOS)
456 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
458 bytes += (sector * 256) + (trk * 256 * 16);//*/
460 memcpy(bytes, buffer + 0x56, 256);
465 // Memory mapped I/O functions
468 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
469 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
470 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
471 are in a different sequence. The NIB format is a nybblized format: a more direct representation
472 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
473 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
474 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
475 other unusual encodings.
478 void FloppyDrive::ControlStepper(uint8 addr)
482 What I can gather here:
483 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
484 bit 0 is the "do something" bit.
488 uint8 newPhase = (addr >> 1) & 0x03;
489 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
491 if (((phase + 1) & 0x03) == newPhase)
492 phase += (phase < 79 ? 1 : 0);
494 if (((phase - 1) & 0x03) == newPhase)
495 phase -= (phase > 0 ? 1 : 0);
499 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
502 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
503 SpawnMessage("Stepping to track %u...", track);
506 // return something if read mode...
509 void FloppyDrive::ControlMotor(uint8 addr)
515 void FloppyDrive::DriveEnable(uint8 addr)
521 uint8 FloppyDrive::ReadWrite(void)
523 SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
524 (ioMode == IO_MODE_READ ? "Read" : "Write"),
525 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
528 I think what happens here is that once a track is read its nybblized form
529 is fed through here, one byte at a time--which means for DO disks, we have
530 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
533 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
535 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
536 imageDirty[activeDrive] = true;
539 uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
540 currentPos = (currentPos + 1) % 6656;
542 //WriteLog("FL: diskByte=%02X, currentPos=%u\n", diskByte, currentPos);
546 uint8 FloppyDrive::GetLatchValue(void)
552 void FloppyDrive::SetLatchValue(uint8 value)
558 void FloppyDrive::SetReadMode(void)
561 ioMode = IO_MODE_READ;
564 void FloppyDrive::SetWriteMode(void)
567 ioMode = IO_MODE_WRITE;
571 PRODOS 8 MLI ERROR CODES
574 $01: Bad system call number
575 $04: Bad system call parameter count
576 $25: Interrupt table full
578 $28: No device connected
579 $2B: Disk write protected
581 $40: Invalid pathname
582 $42: Maximum number of files open
583 $43: Invalid reference number
584 $44: Directory not found
585 $45: Volume not found
587 $47: Duplicate filename
589 $49: Volume directory full
590 $4A: Incompatible file format, also a ProDOS directory
591 $4B: Unsupported storage_type
592 $4C: End of file encountered
593 $4D: Position out of range
594 $4E: File access error, also file locked
596 $51: Directory structure damaged
597 $52: Not a ProDOS volume
598 $53: Invalid system call parameter
599 $55: Volume Control Block table full
600 $56: Bad buffer address
601 $57: Duplicate volume
602 $5A: File structure damaged