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