2 // Apple 2 floppy disk support
5 // (c) 2005 Underground Software
7 // JLH = James L. Hammons <jlhamm@acm.org>
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
22 #include "applevideo.h" // For message spawning... Though there's probably a better approach than this!
24 //using namespace std;
28 enum { IO_MODE_READ, IO_MODE_WRITE };
30 // FloppyDrive class variable initialization
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 char FloppyDrive::nameBuf[MAX_PATH];
42 // FloppyDrive class implementation...
44 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
46 disk[0] = disk[1] = NULL;
47 diskSize[0] = diskSize[1] = 0;
48 diskType[0] = diskType[1] = DT_UNKNOWN;
49 imageDirty[0] = imageDirty[1] = false;
50 writeProtected[0] = writeProtected[1] = false;
51 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
54 FloppyDrive::~FloppyDrive()
63 bool FloppyDrive::LoadImage(const char * filename, uint8 driveNum/*= 0*/)
65 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
69 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
73 imageName[driveNum][0] = 0; // Zero out filename, in case it doesn't load
75 FILE * fp = fopen(filename, "rb");
79 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
84 delete[] disk[driveNum];
86 fseek(fp, 0, SEEK_END);
87 diskSize[driveNum] = ftell(fp);
88 fseek(fp, 0, SEEK_SET);
89 disk[driveNum] = new uint8[diskSize[driveNum]];
90 fread(disk[driveNum], 1, diskSize[driveNum], fp);
93 //printf("Read disk image: %u bytes.\n", diskSize);
94 DetectImageType(filename, driveNum);
95 strcpy(imageName[driveNum], filename);
98 WriteLog("FLOPPY: Opening image for drive #%u.\n", driveNum);
99 FILE * fp2 = fopen("bt-nybblized.nyb", "wb");
102 WriteLog("FLOPPY: Failed to open image file 'bt-nybblized.nyb' for writing...\n");
105 fwrite(nybblizedImage[driveNum], 1, 232960, fp2);
109 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
114 bool FloppyDrive::SaveImage(uint8 driveNum/*= 0*/)
118 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
122 if (!imageDirty[driveNum])
124 WriteLog("FLOPPY: No need to save unchanged image...\n");
128 if (imageName[driveNum][0] == 0)
130 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
134 if (diskType[driveNum] == DT_NYBBLE)
135 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
137 DenybblizeImage(driveNum);
139 FILE * fp = fopen(imageName[driveNum], "wb");
143 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
147 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
150 WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]);
155 bool FloppyDrive::SaveImageAs(const char * filename, uint8 driveNum/*= 0*/)
157 //WARNING: Buffer overflow possibility
158 #warning "Buffer overflow possible--!!! FIX !!!"
159 strcpy(imageName[driveNum], filename);
160 return SaveImage(driveNum);
163 void FloppyDrive::CreateBlankImage(uint8 driveNum/*= 0*/)
165 if (disk[driveNum] != NULL)
166 delete disk[driveNum];
168 disk[driveNum] = new uint8[143360];
169 diskSize[driveNum] = 143360;
170 memset(disk[driveNum], 0x00, 143360);
171 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
172 diskType[driveNum] = DT_DOS33;
173 strcpy(imageName[driveNum], "newblank.dsk");
174 writeProtected[driveNum] = false;
175 SpawnMessage("New blank image inserted in drive %u...", driveNum);
178 void FloppyDrive::SwapImages(void)
180 uint8 nybblizedImageTmp[232960];
181 char imageNameTmp[MAX_PATH];
183 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
184 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
185 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
187 memcpy(imageNameTmp, imageName[0], MAX_PATH);
188 memcpy(imageName[0], imageName[1], MAX_PATH);
189 memcpy(imageName[1], imageNameTmp, MAX_PATH);
191 uint8 * diskTmp = disk[0];
195 uint32 diskSizeTmp = diskSize[0];
196 diskSize[0] = diskSize[1];
197 diskSize[1] = diskSizeTmp;
199 uint8 diskTypeTmp = diskType[0];
200 diskType[0] = diskType[1];
201 diskType[1] = diskTypeTmp;
203 uint8 imageDirtyTmp = imageDirty[0];
204 imageDirty[0] = imageDirty[1];
205 imageDirty[1] = imageDirtyTmp;
207 uint8 writeProtectedTmp = writeProtected[0];
208 writeProtected[0] = writeProtected[1];
209 writeProtected[1] = writeProtectedTmp;
210 SpawnMessage("Drive 0: %s...", imageName[0]);
213 void FloppyDrive::DetectImageType(const char * filename, uint8 driveNum)
215 diskType[driveNum] = DT_UNKNOWN;
217 if (diskSize[driveNum] == 232960)
219 diskType[driveNum] = DT_NYBBLE;
220 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
222 else if (diskSize[driveNum] == 143360)
224 const char * ext = strrchr(filename, '.');
228 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
230 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
231 //[DONE, see below why we don't need it]
232 if (strcasecmp(ext, ".po") == 0)
233 diskType[driveNum] = DT_PRODOS;
234 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
236 diskType[driveNum] = DT_DOS33;
237 //WriteLog("Detected DOS 3.3 disk!\n");
239 This doesn't seem to be accurate... Maybe it's just a ProDOS disk in a DOS33 order...
240 That would seem to be the case--just because it's a ProDOS disk doesn't mean anything
241 WRT to the disk image itself.
242 // This could really be a ProDOS order disk with a .dsk extension, so let's see...
243 char fingerprint[3][4] = {
244 { 0x04, 0x00, 0x00, 0x00 }, // @ $500
245 { 0x03, 0x00, 0x05, 0x00 }, // @ $700
246 { 0x02, 0x00, 0x04, 0x00 } }; // @ $900
248 if ((strcmp((char *)(disk[driveNum] + 0x500), fingerprint[0]) == 0)
249 && (strcmp((char *)(disk[driveNum] + 0x700), fingerprint[1]) == 0)
250 && (strcmp((char *)(disk[driveNum] + 0x900), fingerprint[2]) == 0))
251 diskType[driveNum] = DT_PRODOS;
255 // Actually, it just might matter WRT to nybblyzing/denybblyzing
256 // Here, we check for BT3
258 //diskType[driveNum] = DT_PRODOS;
260 NybblizeImage(driveNum);
263 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
265 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
266 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
267 "DOS 3.3 image" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown"))));
270 void FloppyDrive::NybblizeImage(uint8 driveNum)
272 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
273 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
274 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
275 // This is now correct, BTW
276 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
277 // (not incl. 64 byte track marker)
280 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
281 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
282 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
283 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
284 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
285 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
287 uint8 diskbyte[0x40] = {
288 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
289 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
290 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
291 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
292 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
293 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
294 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
295 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
297 uint8 * img = nybblizedImage[driveNum];
298 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
300 for(uint8 trk=0; trk<35; trk++)
302 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
305 for(uint8 sector=0; sector<16; sector++)
307 memcpy(img, header, 21); // Set up the sector header
309 img[5] = ((trk >> 1) & 0x55) | 0xAA;
310 img[6] = (trk & 0x55) | 0xAA;
311 img[7] = ((sector >> 1) & 0x55) | 0xAA;
312 img[8] = (sector & 0x55) | 0xAA;
313 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
314 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
317 uint8 * bytes = disk[driveNum];
319 if (diskType[driveNum] == DT_DOS33)
320 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
321 else if (diskType[driveNum] == DT_PRODOS)
322 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
324 bytes += (sector * 256) + (trk * 256 * 16);
326 // Convert the 256 8-bit bytes into 342 6-bit bytes.
328 for(uint16 i=0; i<0x56; i++)
330 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
331 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
332 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
333 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
334 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
335 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
340 memcpy(img + 0x56, bytes, 256);
342 // XOR the data block with itself, offset by one byte,
343 // creating a 343rd byte which is used as a cheksum.
347 for(uint16 i=342; i>0; i--)
348 img[i] = img[i] ^ img[i - 1];
350 // Using a lookup table, convert the 6-bit bytes into disk bytes.
352 for(uint16 i=0; i<343; i++)
353 //#define TEST_NYBBLIZATION
354 #ifdef TEST_NYBBLIZATION
356 WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img[i] >> 2]);
358 img[i] = diskbyte[img[i] >> 2];
359 #ifdef TEST_NYBBLIZATION
360 //WriteLog(" img[i] = %02X\n", img[i]);
365 // Done with the nybblization, now for the epilogue...
367 memcpy(img, footer, 48);
373 void FloppyDrive::DenybblizeImage(uint8 driveNum)
375 uint8 decodeNybble[0x80] = {
376 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
377 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
378 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
379 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
380 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
381 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
382 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
383 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
384 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
385 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
386 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
387 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
388 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
389 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
390 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
391 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
394 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
396 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
397 driveNum, disk[driveNum], diskSize[driveNum]);
401 uint8 * srcImg = nybblizedImage[driveNum];
402 uint8 * dstImg = disk[driveNum];
403 uint8 buffer[345]; // 2 extra bytes for the unpack routine below...
405 for(uint8 trk=0; trk<35; trk++)
407 uint8 * trackBase = srcImg + (trk * 6656);
409 for(uint8 sector=0; sector<16; sector++)
411 uint16 sectorStart = (uint16)-1;
413 for(uint16 i=0; i<6656; i++)
415 if (trackBase[i] == header[0]
416 && trackBase[(i + 1) % 6656] == header[1]
417 && trackBase[(i + 2) % 6656] == header[2]
418 && trackBase[(i + 3) % 6656] == header[3]
419 && trackBase[(i + 4) % 6656] == header[4])
421 //Could also check the track # at +5,6...
422 uint8 foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
423 | (trackBase[(i + 8) % 6656] & 0x55);
425 if (foundSector == sector)
427 sectorStart = (i + 21) % 6656;
434 if (sectorStart == (uint16)-1)
436 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
441 // Using a lookup table, convert the disk bytes into 6-bit bytes.
443 for(uint16 i=0; i<343; i++)
444 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
446 // XOR the data block with itself, offset by one byte.
448 for(uint16 i=1; i<342; i++)
449 buffer[i] = buffer[i] ^ buffer[i - 1];
451 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
453 for(uint16 i=0; i<0x56; i++)
455 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
456 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
457 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
460 uint8 * bytes = dstImg;
462 if (diskType[driveNum] == DT_DOS33)
463 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
464 else if (diskType[driveNum] == DT_PRODOS)
465 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
467 bytes += (sector * 256) + (trk * 256 * 16);//*/
469 memcpy(bytes, buffer + 0x56, 256);
474 const char * FloppyDrive::GetImageName(uint8 driveNum/*= 0*/)
476 // Set up a zero-length string for return value
481 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
485 // Now we attempt to strip out extraneous paths/extensions to get just the filename
486 const char * startOfFile = strrchr(imageName[driveNum], '/');
487 const char * startOfExt = strrchr(imageName[driveNum], '.');
489 // If there isn't a path, assume we're starting at the beginning
490 if (startOfFile == NULL)
491 startOfFile = &imageName[driveNum][0];
495 // If there isn't an extension, assume it's at the terminating NULL
496 if (startOfExt == NULL)
497 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
499 // Now copy the filename (may copy nothing!)
502 for(const char * i=startOfFile; i<startOfExt; i++)
510 void FloppyDrive::EjectImage(uint8 driveNum/*= 0*/)
512 // Probably want to save a dirty image... ;-)
515 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
518 delete[] disk[driveNum];
520 disk[driveNum] = NULL;
521 diskSize[driveNum] = 0;
522 diskType[driveNum] = DT_UNKNOWN;
523 imageDirty[driveNum] = false;
524 writeProtected[driveNum] = false;
525 imageName[driveNum][0] = 0; // Zero out filenames
526 memset(nybblizedImage[driveNum], 0xFF, 232960); // Doesn't matter if 00s or FFs...
529 bool FloppyDrive::DriveIsEmpty(uint8 driveNum/*= 0*/)
533 WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum);
537 // This is kinda gay, but it works
538 return (imageName[driveNum][0] == 0 ? true : false);
541 bool FloppyDrive::DiskIsWriteProtected(uint8 driveNum/*= 0*/)
545 WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum);
549 return writeProtected[driveNum];
552 void FloppyDrive::SetWriteProtect(bool state, uint8 driveNum/*= 0*/)
556 WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum);
560 writeProtected[driveNum] = state;
564 // Memory mapped I/O functions
567 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
568 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
569 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
570 are in a different sequence. The NIB format is a nybblized format: a more direct representation
571 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
572 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
573 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
574 other unusual encodings.
577 void FloppyDrive::ControlStepper(uint8 addr)
581 What I can gather here:
582 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
583 bit 0 is the "do something" bit.
587 uint8 newPhase = (addr >> 1) & 0x03;
588 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
590 if (((phase + 1) & 0x03) == newPhase)
591 phase += (phase < 79 ? 1 : 0);
593 if (((phase - 1) & 0x03) == newPhase)
594 phase -= (phase > 0 ? 1 : 0);
598 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
601 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
602 SpawnMessage("Stepping to track %u...", track);
605 // return something if read mode...
608 void FloppyDrive::ControlMotor(uint8 addr)
614 void FloppyDrive::DriveEnable(uint8 addr)
620 uint8 FloppyDrive::ReadWrite(void)
622 SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
623 (ioMode == IO_MODE_READ ? "Read" : "Write"),
624 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
627 I think what happens here is that once a track is read its nybblized form
628 is fed through here, one byte at a time--which means for DO disks, we have
629 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
632 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
634 // Does it behave like this?
635 #warning "Write protection kludged in--investigate real behavior!"
636 if (!writeProtected[activeDrive])
638 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
639 imageDirty[activeDrive] = true;
643 uint8 diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
644 currentPos = (currentPos + 1) % 6656;
646 //WriteLog("FL: diskByte=%02X, currentPos=%u\n", diskByte, currentPos);
650 uint8 FloppyDrive::GetLatchValue(void)
656 void FloppyDrive::SetLatchValue(uint8 value)
662 void FloppyDrive::SetReadMode(void)
665 ioMode = IO_MODE_READ;
668 void FloppyDrive::SetWriteMode(void)
671 ioMode = IO_MODE_WRITE;
675 PRODOS 8 MLI ERROR CODES
678 $01: Bad system call number
679 $04: Bad system call parameter count
680 $25: Interrupt table full
682 $28: No device connected
683 $2B: Disk write protected
685 $40: Invalid pathname
686 $42: Maximum number of files open
687 $43: Invalid reference number
688 $44: Directory not found
689 $45: Volume not found
691 $47: Duplicate filename
693 $49: Volume directory full
694 $4A: Incompatible file format, also a ProDOS directory
695 $4B: Unsupported storage_type
696 $4C: End of file encountered
697 $4D: Position out of range
698 $4E: File access error, also file locked
700 $51: Directory structure damaged
701 $52: Not a ProDOS volume
702 $53: Invalid system call parameter
703 $55: Volume Control Block table full
704 $56: Bad buffer address
705 $57: Duplicate volume
706 $5A: File structure damaged