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