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