2 // Apple 2 floppy disk support
5 // (c) 2005 Underground Software
7 // JLH = James 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_t FloppyDrive::header[21] = {
33 0xD5, 0xAA, 0x96, 0xFF, 0xFE, 0x00, 0x00, 0x00,
34 0x00, 0x00, 0x00, 0xDE, 0xAA, 0xFF, 0xFF, 0xFF,
35 0xFF, 0xFF, 0xD5, 0xAA, 0xAD };
36 uint8_t FloppyDrive::doSector[16] = {
37 0x0, 0x7, 0xE, 0x6, 0xD, 0x5, 0xC, 0x4, 0xB, 0x3, 0xA, 0x2, 0x9, 0x1, 0x8, 0xF };
38 uint8_t FloppyDrive::poSector[16] = {
39 0x0, 0x8, 0x1, 0x9, 0x2, 0xA, 0x3, 0xB, 0x4, 0xC, 0x5, 0xD, 0x6, 0xE, 0x7, 0xF };
40 char FloppyDrive::nameBuf[MAX_PATH];
43 // FloppyDrive class implementation...
45 FloppyDrive::FloppyDrive(): motorOn(0), activeDrive(0), ioMode(IO_MODE_READ), phase(0), track(0)
47 disk[0] = disk[1] = NULL;
48 diskSize[0] = diskSize[1] = 0;
49 diskType[0] = diskType[1] = DT_UNKNOWN;
50 imageDirty[0] = imageDirty[1] = false;
51 writeProtected[0] = writeProtected[1] = false;
52 imageName[0][0] = imageName[1][0] = 0; // Zero out filenames
56 FloppyDrive::~FloppyDrive()
66 bool FloppyDrive::LoadImage(const char * filename, uint8_t driveNum/*= 0*/)
68 WriteLog("FLOPPY: Attempting to load image '%s' in drive #%u.\n", filename, driveNum);
72 WriteLog("FLOPPY: Attempted to load image to drive #%u!\n", driveNum);
76 imageName[driveNum][0] = 0; // Zero out filename, in case it doesn't load
78 FILE * fp = fopen(filename, "rb");
82 WriteLog("FLOPPY: Failed to open image file '%s' for reading...\n", filename);
87 delete[] disk[driveNum];
89 fseek(fp, 0, SEEK_END);
90 diskSize[driveNum] = ftell(fp);
91 fseek(fp, 0, SEEK_SET);
92 disk[driveNum] = new uint8_t[diskSize[driveNum]];
93 fread(disk[driveNum], 1, diskSize[driveNum], fp);
96 //printf("Read disk image: %u bytes.\n", diskSize);
97 DetectImageType(filename, driveNum);
98 strcpy(imageName[driveNum], filename);
101 WriteLog("FLOPPY: Opening image for drive #%u.\n", driveNum);
102 FILE * fp2 = fopen("bt-nybblized.nyb", "wb");
105 WriteLog("FLOPPY: Failed to open image file 'bt-nybblized.nyb' for writing...\n");
108 fwrite(nybblizedImage[driveNum], 1, 232960, fp2);
112 //writeProtected[driveNum] = true;
113 WriteLog("FLOPPY: Loaded image '%s' for drive #%u.\n", filename, driveNum);
119 bool FloppyDrive::SaveImage(uint8_t driveNum/*= 0*/)
123 WriteLog("FLOPPY: Attempted to save image to drive #%u!\n", driveNum);
127 if (!imageDirty[driveNum])
129 WriteLog("FLOPPY: No need to save unchanged image...\n");
133 if (imageName[driveNum][0] == 0)
135 WriteLog("FLOPPY: Attempted to save non-existant image!\n");
139 if (diskType[driveNum] == DT_NYBBLE)
140 memcpy(disk[driveNum], nybblizedImage[driveNum], 232960);
142 DenybblizeImage(driveNum);
144 FILE * fp = fopen(imageName[driveNum], "wb");
148 WriteLog("FLOPPY: Failed to open image file '%s' for writing...\n", imageName[driveNum]);
152 fwrite(disk[driveNum], 1, diskSize[driveNum], fp);
155 WriteLog("FLOPPY: Successfully wrote image file '%s'...\n", imageName[driveNum]);
161 bool FloppyDrive::SaveImageAs(const char * filename, uint8_t driveNum/*= 0*/)
163 //WARNING: Buffer overflow possibility
164 #warning "Buffer overflow possible--!!! FIX !!!"
165 strcpy(imageName[driveNum], filename);
166 return SaveImage(driveNum);
170 void FloppyDrive::CreateBlankImage(uint8_t driveNum/*= 0*/)
172 if (disk[driveNum] != NULL)
173 delete disk[driveNum];
175 disk[driveNum] = new uint8_t[143360];
176 diskSize[driveNum] = 143360;
177 memset(disk[driveNum], 0x00, 143360);
178 memset(nybblizedImage[driveNum], 0x00, 232960); // Set it to 0 instead of $FF for proper formatting...
179 diskType[driveNum] = DT_DOS33;
180 strcpy(imageName[driveNum], "newblank.dsk");
181 writeProtected[driveNum] = false;
182 SpawnMessage("New blank image inserted in drive %u...", driveNum);
186 void FloppyDrive::SwapImages(void)
188 uint8_t nybblizedImageTmp[232960];
189 char imageNameTmp[MAX_PATH];
191 memcpy(nybblizedImageTmp, nybblizedImage[0], 232960);
192 memcpy(nybblizedImage[0], nybblizedImage[1], 232960);
193 memcpy(nybblizedImage[1], nybblizedImageTmp, 232960);
195 memcpy(imageNameTmp, imageName[0], MAX_PATH);
196 memcpy(imageName[0], imageName[1], MAX_PATH);
197 memcpy(imageName[1], imageNameTmp, MAX_PATH);
199 uint8_t * diskTmp = disk[0];
203 uint32_t diskSizeTmp = diskSize[0];
204 diskSize[0] = diskSize[1];
205 diskSize[1] = diskSizeTmp;
207 uint8_t diskTypeTmp = diskType[0];
208 diskType[0] = diskType[1];
209 diskType[1] = diskTypeTmp;
211 uint8_t imageDirtyTmp = imageDirty[0];
212 imageDirty[0] = imageDirty[1];
213 imageDirty[1] = imageDirtyTmp;
215 uint8_t writeProtectedTmp = writeProtected[0];
216 writeProtected[0] = writeProtected[1];
217 writeProtected[1] = writeProtectedTmp;
218 SpawnMessage("Drive 0: %s...", imageName[0]);
222 void FloppyDrive::DetectImageType(const char * filename, uint8_t driveNum)
224 diskType[driveNum] = DT_UNKNOWN;
226 if (diskSize[driveNum] == 232960)
228 diskType[driveNum] = DT_NYBBLE;
229 memcpy(nybblizedImage[driveNum], disk[driveNum], 232960);
231 else if (diskSize[driveNum] == 143360)
233 const char * ext = strrchr(filename, '.');
237 WriteLog("FLOPPY: Found extension [%s]...\n", ext);
239 //Apparently .dsk can house either DOS order OR PRODOS order... !!! FIX !!!
240 if (strcasecmp(ext, ".po") == 0)
241 diskType[driveNum] = DT_PRODOS;
242 else if ((strcasecmp(ext, ".do") == 0) || (strcasecmp(ext, ".dsk") == 0))
244 // We assume this, but check for a PRODOS fingerprint. Trust, but
246 diskType[driveNum] = DT_DOS33;
248 uint8_t fingerprint[4][4] = {
249 { 0x00, 0x00, 0x03, 0x00 }, // @ $400
250 { 0x02, 0x00, 0x04, 0x00 }, // @ $600
251 { 0x03, 0x00, 0x05, 0x00 }, // @ $800
252 { 0x04, 0x00, 0x00, 0x00 } // @ $A00
255 bool foundProdos = true;
257 for(uint32_t i=0; i<4; i++)
259 for(uint32_t j=0; j<4; j++)
261 if (disk[driveNum][0x400 + (i * 0x200) + j] != fingerprint[i][j])
270 diskType[driveNum] = DT_PRODOS;
273 // Actually, it just might matter WRT to nybblyzing/denybblyzing
274 // (and, it does... :-P)
275 NybblizeImage(driveNum);
277 else if (diskSize[driveNum] == 143488)
279 diskType[driveNum] = DT_DOS33_HDR;
280 NybblizeImage(driveNum);
283 #warning "Should we attempt to nybblize unknown images here? Definitely SHOULD issue a warning!"
285 WriteLog("FLOPPY: Detected image type %s...\n", (diskType[driveNum] == DT_NYBBLE ?
286 "Nybble image" : (diskType[driveNum] == DT_DOS33 ?
287 "DOS 3.3 image" : (diskType[driveNum] == DT_DOS33_HDR ?
288 "DOS 3.3 image (headered)" : (diskType[driveNum] == DT_PRODOS ? "ProDOS image" : "unknown")))));
292 void FloppyDrive::NybblizeImage(uint8_t driveNum)
294 // Format of a sector is header (23) + nybbles (343) + footer (30) = 396
295 // (short by 20 bytes of 416 [413 if 48 byte header is one time only])
296 // Hmph. Who'da thunk that AppleWin's nybblization routines would be wrong?
297 // This is now correct, BTW
298 // hdr (21) + nybbles (343) + footer (48) = 412 bytes per sector
299 // (not incl. 64 byte track marker)
301 uint8_t footer[48] = {
302 0xDE, 0xAA, 0xEB, 0xFF, 0xEB, 0xFF, 0xFF, 0xFF,
303 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
304 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 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 };
309 uint8_t diskbyte[0x40] = {
310 0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
311 0xA7, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB2, 0xB3,
312 0xB4, 0xB5, 0xB6, 0xB7, 0xB9, 0xBA, 0xBB, 0xBC,
313 0xBD, 0xBE, 0xBF, 0xCB, 0xCD, 0xCE, 0xCF, 0xD3,
314 0xD6, 0xD7, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE,
315 0xDF, 0xE5, 0xE6, 0xE7, 0xE9, 0xEA, 0xEB, 0xEC,
316 0xED, 0xEE, 0xEF, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6,
317 0xF7, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF };
319 uint8_t * img = nybblizedImage[driveNum];
320 memset(img, 0xFF, 232960); // Doesn't matter if 00s or FFs...
322 for(uint8_t trk=0; trk<35; trk++)
324 memset(img, 0xFF, 64); // Write gap 1, 64 bytes (self-sync)
327 for(uint8_t sector=0; sector<16; sector++)
329 memcpy(img, header, 21); // Set up the sector header
331 img[5] = ((trk >> 1) & 0x55) | 0xAA;
332 img[6] = (trk & 0x55) | 0xAA;
333 img[7] = ((sector >> 1) & 0x55) | 0xAA;
334 img[8] = (sector & 0x55) | 0xAA;
335 img[9] = (((trk ^ sector ^ 0xFE) >> 1) & 0x55) | 0xAA;
336 img[10] = ((trk ^ sector ^ 0xFE) & 0x55) | 0xAA;
339 uint8_t * bytes = disk[driveNum];
341 if (diskType[driveNum] == DT_DOS33)
342 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
343 else if (diskType[driveNum] == DT_DOS33_HDR)
344 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
345 else if (diskType[driveNum] == DT_PRODOS)
346 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
348 bytes += (sector * 256) + (trk * 256 * 16);
350 // Convert the 256 8-bit bytes into 342 6-bit bytes.
352 for(uint16_t i=0; i<0x56; i++)
354 img[i] = ((bytes[(i + 0xAC) & 0xFF] & 0x01) << 7)
355 | ((bytes[(i + 0xAC) & 0xFF] & 0x02) << 5)
356 | ((bytes[(i + 0x56) & 0xFF] & 0x01) << 5)
357 | ((bytes[(i + 0x56) & 0xFF] & 0x02) << 3)
358 | ((bytes[(i + 0x00) & 0xFF] & 0x01) << 3)
359 | ((bytes[(i + 0x00) & 0xFF] & 0x02) << 1);
364 memcpy(img + 0x56, bytes, 256);
366 // XOR the data block with itself, offset by one byte,
367 // creating a 343rd byte which is used as a cheksum.
371 for(uint16_t i=342; i>0; i--)
372 img[i] = img[i] ^ img[i - 1];
374 // Using a lookup table, convert the 6-bit bytes into disk bytes.
376 for(uint16_t i=0; i<343; i++)
377 //#define TEST_NYBBLIZATION
378 #ifdef TEST_NYBBLIZATION
380 WriteLog("FL: i = %u, img[i] = %02X, diskbyte = %02X\n", i, img[i], diskbyte[img[i] >> 2]);
382 img[i] = diskbyte[img[i] >> 2];
383 #ifdef TEST_NYBBLIZATION
384 //WriteLog(" img[i] = %02X\n", img[i]);
389 // Done with the nybblization, now for the epilogue...
391 memcpy(img, footer, 48);
398 void FloppyDrive::DenybblizeImage(uint8_t driveNum)
400 uint8_t decodeNybble[0x80] = {
401 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
402 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
403 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
404 0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
405 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
406 0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
407 0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
408 0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
409 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
410 0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
411 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
412 0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
413 0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
414 0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
415 0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
416 0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
419 if (disk[driveNum] == NULL || diskSize[driveNum] < 143360)
421 WriteLog("FLOPPY: Source disk image invalid! [drive=%u, disk=%08X, diskSize=%u]\n",
422 driveNum, disk[driveNum], diskSize[driveNum]);
426 uint8_t * srcImg = nybblizedImage[driveNum];
427 uint8_t * dstImg = disk[driveNum];
428 uint8_t buffer[345]; // 2 extra bytes for the unpack routine below...
430 for(uint8_t trk=0; trk<35; trk++)
432 uint8_t * trackBase = srcImg + (trk * 6656);
434 for(uint8_t sector=0; sector<16; sector++)
436 uint16_t sectorStart = (uint16_t)-1;
438 for(uint16_t i=0; i<6656; i++)
440 if (trackBase[i] == header[0]
441 && trackBase[(i + 1) % 6656] == header[1]
442 && trackBase[(i + 2) % 6656] == header[2]
443 && trackBase[(i + 3) % 6656] == header[3]
444 && trackBase[(i + 4) % 6656] == header[4])
446 //Could also check the track # at +5,6...
447 uint8_t foundSector = ((trackBase[(i + 7) % 6656] & 0x55) << 1)
448 | (trackBase[(i + 8) % 6656] & 0x55);
450 if (foundSector == sector)
452 sectorStart = (i + 21) % 6656;
459 if (sectorStart == (uint16_t)-1)
461 WriteLog("FLOPPY: Failed to find sector %u (track %u) in nybble image!\n",
466 // Using a lookup table, convert the disk bytes into 6-bit bytes.
468 for(uint16_t i=0; i<343; i++)
469 buffer[i] = decodeNybble[trackBase[(sectorStart + i) % 6656] & 0x7F];
471 // XOR the data block with itself, offset by one byte.
473 for(uint16_t i=1; i<342; i++)
474 buffer[i] = buffer[i] ^ buffer[i - 1];
476 // Convert the 342 6-bit bytes into 256 8-bit bytes (at buffer + $56).
478 for(uint16_t i=0; i<0x56; i++)
480 buffer[0x056 + i] |= ((buffer[i] >> 3) & 0x01) | ((buffer[i] >> 1) & 0x02);
481 buffer[0x0AC + i] |= ((buffer[i] >> 5) & 0x01) | ((buffer[i] >> 3) & 0x02);
482 buffer[0x102 + i] |= ((buffer[i] >> 7) & 0x01) | ((buffer[i] >> 5) & 0x02);
485 uint8_t * bytes = dstImg;
487 if (diskType[driveNum] == DT_DOS33)
488 bytes += (doSector[sector] * 256) + (trk * 256 * 16);
489 else if (diskType[driveNum] == DT_DOS33_HDR)
490 bytes += (doSector[sector] * 256) + (trk * 256 * 16) + 128;
491 else if (diskType[driveNum] == DT_PRODOS)
492 bytes += (poSector[sector] * 256) + (trk * 256 * 16);
494 bytes += (sector * 256) + (trk * 256 * 16);//*/
496 memcpy(bytes, buffer + 0x56, 256);
502 const char * FloppyDrive::GetImageName(uint8_t driveNum/*= 0*/)
504 // Set up a zero-length string for return value
509 WriteLog("FLOPPY: Attempted to get image name for drive #%u!\n", driveNum);
513 // Now we attempt to strip out extraneous paths/extensions to get just the filename
514 const char * startOfFile = strrchr(imageName[driveNum], '/');
515 const char * startOfExt = strrchr(imageName[driveNum], '.');
517 // If there isn't a path, assume we're starting at the beginning
518 if (startOfFile == NULL)
519 startOfFile = &imageName[driveNum][0];
523 // If there isn't an extension, assume it's at the terminating NULL
524 if (startOfExt == NULL)
525 startOfExt = &imageName[driveNum][0] + strlen(imageName[driveNum]);
527 // Now copy the filename (may copy nothing!)
530 for(const char * i=startOfFile; i<startOfExt; i++)
539 void FloppyDrive::EjectImage(uint8_t driveNum/*= 0*/)
541 // Probably want to save a dirty image... ;-)
544 WriteLog("FLOPPY: Ejected image file '%s' from drive %u...\n", imageName[driveNum], driveNum);
547 delete[] disk[driveNum];
549 disk[driveNum] = NULL;
550 diskSize[driveNum] = 0;
551 diskType[driveNum] = DT_UNKNOWN;
552 imageDirty[driveNum] = false;
553 writeProtected[driveNum] = false;
554 imageName[driveNum][0] = 0; // Zero out filenames
555 memset(nybblizedImage[driveNum], 0xFF, 232960); // Doesn't matter if 00s or FFs...
559 bool FloppyDrive::DriveIsEmpty(uint8_t driveNum/*= 0*/)
563 WriteLog("FLOPPY: Attempted DriveIsEmtpy() for drive #%u!\n", driveNum);
567 // This is kinda gay, but it works
568 return (imageName[driveNum][0] == 0 ? true : false);
572 bool FloppyDrive::DiskIsWriteProtected(uint8_t driveNum/*= 0*/)
576 WriteLog("FLOPPY: Attempted DiskIsWriteProtected() for drive #%u!\n", driveNum);
580 return writeProtected[driveNum];
584 void FloppyDrive::SetWriteProtect(bool state, uint8_t driveNum/*= 0*/)
588 WriteLog("FLOPPY: Attempted set write protect for drive #%u!\n", driveNum);
592 writeProtected[driveNum] = state;
596 // Memory mapped I/O functions
599 The DSK format is a byte-for-byte image of a 16-sector Apple II floppy disk: 35 tracks of 16
600 sectors of 256 bytes each, making 143,360 bytes in total. The PO format is exactly the same
601 size as DSK and is also organized as 35 sequential tracks, but the sectors within each track
602 are in a different sequence. The NIB format is a nybblized format: a more direct representation
603 of the disk's data as encoded by the Apple II floppy drive hardware. NIB contains 35 tracks of
604 6656 bytes each, for a total size of 232,960 bytes. Although this format is much larger, it is
605 also more versatile and can represent the older 13-sector disks, many copy-protected disks, and
606 other unusual encodings.
609 void FloppyDrive::ControlStepper(uint8_t addr)
613 What I can gather here:
614 bits 1-2 are the "phase" of the track (which is 1/4 of a full track (?))
615 bit 0 is the "do something" bit.
619 uint8_t newPhase = (addr >> 1) & 0x03;
620 //WriteLog("*** Stepper change [%u]: track = %u, phase = %u, newPhase = %u\n", addr, track, phase, newPhase);
622 if (((phase + 1) & 0x03) == newPhase)
623 phase += (phase < 79 ? 1 : 0);
625 if (((phase - 1) & 0x03) == newPhase)
626 phase -= (phase > 0 ? 1 : 0);
630 track = ((phase >> 1) < 35 ? phase >> 1 : 34);
633 //WriteLog(" track = %u, phase = %u, newPhase = %u\n", track, phase, newPhase);
634 SpawnMessage("Stepping to track %u...", track);
637 // return something if read mode...
641 void FloppyDrive::ControlMotor(uint8_t addr)
648 void FloppyDrive::DriveEnable(uint8_t addr)
655 uint8_t FloppyDrive::ReadWrite(void)
657 SpawnMessage("%u:%sing %s track %u, sector %u...", activeDrive,
658 (ioMode == IO_MODE_READ ? "Read" : "Write"),
659 (ioMode == IO_MODE_READ ? "from" : "to"), track, currentPos / 396);
662 I think what happens here is that once a track is read its nybblized form
663 is fed through here, one byte at a time--which means for DO disks, we have
664 to convert the actual 256 byte sector to a 416 byte nybblized data "sector".
667 if (ioMode == IO_MODE_WRITE && (latchValue & 0x80))
669 // Does it behave like this?
670 #warning "Write protection kludged in--investigate real behavior!"
671 if (!writeProtected[activeDrive])
673 nybblizedImage[activeDrive][(track * 6656) + currentPos] = latchValue;
674 imageDirty[activeDrive] = true;
677 //doesn't seem to do anything
678 return 0;//is this more like it?
681 uint8_t diskByte = nybblizedImage[activeDrive][(track * 6656) + currentPos];
682 currentPos = (currentPos + 1) % 6656;
684 //WriteLog("FL: diskByte=%02X, currentPos=%u\n", diskByte, currentPos);
689 uint8_t FloppyDrive::GetLatchValue(void)
696 void FloppyDrive::SetLatchValue(uint8_t value)
703 void FloppyDrive::SetReadMode(void)
706 ioMode = IO_MODE_READ;
710 void FloppyDrive::SetWriteMode(void)
713 ioMode = IO_MODE_WRITE;
717 PRODOS 8 MLI ERROR CODES
720 $01: Bad system call number
721 $04: Bad system call parameter count
722 $25: Interrupt table full
724 $28: No device connected
725 $2B: Disk write protected
727 $40: Invalid pathname
728 $42: Maximum number of files open
729 $43: Invalid reference number
730 $44: Directory not found
731 $45: Volume not found
733 $47: Duplicate filename
735 $49: Volume directory full
736 $4A: Incompatible file format, also a ProDOS directory
737 $4B: Unsupported storage_type
738 $4C: End of file encountered
739 $4D: Position out of range
740 $4E: File access error, also file locked
742 $51: Directory structure damaged
743 $52: Not a ProDOS volume
744 $53: Invalid system call parameter
745 $55: Volume Control Block table full
746 $56: Bad buffer address
747 $57: Duplicate volume
748 $5A: File structure damaged