]> Shamusworld >> Repos - apple2/blob - src/floppy.cpp
335dbdbb1198ff71a3ca7fd94237a6c78c142671
[apple2] / src / floppy.cpp
1 //
2 // Apple 2 floppy disk support
3 //
4 // by James Hammons
5 // (c) 2005 Underground Software
6 //
7 // JLH = James Hammons <jlhamm@acm.org>
8 //
9 // WHO  WHEN        WHAT
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
14 //
15
16 #include "floppy.h"
17
18 #include <stdio.h>
19 #include <string.h>
20 #include "apple2.h"
21 #include "log.h"
22 #include "applevideo.h"                                 // For message spawning... Though there's probably a better approach than this!
23
24 // Useful enums
25
26 enum { IO_MODE_READ, IO_MODE_WRITE };
27
28 // FloppyDrive class variable initialization
29
30 uint8_t FloppyDrive::header[21] = {
31         0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
32         0x00, 0x00, 0x00, 0xDE, 0xAA, 0xFF,     0xFF, 0xFF,
33         0xFF, 0xFF, 0xD5, 0xAA, 0xAD };
34 uint8_t FloppyDrive::doSector[16] = {
35         0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
36 uint8_t FloppyDrive::poSector[16] = {
37         0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
38 char FloppyDrive::nameBuf[MAX_PATH];
39
40
41 // FloppyDrive class implementation...
42
43 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0), ioHappened(false)
44 {
45         disk[0] = disk[1] = NULL;
46         diskSize[0] = diskSize[1] = 0;
47         diskType[0] = diskType[1] = DFT_UNKNOWN;
48         imageDirty[0] = imageDirty[1] = false;
49         writeProtected[0] = writeProtected[1] = false;
50         imageName[0][0] = imageName[1][0] = 0;                  // Zero out filenames
51 }
52
53
54 FloppyDrive::~FloppyDrive()
55 {
56         if (disk[0])
57                 delete[] disk[0];
58
59         if (disk[1])
60                 delete[] disk[1];
61 }
62
63
64 bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
65 {
66         WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
67
68         if (driveNum > 1)
69         {
70                 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
71                 return false;
72         }
73
74         imageName[driveNum][0] = 0;                                     // Zero out filename, in case it doesn't load
75
76         FILE * fp = fopen(filename, "rb");
77
78         if (fp == NULL)
79         {
80                 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
81                 return false;
82         }
83
84         if (disk[driveNum])
85                 delete[] disk[driveNum];
86
87         fseek(fp, 0, SEEK_END);
88         diskSize[driveNum] = ftell(fp);
89         fseek(fp, 0, SEEK_SET);
90         disk[driveNum] =  new uint8_t[diskSize[driveNum]];
91         fread(disk[driveNum], 1, diskSize[driveNum], fp);
92
93         fclose(fp);
94 //printf("Read disk image: %u bytes.\n", diskSize);
95         DetectImageType(filename, driveNum);
96         strcpy(imageName[driveNum], filename);
97
98 #if 0
99         WriteLog("FLOPPY: Opening image for drive #%u.\n", driveNum);
100         FILE * fp2 = fopen("bt-nybblized.nyb", "wb");
101
102         if (fp2 == NULL)
103                 WriteLog("FLOPPY: Failed to open image file 'bt-nybblized.nyb' for writing...\n");
104         else
105         {
106                 fwrite(nybblizedImage[driveNum], 1, 232960, fp2);
107                 fclose(fp2);
108         }
109 #endif
110 //writeProtected[driveNum] = true;
111         WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
112
113         return true;
114 }
115
116
117 bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
118 {
119         // Various sanity checks...
120         if (driveNum > 1)
121         {
122                 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
123                 return false;
124         }
125
126         if (!imageDirty[driveNum])
127         {
128                 WriteLog("FLOPPY: No need to save unchanged image...\n");
129                 return false;
130         }
131
132         if (imageName[driveNum][0] == 0)
133         {
134                 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
135                 return false;
136         }
137
138         // Handle nybbylization, if necessary
139         if (diskType[driveNum] == DT_NYBBLE)
140                 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
141         else
142                 DenybblizeImage(driveNum);
143
144         // Finally, write the damn image
145         FILE * fp = fopen(imageName[driveNum], "wb");
146
147         if (fp == NULL)
148         {
149                 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
150                 return false;
151         }
152
153         fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
154         fclose(fp);
155
156         WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]);
157
158         return true;
159 }
160
161
162 bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
163 {
164 //WARNING: Buffer overflow possibility
165 #warning "Buffer overflow possible--!!! FIX !!!"
166         strcpy(imageName[driveNum], filename);
167         return SaveImage(driveNum);
168 }
169
170
171 void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
172 {
173         if (disk[driveNum] != NULL)
174                 delete disk[driveNum];
175
176         disk[driveNum] = new uint8_t[143360];
177         diskSize[driveNum] = 143360;
178         memset(disk[driveNum], 0x00, 143360);
179         memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
180         diskType[driveNum] = DT_DOS33;
181         strcpy(imageName[driveNum], "newblank.dsk");
182         writeProtected[driveNum] = false;
183 SpawnMessage("New blank image inserted in drive %u...", driveNum);
184 }
185
186
187 void FloppyDrive::SwapImages(void)
188 {
189         uint8_t nybblizedImageTmp[232960];
190         char imageNameTmp[MAX_PATH];
191
192         memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
193         memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
194         memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
195
196         memcpy(imageNameTmp, imageName[0], MAX_PATH);
197         memcpy(imageName[0], imageName[1], MAX_PATH);
198         memcpy(imageName[1], imageNameTmp, MAX_PATH);
199
200         uint8_t * diskTmp = disk[0];
201         disk[0] = disk[1];
202         disk[1] = diskTmp;
203
204         uint32_t diskSizeTmp = diskSize[0];
205         diskSize[0] = diskSize[1];
206         diskSize[1] = diskSizeTmp;
207
208         uint8_t diskTypeTmp = diskType[0];
209         diskType[0] = diskType[1];
210         diskType[1] = diskTypeTmp;
211
212         uint8_t imageDirtyTmp = imageDirty[0];
213         imageDirty[0] = imageDirty[1];
214         imageDirty[1] = imageDirtyTmp;
215
216         uint8_t writeProtectedTmp = writeProtected[0];
217         writeProtected[0] = writeProtected[1];
218         writeProtected[1] = writeProtectedTmp;
219 SpawnMessage("Drive 0: %s...", imageName[0]);
220 }
221
222
223 void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
224 {
225         diskType[driveNum] = DFT_UNKNOWN;
226
227         if (diskSize[driveNum] == 232960)
228         {
229                 diskType[driveNum] = DT_NYBBLE;
230                 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
231         }
232         else if (diskSize[driveNum] == 143360)
233         {
234                 const char * ext = strrchr(filename, '.');
235
236                 if (ext == NULL)
237                         return;
238 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
239
240 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
241                 if (strcasecmp(ext, ".po") == 0)
242                         diskType[driveNum] = DT_PRODOS;
243                 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
244                 {
245                         // We assume this, but check for a PRODOS fingerprint. Trust, but
246                         // verify. ;-)
247                         diskType[driveNum] = DT_DOS33;
248
249                         uint8_t fingerprint[4][4] = {
250                                 { 0x00, 0x00, 0x03, 0x00 },             // @ $400
251                                 { 0x02, 0x00, 0x04, 0x00 },             // @ $600
252                                 { 0x03, 0x00, 0x05, 0x00 },             // @ $800
253                                 { 0x04, 0x00, 0x00, 0x00 }              // @ $A00
254                         };
255
256                         bool foundProdos = true;
257
258                         for(uint32_t i=0; i<4; i++)
259                         {
260                                 for(uint32_t j=0; j<4; j++)
261                                 {
262                                         if (disk[driveNum][0x400 + (i * 0x200) + j] != fingerprint[i][j])
263                                         {
264                                                 foundProdos = false;
265                                                 break;
266                                         }
267                                 }
268                         }
269
270                         if (foundProdos)
271                                 diskType[driveNum] = DT_PRODOS;
272                 }
273
274 // Actually, it just might matter WRT to nybblyzing/denybblyzing
275 // (and, it does... :-P)
276                 NybblizeImage(driveNum);
277         }
278         else if (diskSize[driveNum] == 143488)
279         {
280                 diskType[driveNum] = DT_DOS33_HDR;
281                 NybblizeImage(driveNum);
282         }
283
284 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
285
286 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
287         "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
288         "DOS 3.3 image" : (diskType[driveNum] == DT_DOS33_HDR ?
289         "DOS 3.3 image (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown")))));
290 }
291
292
293 void FloppyDrive::NybblizeImage(uint8_t driveNum)
294 {
295         // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
296         // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
297 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
298 // This is now correct, BTW
299         // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
300         // (not incl. 64 byte track marker)
301
302         uint8_t footer[48] = {
303                 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
304                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
305                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
306                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
307                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
308                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
309
310         uint8_t diskbyte[0x40] = {
311                 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
312                 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
313                 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
314                 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
315                 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
316                 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
317                 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
318                 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
319
320         uint8_t * img = nybblizedImage[driveNum];
321         memset(img, 0xFF, 232960);                                      // Doesn't matter if 00s or FFs...
322
323         for(uint8_t trk=0; trk<35; trk++)
324         {
325                 memset(img, 0xFF, 64);                                  // Write gap 1, 64 bytes (self-sync)
326                 img += 64;
327
328                 for(uint8_t sector=0; sector<16; sector++)
329                 {
330                         memcpy(img, header, 21);                        // Set up the sector header
331
332                         img[5] = ((trk >> 1) & 0x55) | 0xAA;
333                         img[6] =  (trk       & 0x55) | 0xAA;
334                         img[7] = ((sector >> 1) & 0x55) | 0xAA;
335                         img[8] =  (sector       & 0x55) | 0xAA;
336                         img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
337                         img[10] = ((trk ^ sector ^ 0xFE)       & 0x55) | 0xAA;
338
339                         img += 21;
340                         uint8_t * bytes = disk[driveNum];
341
342                         if (diskType[driveNum] == DT_DOS33)
343                                 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
344                         else if (diskType[driveNum] == DT_DOS33_HDR)
345                                 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
346                         else if (diskType[driveNum] == DT_PRODOS)
347                                 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
348                         else
349                                 bytes += (sector * 256) + (trk * 256 * 16);
350
351                         // Convert the 256 8-bit bytes into 342 6-bit bytes.
352
353                         for(uint16_t i=0; i<0x56; i++)
354                         {
355                                 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
356                                         | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
357                                         | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
358                                         | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
359                                         | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
360                                         | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
361                         }
362
363                         img[0x54] &= 0x3F;
364                         img[0x55] &= 0x3F;
365                         memcpy(img + 0x56, bytes, 256);
366
367                         // XOR the data block with itself, offset by one byte,
368                         // creating a 343rd byte which is used as a cheksum.
369
370                         img[342] = 0x00;
371
372                         for(uint16_t i=342; i>0; i--)
373                                 img[i] = img[i] ^ img[i - 1];
374
375                         // Using a lookup table, convert the 6-bit bytes into disk bytes.
376
377                         for(uint16_t i=0; i<343; i++)
378 //#define TEST_NYBBLIZATION
379 #ifdef TEST_NYBBLIZATION
380 {
381 WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img[i] >> 2]);
382 #endif
383                                 img[i] = diskbyte[img[i] >> 2];
384 #ifdef TEST_NYBBLIZATION
385 //WriteLog("            img[i] = %02X\n", img[i]);
386 }
387 #endif
388                         img += 343;
389
390                         // Done with the nybblization, now for the epilogue...
391
392                         memcpy(img, footer, 48);
393                         img += 48;
394                 }
395         }
396 }
397
398
399 void FloppyDrive::DenybblizeImage(uint8_t driveNum)
400 {
401         uint8_t decodeNybble[0x80] = {
402                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
403                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
404                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
405                 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
406                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
407                 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
408                 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
409                 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
410                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
411                 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
412                 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
413                 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
414                 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
415                 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
416                 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
417                 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
418
419         // Sanity checks...
420         if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
421         {
422                 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
423                         driveNum, disk[driveNum], diskSize[driveNum]);
424                 return;
425         }
426
427         uint8_t * srcImg = nybblizedImage[driveNum];
428         uint8_t * dstImg = disk[driveNum];
429         uint8_t buffer[345];                                                    // 2 extra bytes for the unpack routine below...
430
431         for(uint8_t trk=0; trk<35; trk++)
432         {
433                 uint8_t * trackBase = srcImg + (trk * 6656);
434
435                 for(uint8_t sector=0; sector<16; sector++)
436                 {
437                         uint16_t sectorStart = (uint16_t)-1;
438
439                         for(uint16_t i=0; i<6656; i++)
440                         {
441                                 if (trackBase[i] == header[0]
442                                         && trackBase[(i + 1) % 6656] == header[1]
443                                         && trackBase[(i + 2) % 6656] == header[2]
444                                         && trackBase[(i + 3) % 6656] == header[3]
445                                         && trackBase[(i + 4) % 6656] == header[4])
446                                 {
447 //Could also check the track # at +5,6...
448                                         uint8_t foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
449                                                 | (trackBase[(i + 8) % 6656] & 0x55);
450
451                                         if (foundSector == sector)
452                                         {
453                                                 sectorStart = (i + 21) % 6656;
454                                                 break;
455                                         }
456                                 }
457                         }
458
459                         // Sanity check...
460                         if (sectorStart == (uint16_t)-1)
461                         {
462                                 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
463                                         sector, trk);
464                                 return;
465                         }
466
467                         // Using a lookup table, convert the disk bytes into 6-bit bytes.
468
469                         for(uint16_t i=0; i<343; i++)
470                                 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
471
472                         // XOR the data block with itself, offset by one byte.
473
474                         for(uint16_t i=1; i<342; i++)
475                                 buffer[i] = buffer[i] ^ buffer[i - 1];
476
477                         // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
478
479                         for(uint16_t i=0; i<0x56; i++)
480                         {
481                                 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
482                                 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
483                                 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
484                         }
485
486                         uint8_t * bytes = dstImg;
487
488                         if (diskType[driveNum] == DT_DOS33)
489                                 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
490                         else if (diskType[driveNum] == DT_DOS33_HDR)
491                                 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
492                         else if (diskType[driveNum] == DT_PRODOS)
493                                 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
494                         else
495                                 bytes += (sector * 256) + (trk * 256 * 16);//*/
496
497                         memcpy(bytes, buffer + 0x56, 256);
498                 }
499         }
500 }
501
502
503 const char * FloppyDrive::ImageName(uint8_t driveNum/*= 0*/)
504 {
505         // Set up a zero-length string for return value
506         nameBuf[0] = 0;
507
508         if (driveNum > 1)
509         {
510                 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
511                 return nameBuf;
512         }
513
514         // Now we attempt to strip out extraneous paths/extensions to get just the filename
515         const char * startOfFile = strrchr(imageName[driveNum], '/');
516         const char * startOfExt = strrchr(imageName[driveNum], '.');
517
518         // If there isn't a path, assume we're starting at the beginning
519         if (startOfFile == NULL)
520                 startOfFile = &imageName[driveNum][0];
521         else
522                 startOfFile++;
523
524         // If there isn't an extension, assume it's at the terminating NULL
525         if (startOfExt == NULL)
526                 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
527
528         // Now copy the filename (may copy nothing!)
529         int j = 0;
530
531         for(const char * i=startOfFile; i<startOfExt; i++)
532                 nameBuf[j++] = *i;
533
534         nameBuf[j] = 0;
535
536         return nameBuf;
537 }
538
539
540 void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
541 {
542         // Sanity check
543         if (IsEmpty(driveNum))
544                 return;
545
546         // Probably want to save a dirty image... ;-)
547         if (SaveImage(driveNum))
548                 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
549
550         if (disk[driveNum])
551                 delete[] disk[driveNum];
552
553         disk[driveNum] = NULL;
554         diskSize[driveNum] = 0;
555         diskType[driveNum] = DFT_UNKNOWN;
556         imageDirty[driveNum] = false;
557         writeProtected[driveNum] = false;
558         imageName[driveNum][0] = 0;                     // Zero out filenames
559         memset(nybblizedImage[driveNum], 0xFF, 232960); // Doesn't matter if 00s or FFs...
560 }
561
562
563 bool FloppyDrive::IsEmpty(uint8_t driveNum/*= 0*/)
564 {
565         if (driveNum > 1)
566         {
567                 WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum);
568                 return true;
569         }
570
571         // This is kinda gay, but it works
572         return (imageName[driveNum][0] == 0 ? true : false);
573 }
574
575
576 bool FloppyDrive::IsWriteProtected(uint8_t driveNum/*= 0*/)
577 {
578         if (driveNum > 1)
579         {
580                 WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum);
581                 return true;
582         }
583
584         return writeProtected[driveNum];
585 }
586
587
588 void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
589 {
590         if (driveNum > 1)
591         {
592                 WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum);
593                 return;
594         }
595
596         writeProtected[driveNum] = state;
597 }
598
599
600 int FloppyDrive::DriveLightStatus(uint8_t driveNum/*= 0*/)
601 {
602         int retval = DLS_OFF;
603
604         if (activeDrive != driveNum)
605                 return DLS_OFF;
606
607         if (ioHappened)
608                 retval = (ioMode == IO_MODE_READ ? DLS_READ : DLS_WRITE);
609
610         ioHappened = false;
611         return retval;
612 }
613
614
615 void FloppyDrive::SaveState(FILE * file)
616 {
617         // Internal state vars
618         fputc(motorOn, file);
619         fputc(activeDrive, file);
620         fputc(ioMode, file);
621         fputc(latchValue, file);
622         fputc(phase, file);
623         fputc(track, file);
624         fputc((ioHappened ? 1 : 0), file);
625         WriteLong(file, currentPos);
626
627         // Disk #1
628         if (disk[0] != NULL)
629         {
630                 WriteLong(file, diskSize[0]);
631                 WriteLong(file, diskType[0]);
632                 fputc((imageDirty[0] ? 1 : 0), file);
633                 fputc((writeProtected[0] ? 1 : 0), file);
634                 fwrite(nybblizedImage[0], 1, 232960, file);
635                 fwrite(imageName[0], 1, MAX_PATH, file);
636         }
637         else
638                 WriteLong(file, 0);
639
640         // Disk #2
641         if (disk[1] != NULL)
642         {
643                 WriteLong(file, diskSize[1]);
644                 WriteLong(file, diskType[1]);
645                 fputc((imageDirty[1] ? 1 : 0), file);
646                 fputc((writeProtected[1] ? 1 : 0), file);
647                 fwrite(nybblizedImage[1], 1, 232960, file);
648                 fwrite(imageName[1], 1, MAX_PATH, file);
649         }
650         else
651                 WriteLong(file, 0);
652 }
653
654
655 void FloppyDrive::LoadState(FILE * file)
656 {
657         // Eject images if they're loaded
658         EjectImage(0);
659         EjectImage(1);
660
661         // Read internal state variables
662         motorOn = fgetc(file);
663         activeDrive = fgetc(file);
664         ioMode = fgetc(file);
665         latchValue = fgetc(file);
666         phase = fgetc(file);
667         track = fgetc(file);
668         ioHappened = (fgetc(file) == 1 ? true : false);
669         currentPos = ReadLong(file);
670
671         diskSize[0] = ReadLong(file);
672
673         if (diskSize[0])
674         {
675                 disk[0] = new uint8_t[diskSize[0]];
676                 diskType[0] = (uint8_t)ReadLong(file);
677                 imageDirty[0] = (fgetc(file) == 1 ? true : false);
678                 writeProtected[0] = (fgetc(file) == 1 ? true : false);
679                 fread(nybblizedImage[0], 1, 232960, file);
680                 fread(imageName[0], 1, MAX_PATH, file);
681         }
682
683         diskSize[1] = ReadLong(file);
684
685         if (diskSize[1])
686         {
687                 disk[1] = new uint8_t[diskSize[1]];
688                 diskType[1] = (uint8_t)ReadLong(file);
689                 imageDirty[1] = (fgetc(file) == 1 ? true : false);
690                 writeProtected[1] = (fgetc(file) == 1 ? true : false);
691                 fread(nybblizedImage[1], 1, 232960, file);
692                 fread(imageName[1], 1, MAX_PATH, file);
693         }
694 }
695
696
697 uint32_t FloppyDrive::ReadLong(FILE * file)
698 {
699         uint32_t r = 0;
700
701         for(int i=0; i<4; i++)
702                 r = (r << 8) | fgetc(file);
703
704         return r;
705 }
706
707
708 void FloppyDrive::WriteLong(FILE * file, uint32_t l)
709 {
710         for(int i=0; i<4; i++)
711         {
712                 fputc((l >> 24) & 0xFF, file);
713                 l = l << 8;
714         }
715 }
716
717
718 // Memory mapped I/O functions
719
720 /*
721 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35
722 tracks of 16 sectors of 256 bytes each, making 143,360 bytes in total. The PO
723 format is exactly the same size as DSK and is also organized as 35 sequential
724 tracks, but the sectors within each track are in a different sequence. The NIB
725 format is a nybblized format: a more direct representation of the disk's data
726 as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
727 6656 bytes each, for a total size of 232,960 bytes. Although this format is
728 much larger, it is also more versatile and can represent the older 13-sector
729 disks, many copy-protected disks, and other unusual encodings.
730 */
731
732 void FloppyDrive::ControlStepper(uint8_t addr)
733 {
734         // $C0E0 - 7
735 /*
736 What I can gather here:
737 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
738 bit 0 is the "do something" bit.
739 */
740         if (addr & 0x01)
741         {
742                 uint8_t newPhase = (addr >> 1) & 0x03;
743 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
744
745                 if (((phase + 1) & 0x03) == newPhase)
746                         phase += (phase < 79 ? 1 : 0);
747
748                 if (((phase - 1) & 0x03) == newPhase)
749                         phase -= (phase > 0 ? 1 : 0);
750
751                 if (!(phase & 0x01))
752                 {
753                         track = ((phase >> 1) < 35 ? phase >> 1 : 34);
754                         currentPos = 0;
755                 }
756 //WriteLog("                        track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
757 SpawnMessage("Stepping to track %u...", track);
758         }
759
760 //      return something if read mode...
761 }
762
763
764 void FloppyDrive::ControlMotor(uint8_t addr)
765 {
766         // $C0E8 - 9
767         motorOn = addr;
768 }
769
770
771 void FloppyDrive::DriveEnable(uint8_t addr)
772 {
773         // $C0EA - B
774         activeDrive = addr;
775 }
776
777
778 uint8_t FloppyDrive::ReadWrite(void)
779 {
780 SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
781         (ioMode == IO_MODE_READ ? "Read" : "Write"),
782         (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
783         // $C0EC
784         ioHappened = true;
785 /*
786 I think what happens here is that once a track is read its nybblized form
787 is fed through here, one byte at a time--which means for DO disks, we have
788 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
789 Which we now do. :-)
790 */
791         if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
792         {
793                 // Does it behave like this?
794 #warning "Write protection kludged in--investigate real behavior!"
795                 if (writeProtected[activeDrive])
796 //doesn't seem to do anything
797                         return 0;//is this more like it?
798
799                 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
800                 imageDirty[activeDrive] = true;
801         }
802
803         uint8_t diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
804         currentPos = (currentPos + 1) % 6656;
805
806 //WriteLog("FL: diskByte=%02X, currentPos=%u\n", diskByte, currentPos);
807         return diskByte;
808 }
809
810
811 uint8_t FloppyDrive::GetLatchValue(void)
812 {
813         // $C0ED
814         return latchValue;
815 }
816
817
818 void FloppyDrive::SetLatchValue(uint8_t value)
819 {
820         // $C0ED
821         latchValue = value;
822 }
823
824
825 void FloppyDrive::SetReadMode(void)
826 {
827         // $C0EE
828         ioMode = IO_MODE_READ;
829 }
830
831
832 void FloppyDrive::SetWriteMode(void)
833 {
834         // $C0EF
835         ioMode = IO_MODE_WRITE;
836 }
837
838 /*
839 PRODOS 8 MLI ERROR CODES
840
841 $00:    No error
842 $01:    Bad system call number
843 $04:    Bad system call parameter count
844 $25:    Interrupt table full
845 $27:    I/O error
846 $28:    No device connected
847 $2B:    Disk write protected
848 $2E:    Disk switched
849 $40:    Invalid pathname
850 $42:    Maximum number of files open
851 $43:    Invalid reference number
852 $44:    Directory not found
853 $45:    Volume not found
854 $46:    File not found
855 $47:    Duplicate filename
856 $48:    Volume full
857 $49:    Volume directory full
858 $4A:    Incompatible file format, also a ProDOS directory
859 $4B:    Unsupported storage_type
860 $4C:    End of file encountered
861 $4D:    Position out of range
862 $4E:    File access error, also file locked
863 $50:    File is open
864 $51:    Directory structure damaged
865 $52:    Not a ProDOS volume
866 $53:    Invalid system call parameter
867 $55:    Volume Control Block table full
868 $56:    Bad buffer address
869 $57:    Duplicate volume
870 $5A:    File structure damaged
871 */