]> Shamusworld >> Repos - apple2/blob - src/floppy.cpp
59c9e6dfc1a872f938b33deae7f8af4a93b64920
[apple2] / src / floppy.cpp
1 //
2 // Apple 2 floppy disk support
3 //
4 // by James L. Hammons
5 // (c) 2005 Underground Software
6 //
7 // JLH = James L. 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 FloppyDrive::header[21] = {
33         0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
34         0x00, 0x00, 0x00, 0xDE, 0xAA, 0xFF,     0xFF, 0xFF,
35         0xFF, 0xFF, 0xD5, 0xAA, 0xAD };
36 uint8 FloppyDrive::doSector[16] = {
37         0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
38 uint8 FloppyDrive::poSector[16] = {
39         0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
40
41 // FloppyDrive class implementation...
42
43 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
44 {
45         disk[0] = disk[1] = NULL;
46         diskSize[0] = diskSize[1] = 0;
47         diskType[0] = diskType[1] = DT_UNKNOWN;
48         imageDirty[0] = imageDirty[1] = false;
49         imageName[0][0] = imageName[1][0] = 0;                  // Zero out filenames
50 }
51
52 FloppyDrive::~FloppyDrive()
53 {
54         if (disk[0])
55                 delete[] disk[0];
56
57         if (disk[1])
58                 delete[] disk[1];
59 }
60
61 bool FloppyDrive::LoadImage(const char * filename, uint8 driveNum/*= 0*/)
62 {
63         if (driveNum > 1)
64         {
65                 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
66                 return false;
67         }
68
69         imageName[driveNum][0] = 0;                                     // Zero out filename, in case it doesn't load
70
71         FILE * fp = fopen(filename, "rb");
72
73         if (fp == NULL)
74         {
75                 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
76                 return false;
77         }
78
79         if (disk[driveNum])
80                 delete[] disk[driveNum];
81
82         fseek(fp, 0, SEEK_END);
83         diskSize[driveNum] = ftell(fp);
84         fseek(fp, 0, SEEK_SET);
85         disk[driveNum] =  new uint8[diskSize[driveNum]];
86         fread(disk[driveNum], 1, diskSize[driveNum], fp);
87
88         fclose(fp);
89 //printf("Read disk image: %u bytes.\n", diskSize);
90         DetectImageType(filename, driveNum);
91         strcpy(imageName[driveNum], filename);
92
93         return true;
94 }
95
96 bool FloppyDrive::SaveImage(uint8 driveNum/*= 0*/)
97 {
98         if (driveNum > 1)
99         {
100                 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
101                 return false;
102         }
103
104         if (!imageDirty[driveNum])
105         {
106                 WriteLog("FLOPPY: No need to save unchanged image...\n");
107                 return false;
108         }
109
110         if (imageName[driveNum][0] == 0)
111         {
112                 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
113                 return false;
114         }
115
116         if (diskType[driveNum] == DT_NYBBLE)
117                 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
118         else
119                 DenybblizeImage(driveNum);
120
121         FILE * fp = fopen(imageName[driveNum], "wb");
122
123         if (fp == NULL)
124         {
125                 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
126                 return false;
127         }
128
129         fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
130         fclose(fp);
131
132         return true;
133 }
134
135 bool FloppyDrive::SaveImageAs(const char * filename, uint8 driveNum/*= 0*/)
136 {
137 //WARNING: Buffer overflow possibility
138 #warning "Buffer overflow possible--!!! FIX !!!"
139         strcpy(imageName[driveNum], filename);
140         return SaveImage(driveNum);
141 }
142
143 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
144 {
145         if (disk[driveNum] != NULL)
146                 delete disk[driveNum];
147
148         disk[driveNum] = new uint8[143360];
149         diskSize[driveNum] = 143360;
150         memset(disk[driveNum], 0x00, 143360);
151         memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
152         diskType[driveNum] = DT_DOS33;
153         strcpy(imageName[driveNum], "newblank.dsk");
154 SpawnMessage("New blank image inserted in drive %u...", driveNum);
155 }
156
157 void FloppyDrive::SwapImages(void)
158 {
159         uint8 nybblizedImageTmp[232960];
160         char imageNameTmp[MAX_PATH];
161
162         memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
163         memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
164         memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
165
166         memcpy(imageNameTmp, imageName[0], MAX_PATH);
167         memcpy(imageName[0], imageName[1], MAX_PATH);
168         memcpy(imageName[1], imageNameTmp, MAX_PATH);
169
170         uint8 * diskTmp = disk[0];
171         disk[0] = disk[1];
172         disk[1] = diskTmp;
173
174         uint32 diskSizeTmp = diskSize[0];
175         diskSize[0] = diskSize[1];
176         diskSize[1] = diskSizeTmp;
177
178         uint8 diskTypeTmp = diskType[0];
179         diskType[0] = diskType[1];
180         diskType[1] = diskTypeTmp;
181
182         uint8 imageDirtyTmp = imageDirty[0];
183         imageDirty[0] = imageDirty[1];
184         imageDirty[1] = imageDirtyTmp;
185 SpawnMessage("Drive 0: %s...", imageName[0]);
186 }
187
188 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
189 {
190         diskType[driveNum] = DT_UNKNOWN;
191
192         if (diskSize[driveNum] == 232960)
193         {
194                 diskType[driveNum] = DT_NYBBLE;
195                 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
196         }
197         else if (diskSize[driveNum] == 143360)
198         {
199                 const char * ext = strrchr(filename, '.');
200
201                 if (ext == NULL)
202                         return;
203 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
204
205 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
206 //[DONE, see below why we don't need it]
207                 if (strcasecmp(ext, ".po") == 0)
208                         diskType[driveNum] = DT_PRODOS;
209                 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
210                 {
211                         diskType[driveNum] = DT_DOS33;
212 //WriteLog("Detected DOS 3.3 disk!\n");
213 /*
214 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
215 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
216 WRT to the disk image itself.
217                         // This could really be a ProDOS order disk with a .dsk extension, so let's see...
218                         char fingerprint[3][4] = {
219                                 { 0x04, 0x00, 0x00, 0x00 },             // @ $500
220                                 { 0x03, 0x00, 0x05, 0x00 },             // @ $700
221                                 { 0x02, 0x00, 0x04, 0x00 } };   // @ $900
222
223                         if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
224                                 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
225                                 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
226                                 diskType[driveNum] = DT_PRODOS;
227 //*/
228                 }
229
230                 NybblizeImage(driveNum);
231         }
232 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
233         "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
234         "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
235 }
236
237 void FloppyDrive::NybblizeImage(uint8 driveNum)
238 {
239         // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
240         // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
241 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
242 // This is now correct, BTW
243         // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
244         // (not incl. 64 byte track marker)
245
246         uint8 footer[48] = {
247                 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
248                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
249                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
250                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
251                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
252                 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
253
254         uint8 diskbyte[0x40] = {
255                 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
256                 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
257                 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
258                 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
259                 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
260                 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
261                 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
262                 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
263
264         uint8 * img = nybblizedImage[driveNum];
265         memset(img, 0xFF, 232960);                                      // Doesn't matter if 00s or FFs...
266
267         for(uint8 trk=0; trk<35; trk++)
268         {
269                 memset(img, 0xFF, 64);                                  // Write gap 1, 64 bytes (self-sync)
270                 img += 64;
271
272                 for(uint8 sector=0; sector<16; sector++)
273                 {
274                         memcpy(img, header, 21);                        // Set up the sector header
275
276                         img[5] = ((trk >> 1) & 0x55) | 0xAA;
277                         img[6] =  (trk       & 0x55) | 0xAA;
278                         img[7] = ((sector >> 1) & 0x55) | 0xAA;
279                         img[8] =  (sector       & 0x55) | 0xAA;
280                         img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
281                         img[10] = ((trk ^ sector ^ 0xFE)       & 0x55) | 0xAA;
282
283                         img += 21;
284                         uint8 * bytes = disk[driveNum];
285
286                         if (diskType[driveNum] == DT_DOS33)
287                                 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
288                         else if (diskType[driveNum] == DT_PRODOS)
289                                 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
290                         else
291                                 bytes += (sector * 256) + (trk * 256 * 16);
292
293                         // Convert the 256 8-bit bytes into 342 6-bit bytes.
294
295                         for(uint16 i=0; i<0x56; i++)
296                         {
297                                 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
298                                         | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
299                                         | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
300                                         | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
301                                         | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
302                                         | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
303                         }
304
305                         img[0x54] &= 0x3F;
306                         img[0x55] &= 0x3F;
307                         memcpy(img + 0x56, bytes, 256);
308
309                         // XOR the data block with itself, offset by one byte,
310                         // creating a 343rd byte which is used as a cheksum.
311
312                         img[342] = 0x00;
313
314                         for(uint16 i=342; i>0; i--)
315                                 img[i] = img[i] ^ img[i - 1];
316
317                         // Using a lookup table, convert the 6-bit bytes into disk bytes.
318
319                         for(uint16 i=0; i<343; i++)
320                                 img[i] = diskbyte[img[i] >> 2];
321
322                         img += 343;
323
324                         // Done with the nybblization, now for the epilogue...
325
326                         memcpy(img, footer, 48);
327                         img += 48;
328                 }
329         }
330 }
331
332 void FloppyDrive::DenybblizeImage(uint8 driveNum)
333 {
334         uint8 decodeNybble[0x80] = {
335                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
336                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
337                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
338                 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
339                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
340                 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
341                 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
342                 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
343                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
344                 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
345                 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
346                 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
347                 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
348                 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
349                 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
350                 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
351
352         // Sanity checks...
353         if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
354         {
355                 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
356                         driveNum, disk[driveNum], diskSize[driveNum]);
357                 return;
358         }
359
360         uint8 * srcImg = nybblizedImage[driveNum];
361         uint8 * dstImg = disk[driveNum];
362         uint8 buffer[345];                                                      // 2 extra bytes for the unpack routine below...
363
364         for(uint8 trk=0; trk<35; trk++)
365         {
366                 uint8 * trackBase = srcImg + (trk * 6656);
367
368                 for(uint8 sector=0; sector<16; sector++)
369                 {
370                         uint16 sectorStart = (uint16)-1;
371
372                         for(uint16 i=0; i<6656; i++)
373                         {
374                                 if (trackBase[i] == header[0]
375                                         && trackBase[(i + 1) % 6656] == header[1]
376                                         && trackBase[(i + 2) % 6656] == header[2]
377                                         && trackBase[(i + 3) % 6656] == header[3]
378                                         && trackBase[(i + 4) % 6656] == header[4])
379                                 {
380 //Could also check the track # at +5,6...
381                                         uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
382                                                 | (trackBase[(i + 8) % 6656] & 0x55);
383
384                                         if (foundSector == sector)
385                                         {
386                                                 sectorStart = (i + 21) % 6656;
387                                                 break;
388                                         }
389                                 }
390                         }
391
392                         // Sanity check...
393                         if (sectorStart == (uint16)-1)
394                         {
395                                 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
396                                         sector, trk);
397                                 return;
398                         }
399
400                         // Using a lookup table, convert the disk bytes into 6-bit bytes.
401
402                         for(uint16 i=0; i<343; i++)
403                                 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
404
405                         // XOR the data block with itself, offset by one byte.
406
407                         for(uint16 i=1; i<342; i++)
408                                 buffer[i] = buffer[i] ^ buffer[i - 1];
409
410                         // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
411
412                         for(uint16 i=0; i<0x56; i++)
413                         {
414                                 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
415                                 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
416                                 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
417                         }
418
419                         uint8 * bytes = dstImg;
420
421                         if (diskType[driveNum] == DT_DOS33)
422                                 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
423                         else if (diskType[driveNum] == DT_PRODOS)
424                                 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
425                         else
426                                 bytes += (sector * 256) + (trk * 256 * 16);//*/
427
428                         memcpy(bytes, buffer + 0x56, 256);
429                 }
430         }
431 }
432
433 // Memory mapped I/O functions
434
435 /*
436 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
437 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
438 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
439 are in a different sequence. The NIB format is a nybblized format: a more direct representation
440 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
441 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
442 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
443 other unusual encodings.
444 */
445
446 void FloppyDrive::ControlStepper(uint8 addr)
447 {
448         // $C0E0 - 7
449 /*
450 What I can gather here:
451 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
452 bit 0 is the "do something" bit.
453 */
454         if (addr & 0x01)
455         {
456                 uint8 newPhase = (addr >> 1) & 0x03;
457 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
458
459                 if (((phase + 1) & 0x03) == newPhase)
460                         phase += (phase < 79 ? 1 : 0);
461
462                 if (((phase - 1) & 0x03) == newPhase)
463                         phase -= (phase > 0 ? 1 : 0);
464
465                 if (!(phase & 0x01))
466                 {
467                         track = ((phase >> 1) < 35 ? phase >> 1 : 34);
468                         currentPos = 0;
469                 }
470 //WriteLog("                        track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
471 SpawnMessage("Stepping to track %u...", track);
472         }
473
474 //      return something if read mode...
475 }
476
477 void FloppyDrive::ControlMotor(uint8 addr)
478 {
479         // $C0E8 - 9
480         motorOn = addr;
481 }
482
483 void FloppyDrive::DriveEnable(uint8 addr)
484 {
485         // $C0EA - B
486         activeDrive = addr;
487 }
488
489 uint8 FloppyDrive::ReadWrite(void)
490 {
491 SpawnMessage("%sing %s track %u, sector %u...", (ioMode == IO_MODE_READ ? "Read" : "Write"),
492         (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
493         // $C0EC
494 /*
495 I think what happens here is that once a track is read its nybblized form
496 is fed through here, one byte at a time--which means for DO disks, we have
497 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
498 Which we now do. :-)
499 */
500         if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
501         {
502                 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
503                 imageDirty[activeDrive] = true;
504         }
505
506         uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
507         currentPos = (currentPos + 1) % 6656;
508
509         return diskByte;
510 }
511
512 uint8 FloppyDrive::GetLatchValue(void)
513 {
514         // $C0ED
515         return latchValue;
516 }
517
518 void FloppyDrive::SetLatchValue(uint8 value)
519 {
520         // $C0ED
521         latchValue = value;
522 }
523
524 void FloppyDrive::SetReadMode(void)
525 {
526         // $C0EE
527         ioMode = IO_MODE_READ;
528 }
529
530 void FloppyDrive::SetWriteMode(void)
531 {
532         // $C0EF
533         ioMode = IO_MODE_WRITE;
534 }