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!
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*/)
65 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
69 imageName[driveNum][0] = 0; // Zero out filename, in case it doesn't load
71 FILE * fp = fopen(filename, "rb");
75 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
80 delete[] disk[driveNum];
82 fseek(fp, 0, SEEK_END);
83 diskSize[driveNum] = ftell(fp);
84 fseek(fp, 0, SEEK_SET);
85 disk[driveNum] = new uint8[diskSize[driveNum]];
86 fread(disk[driveNum], 1, diskSize[driveNum], fp);
89 //printf("Read disk image: %u bytes.\n", diskSize);
90 DetectImageType(filename, driveNum);
91 strcpy(imageName[driveNum], filename);
96 bool FloppyDrive::SaveImage(uint8 driveNum/*= 0*/)
100 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
104 if (!imageDirty[driveNum])
106 WriteLog("FLOPPY: No need to save unchanged image...\n");
110 if (imageName[driveNum][0] == 0)
112 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
116 if (diskType[driveNum] == DT_NYBBLE)
117 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
119 DenybblizeImage(driveNum);
121 FILE * fp = fopen(imageName[driveNum], "wb");
125 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
129 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
135 bool FloppyDrive::SaveImageAs(const char * filename, uint8 driveNum/*= 0*/)
137 //WARNING: Buffer overflow possibility
138 strcpy(imageName[driveNum], filename);
139 return SaveImage(driveNum);
142 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
144 if (disk[driveNum] != NULL)
145 delete disk[driveNum];
147 disk[driveNum] = new uint8[143360];
148 diskSize[driveNum] = 143360;
149 memset(disk[driveNum], 0x00, 143360);
150 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
151 diskType[driveNum] = DT_DOS33;
152 strcpy(imageName[driveNum], "newblank.dsk");
153 SpawnMessage("New blank image inserted in drive %u...", driveNum);
156 void FloppyDrive::SwapImages(void)
158 uint8 nybblizedImageTmp[232960];
159 char imageNameTmp[MAX_PATH];
161 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
162 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
163 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
165 memcpy(imageNameTmp, imageName[0], MAX_PATH);
166 memcpy(imageName[0], imageName[1], MAX_PATH);
167 memcpy(imageName[1], imageNameTmp, MAX_PATH);
169 uint8 * diskTmp = disk[0];
173 uint32 diskSizeTmp = diskSize[0];
174 diskSize[0] = diskSize[1];
175 diskSize[1] = diskSizeTmp;
177 uint8 diskTypeTmp = diskType[0];
178 diskType[0] = diskType[1];
179 diskType[1] = diskTypeTmp;
181 uint8 imageDirtyTmp = imageDirty[0];
182 imageDirty[0] = imageDirty[1];
183 imageDirty[1] = imageDirtyTmp;
184 SpawnMessage("Drive 0: %s...", imageName[0]);
187 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
189 diskType[driveNum] = DT_UNKNOWN;
191 if (diskSize[driveNum] == 232960)
193 diskType[driveNum] = DT_NYBBLE;
194 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
196 else if (diskSize[driveNum] == 143360)
198 const char * ext = strrchr(filename, '.');
202 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
204 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
205 //[DONE, see below why we don't need it]
206 if (strcasecmp(ext, ".po") == 0)
207 diskType[driveNum] = DT_PRODOS;
208 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
210 diskType[driveNum] = DT_DOS33;
211 //WriteLog("Detected DOS 3.3 disk!\n");
213 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
214 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
215 WRT to the disk image itself.
216 // This could really be a ProDOS order disk with a .dsk extension, so let's see...
217 char fingerprint[3][4] = {
218 { 0x04, 0x00, 0x00, 0x00 }, // @ $500
219 { 0x03, 0x00, 0x05, 0x00 }, // @ $700
220 { 0x02, 0x00, 0x04, 0x00 } }; // @ $900
222 if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
223 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
224 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
225 diskType[driveNum] = DT_PRODOS;
229 NybblizeImage(driveNum);
231 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
232 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
233 "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
236 void FloppyDrive::NybblizeImage(uint8 driveNum)
238 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
239 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
240 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
241 // This is now correct, BTW
242 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
243 // (not incl. 64 byte track marker)
246 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
247 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
248 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
249 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
250 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
251 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
253 uint8 diskbyte[0x40] = {
254 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
255 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
256 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
257 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
258 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
259 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
260 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
261 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
263 uint8 * img = nybblizedImage[driveNum];
264 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
266 for(uint8 trk=0; trk<35; trk++)
268 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
271 for(uint8 sector=0; sector<16; sector++)
273 memcpy(img, header, 21); // Set up the sector header
275 img[5] = ((trk >> 1) & 0x55) | 0xAA;
276 img[6] = (trk & 0x55) | 0xAA;
277 img[7] = ((sector >> 1) & 0x55) | 0xAA;
278 img[8] = (sector & 0x55) | 0xAA;
279 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
280 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
283 uint8 * bytes = disk[driveNum];
285 if (diskType[driveNum] == DT_DOS33)
286 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
287 else if (diskType[driveNum] == DT_PRODOS)
288 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
290 bytes += (sector * 256) + (trk * 256 * 16);
292 // Convert the 256 8-bit bytes into 342 6-bit bytes.
294 for(uint16 i=0; i<0x56; i++)
296 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
297 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
298 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
299 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
300 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
301 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
306 memcpy(img + 0x56, bytes, 256);
308 // XOR the data block with itself, offset by one byte,
309 // creating a 343rd byte which is used as a cheksum.
313 for(uint16 i=342; i>0; i--)
314 img[i] = img[i] ^ img[i - 1];
316 // Using a lookup table, convert the 6-bit bytes into disk bytes.
318 for(uint16 i=0; i<343; i++)
319 img[i] = diskbyte[img[i] >> 2];
323 // Done with the nybblization, now for the epilogue...
325 memcpy(img, footer, 48);
331 void FloppyDrive::DenybblizeImage(uint8 driveNum)
333 uint8 decodeNybble[0x80] = {
334 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
335 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
337 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
338 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
339 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
340 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
341 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
342 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
343 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
344 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
345 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
346 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
347 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
348 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
349 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
352 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
354 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
355 driveNum, disk[driveNum], diskSize[driveNum]);
359 uint8 * srcImg = nybblizedImage[driveNum];
360 uint8 * dstImg = disk[driveNum];
361 uint8 buffer[345]; // 2 extra bytes for the unpack routine below...
363 for(uint8 trk=0; trk<35; trk++)
365 uint8 * trackBase = srcImg + (trk * 6656);
367 for(uint8 sector=0; sector<16; sector++)
369 uint16 sectorStart = (uint16)-1;
371 for(uint16 i=0; i<6656; i++)
373 if (trackBase[i] == header[0]
374 && trackBase[(i + 1) % 6656] == header[1]
375 && trackBase[(i + 2) % 6656] == header[2]
376 && trackBase[(i + 3) % 6656] == header[3]
377 && trackBase[(i + 4) % 6656] == header[4])
379 //Could also check the track # at +5,6...
380 uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
381 | (trackBase[(i + 8) % 6656] & 0x55);
383 if (foundSector == sector)
385 sectorStart = (i + 21) % 6656;
392 if (sectorStart == (uint16)-1)
394 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
399 // Using a lookup table, convert the disk bytes into 6-bit bytes.
401 for(uint16 i=0; i<343; i++)
402 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
404 // XOR the data block with itself, offset by one byte.
406 for(uint16 i=1; i<342; i++)
407 buffer[i] = buffer[i] ^ buffer[i - 1];
409 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
411 for(uint16 i=0; i<0x56; i++)
413 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
414 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
415 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
418 uint8 * bytes = dstImg;
420 if (diskType[driveNum] == DT_DOS33)
421 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
422 else if (diskType[driveNum] == DT_PRODOS)
423 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
425 bytes += (sector * 256) + (trk * 256 * 16);//*/
427 memcpy(bytes, buffer + 0x56, 256);
432 // Memory mapped I/O functions
435 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
436 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
437 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
438 are in a different sequence. The NIB format is a nybblized format: a more direct representation
439 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
440 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
441 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
442 other unusual encodings.
445 void FloppyDrive::ControlStepper(uint8 addr)
449 What I can gather here:
450 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
451 bit 0 is the "do something" bit.
455 uint8 newPhase = (addr >> 1) & 0x03;
456 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
458 if (((phase + 1) & 0x03) == newPhase)
459 phase += (phase < 79 ? 1 : 0);
461 if (((phase - 1) & 0x03) == newPhase)
462 phase -= (phase > 0 ? 1 : 0);
466 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
469 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
470 SpawnMessage("Stepping to track %u...", track);
473 // return something if read mode...
476 void FloppyDrive::ControlMotor(uint8 addr)
482 void FloppyDrive::DriveEnable(uint8 addr)
488 uint8 FloppyDrive::ReadWrite(void)
490 SpawnMessage("%sing %s track %u, sector %u...", (ioMode == IO_MODE_READ ? "Read" : "Write"),
491 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
494 I think what happens here is that once a track is read its nybblized form
495 is fed through here, one byte at a time--which means for DO disks, we have
496 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
499 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
501 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
502 imageDirty[activeDrive] = true;
505 uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
506 currentPos = (currentPos + 1) % 6656;
511 uint8 FloppyDrive::GetLatchValue(void)
517 void FloppyDrive::SetLatchValue(uint8 value)
523 void FloppyDrive::SetReadMode(void)
526 ioMode = IO_MODE_READ;
529 void FloppyDrive::SetWriteMode(void)
532 ioMode = IO_MODE_WRITE;