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 #warning "Buffer overflow possible--!!! FIX !!!"
139 strcpy(imageName[driveNum], filename);
140 return SaveImage(driveNum);
143 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
145 if (disk[driveNum] != NULL)
146 delete disk[driveNum];
148 disk[driveNum] = new uint8[143360];
149 diskSize[driveNum] = 143360;
150 memset(disk[driveNum], 0x00, 143360);
151 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
152 diskType[driveNum] = DT_DOS33;
153 strcpy(imageName[driveNum], "newblank.dsk");
154 SpawnMessage("New blank image inserted in drive %u...", driveNum);
157 void FloppyDrive::SwapImages(void)
159 uint8 nybblizedImageTmp[232960];
160 char imageNameTmp[MAX_PATH];
162 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
163 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
164 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
166 memcpy(imageNameTmp, imageName[0], MAX_PATH);
167 memcpy(imageName[0], imageName[1], MAX_PATH);
168 memcpy(imageName[1], imageNameTmp, MAX_PATH);
170 uint8 * diskTmp = disk[0];
174 uint32 diskSizeTmp = diskSize[0];
175 diskSize[0] = diskSize[1];
176 diskSize[1] = diskSizeTmp;
178 uint8 diskTypeTmp = diskType[0];
179 diskType[0] = diskType[1];
180 diskType[1] = diskTypeTmp;
182 uint8 imageDirtyTmp = imageDirty[0];
183 imageDirty[0] = imageDirty[1];
184 imageDirty[1] = imageDirtyTmp;
185 SpawnMessage("Drive 0: %s...", imageName[0]);
188 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
190 diskType[driveNum] = DT_UNKNOWN;
192 if (diskSize[driveNum] == 232960)
194 diskType[driveNum] = DT_NYBBLE;
195 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
197 else if (diskSize[driveNum] == 143360)
199 const char * ext = strrchr(filename, '.');
203 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
205 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
206 //[DONE, see below why we don't need it]
207 if (strcasecmp(ext, ".po") == 0)
208 diskType[driveNum] = DT_PRODOS;
209 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
211 diskType[driveNum] = DT_DOS33;
212 //WriteLog("Detected DOS 3.3 disk!\n");
214 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
215 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
216 WRT to the disk image itself.
217 // This could really be a ProDOS order disk with a .dsk extension, so let's see...
218 char fingerprint[3][4] = {
219 { 0x04, 0x00, 0x00, 0x00 }, // @ $500
220 { 0x03, 0x00, 0x05, 0x00 }, // @ $700
221 { 0x02, 0x00, 0x04, 0x00 } }; // @ $900
223 if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
224 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
225 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
226 diskType[driveNum] = DT_PRODOS;
230 NybblizeImage(driveNum);
232 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
233 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
234 "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
237 void FloppyDrive::NybblizeImage(uint8 driveNum)
239 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
240 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
241 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
242 // This is now correct, BTW
243 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
244 // (not incl. 64 byte track marker)
247 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 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,
252 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
254 uint8 diskbyte[0x40] = {
255 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
256 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
257 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
258 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
259 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
260 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
261 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
262 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
264 uint8 * img = nybblizedImage[driveNum];
265 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
267 for(uint8 trk=0; trk<35; trk++)
269 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
272 for(uint8 sector=0; sector<16; sector++)
274 memcpy(img, header, 21); // Set up the sector header
276 img[5] = ((trk >> 1) & 0x55) | 0xAA;
277 img[6] = (trk & 0x55) | 0xAA;
278 img[7] = ((sector >> 1) & 0x55) | 0xAA;
279 img[8] = (sector & 0x55) | 0xAA;
280 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
281 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
284 uint8 * bytes = disk[driveNum];
286 if (diskType[driveNum] == DT_DOS33)
287 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
288 else if (diskType[driveNum] == DT_PRODOS)
289 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
291 bytes += (sector * 256) + (trk * 256 * 16);
293 // Convert the 256 8-bit bytes into 342 6-bit bytes.
295 for(uint16 i=0; i<0x56; i++)
297 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
298 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
299 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
300 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
301 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
302 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
307 memcpy(img + 0x56, bytes, 256);
309 // XOR the data block with itself, offset by one byte,
310 // creating a 343rd byte which is used as a cheksum.
314 for(uint16 i=342; i>0; i--)
315 img[i] = img[i] ^ img[i - 1];
317 // Using a lookup table, convert the 6-bit bytes into disk bytes.
319 for(uint16 i=0; i<343; i++)
320 img[i] = diskbyte[img[i] >> 2];
324 // Done with the nybblization, now for the epilogue...
326 memcpy(img, footer, 48);
332 void FloppyDrive::DenybblizeImage(uint8 driveNum)
334 uint8 decodeNybble[0x80] = {
335 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
337 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
338 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
339 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
340 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
341 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
342 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
343 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
344 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
345 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
346 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
347 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
348 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
349 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
350 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
353 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
355 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
356 driveNum, disk[driveNum], diskSize[driveNum]);
360 uint8 * srcImg = nybblizedImage[driveNum];
361 uint8 * dstImg = disk[driveNum];
362 uint8 buffer[345]; // 2 extra bytes for the unpack routine below...
364 for(uint8 trk=0; trk<35; trk++)
366 uint8 * trackBase = srcImg + (trk * 6656);
368 for(uint8 sector=0; sector<16; sector++)
370 uint16 sectorStart = (uint16)-1;
372 for(uint16 i=0; i<6656; i++)
374 if (trackBase[i] == header[0]
375 && trackBase[(i + 1) % 6656] == header[1]
376 && trackBase[(i + 2) % 6656] == header[2]
377 && trackBase[(i + 3) % 6656] == header[3]
378 && trackBase[(i + 4) % 6656] == header[4])
380 //Could also check the track # at +5,6...
381 uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
382 | (trackBase[(i + 8) % 6656] & 0x55);
384 if (foundSector == sector)
386 sectorStart = (i + 21) % 6656;
393 if (sectorStart == (uint16)-1)
395 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
400 // Using a lookup table, convert the disk bytes into 6-bit bytes.
402 for(uint16 i=0; i<343; i++)
403 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
405 // XOR the data block with itself, offset by one byte.
407 for(uint16 i=1; i<342; i++)
408 buffer[i] = buffer[i] ^ buffer[i - 1];
410 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
412 for(uint16 i=0; i<0x56; i++)
414 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
415 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
416 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
419 uint8 * bytes = dstImg;
421 if (diskType[driveNum] == DT_DOS33)
422 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
423 else if (diskType[driveNum] == DT_PRODOS)
424 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
426 bytes += (sector * 256) + (trk * 256 * 16);//*/
428 memcpy(bytes, buffer + 0x56, 256);
433 // Memory mapped I/O functions
436 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
437 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
438 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
439 are in a different sequence. The NIB format is a nybblized format: a more direct representation
440 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
441 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
442 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
443 other unusual encodings.
446 void FloppyDrive::ControlStepper(uint8 addr)
450 What I can gather here:
451 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
452 bit 0 is the "do something" bit.
456 uint8 newPhase = (addr >> 1) & 0x03;
457 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
459 if (((phase + 1) & 0x03) == newPhase)
460 phase += (phase < 79 ? 1 : 0);
462 if (((phase - 1) & 0x03) == newPhase)
463 phase -= (phase > 0 ? 1 : 0);
467 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
470 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
471 SpawnMessage("Stepping to track %u...", track);
474 // return something if read mode...
477 void FloppyDrive::ControlMotor(uint8 addr)
483 void FloppyDrive::DriveEnable(uint8 addr)
489 uint8 FloppyDrive::ReadWrite(void)
491 SpawnMessage("%sing %s track %u, sector %u...", (ioMode == IO_MODE_READ ? "Read" : "Write"),
492 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
495 I think what happens here is that once a track is read its nybblized form
496 is fed through here, one byte at a time--which means for DO disks, we have
497 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
500 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
502 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
503 imageDirty[activeDrive] = true;
506 uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
507 currentPos = (currentPos + 1) % 6656;
512 uint8 FloppyDrive::GetLatchValue(void)
518 void FloppyDrive::SetLatchValue(uint8 value)
524 void FloppyDrive::SetReadMode(void)
527 ioMode = IO_MODE_READ;
530 void FloppyDrive::SetWriteMode(void)
533 ioMode = IO_MODE_WRITE;