/* * CiderPress * Copyright (C) 2009 by CiderPress authors. All Rights Reserved. * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Implementation of the DiskImg class. */ #include "StdAfx.h" #include "DiskImgPriv.h" #include "TwoImg.h" /* * =========================================================================== * DiskImg * =========================================================================== */ /* * Standard NibbleDescr profiles. * * These will be tried in the order in which they appear here. * * IMPORTANT: if you add or remove an entry, update the StdNibbleDescr enum * in DiskImg.h. * * Formats that allow the data checksum to be ignored should NOT be written. * It's possible that the DOS on the disk is ignoring the checksums, but * it's more likely that they're using a non-standard seed, and the newly- * written sectors will have the wrong checksum value. * * Non-standard headers are usually okay, because we don't rewrite the * headers, just the sector contents. */ /*static*/ const DiskImg::NibbleDescr DiskImg::kStdNibbleDescrs[] = { { "DOS 3.3 Standard", 16, { 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed true, // verify checksum true, // verify track 2, // epilog verify count { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed true, // verify checksum 2, // epilog verify count kNibbleEnc62, kNibbleSpecialNone, }, { "DOS 3.3 Patched", 16, { 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed false, // verify checksum false, // verify track 0, // epilog verify count { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed true, // verify checksum 0, // epilog verify count kNibbleEnc62, kNibbleSpecialNone, }, { "DOS 3.3 Ignore Checksum", 16, { 0xd5, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed false, // verify checksum false, // verify track 0, // epilog verify count { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, // checksum seed false, // verify checksum 0, // epilog verify count kNibbleEnc62, kNibbleSpecialNone, }, { "DOS 3.2 Standard", 13, { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, 0x00, true, true, 2, { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, true, 2, kNibbleEnc53, kNibbleSpecialNone, }, { "DOS 3.2 Patched", 13, { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, 0x00, false, false, 0, { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, true, 0, kNibbleEnc53, kNibbleSpecialNone, }, { "Muse DOS 3.2", // standard DOS 3.2 with doubled sectors 13, { 0xd5, 0xaa, 0xb5 }, { 0xde, 0xaa, 0xeb }, 0x00, true, true, 2, { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, true, 2, kNibbleEnc53, kNibbleSpecialMuse, }, { "RDOS 3.3", // SSI 16-sector RDOS, with altered headers 16, { 0xd4, 0xaa, 0x96 }, { 0xde, 0xaa, 0xeb }, 0x00, true, true, 0, // epilog verify count { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, true, 2, kNibbleEnc62, kNibbleSpecialSkipFirstAddrByte, /* odd tracks use d4aa96, even tracks use d5aa96 */ }, { "RDOS 3.2", // SSI 13-sector RDOS, with altered headers 13, { 0xd4, 0xaa, 0xb7 }, { 0xde, 0xaa, 0xeb }, 0x00, true, true, 2, { 0xd5, 0xaa, 0xad }, { 0xde, 0xaa, 0xeb }, 0x00, true, 2, kNibbleEnc53, kNibbleSpecialNone, }, { "Custom", // reserve space for empty slot 0, }, }; /*static*/ const DiskImg::NibbleDescr* DiskImg::GetStdNibbleDescr(StdNibbleDescr idx) { if ((int)idx < 0 || (int)idx >= (int) NELEM(kStdNibbleDescrs)) return NULL; return &kStdNibbleDescrs[(int)idx]; } /* * Initialize the members during construction. */ DiskImg::DiskImg(void) { assert(Global::GetAppInitCalled()); fOuterFormat = kOuterFormatUnknown; fFileFormat = kFileFormatUnknown; fPhysical = kPhysicalFormatUnknown; fpNibbleDescr = NULL; fOrder = kSectorOrderUnknown; fFormat = kFormatUnknown; fFileSysOrder = kSectorOrderUnknown; fSectorPairing = false; fSectorPairOffset = -1; fpOuterGFD = NULL; fpWrapperGFD = NULL; fpDataGFD = NULL; fpOuterWrapper = NULL; fpImageWrapper = NULL; fpParentImg = NULL; fDOSVolumeNum = kVolumeNumNotSet; fOuterLength = -1; fWrappedLength = -1; fLength = -1; fExpandable = false; fReadOnly = true; fDirty = false; fHasSectors = false; fHasBlocks = false; fHasNibbles = false; fNumTracks = -1; fNumSectPerTrack = -1; fNumBlocks = -1; fpScanProgressCallback = NULL; fScanProgressCookie = NULL; fScanCount = 0; fScanMsg[0] = '\0'; fScanLastMsgWhen = 0; /* * Create a working copy of the nibble descr table. We want to leave * open the possibility of applications editing or discarding entries, * so we work off of a copy. * * Ideally we'd allow these to be set per-track, so that certain odd * formats could be handled transparently (e.g. Muse tweaked DOS 3.2) * for formatting as well as reading. */ assert(kStdNibbleDescrs[kNibbleDescrCustom].numSectors == 0); assert(kNibbleDescrCustom == NELEM(kStdNibbleDescrs)-1); fpNibbleDescrTable = new NibbleDescr[NELEM(kStdNibbleDescrs)]; fNumNibbleDescrEntries = NELEM(kStdNibbleDescrs); memcpy(fpNibbleDescrTable, kStdNibbleDescrs, sizeof(kStdNibbleDescrs)); fNibbleTrackBuf = NULL; fNibbleTrackLoaded = -1; fNuFXCompressType = kNuThreadFormatLZW2; fNotes = NULL; fpBadBlockMap = NULL; fDiskFSRefCnt = 0; } /* * Throw away local storage. */ DiskImg::~DiskImg(void) { if (fpDataGFD != NULL) { LOGI("~DiskImg closing GenericFD(s)"); } (void) CloseImage(); delete[] fpNibbleDescrTable; delete[] fNibbleTrackBuf; delete[] fNotes; delete fpBadBlockMap; /* normally these will be closed, but perhaps not if something failed */ if (fpOuterGFD != NULL) delete fpOuterGFD; if (fpWrapperGFD != NULL) delete fpWrapperGFD; if (fpDataGFD != NULL) delete fpDataGFD; if (fpOuterWrapper != NULL) delete fpOuterWrapper; if (fpImageWrapper != NULL) delete fpImageWrapper; fDiskFSRefCnt = 100; // flag as freed } /* * Set the nibble descr pointer. */ void DiskImg::SetNibbleDescr(int idx) { assert(idx >= 0 && idx < kNibbleDescrMAX); fpNibbleDescr = &fpNibbleDescrTable[idx]; } /* * Set up a custom nibble descriptor. */ void DiskImg::SetCustomNibbleDescr(const NibbleDescr* pDescr) { if (pDescr == NULL) { fpNibbleDescr = NULL; } else { assert(fpNibbleDescrTable != NULL); //LOGI("Overwriting entry %d with new value (special=%d)", // kNibbleDescrCustom, pDescr->special); fpNibbleDescrTable[kNibbleDescrCustom] = *pDescr; fpNibbleDescr = &fpNibbleDescrTable[kNibbleDescrCustom]; } } const char* A2File::GetRawFileName(size_t* size) const { // get unmodified file name if (size) { *size = strlen(GetFileName()); } return GetFileName(); } /* * Open a volume or a file on disk. * * For Windows, we need to handle logical/physical volumes specially. If * the filename matches the appropriate pattern, use a different GFD. */ DIError DiskImg::OpenImage(const char* pathName, char fssep, bool readOnly) { DIError dierr = kDIErrNone; bool isWinDevice = false; if (fpDataGFD != NULL) { LOGI(" DI already open!"); return kDIErrAlreadyOpen; } LOGI(" DI OpenImage '%s' '%.1s' ro=%d", pathName, &fssep, readOnly); fReadOnly = readOnly; #ifdef _WIN32 if ((fssep == '\0' || fssep == '\\') && pathName[0] >= 'A' && pathName[0] <= 'Z' && pathName[1] == ':' && pathName[2] == '\\' && pathName[3] == '\0') { isWinDevice = true; // logical volume ("A:\") } if ((fssep == '\0' || fssep == '\\') && isdigit(pathName[0]) && isdigit(pathName[1]) && pathName[2] == ':' && pathName[3] == '\\' && pathName[4] == '\0') { isWinDevice = true; // physical volume ("80:\") } if ((fssep == '\0' || fssep == '\\') && strncmp(pathName, kASPIDev, strlen(kASPIDev)) == 0 && pathName[strlen(pathName)-1] == '\\') { isWinDevice = true; // ASPI volume ("ASPI:x:y:z\") } #endif if (isWinDevice) { #ifdef _WIN32 GFDWinVolume* pGFDWinVolume = new GFDWinVolume; dierr = pGFDWinVolume->Open(pathName, fReadOnly); if (dierr != kDIErrNone) { delete pGFDWinVolume; goto bail; } fpWrapperGFD = pGFDWinVolume; // Use a unique extension to skip some of the probing. dierr = AnalyzeImageFile("CPDevice.cp-win-vol", '\0'); if (dierr != kDIErrNone) goto bail; #endif } else { GFDFile* pGFDFile = new GFDFile; dierr = pGFDFile->Open(pathName, fReadOnly); if (dierr != kDIErrNone) { delete pGFDFile; goto bail; } //fImageFileName = new char[strlen(pathName) + 1]; //strcpy(fImageFileName, pathName); fpWrapperGFD = pGFDFile; pGFDFile = NULL; dierr = AnalyzeImageFile(pathName, fssep); if (dierr != kDIErrNone) goto bail; } assert(fpDataGFD != NULL); bail: return dierr; } DIError DiskImg::OpenImageFromBufferRO(const uint8_t* buffer, long length) { return OpenImageFromBuffer(const_cast(buffer), length, true); } DIError DiskImg::OpenImageFromBufferRW(uint8_t* buffer, long length) { return OpenImageFromBuffer(buffer, length, false); } /* * Open from a buffer, which could point to unadorned ready-to-go content * or to a preloaded image file. */ DIError DiskImg::OpenImageFromBuffer(uint8_t* buffer, long length, bool readOnly) { if (fpDataGFD != NULL) { LOGW(" DI already open!"); return kDIErrAlreadyOpen; } LOGI(" DI OpenImage %08lx %ld ro=%d", (long) buffer, length, readOnly); DIError dierr; GFDBuffer* pGFDBuffer; fReadOnly = readOnly; pGFDBuffer = new GFDBuffer; dierr = pGFDBuffer->Open(buffer, length, false, false, readOnly); if (dierr != kDIErrNone) { delete pGFDBuffer; return dierr; } fpWrapperGFD = pGFDBuffer; pGFDBuffer = NULL; dierr = AnalyzeImageFile("", '\0'); if (dierr != kDIErrNone) return dierr; assert(fpDataGFD != NULL); return kDIErrNone; } /* * Open a range of blocks from an already-open disk image. This is only * useful for things like UNIDOS volumes, which don't have an associated * file in the image and are linear. * * The "read only" flag is inherited from the parent. * * For embedded images with visible file structure, we should be using * an EmbeddedFD instead. [Note these were never implemented.] * * NOTE: there is an implicit ProDOS block ordering imposed on the parent * image. It turns out that all of our current embedded parents use * ProDOS-ordered blocks, so it works out okay, but the "linear" requirement * above goes beyond just having contiguous blocks. */ DIError DiskImg::OpenImage(DiskImg* pParent, long firstBlock, long numBlocks) { LOGI(" DI OpenImage parent=0x%08lx %ld %ld", (long) pParent, firstBlock, numBlocks); if (fpDataGFD != NULL) { LOGI(" DW already open!"); return kDIErrAlreadyOpen; } if (pParent == NULL || firstBlock < 0 || numBlocks <= 0 || firstBlock + numBlocks > pParent->GetNumBlocks()) { assert(false); return kDIErrInvalidArg; } fReadOnly = pParent->GetReadOnly(); // very important DIError dierr; GFDGFD* pGFDGFD; pGFDGFD = new GFDGFD; dierr = pGFDGFD->Open(pParent->fpDataGFD, firstBlock * kBlockSize, fReadOnly); if (dierr != kDIErrNone) { delete pGFDGFD; return dierr; } fpDataGFD = pGFDGFD; assert(fpWrapperGFD == NULL); /* * This replaces the call to "analyze image file" because we know we * already have an open file with specific characteristics. */ //fOffset = pParent->fOffset + kBlockSize * firstBlock; fLength = (di_off_t)numBlocks * kBlockSize; fOuterLength = fWrappedLength = fLength; fFileFormat = kFileFormatUnadorned; fPhysical = pParent->fPhysical; fOrder = pParent->fOrder; fpParentImg = pParent; return dierr; } DIError DiskImg::OpenImage(DiskImg* pParent, long firstTrack, long firstSector, long numSectors) { LOGI(" DI OpenImage parent=0x%08lx %ld %ld %ld", (long) pParent, firstTrack, firstSector, numSectors); if (fpDataGFD != NULL) { LOGW(" DI already open!"); return kDIErrAlreadyOpen; } if (pParent == NULL) return kDIErrInvalidArg; int prntSectPerTrack = pParent->GetNumSectPerTrack(); int lastTrack = firstTrack + (numSectors + prntSectPerTrack-1) / prntSectPerTrack; if (firstTrack < 0 || numSectors <= 0 || lastTrack > pParent->GetNumTracks()) { return kDIErrInvalidArg; } fReadOnly = pParent->GetReadOnly(); // very important DIError dierr; GFDGFD* pGFDGFD; pGFDGFD = new GFDGFD; dierr = pGFDGFD->Open(pParent->fpDataGFD, kSectorSize * firstTrack * prntSectPerTrack, fReadOnly); if (dierr != kDIErrNone) { delete pGFDGFD; return dierr; } fpDataGFD = pGFDGFD; assert(fpWrapperGFD == NULL); /* * This replaces the call to "analyze image file" because we know we * already have an open file with specific characteristics. */ assert(firstSector == 0); // else fOffset calculation breaks //fOffset = pParent->fOffset + kSectorSize * firstTrack * prntSectPerTrack; fLength = numSectors * kSectorSize; fOuterLength = fWrappedLength = fLength; fFileFormat = kFileFormatUnadorned; fPhysical = pParent->fPhysical; fOrder = pParent->fOrder; fpParentImg = pParent; return dierr; } /* * Enable sector pairing. Useful for OzDOS. */ void DiskImg::SetPairedSectors(bool enable, int idx) { fSectorPairing = enable; fSectorPairOffset = idx; if (enable) { assert(idx == 0 || idx == 1); } } /* * Close the image, freeing resources. * * If we write to a child DiskImg, it's responsible for setting the "dirty" * flag in its parent (and so on up the chain). That's necessary so that, * when we close the file, changes made to a child DiskImg cause the parent * to do any necessary recompression. * * [ This is getting called even when image creation failed with an error. * This is probably the correct behavior, but we may want to be aborting the * image creation instead of completing it. That's a higher-level decision * though. ++ATM 20040506 ] */ DIError DiskImg::CloseImage(void) { DIError dierr; LOGI("CloseImage 0x%p", this); /* check for DiskFS objects that still point to us */ if (fDiskFSRefCnt != 0) { LOGE("ERROR: CloseImage: fDiskFSRefCnt=%d", fDiskFSRefCnt); assert(false); //DebugBreak(); } /* * Flush any changes. */ dierr = FlushImage(kFlushAll); if (dierr != kDIErrNone) return dierr; /* * Clean up. Close GFD, OrigGFD, and OuterGFD. Delete ImageWrapper * and OuterWrapper. * * In some cases we will have the file open more than once (e.g. a * NuFX archive, which must be opened on disk). */ if (fpDataGFD != NULL) { fpDataGFD->Close(); delete fpDataGFD; fpDataGFD = NULL; } if (fpWrapperGFD != NULL) { fpWrapperGFD->Close(); delete fpWrapperGFD; fpWrapperGFD = NULL; } if (fpOuterGFD != NULL) { fpOuterGFD->Close(); delete fpOuterGFD; fpOuterGFD = NULL; } delete fpImageWrapper; fpImageWrapper = NULL; delete fpOuterWrapper; fpOuterWrapper = NULL; return dierr; } /* * Flush data to disk. * * The only time this really needs to do anything on a disk image file is * when we have compressed data (NuFX, DDD, .gz, .zip). The uncompressed * wrappers either don't do anything ("unadorned") or just update some * header fields (DiskCopy42). * * If "mode" is kFlushFastOnly, we only flush the formats that don't really * need flushing. This is part of a scheme to keep the disk contents in a * reasonable state on the off chance we crash with a modified file open. * It also helps the user understand when changes are being made immediately * vs. when they're written to memory and compressed later. We could just * refuse to raise the "dirty" flag when modifying "simple" file formats, * but that would change the meaning of the flag from "something has been * changed" to "what's in the file and what's in memory differ". I want it * to be a "dirty" flag. */ DIError DiskImg::FlushImage(FlushMode mode) { DIError dierr = kDIErrNone; LOGI(" DI FlushImage (dirty=%d mode=%d)", fDirty, mode); if (!fDirty) return kDIErrNone; if (fpDataGFD == NULL) { /* * This can happen if we tried to create a disk image but failed, e.g. * couldn't create the output file because of access denied on the * directory. There's no data, therefore nothing to flush, but the * "dirty" flag is set because CreateImageCommon sets it almost * immediately. */ LOGI(" (disk must've failed during creation)"); fDirty = false; return kDIErrNone; } if (mode == kFlushFastOnly && ((fpImageWrapper != NULL && !fpImageWrapper->HasFastFlush()) || (fpOuterWrapper != NULL && !fpOuterWrapper->HasFastFlush()) )) { LOGI("DI fast flush requested, but one or both wrappers are slow"); return kDIErrNone; } /* * Step 1: make sure any local caches have been flushed. */ /* (none) */ /* * Step 2: push changes from fpDataGFD to fpWrapperGFD. This will * cause ImageWrapper to rebuild itself (SHK, DDD, whatever). In * some cases this amounts to copying the data on top of itself, * which we can avoid easily. * * Embedded volumes don't have wrappers; when you write to an * embedded volume, it passes straight through to the parent. * * (Note to self: formats like NuFX that write to a temp file and then * rename over the old will close fpWrapperGFD and just access it * directly. This is bad, because it doesn't allow them to have an * "outer" format, but it's the way life is. The point is that it's * okay for fpWrapperGFD to be non-NULL but represent a closed file, * so long as the "Flush" function has it figured out.) */ if (fpWrapperGFD != NULL) { LOGI(" DI flushing data changes to wrapper (fLen=%ld fWrapLen=%ld)", (long) fLength, (long) fWrappedLength); dierr = fpImageWrapper->Flush(fpWrapperGFD, fpDataGFD, fLength, &fWrappedLength); if (dierr != kDIErrNone) { LOGI(" ERROR: wrapper flush failed (err=%d)", dierr); return dierr; } /* flush the GFD in case it's a Win32 volume with block caching */ dierr = fpWrapperGFD->Flush(); } else { assert(fpParentImg != NULL); } /* * Step 3: if we have an fpOuterGFD, rebuild the file with the data * in fpWrapperGFD. */ if (fpOuterWrapper != NULL) { LOGI(" DI saving wrapper to outer, fWrapLen=%ld", (long) fWrappedLength); assert(fpOuterGFD != NULL); dierr = fpOuterWrapper->Save(fpOuterGFD, fpWrapperGFD, fWrappedLength); if (dierr != kDIErrNone) { LOGI(" ERROR: outer save failed (err=%d)", dierr); return dierr; } } fDirty = false; return kDIErrNone; } /* * Given the filename extension and a GFD, figure out what's inside. * * The filename extension should give us some idea what to expect: * SHK, SDK, BXY - ShrinkIt compressed disk image * GZ - gzip-compressed file (with something else inside) * ZIP - ZIP archive with a single disk image inside * DDD - DDD, DDD Pro, or DDD5.0 compressed image * DSK - DiskCopy 4.2 or DO/PO * DC - DiskCopy 4.2 (or 6?) * DC6 - DiskCopy 6 (usually just raw sectors) * DO, PO, D13, RAW? - DOS-order or ProDOS-order uncompressed * IMG - Copy ][+ image (unadorned, physical sector order) * HDV - virtual hard drive image * NIB, RAW? - nibblized image * (no extension) uncompressed * cp-win-vol - our "magic" extension to indicate a Windows logical volume * * We can also examine the file length to see if it's a standard size * (140K, 800K) and look for magic values in the header. * * If we can access the contents directly from disk, we do so. It's * possibly more efficient to load the whole thing into memory, but if * we have that much memory then the OS should cache it for us. (I have * some 20MB disk images from my hard drive that shouldn't be loaded * in their entirety. Certainly don't want to load a 512MB CFFA image.) * * On input, the following fields must be set: * fpWrapperGFD - GenericFD for the file pointed to by "pathname" (or for a * memory buffer if this is a sub-volume) * * On success, the following fields will be set: * fWrappedLength, fOuterLength - set appropriately * fpDataGFD - GFD for the raw data, possibly just a GFDGFD with an offset * fLength - length of unadorned data in the file, or the length of * data stored in fBuffer (test for fBuffer!=NULL) * fFileFormat - set to the overall file format, mostly interesting * for identification of the file "wrapper" * fPhysicalFormat - set to the type of data this holds * (maybe) fOrder - set when the file format or extension dictates, e.g. * 2MG or *.po; not always reliable * (maybe) fDOSVolumeNum - set to DOS volume number from wrapper * * This may set fReadOnly if one of the wrappers looks okay but is reporting * a bad checksum. */ DIError DiskImg::AnalyzeImageFile(const char* pathName, char fssep) { DIError dierr = kDIErrNone; FileFormat probableFormat; bool reliableExt; const char* ext = FindExtension(pathName, fssep); char* extBuf = NULL; // uses malloc/free bool needExtFromOuter = false; if (ext != NULL) { assert(*ext == '.'); ext++; } else ext = ""; LOGI(" DI AnalyzeImageFile '%s' '%c' ext='%s'", pathName, fssep, ext); /* sanity check: nobody should have configured these yet */ assert(fOuterFormat == kOuterFormatUnknown); assert(fFileFormat == kFileFormatUnknown); assert(fOrder == kSectorOrderUnknown); assert(fFormat == kFormatUnknown); fLength = -1; dierr = fpWrapperGFD->Seek(0, kSeekEnd); if (dierr != kDIErrNone) { LOGW(" DI Couldn't seek to end of wrapperGFD"); goto bail; } fWrappedLength = fOuterLength = fpWrapperGFD->Tell(); /* quick test for zero-length files */ if (fWrappedLength == 0) return kDIErrUnrecognizedFileFmt; /* * Start by checking for a zip/gzip "wrapper wrapper". We want to strip * that away before we do anything else. Because web sites tend to * gzip everything in sight whether it needs it or not, we treat this * as a special case and assume that anything could be inside. * * Some cases are difficult to handle, e.g. ".SDK", since NufxLib * doesn't let us open an archive that is sitting in memory. * * We could also handle disk images stored as ordinary files stored * inside SHK. Not much point in handling multiple files down at * this level though. */ if (strcasecmp(ext, "gz") == 0 && OuterGzip::Test(fpWrapperGFD, fOuterLength) == kDIErrNone) { LOGI(" DI found gz outer wrapper"); fpOuterWrapper = new OuterGzip(); if (fpOuterWrapper == NULL) { dierr = kDIErrMalloc; goto bail; } fOuterFormat = kOuterFormatGzip; /* drop the ".gz" and get down to the next extension */ ext = ""; extBuf = strdup(pathName); if (extBuf != NULL) { char* localExt; localExt = (char*) FindExtension(extBuf, fssep); if (localExt != NULL) *localExt = '\0'; localExt = (char*) FindExtension(extBuf, fssep); if (localExt != NULL) { ext = localExt; assert(*ext == '.'); ext++; } } LOGI(" DI after gz, ext='%s'", ext == NULL ? "(NULL)" : ext); } else if (strcasecmp(ext, "zip") == 0) { dierr = OuterZip::Test(fpWrapperGFD, fOuterLength); if (dierr != kDIErrNone) goto bail; LOGI(" DI found ZIP outer wrapper"); fpOuterWrapper = new OuterZip(); if (fpOuterWrapper == NULL) { dierr = kDIErrMalloc; goto bail; } fOuterFormat = kOuterFormatZip; needExtFromOuter = true; } else { fOuterFormat = kOuterFormatNone; } /* finish up outer wrapper stuff */ if (fOuterFormat != kOuterFormatNone) { GenericFD* pNewGFD = NULL; dierr = fpOuterWrapper->Load(fpWrapperGFD, fOuterLength, fReadOnly, &fWrappedLength, &pNewGFD); if (dierr != kDIErrNone) { LOGI(" DW outer prep failed"); /* extensions are "reliable", so failure is unavoidable */ goto bail; } /* Load() sets this */ if (fpOuterWrapper->IsDamaged()) { AddNote(kNoteWarning, "The zip/gzip wrapper appears to be damaged."); fReadOnly = true; } /* shift GFDs */ fpOuterGFD = fpWrapperGFD; fpWrapperGFD = pNewGFD; if (needExtFromOuter) { ext = fpOuterWrapper->GetExtension(); if (ext == NULL) ext = ""; } } /* * Try to figure out what format the file is in. * * First pass, try only what the filename says it is. This way, if * two file formats look alike, we have a good chance of getting it * right. * * The "Test" functions have the complete file at their disposal. The * file's length is stored in "fWrappedLength" for convenience. */ reliableExt = false; probableFormat = kFileFormatUnknown; if (strcasecmp(ext, "2mg") == 0 || strcasecmp(ext, "2img") == 0) { reliableExt = true; if (Wrapper2MG::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) probableFormat = kFileFormat2MG; } else if (strcasecmp(ext, "shk") == 0 || strcasecmp(ext, "sdk") == 0 || strcasecmp(ext, "bxy") == 0) { DIError dierr2; reliableExt = true; dierr2 = WrapperNuFX::Test(fpWrapperGFD, fWrappedLength); if (dierr2 == kDIErrNone) probableFormat = kFileFormatNuFX; else if (dierr2 == kDIErrFileArchive) { LOGI(" AnalyzeImageFile thinks it found a NuFX file archive"); dierr = dierr2; goto bail; } } else if (strcasecmp(ext, "hdv") == 0) { /* usually just a "raw" disk, but check for Sim //e */ if (WrapperSim2eHDV::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) probableFormat = kFileFormatSim2eHDV; /* ProDOS .hdv volumes can expand */ fExpandable = true; } else if (strcasecmp(ext, "dsk") == 0 || strcasecmp(ext, "dc") == 0) { /* might be DiskCopy */ if (WrapperDiskCopy42::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) probableFormat = kFileFormatDiskCopy42; } else if (strcasecmp(ext, "ddd") == 0) { /* do this after compressed formats but before unadorned */ reliableExt = true; if (WrapperDDD::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) probableFormat = kFileFormatDDD; } else if (strcasecmp(ext, "app") == 0) { reliableExt = true; if (WrapperTrackStar::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) probableFormat = kFileFormatTrackStar; } else if (strcasecmp(ext, "fdi") == 0) { reliableExt = true; if (WrapperFDI::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) probableFormat = kFileFormatFDI; } else if (strcasecmp(ext, "img") == 0) { if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatUnadorned; fPhysical = kPhysicalFormatSectors; fOrder = kSectorOrderPhysical; } } else if (strcasecmp(ext, "nib") == 0 || strcasecmp(ext, "raw") == 0) { if (WrapperUnadornedNibble::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatUnadorned; fPhysical = kPhysicalFormatNib525_6656; /* figure out NibbleFormat later */ } } else if (strcasecmp(ext, "do") == 0 || strcasecmp(ext, "po") == 0 || strcasecmp(ext, "d13") == 0 || strcasecmp(ext, "dc6") == 0) { if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatUnadorned; fPhysical = kPhysicalFormatSectors; if (strcasecmp(ext, "do") == 0 || strcasecmp(ext, "d13") == 0) fOrder = kSectorOrderDOS; else fOrder = kSectorOrderProDOS; // po, dc6 LOGI(" DI guessing order is %d by extension", fOrder); } } else if (strcasecmp(ext, "cp-win-vol") == 0) { /* this is a Windows logical volume */ reliableExt = true; probableFormat = kFileFormatUnadorned; fPhysical = kPhysicalFormatSectors; fOrder = kSectorOrderProDOS; } else { /* no match on the filename extension; start guessing */ } if (probableFormat != kFileFormatUnknown) { /* * Found a match. Use "probableFormat" to open the file. */ LOGI(" DI scored hit on extension '%s'", ext); } else { /* * Didn't work. If the file extension was marked "reliable", then * either we have the wrong extension on the file, or the contents * are damaged. * * If the extension isn't reliable, or simply absent, then we have * to probe through the formats we know and just hope for the best. * * If the "test" function returns with a checksum failure, we take * it to mean that the format was positively identified, but the * data inside is corrupted. This results in an immediate return * with the checksum failure noted. Only a few wrapper formats * have checksums embedded. (The "test" functions should only * be looking at header checksums.) */ if (reliableExt) { LOGI(" DI file extension '%s' did not match contents", ext); dierr = kDIErrBadFileFormat; goto bail; } else { LOGI(" DI extension '%s' not useful, probing formats", ext); dierr = WrapperNuFX::Test(fpWrapperGFD, fWrappedLength); if (dierr == kDIErrNone) { probableFormat = kFileFormatNuFX; goto gotit; } else if (dierr == kDIErrFileArchive) goto bail; // we know it's NuFX, we know we can't use it else if (dierr == kDIErrBadChecksum) goto bail; // right file type, bad data dierr = WrapperDiskCopy42::Test(fpWrapperGFD, fWrappedLength); if (dierr == kDIErrNone) { probableFormat = kFileFormatDiskCopy42; goto gotit; } else if (dierr == kDIErrBadChecksum) goto bail; // right file type, bad data if (Wrapper2MG::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormat2MG; } else if (WrapperDDD::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatDDD; } else if (WrapperSim2eHDV::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatSim2eHDV; } else if (WrapperTrackStar::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatTrackStar; } else if (WrapperFDI::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatFDI; } else if (WrapperUnadornedNibble::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatUnadorned; fPhysical = kPhysicalFormatNib525_6656; // placeholder } else if (WrapperUnadornedSector::Test(fpWrapperGFD, fWrappedLength) == kDIErrNone) { probableFormat = kFileFormatUnadorned; fPhysical = kPhysicalFormatSectors; } gotit: ; } } /* * Either we recognize it or we don't. Finish opening the file by * setting up "fLength" and "fPhysical" values, extracting data * into a memory buffer if necessary. fpDataGFD is set up by the * "prep" function. * * If we're lucky, this will also configure "fOrder" for us, which is * important when we can't recognize the filesystem format (for correct * operation of disk tools). */ switch (probableFormat) { case kFileFormat2MG: fpImageWrapper = new Wrapper2MG(); break; case kFileFormatDiskCopy42: fpImageWrapper = new WrapperDiskCopy42(); break; case kFileFormatSim2eHDV: fpImageWrapper = new WrapperSim2eHDV(); break; case kFileFormatTrackStar: fpImageWrapper = new WrapperTrackStar(); break; case kFileFormatFDI: fpImageWrapper = new WrapperFDI(); fReadOnly = true; // writing to FDI not yet supported break; case kFileFormatNuFX: fpImageWrapper = new WrapperNuFX(); ((WrapperNuFX*)fpImageWrapper)->SetCompressType( (NuThreadFormat) fNuFXCompressType); break; case kFileFormatDDD: fpImageWrapper = new WrapperDDD(); break; case kFileFormatUnadorned: if (IsSectorFormat(fPhysical)) fpImageWrapper = new WrapperUnadornedSector(); else if (IsNibbleFormat(fPhysical)) fpImageWrapper = new WrapperUnadornedNibble(); else { assert(false); } break; default: LOGI(" DI couldn't figure out the file format"); dierr = kDIErrUnrecognizedFileFmt; break; } if (fpImageWrapper != NULL) { assert(fpDataGFD == NULL); dierr = fpImageWrapper->Prep(fpWrapperGFD, fWrappedLength, fReadOnly, &fLength, &fPhysical, &fOrder, &fDOSVolumeNum, &fpBadBlockMap, &fpDataGFD); } else { /* could be a mem alloc failure that didn't set dierr */ if (dierr == kDIErrNone) dierr = kDIErrGeneric; } if (dierr != kDIErrNone) { LOGI(" DI wrapper prep failed (err=%d)", dierr); goto bail; } /* check for non-fatal checksum failures, e.g. DiskCopy42 */ if (fpImageWrapper->IsDamaged()) { AddNote(kNoteWarning, "File checksum didn't match."); fReadOnly = true; } fFileFormat = probableFormat; assert(fLength >= 0); assert(fpDataGFD != NULL); assert(fOuterFormat != kOuterFormatUnknown); assert(fFileFormat != kFileFormatUnknown); assert(fPhysical != kPhysicalFormatUnknown); bail: free(extBuf); return dierr; } /* * Try to figure out what we're looking at. * * Returns an error if we don't think this is even a disk image. If we * just can't figure it out, we return success but with the format value * set to "unknown". This gives the caller a chance to use "override" * to help us find our way. * * On entry: * fpDataGFD, fLength, and fFileFormat are defined * fSectorPairing is specified * fOrder has a semi-reliable guess at sector ordering * On exit: * fOrder and fFormat are set to the best of our ability * fNumTracks, fNumSectPerTrack, and fNumBlocks are set * fHasSectors, fHasTracks, and fHasNibbles are set * fFileSysOrder is set * fpNibbleDescr will be set for nibble images */ DIError DiskImg::AnalyzeImage(void) { assert(fLength >= 0); assert(fpDataGFD != NULL); assert(fFileFormat != kFileFormatUnknown); assert(fPhysical != kPhysicalFormatUnknown); assert(fFormat == kFormatUnknown); assert(fFileSysOrder == kSectorOrderUnknown); assert(fNumTracks == -1); assert(fNumSectPerTrack == -1); assert(fNumBlocks == -1); if (fpDataGFD == NULL) return kDIErrInternal; /* * Figure out how many tracks and sectors the image has. * * For an odd-sized ProDOS image, there will be no tracks and sectors. */ if (IsSectorFormat(fPhysical)) { if (!fLength) { LOGI(" DI zero-length disk images not allowed"); return kDIErrOddLength; } if (fLength == kD13Length) { /* 13-sector .d13 image */ fHasSectors = true; fNumSectPerTrack = 13; fNumTracks = kTrackCount525; assert(!fHasBlocks); } else if (fLength % (16 * kSectorSize) == 0) { /* looks like a collection of 16-sector tracks */ fHasSectors = true; fNumSectPerTrack = 16; fNumTracks = (int) (fLength / (fNumSectPerTrack * kSectorSize)); /* sector pairing effectively cuts #of tracks in half */ if (fSectorPairing) { if ((fNumTracks & 0x01) != 0) { LOGI(" DI error: bad attempt at sector pairing"); assert(false); fSectorPairing = false; } } if (fSectorPairing) fNumTracks /= 2; } else { if (fSectorPairing) { LOGI("GLITCH: sector pairing enabled, but fLength=%ld", (long) fLength); return kDIErrOddLength; } assert(fNumTracks == -1); assert(fNumSectPerTrack == -1); assert((fLength % kBlockSize) == 0); fHasBlocks = true; fNumBlocks = (long) (fLength / kBlockSize); } } else if (IsNibbleFormat(fPhysical)) { fHasNibbles = fHasSectors = true; /* * Figure out if it's 13-sector or 16-sector (or garbage). We * have to make an assessment of the entire disk so we can declare * it to be 13-sector or 16-sector, which is useful for DiskFS * which will want to scan for DOS VTOCs and other goodies. We * also want to provide a default NibbleDescr. * * Failing that, we still allow it to be opened for raw track access. * * This also sets fNumTracks, which could be more than 35 if we're * working with a TrackStar or FDI image. */ DIError dierr; dierr = AnalyzeNibbleData(); // sets nibbleDescr and DOS vol num if (dierr == kDIErrNone) { assert(fpNibbleDescr != NULL); fNumSectPerTrack = fpNibbleDescr->numSectors; fOrder = kSectorOrderPhysical; if (!fReadOnly && !fpNibbleDescr->dataVerifyChecksum) { LOGI("DI nibbleDescr does not verify data checksum, disabling writes"); AddNote(kNoteInfo, "Sectors use non-standard data checksums; writing disabled."); fReadOnly = true; } } else { //assert(fpNibbleDescr == NULL); fNumSectPerTrack = -1; fOrder = kSectorOrderPhysical; fHasSectors = false; } } else { LOGI("Unsupported physical %d", fPhysical); assert(false); return kDIErrGeneric; } /* * Compute the number of blocks. For a 13-sector disk, block access * is not possible. * * For nibble formats, we have to base the block count on the number * of sectors rather than the file length. */ if (fHasSectors) { assert(fNumSectPerTrack > 0); if ((fNumSectPerTrack & 0x01) == 0) { /* not a 13-sector disk, so define blocks in terms of sectors */ /* (effects of sector pairing are already taken into account) */ fHasBlocks = true; fNumBlocks = (fNumTracks * fNumSectPerTrack) / 2; } } else if (fHasBlocks) { if ((fLength % kBlockSize) == 0) { /* not sector-oriented, so define blocks based on length */ fHasBlocks = true; fNumBlocks = (long) (fLength / kBlockSize); if (fSectorPairing) { if ((fNumBlocks & 0x01) != 0) { LOGI(" DI error: bad attempt at sector pairing (blk)"); assert(false); fSectorPairing = false; } else fNumBlocks /= 2; } } else { assert(false); return kDIErrGeneric; } } else if (fHasNibbles) { assert(fNumBlocks == -1); } else { LOGI(" DI none of fHasSectors/fHasBlocks/fHasNibbles are set"); assert(false); return kDIErrInternal; } /* * We've got the track/sector/block layout sorted out; now figure out * what kind of filesystem we're dealing with. */ AnalyzeImageFS(); LOGI(" DI AnalyzeImage tracks=%ld sectors=%d blocks=%ld fileSysOrder=%d", fNumTracks, fNumSectPerTrack, fNumBlocks, fFileSysOrder); LOGI(" hasBlocks=%d hasSectors=%d hasNibbles=%d", fHasBlocks, fHasSectors, fHasNibbles); return kDIErrNone; } /* * Try to figure out what filesystem exists on this disk image. * * We want to test for DOS before ProDOS, because sometimes they overlap (e.g. * 800K ProDOS disk with five 160K DOS volumes on it). * * Sets fFormat, fOrder, and fFileSysOrder. */ void DiskImg::AnalyzeImageFS(void) { /* * In some circumstances it would be useful to have a set describing * what filesystems we might expect to find, e.g. we're not likely to * encounter RDOS embedded in a CF card. */ if (DiskFSMacPart::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatMacPart); LOGI(" DI found MacPart, order=%d", fOrder); } else if (DiskFSMicroDrive::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatMicroDrive); LOGI(" DI found MicroDrive, order=%d", fOrder); } else if (DiskFSFocusDrive::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatFocusDrive); LOGI(" DI found FocusDrive, order=%d", fOrder); } else if (DiskFSCFFA::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { // The CFFA format doesn't have a partition map, but we do insist // on finding multiple volumes. It needs to come after MicroDrive, // because a disk formatted for CFFA then subsequently partitioned // for MicroDrive will still look like valid CFFA unless you zero // out the blocks. assert(fFormat == kFormatCFFA4 || fFormat == kFormatCFFA8); LOGI(" DI found CFFA, order=%d", fOrder); } else if (DiskFSFAT::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { // This is really just a trap to catch CFFA cards that were formatted // for ProDOS and then re-formatted for MSDOS. As such it needs to // come before the ProDOS test. It only works on larger volumes, // and can be overridden, so it's pretty safe. assert(fFormat == kFormatMSDOS); LOGI(" DI found MSDOS, order=%d", fOrder); } else if (DiskFSDOS33::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatDOS32 || fFormat == kFormatDOS33); LOGI(" DI found DOS3.x, order=%d", fOrder); if (fNumSectPerTrack == 13) fFormat = kFormatDOS32; } else if (DiskFSUNIDOS::TestWideFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { // Should only succeed on 400K embedded chunks. assert(fFormat == kFormatDOS33); fNumSectPerTrack = 32; fNumTracks /= 2; LOGI(" DI found 'wide' DOS3.3, order=%d", fOrder); } else if (DiskFSUNIDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatUNIDOS); fNumSectPerTrack = 32; fNumTracks /= 2; LOGI(" DI found UNIDOS, order=%d", fOrder); } else if (DiskFSOzDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatOzDOS); fNumSectPerTrack = 32; fNumTracks /= 2; LOGI(" DI found OzDOS, order=%d", fOrder); } else if (DiskFSProDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatProDOS); LOGI(" DI found ProDOS, order=%d", fOrder); } else if (DiskFSPascal::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatPascal); LOGI(" DI found Pascal, order=%d", fOrder); } else if (DiskFSCPM::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatCPM); LOGI(" DI found CP/M, order=%d", fOrder); } else if (DiskFSRDOS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatRDOS33 || fFormat == kFormatRDOS32 || fFormat == kFormatRDOS3); LOGI(" DI found RDOS 3.3, order=%d", fOrder); } else if (DiskFSHFS::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatMacHFS); LOGI(" DI found HFS, order=%d", fOrder); } else if (DiskFSGutenberg::TestFS(this, &fOrder, &fFormat, DiskFS::kLeniencyNot) == kDIErrNone) { assert(fFormat == kFormatGutenberg); LOGI(" DI found Gutenberg, order=%d", fOrder); } else { fFormat = kFormatUnknown; LOGI(" DI no recognizeable filesystem found (fOrder=%d)", fOrder); } fFileSysOrder = CalcFSSectorOrder(); } /* * Override the format determined by the analyzer. * * If they insist on the presence of a valid filesystem, check to make sure * that filesystem actually exists. * * Note that this does not allow overriding the file structure, which must * be clearly identifiable to be at all useful. If the file has no "wrapper" * structure, the "unadorned" format should be specified, and the contents * identified by the PhysicalFormat. */ DIError DiskImg::OverrideFormat(PhysicalFormat physical, FSFormat format, SectorOrder order) { DIError dierr = kDIErrNone; SectorOrder newOrder; FSFormat newFormat; LOGI(" DI override: physical=%d format=%d order=%d", physical, format, order); if (!IsSectorFormat(physical) && !IsNibbleFormat(physical)) return kDIErrUnsupportedPhysicalFmt; /* don't allow forcing physical format change */ if (physical != fPhysical) return kDIErrInvalidArg; /* optimization */ if (physical == fPhysical && format == fFormat && order == fOrder) { LOGI(" DI override matches existing, ignoring"); return kDIErrNone; } newOrder = order; newFormat = format; switch (format) { case kFormatDOS33: case kFormatDOS32: dierr = DiskFSDOS33::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); // Go ahead and allow the override even if the DOS version is wrong. // So long as the sector count is correct, it's okay. break; case kFormatProDOS: dierr = DiskFSProDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatPascal: dierr = DiskFSPascal::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatMacHFS: dierr = DiskFSHFS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatUNIDOS: dierr = DiskFSUNIDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatOzDOS: dierr = DiskFSOzDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatCFFA4: case kFormatCFFA8: dierr = DiskFSCFFA::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); // So long as it's CFFA, we allow the user to force it to be 4-mode // or 8-mode. Don't require newFormat==format. break; case kFormatMacPart: dierr = DiskFSMacPart::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatMicroDrive: dierr = DiskFSMicroDrive::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatFocusDrive: dierr = DiskFSFocusDrive::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatCPM: dierr = DiskFSCPM::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatMSDOS: dierr = DiskFSFAT::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); break; case kFormatRDOS33: case kFormatRDOS32: case kFormatRDOS3: dierr = DiskFSRDOS::TestFS(this, &newOrder, &newFormat, DiskFS::kLeniencyVery); if (newFormat != format) dierr = kDIErrFilesystemNotFound; // found RDOS, but wrong flavor break; case kFormatGenericPhysicalOrd: case kFormatGenericProDOSOrd: case kFormatGenericDOSOrd: case kFormatGenericCPMOrd: /* no discussion possible, since there's no FS to validate */ newFormat = format; newOrder = order; break; case kFormatUnknown: /* only valid in rare situations, e.g. CFFA CreatePlaceholder */ newFormat = format; newOrder = order; break; default: dierr = kDIErrUnsupportedFSFmt; break; } if (dierr != kDIErrNone) { LOGI(" DI override failed"); goto bail; } /* * We passed in "order" to TestFS. If it came back with something * different, it means that it didn't like the new order value even * when "leniency" was granted. */ if (newOrder != order) { dierr = kDIErrBadOrdering; goto bail; } fFormat = format; fOrder = newOrder; fFileSysOrder = CalcFSSectorOrder(); LOGI(" DI override accepted"); bail: return dierr; } /* * Figure out the sector ordering for this filesystem, so we can decide * how the sectors need to be re-arranged when we're reading them. * * If the value returned by this function matches fOrder, then no swapping * will be done. * * NOTE: this table is redundant with some knowledge embedded in the * individual "TestFS" functions. */ DiskImg::SectorOrder DiskImg::CalcFSSectorOrder(void) const { /* in the absence of information, just leave it alone */ if (fFormat == kFormatUnknown || fOrder == kSectorOrderUnknown) { LOGI(" DI WARNING: FindSectorOrder but format not known"); return fOrder; } assert(fOrder == kSectorOrderPhysical || fOrder == kSectorOrderCPM || fOrder == kSectorOrderProDOS || fOrder == kSectorOrderDOS); switch (fFormat) { case kFormatGenericPhysicalOrd: case kFormatRDOS32: case kFormatRDOS3: return kSectorOrderPhysical; case kFormatGenericDOSOrd: case kFormatDOS33: case kFormatDOS32: case kFormatUNIDOS: case kFormatOzDOS: case kFormatGutenberg: return kSectorOrderDOS; case kFormatGenericCPMOrd: case kFormatCPM: return kSectorOrderCPM; case kFormatGenericProDOSOrd: case kFormatProDOS: case kFormatRDOS33: case kFormatPascal: case kFormatMacHFS: case kFormatMacMFS: case kFormatLisa: case kFormatMSDOS: case kFormatISO9660: case kFormatCFFA4: case kFormatCFFA8: case kFormatMacPart: case kFormatMicroDrive: case kFormatFocusDrive: return kSectorOrderProDOS; default: assert(false); return fOrder; } } /* * Based on the disk format, figure out if we should prefer blocks or * sectors when examining disk contents. */ bool DiskImg::ShowAsBlocks(void) const { if (!fHasBlocks) return false; /* in the absence of information, assume sectors */ if (fFormat == kFormatUnknown) { if (fOrder == kSectorOrderProDOS) return true; else return false; } switch (fFormat) { case kFormatGenericPhysicalOrd: case kFormatGenericDOSOrd: case kFormatDOS33: case kFormatDOS32: case kFormatRDOS3: case kFormatRDOS33: case kFormatUNIDOS: case kFormatOzDOS: case kFormatGutenberg: return false; case kFormatGenericProDOSOrd: case kFormatGenericCPMOrd: case kFormatProDOS: case kFormatPascal: case kFormatMacHFS: case kFormatMacMFS: case kFormatLisa: case kFormatCPM: case kFormatMSDOS: case kFormatISO9660: case kFormatCFFA4: case kFormatCFFA8: case kFormatMacPart: case kFormatMicroDrive: case kFormatFocusDrive: return true; default: assert(false); return false; } } /* * Format an image with the requested fileystem format. This only works if * the matching DiskFS supports formatting of disks. */ DIError DiskImg::FormatImage(FSFormat format, const char* volName) { DIError dierr = kDIErrNone; DiskFS* pDiskFS = NULL; FSFormat savedFormat; LOGI(" DI FormatImage '%s'", volName); /* * Open a temporary DiskFS for the requested format. We do this via the * standard OpenAppropriate call, so we temporarily switch our format * out. (We will eventually replace it, but we want to make sure that * local error handling works correctly, so we restore it for now.) */ savedFormat = fFormat; fFormat = format; pDiskFS = OpenAppropriateDiskFS(false); fFormat = savedFormat; if (pDiskFS == NULL) { dierr = kDIErrUnsupportedFSFmt; goto bail; } dierr = pDiskFS->Format(this, volName); if (dierr != kDIErrNone) goto bail; LOGI("DI format successful"); fFormat = format; bail: delete pDiskFS; return dierr; } /* * Clear an image to zeros, usually done as a prelude to a higher-level format. * * BUG: this should also handle the track/sector case. * * HEY: this is awfully slow on large disks... should have some sort of * optimized path that just writes to the GFD or something. Maybe even just * a "ZeroBlock" instead of "WriteBlock" so we can memset instead of memcpy? */ DIError DiskImg::ZeroImage(void) { DIError dierr = kDIErrNone; uint8_t blkBuf[kBlockSize]; long block; LOGI(" DI ZeroImage (%ld blocks)", GetNumBlocks()); memset(blkBuf, 0, sizeof(blkBuf)); for (block = 0; block < GetNumBlocks(); block++) { dierr = WriteBlock(block, blkBuf); if (dierr != kDIErrNone) break; } return dierr; } /* * Set the "scan progress" function. * * We want to use the same function for our sub-volumes too. */ void DiskImg::SetScanProgressCallback(ScanProgressCallback func, void* cookie) { if (fpParentImg != NULL) { /* unexpected, but perfectly okay */ DebugBreak(); } fpScanProgressCallback = func; fScanProgressCookie = cookie; fScanCount = 0; fScanMsg[0] = '\0'; fScanLastMsgWhen = time(NULL); } /* * Update the progress. Call with a string at the start of a volume, then * call with a NULL pointer every time we add a file. */ bool DiskImg::UpdateScanProgress(const char* newStr) { ScanProgressCallback func = fpScanProgressCallback; DiskImg* pImg = this; bool result = true; /* search up the tree to find a progress updater */ while (func == NULL) { pImg = pImg->fpParentImg; if (pImg == NULL) return result; // none defined, bail out func = pImg->fpScanProgressCallback; } time_t now = time(NULL); if (newStr == NULL) { fScanCount++; //if ((fScanCount % 100) == 0) if (fScanLastMsgWhen != now) { result = (*func)(fScanProgressCookie, fScanMsg, fScanCount); fScanLastMsgWhen = now; } } else { fScanCount = 0; strncpy(fScanMsg, newStr, sizeof(fScanMsg)); fScanMsg[sizeof(fScanMsg)-1] = '\0'; result = (*func)(fScanProgressCookie, fScanMsg, fScanCount); fScanLastMsgWhen = now; } return result; } /* * ========================================================================== * Block/track/sector I/O * ========================================================================== */ /* * Handle sector order conversions. */ DIError DiskImg::CalcSectorAndOffset(long track, int sector, SectorOrder imageOrder, SectorOrder fsOrder, di_off_t* pOffset, int* pNewSector) { if (!fHasSectors) return kDIErrUnsupportedAccess; /* * Sector order conversions. No table is needed for Copy ][+ format, * which is equivalent to "physical". */ static const int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 }; static const int dos2raw[16] = { 0, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 15 }; static const int raw2prodos[16] = { 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15 }; static const int prodos2raw[16] = { 0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15 }; static const int raw2cpm[16] = { 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5 }; static const int cpm2raw[16] = { 0, 3, 6, 9, 12, 15, 2, 5, 8, 11, 14, 1, 4, 7, 10, 13 }; if (track < 0 || track >= fNumTracks) { LOGI(" DI read invalid track %ld", track); return kDIErrInvalidTrack; } if (sector < 0 || sector >= fNumSectPerTrack) { LOGI(" DI read invalid sector %d", sector); return kDIErrInvalidSector; } di_off_t offset; int newSector = -1; /* * 16-sector disks write sectors in ascending order and then remap * them with a translation table. */ if (fNumSectPerTrack == 16 || fNumSectPerTrack == 32) { if (fSectorPairing) { assert(fSectorPairOffset == 0 || fSectorPairOffset == 1); // this pushes "track" beyond fNumTracks track *= 2; if (sector >= 16) { track++; sector -= 16; } offset = track * fNumSectPerTrack * kSectorSize; sector = sector * 2 + fSectorPairOffset; if (sector >= 16) { offset += 16*kSectorSize; sector -= 16; } } else { offset = track * fNumSectPerTrack * kSectorSize; if (sector >= 16) { offset += 16*kSectorSize; sector -= 16; } } assert(sector >= 0 && sector < 16); /* convert request to "raw" sector number */ switch (fsOrder) { case kSectorOrderProDOS: newSector = prodos2raw[sector]; break; case kSectorOrderDOS: newSector = dos2raw[sector]; break; case kSectorOrderCPM: newSector = cpm2raw[sector]; break; case kSectorOrderPhysical: // used for Copy ][+ newSector = sector; break; case kSectorOrderUnknown: // should never happen; fall through to "default" default: assert(false); newSector = sector; break; } /* convert "raw" request to the image's ordering */ switch (imageOrder) { case kSectorOrderProDOS: newSector = raw2prodos[newSector]; break; case kSectorOrderDOS: newSector = raw2dos[newSector]; break; case kSectorOrderCPM: newSector = raw2cpm[newSector]; break; case kSectorOrderPhysical: //newSector = newSector; break; case kSectorOrderUnknown: // should never happen; fall through to "default" default: assert(false); //newSector = newSector; break; } if (imageOrder == fsOrder) { assert(sector == newSector); } offset += newSector * kSectorSize; } else if (fNumSectPerTrack == 13) { /* sector skew has no meaning, so assume no translation */ offset = track * fNumSectPerTrack * kSectorSize; newSector = sector; offset += newSector * kSectorSize; if (imageOrder != fsOrder) { /* translation expected */ LOGI("NOTE: CalcSectorAndOffset for nspt=13 with img=%d fs=%d", imageOrder, fsOrder); } } else { assert(false); // should not be here /* try to do something reasonable */ assert(imageOrder == fsOrder); offset = (di_off_t)track * fNumSectPerTrack * kSectorSize; offset += sector * kSectorSize; } *pOffset = offset; *pNewSector = newSector; return kDIErrNone; } /* * Determine whether an image uses a linear mapping. This allows us to * optimize block reads & writes, very useful when dealing with logical * volumes under Windows (which also use 512-byte blocks). * * The "imageOrder" argument usually comes from fOrder, and "fsOrder" * comes from "fFileSysOrder". */ inline bool DiskImg::IsLinearBlocks(SectorOrder imageOrder, SectorOrder fsOrder) { /* * Any time fOrder==fFileSysOrder, we know that we have a linear * mapping. This holds true for reading ProDOS blocks from a ".po" * file or reading DOS sectors from a ".do" file. */ return (IsSectorFormat(fPhysical) && fHasBlocks && imageOrder == fsOrder); } /* * Read the specified track and sector, adjusting for sector ordering as * appropriate. * * Copies 256 bytes into "*buf". * * Returns 0 on success, nonzero on failure. */ DIError DiskImg::ReadTrackSectorSwapped(long track, int sector, void* buf, SectorOrder imageOrder, SectorOrder fsOrder) { DIError dierr; di_off_t offset; int newSector = -1; if (buf == NULL) return kDIErrInvalidArg; #if 0 // Pre-d13 if (fNumSectPerTrack == 13) { /* no sector skewing possible for 13-sector disks */ assert(fHasNibbles); return ReadNibbleSector(track, sector, buf, fpNibbleDescr); } #endif dierr = CalcSectorAndOffset(track, sector, imageOrder, fsOrder, &offset, &newSector); if (dierr != kDIErrNone) return dierr; if (IsSectorFormat(fPhysical)) { assert(offset+kSectorSize <= fLength); //LOGI(" DI t=%d s=%d", track, // (offset - track * fNumSectPerTrack * kSectorSize) / kSectorSize); dierr = CopyBytesOut(buf, offset, kSectorSize); } else if (IsNibbleFormat(fPhysical)) { if (imageOrder != kSectorOrderPhysical) { LOGI(" NOTE: nibble imageOrder is %d (expected %d)", imageOrder, kSectorOrderPhysical); } dierr = ReadNibbleSector(track, newSector, buf, fpNibbleDescr); } else { assert(false); dierr = kDIErrInternal; } return dierr; } /* * Write the specified track and sector, adjusting for sector ordering as * appropriate. * * Copies 256 bytes out of "buf". * * Returns 0 on success, nonzero on failure. */ DIError DiskImg::WriteTrackSector(long track, int sector, const void* buf) { DIError dierr; di_off_t offset; int newSector = -1; if (buf == NULL) return kDIErrInvalidArg; if (fReadOnly) return kDIErrAccessDenied; #if 0 // Pre-d13 if (fNumSectPerTrack == 13) { /* no sector skewing possible for 13-sector disks */ assert(fHasNibbles); return WriteNibbleSector(track, sector, buf, fpNibbleDescr); } #endif dierr = CalcSectorAndOffset(track, sector, fOrder, fFileSysOrder, &offset, &newSector); if (dierr != kDIErrNone) return dierr; if (IsSectorFormat(fPhysical)) { assert(offset+kSectorSize <= fLength); //LOGI(" DI t=%d s=%d", track, // (offset - track * fNumSectPerTrack * kSectorSize) / kSectorSize); dierr = CopyBytesIn(buf, offset, kSectorSize); } else if (IsNibbleFormat(fPhysical)) { if (fOrder != kSectorOrderPhysical) { LOGI(" NOTE: nibble fOrder is %d (expected %d)", fOrder, kSectorOrderPhysical); } dierr = WriteNibbleSector(track, newSector, buf, fpNibbleDescr); } else { assert(false); dierr = kDIErrInternal; } return dierr; } /* * Read a 512-byte block. * * Copies 512 bytes into "*buf". */ DIError DiskImg::ReadBlockSwapped(long block, void* buf, SectorOrder imageOrder, SectorOrder fsOrder) { if (!fHasBlocks) return kDIErrUnsupportedAccess; if (block < 0 || block >= fNumBlocks) return kDIErrInvalidBlock; if (buf == NULL) return kDIErrInvalidArg; DIError dierr; long track, blkInTrk; /* if we have a bad block map, check it */ if (CheckForBadBlocks(block, 1)) { dierr = kDIErrReadFailed; goto bail; } if (fHasSectors && !IsLinearBlocks(imageOrder, fsOrder)) { /* run it through the t/s call so we handle DOS ordering */ track = block / (fNumSectPerTrack/2); blkInTrk = block - (track * (fNumSectPerTrack/2)); dierr = ReadTrackSectorSwapped(track, blkInTrk*2, buf, imageOrder, fsOrder); if (dierr != kDIErrNone) return dierr; dierr = ReadTrackSectorSwapped(track, blkInTrk*2+1, (char*)buf+kSectorSize, imageOrder, fsOrder); } else if (fHasBlocks) { /* no sectors, so no swapping; must be linear blocks */ if (imageOrder != fsOrder) { LOGI(" DI NOTE: ReadBlockSwapped on non-sector (%d/%d)", imageOrder, fsOrder); } dierr = CopyBytesOut(buf, (di_off_t) block * kBlockSize, kBlockSize); } else { assert(false); dierr = kDIErrInternal; } bail: return dierr; } /* * Read multiple blocks. * * IMPORTANT: this returns immediately when a read fails. The buffer will * probably not contain data from all readable sectors. The application is * expected to retry the blocks individually. */ DIError DiskImg::ReadBlocks(long startBlock, int numBlocks, void* buf) { DIError dierr = kDIErrNone; assert(fHasBlocks); assert(startBlock >= 0); assert(numBlocks > 0); assert(buf != NULL); if (startBlock < 0 || numBlocks + startBlock > GetNumBlocks()) { assert(false); return kDIErrInvalidArg; } /* if we have a bad block map, check it */ if (CheckForBadBlocks(startBlock, numBlocks)) { dierr = kDIErrReadFailed; goto bail; } if (!IsLinearBlocks(fOrder, fFileSysOrder)) { /* * This isn't a collection of linear blocks, so we need to read it one * block at a time with sector swapping. This almost certainly means * that we're not reading from physical media, so performance shouldn't * be an issue. */ if (startBlock == 0) { LOGI(" ReadBlocks: nonlinear, not trying"); } while (numBlocks--) { dierr = ReadBlock(startBlock, buf); if (dierr != kDIErrNone) goto bail; startBlock++; buf = (uint8_t*)buf + kBlockSize; } } else { if (startBlock == 0) { LOGI(" ReadBlocks: doing big linear reads"); } dierr = CopyBytesOut(buf, (di_off_t) startBlock * kBlockSize, numBlocks * kBlockSize); } bail: return dierr; } /* * Check to see if any blocks in a range of blocks show up in the bad * block map. This is primarily useful for 3.5" disk images converted * from nibble images, because we convert them directly to "cooked" * 512-byte blocks. * * Returns "true" if we found bad blocks, "false" if not. */ bool DiskImg::CheckForBadBlocks(long startBlock, int numBlocks) { int i; if (fpBadBlockMap == NULL) return false; for (i = startBlock; i < startBlock+numBlocks; i++) { if (fpBadBlockMap->IsSet(i)) return true; } return false; } /* * Write a block of data to a DiskImg. * * Returns immediately when a block write fails. Does not try to write all * blocks before returning failure. */ DIError DiskImg::WriteBlock(long block, const void* buf) { if (!fHasBlocks) return kDIErrUnsupportedAccess; if (block < 0 || block >= fNumBlocks) return kDIErrInvalidBlock; if (buf == NULL) return kDIErrInvalidArg; if (fReadOnly) return kDIErrAccessDenied; DIError dierr; long track, blkInTrk; if (fHasSectors && !IsLinearBlocks(fOrder, fFileSysOrder)) { /* run it through the t/s call so we handle DOS ordering */ track = block / (fNumSectPerTrack/2); blkInTrk = block - (track * (fNumSectPerTrack/2)); dierr = WriteTrackSector(track, blkInTrk*2, buf); if (dierr != kDIErrNone) return dierr; dierr = WriteTrackSector(track, blkInTrk*2+1, (char*)buf+kSectorSize); } else if (fHasBlocks) { /* no sectors, so no swapping; must be linear blocks */ if (fOrder != fFileSysOrder) { LOGI(" DI NOTE: WriteBlock on non-sector (%d/%d)", fOrder, fFileSysOrder); } dierr = CopyBytesIn(buf, (di_off_t)block * kBlockSize, kBlockSize); } else { assert(false); dierr = kDIErrInternal; } return dierr; } /* * Write multiple blocks. */ DIError DiskImg::WriteBlocks(long startBlock, int numBlocks, const void* buf) { DIError dierr = kDIErrNone; assert(fHasBlocks); assert(startBlock >= 0); assert(numBlocks > 0); assert(buf != NULL); if (startBlock < 0 || numBlocks + startBlock > GetNumBlocks()) { assert(false); return kDIErrInvalidArg; } if (!IsLinearBlocks(fOrder, fFileSysOrder)) { /* * This isn't a collection of linear blocks, so we need to write it * one block at a time with sector swapping. This almost certainly * means that we're not reading from physical media, so performance * shouldn't be an issue. */ if (startBlock == 0) { LOGI(" WriteBlocks: nonlinear, not trying"); } while (numBlocks--) { dierr = WriteBlock(startBlock, buf); if (dierr != kDIErrNone) goto bail; startBlock++; buf = (uint8_t*)buf + kBlockSize; } } else { if (startBlock == 0) { LOGI(" WriteBlocks: doing big linear writes"); } dierr = CopyBytesIn(buf, (di_off_t) startBlock * kBlockSize, numBlocks * kBlockSize); } bail: return dierr; } /* * Copy a chunk of bytes out of the disk image. * * (This is the lowest-level read routine in this class.) */ DIError DiskImg::CopyBytesOut(void* buf, di_off_t offset, int size) const { DIError dierr; dierr = fpDataGFD->Seek(offset, kSeekSet); if (dierr != kDIErrNone) { LOGI(" DI seek off=%ld failed (err=%d)", (long) offset, dierr); return dierr; } dierr = fpDataGFD->Read(buf, size); if (dierr != kDIErrNone) { LOGI(" DI read off=%ld size=%d failed (err=%d)", (long) offset, size, dierr); return dierr; } return kDIErrNone; } /* * Copy a chunk of bytes into the disk image. * * Sets the "dirty" flag. * * (This is the lowest-level write routine in DiskImg.) */ DIError DiskImg::CopyBytesIn(const void* buf, di_off_t offset, int size) { DIError dierr; if (fReadOnly) { DebugBreak(); return kDIErrAccessDenied; } assert(fpDataGFD != NULL); // somebody closed the image? dierr = fpDataGFD->Seek(offset, kSeekSet); if (dierr != kDIErrNone) { LOGI(" DI seek off=%ld failed (err=%d)", (long) offset, dierr); return dierr; } dierr = fpDataGFD->Write(buf, size); if (dierr != kDIErrNone) { LOGI(" DI write off=%ld size=%d failed (err=%d)", (long) offset, size, dierr); return dierr; } /* set the dirty flag here and everywhere above */ DiskImg* pImg = this; while (pImg != NULL) { pImg->fDirty = true; pImg = pImg->fpParentImg; } return kDIErrNone; } /* * =========================================================================== * Image creation * =========================================================================== */ /* * Create a disk image with the specified parameters. * * "storageName" and "pNibbleDescr" may be NULL. */ DIError DiskImg::CreateImage(const char* pathName, const char* storageName, OuterFormat outerFormat, FileFormat fileFormat, PhysicalFormat physical, const NibbleDescr* pNibbleDescr, SectorOrder order, FSFormat format, long numBlocks, bool skipFormat) { assert(fpDataGFD == NULL); // should not be open already! if (numBlocks <= 0) { LOGI("ERROR: bad numBlocks %ld", numBlocks); assert(false); return kDIErrInvalidCreateReq; } fOuterFormat = outerFormat; fFileFormat = fileFormat; fPhysical = physical; SetCustomNibbleDescr(pNibbleDescr); fOrder = order; fFormat = format; fNumBlocks = numBlocks; fHasBlocks = true; return CreateImageCommon(pathName, storageName, skipFormat); } DIError DiskImg::CreateImage(const char* pathName, const char* storageName, OuterFormat outerFormat, FileFormat fileFormat, PhysicalFormat physical, const NibbleDescr* pNibbleDescr, SectorOrder order, FSFormat format, long numTracks, long numSectPerTrack, bool skipFormat) { assert(fpDataGFD == NULL); // should not be open already! if (numTracks <= 0 || numSectPerTrack == 0) { LOGI("ERROR: bad tracks/sectors %ld/%ld", numTracks, numSectPerTrack); assert(false); return kDIErrInvalidCreateReq; } fOuterFormat = outerFormat; fFileFormat = fileFormat; fPhysical = physical; SetCustomNibbleDescr(pNibbleDescr); fOrder = order; fFormat = format; fNumTracks = numTracks; fNumSectPerTrack = numSectPerTrack; fHasSectors = true; if (numSectPerTrack < 0) { /* nibble image with non-standard formatting */ if (!IsNibbleFormat(fPhysical)) { LOGI("Whoa: expected nibble format here"); assert(false); return kDIErrInvalidCreateReq; } LOGI("Sector image w/o sectors, switching to nibble mode"); fHasNibbles = true; fHasSectors = false; fpNibbleDescr = NULL; } return CreateImageCommon(pathName, storageName, skipFormat); } /* * Do the actual disk image creation. */ DIError DiskImg::CreateImageCommon(const char* pathName, const char* storageName, bool skipFormat) { DIError dierr; /* * Step 1: figure out fHasBlocks/fHasSectors/fHasNibbles and any * other misc fields. * * If the disk is a nibble image expected to have a particular * volume number, it should have already been set by the application. */ if (fHasBlocks) { if ((fNumBlocks % 8) == 0) { fHasSectors = true; fNumSectPerTrack = 16; fNumTracks = fNumBlocks / 8; } else { LOGI("NOTE: sector access to new image not possible"); } } else if (fHasSectors) { if ((fNumSectPerTrack & 0x01) == 0) { fHasBlocks = true; fNumBlocks = (fNumTracks * fNumSectPerTrack) / 2; } else { LOGI("NOTE: block access to new image not possible"); } } if (fHasSectors && fPhysical != kPhysicalFormatSectors) fHasNibbles = true; assert(fHasBlocks || fHasSectors || fHasNibbles); fFileSysOrder = CalcFSSectorOrder(); fReadOnly = false; fDirty = true; /* * Step 2: check for invalid arguments and bad combinations. */ dierr = ValidateCreateFormat(); if (dierr != kDIErrNone) { LOGE("ERROR: CIC arg validation failed, bailing"); goto bail; } /* * Step 3: create the destination file. Put this into fpWrapperGFD * or fpOuterGFD. * * The file must not already exist. * * THOUGHT: should allow creation of an in-memory disk image. This won't * work for NuFX, but will work for pretty much everything else. */ LOGI(" CIC: creating '%s'", pathName); int fd; fd = open(pathName, O_CREAT | O_EXCL, 0644); if (fd < 0) { dierr = (DIError) errno; LOGE("ERROR: unable to create file '%s' (errno=%d)", pathName, dierr); goto bail; } close(fd); GFDFile* pGFDFile; pGFDFile = new GFDFile; dierr = pGFDFile->Open(pathName, false); if (dierr != kDIErrNone) { delete pGFDFile; goto bail; } if (fOuterFormat == kOuterFormatNone) fpWrapperGFD = pGFDFile; else fpOuterGFD = pGFDFile; pGFDFile = NULL; /* * Step 4: if we have an outer GFD and therefore don't currently have * an fpWrapperGFD, create an expandable memory buffer to use. * * We want to take a guess at how big the image will be, so compute * fLength now. * * Create an OuterWrapper as needed. */ if (IsSectorFormat(fPhysical)) { if (fHasBlocks) fLength = (di_off_t) GetNumBlocks() * kBlockSize; else fLength = (di_off_t) GetNumTracks() * GetNumSectPerTrack() * kSectorSize; } else { assert(IsNibbleFormat(fPhysical)); fLength = GetNumTracks() * GetNibbleTrackAllocLength(); } assert(fLength > 0); if (fpWrapperGFD == NULL) { /* shift GFDs and create a new memory GFD, pre-sized */ GFDBuffer* pGFDBuffer = new GFDBuffer; /* use fLength as a starting point for buffer size; this may expand */ dierr = pGFDBuffer->Open(NULL, fLength, true, true, false); if (dierr != kDIErrNone) { delete pGFDBuffer; goto bail; } fpWrapperGFD = pGFDBuffer; pGFDBuffer = NULL; } /* create an fpOuterWrapper struct */ switch (fOuterFormat) { case kOuterFormatNone: break; case kOuterFormatGzip: fpOuterWrapper = new OuterGzip; if (fpOuterWrapper == NULL) { dierr = kDIErrMalloc; goto bail; } break; case kOuterFormatZip: fpOuterWrapper = new OuterZip; if (fpOuterWrapper == NULL) { dierr = kDIErrMalloc; goto bail; } break; default: assert(false); dierr = kDIErrInternal; goto bail; } /* * Step 5: tell the ImageWrapper to write itself into the GFD, passing * in the blank memory buffer. * * - Unadorned formats copy from memory buffer to fpWrapperGFD on disk. * (With gz, fpWrapperGFD is actually a memory buffer.) fpDataGFD * becomes an offset into the file. * - 2MG writes header into GFD and follows it with all data; DC42 * and Sim2e do similar things. * - NuFX reopens pathName as SHK file (fpWrapperGFD must point to a * file) and accesses the archive through an fpArchive. fpDataGFD * is created as a memory buffer and the blank image is copied in. * - DDD leaves fpWrapperGFD alone and copies the blank image into a * new buffer for fpDataGFD. * * Sets fWrappedLength when possible, determined from fPhysical and * either fNumBlocks or fNumTracks. Creates fpDataGFD, often as a * GFDGFD offset into fpWrapperGFD. */ switch (fFileFormat) { case kFileFormat2MG: fpImageWrapper = new Wrapper2MG(); break; case kFileFormatDiskCopy42: fpImageWrapper = new WrapperDiskCopy42(); fpImageWrapper->SetStorageName(storageName); break; case kFileFormatSim2eHDV: fpImageWrapper = new WrapperSim2eHDV(); break; case kFileFormatTrackStar: fpImageWrapper = new WrapperTrackStar(); fpImageWrapper->SetStorageName(storageName); break; case kFileFormatFDI: fpImageWrapper = new WrapperFDI(); break; case kFileFormatNuFX: fpImageWrapper = new WrapperNuFX(); fpImageWrapper->SetStorageName(storageName); ((WrapperNuFX*)fpImageWrapper)->SetCompressType( (NuThreadFormat) fNuFXCompressType); break; case kFileFormatDDD: fpImageWrapper = new WrapperDDD(); break; case kFileFormatUnadorned: if (IsSectorFormat(fPhysical)) fpImageWrapper = new WrapperUnadornedSector(); else if (IsNibbleFormat(fPhysical)) fpImageWrapper = new WrapperUnadornedNibble(); else { assert(false); } break; default: assert(fpImageWrapper == NULL); break; } if (fpImageWrapper == NULL) { LOGW(" DI couldn't figure out the file format"); dierr = kDIErrUnrecognizedFileFmt; goto bail; } /* create the wrapper, write the header, and create fpDataGFD */ assert(fpDataGFD == NULL); dierr = fpImageWrapper->Create(fLength, fPhysical, fOrder, fDOSVolumeNum, fpWrapperGFD, &fWrappedLength, &fpDataGFD); if (dierr != kDIErrNone) { LOGE("ImageWrapper Create failed, err=%d", dierr); goto bail; } assert(fpDataGFD != NULL); /* * Step 6: "format" fpDataGFD. * * Note we don't specify an ordering to the "create blank" functions. * Either it's sectors, in which case it's all zeroes, or it's nibbles, * in which case it's always in physical order. * * If we're formatting for nibbles, and the application hasn't specified * a disk volume number, use the default (254). */ if (fPhysical == kPhysicalFormatSectors) dierr = FormatSectors(fpDataGFD, skipFormat); // zero out the image else { assert(!skipFormat); // don't skip low-level nibble formatting! if (fDOSVolumeNum == kVolumeNumNotSet) { fDOSVolumeNum = kDefaultNibbleVolumeNum; LOGD(" Using default nibble volume num"); } dierr = FormatNibbles(fpDataGFD); // write basic nibble stuff } /* * We're done! * * Quick sanity check... */ if (fOuterFormat != kOuterFormatNone) { assert(fpOuterGFD != NULL); assert(fpWrapperGFD != NULL); assert(fpDataGFD != NULL); } bail: return dierr; } /* * Check that the requested format is one we can create. * * We don't allow .SDK.GZ or 6384-byte nibble 2MG. 2MG sector images * must be in DOS or ProDOS order. * * Only "generic" FS formats may be used. The application may choose * to call AnalyzeImage later on to set the actual FS once data has * been written. */ DIError DiskImg::ValidateCreateFormat(void) const { /* * Check for invalid arguments. */ if (fHasBlocks && fNumBlocks >= 4194304) { // 2GB or larger? if (fFileFormat != kFileFormatUnadorned) { LOGW("CreateImage: images >= 2GB can only be unadorned"); return kDIErrInvalidCreateReq; } } if (fOuterFormat == kOuterFormatUnknown || fFileFormat == kFileFormatUnknown || fPhysical == kPhysicalFormatUnknown || fOrder == kSectorOrderUnknown || fFormat == kFormatUnknown) { LOGW("CreateImage: ambiguous format"); return kDIErrInvalidCreateReq; } if (fOuterFormat != kOuterFormatNone && fOuterFormat != kOuterFormatGzip && fOuterFormat != kOuterFormatZip) { LOGW("CreateImage: unsupported outer format %d", fOuterFormat); return kDIErrInvalidCreateReq; } if (fFileFormat != kFileFormatUnadorned && fFileFormat != kFileFormat2MG && fFileFormat != kFileFormatDiskCopy42 && fFileFormat != kFileFormatSim2eHDV && fFileFormat != kFileFormatTrackStar && fFileFormat != kFileFormatFDI && fFileFormat != kFileFormatNuFX && fFileFormat != kFileFormatDDD) { LOGW("CreateImage: unsupported file format %d", fFileFormat); return kDIErrInvalidCreateReq; } if (fFormat != kFormatGenericPhysicalOrd && fFormat != kFormatGenericProDOSOrd && fFormat != kFormatGenericDOSOrd && fFormat != kFormatGenericCPMOrd) { LOGW("CreateImage: may only use 'generic' formats"); return kDIErrInvalidCreateReq; } /* * Check for invalid combinations. */ if (fPhysical != kPhysicalFormatSectors) { if (fOrder != kSectorOrderPhysical) { LOGW("CreateImage: nibble images are always 'physical' order"); return kDIErrInvalidCreateReq; } if (GetHasSectors() == false && GetHasNibbles() == false) { LOGW("CreateImage: must set hasSectors(%d) or hasNibbles(%d)", GetHasSectors(), GetHasNibbles()); return kDIErrInvalidCreateReq; } if (fpNibbleDescr == NULL && GetNumSectPerTrack() > 0) { LOGW("CreateImage: must provide NibbleDescr for non-sector"); return kDIErrInvalidCreateReq; } if (fpNibbleDescr != NULL && fpNibbleDescr->numSectors != GetNumSectPerTrack()) { LOGW("CreateImage: ?? nd->numSectors=%d, GetNumSectPerTrack=%d", fpNibbleDescr->numSectors, GetNumSectPerTrack()); return kDIErrInvalidCreateReq; } if (fpNibbleDescr != NULL && ( (fpNibbleDescr->numSectors == 13 && fpNibbleDescr->encoding != kNibbleEnc53) || (fpNibbleDescr->numSectors == 16 && fpNibbleDescr->encoding != kNibbleEnc62)) ) { LOGW("CreateImage: sector count/encoding mismatch"); return kDIErrInvalidCreateReq; } if (GetNumTracks() != kTrackCount525 && !(GetNumTracks() == 40 && fFileFormat == kFileFormatTrackStar)) { LOGW("CreateImage: unexpected track count %ld", GetNumTracks()); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormat2MG) { if (fPhysical != kPhysicalFormatSectors && fPhysical != kPhysicalFormatNib525_6656) { LOGW("CreateImage: 2MG can't handle physical %d", fPhysical); return kDIErrInvalidCreateReq; } if (fPhysical == kPhysicalFormatSectors && (fOrder != kSectorOrderProDOS && fOrder != kSectorOrderDOS)) { LOGW("CreateImage: 2MG requires DOS or ProDOS ordering"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatNuFX) { if (fOuterFormat != kOuterFormatNone) { LOGW("CreateImage: can't mix NuFX and outer wrapper"); return kDIErrInvalidCreateReq; } if (fPhysical != kPhysicalFormatSectors) { LOGW("CreateImage: NuFX physical must be sectors"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderProDOS) { LOGW("CreateImage: NuFX is always ProDOS-order"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatDiskCopy42) { if (fPhysical != kPhysicalFormatSectors) { LOGW("CreateImage: DC42 physical must be sectors"); return kDIErrInvalidCreateReq; } if ((GetHasBlocks() && GetNumBlocks() != 1600) || (GetHasSectors() && (GetNumTracks() != 200 || GetNumSectPerTrack() != 16))) { LOGW("CreateImage: DC42 only for 800K disks"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderProDOS && fOrder != kSectorOrderDOS) // used for UNIDOS disks?? { LOGW("CreateImage: DC42 is always ProDOS or DOS"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatSim2eHDV) { if (fPhysical != kPhysicalFormatSectors) { LOGW("CreateImage: Sim2eHDV physical must be sectors"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderProDOS) { LOGW("CreateImage: Sim2eHDV is always ProDOS-order"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatTrackStar) { if (fPhysical != kPhysicalFormatNib525_Var) { LOGW("CreateImage: TrackStar physical must be var-nibbles"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatFDI) { if (fPhysical != kPhysicalFormatNib525_Var) { LOGW("CreateImage: FDI physical must be var-nibbles"); return kDIErrInvalidCreateReq; } } if (fFileFormat == kFileFormatDDD) { if (fPhysical != kPhysicalFormatSectors) { LOGW("CreateImage: DDD physical must be sectors"); return kDIErrInvalidCreateReq; } if (fOrder != kSectorOrderDOS) { LOGW("CreateImage: DDD is always DOS-order"); return kDIErrInvalidCreateReq; } if (!GetHasSectors() || GetNumTracks() != 35 || GetNumSectPerTrack() != 16) { LOGW("CreateImage: DDD is only for 16-sector 35-track disks"); return kDIErrInvalidCreateReq; } } return kDIErrNone; } /* * Create a blank image for physical=="sectors". * * fLength must be a multiple of 256. * * If "quickFormat" is set, only the very last sector is written (to set * the EOF on the file). */ DIError DiskImg::FormatSectors(GenericFD* pGFD, bool quickFormat) const { DIError dierr = kDIErrNone; char sctBuf[kSectorSize]; di_off_t length; assert(fLength > 0 && (fLength & 0xff) == 0); //if (!(fLength & 0x01)) // return FormatBlocks(pGFD); memset(sctBuf, 0, sizeof(sctBuf)); pGFD->Rewind(); if (quickFormat) { dierr = pGFD->Seek(fLength - sizeof(sctBuf), kSeekSet); if (dierr != kDIErrNone) { LOGI(" FormatSectors: GFD seek %ld failed (err=%d)", (long) fLength - sizeof(sctBuf), dierr); goto bail; } dierr = pGFD->Write(sctBuf, sizeof(sctBuf), NULL); if (dierr != kDIErrNone) { LOGI(" FormatSectors: GFD quick write failed (err=%d)", dierr); goto bail; } } else { for (length = fLength ; length > 0; length -= sizeof(sctBuf)) { dierr = pGFD->Write(sctBuf, sizeof(sctBuf), NULL); if (dierr != kDIErrNone) { LOGI(" FormatSectors: GFD write failed (err=%d)", dierr); goto bail; } } assert(length == 0); } bail: return dierr; } #if 0 // didn't help /* * Create a blank image for physical=="sectors". This is called from * FormatSectors when it looks like we're formatting entire blocks. */ DIError DiskImg::FormatBlocks(GenericFD* pGFD) const { DIError dierr; char blkBuf[kBlockSize]; long length; time_t start, end; assert(fLength > 0 && (fLength & 0x1ff) == 0); start = time(NULL); memset(blkBuf, 0, sizeof(blkBuf)); pGFD->Rewind(); for (length = fLength ; length > 0; length -= sizeof(blkBuf)) { dierr = pGFD->Write(blkBuf, sizeof(blkBuf), NULL); if (dierr != kDIErrNone) { LOGI(" FormatBlocks: GFD write failed (err=%d)", dierr); return dierr; } } assert(length == 0); end = time(NULL); LOGI("FormatBlocks complete, time=%ld", end - start); return kDIErrNone; } #endif /* * =========================================================================== * Utility functions * =========================================================================== */ /* * Add a note to this disk image. * * This is how we communicate cautions and warnings to the user. Use * linefeeds ('\n') to indicate line breaks. * * The maximum length of a single note is set by the size of "buf". */ void DiskImg::AddNote(NoteType type, const char* fmt, ...) { char buf[512]; char* cp = buf; int maxLen = sizeof(buf); va_list args; int len; /* * Prepend a string that highlights the note. */ switch (type) { case kNoteWarning: strcpy(cp, "- WARNING: "); break; default: strcpy(cp, "- "); break; } len = strlen(cp); cp += len; maxLen -= len; /* * Add the note. */ va_start(args, fmt); #if defined(HAVE_VSNPRINTF) (void) vsnprintf(cp, maxLen, fmt, args); #elif defined(HAVE__VSNPRINTF) (void) _vsnprintf(cp, maxLen, fmt, args); #else # error "hosed" #endif va_end(args); buf[sizeof(buf)-2] = '\0'; // leave room for additional '\n' len = strlen(buf); if (len > 0 && buf[len-1] != '\n') { buf[len] = '\n'; buf[len+1] = '\0'; len++; } LOGD("+++ adding note '%s'", buf); if (fNotes == NULL) { fNotes = new char[len +1]; if (fNotes == NULL) { LOGW("Unable to create notes[%d]", len+1); assert(false); return; } strcpy(fNotes, buf); } else { int existingLen = strlen(fNotes); char* newNotes = new char[existingLen + len +1]; if (newNotes == NULL) { LOGW("Unable to create newNotes[%d]", existingLen+len+1); assert(false); return; } strcpy(newNotes, fNotes); strcpy(newNotes + existingLen, buf); delete[] fNotes; fNotes = newNotes; } } /* * Return a string with the notes in it. */ const char* DiskImg::GetNotes(void) const { if (fNotes == NULL) return ""; else return fNotes; } /* * Get length and offset of tracks in a nibble image. This is necessary * because of formats with variable-length tracks (e.g. TrackStar). */ int DiskImg::GetNibbleTrackLength(long track) const { assert(fpImageWrapper != NULL); return fpImageWrapper->GetNibbleTrackLength(fPhysical, track); } int DiskImg::GetNibbleTrackOffset(long track) const { assert(fpImageWrapper != NULL); return fpImageWrapper->GetNibbleTrackOffset(fPhysical, track); } /* * Return a new object with the appropriate DiskFS sub-class. * * If the image hasn't been analyzed, or was analyzed to no avail, "NULL" * is returned unless "allowUnknown" is set to "true". In that case, a * DiskFSUnknown is returned. * * This doesn't inspire the DiskFS to do any processing, just creates the * new object. */ DiskFS* DiskImg::OpenAppropriateDiskFS(bool allowUnknown) { DiskFS* pDiskFS = NULL; /* * Create an appropriate DiskFS object. */ switch (GetFSFormat()) { case DiskImg::kFormatDOS33: case DiskImg::kFormatDOS32: pDiskFS = new DiskFSDOS33(); break; case DiskImg::kFormatProDOS: pDiskFS = new DiskFSProDOS(); break; case DiskImg::kFormatPascal: pDiskFS = new DiskFSPascal(); break; case DiskImg::kFormatMacHFS: pDiskFS = new DiskFSHFS(); break; case DiskImg::kFormatUNIDOS: pDiskFS = new DiskFSUNIDOS(); break; case DiskImg::kFormatOzDOS: pDiskFS = new DiskFSOzDOS(); break; case DiskImg::kFormatCFFA4: case DiskImg::kFormatCFFA8: pDiskFS = new DiskFSCFFA(); break; case DiskImg::kFormatMacPart: pDiskFS = new DiskFSMacPart(); break; case DiskImg::kFormatMicroDrive: pDiskFS = new DiskFSMicroDrive(); break; case DiskImg::kFormatFocusDrive: pDiskFS = new DiskFSFocusDrive(); break; case DiskImg::kFormatCPM: pDiskFS = new DiskFSCPM(); break; case DiskImg::kFormatMSDOS: pDiskFS = new DiskFSFAT(); break; case DiskImg::kFormatRDOS33: case DiskImg::kFormatRDOS32: case DiskImg::kFormatRDOS3: pDiskFS = new DiskFSRDOS(); break; case DiskImg::kFormatGutenberg: pDiskFS = new DiskFSGutenberg(); break; default: LOGI("WARNING: unhandled DiskFS case %d", GetFSFormat()); assert(false); /* fall through */ case DiskImg::kFormatGenericPhysicalOrd: case DiskImg::kFormatGenericProDOSOrd: case DiskImg::kFormatGenericDOSOrd: case DiskImg::kFormatGenericCPMOrd: case DiskImg::kFormatUnknown: if (allowUnknown) { pDiskFS = new DiskFSUnknown(); break; } } return pDiskFS; } /* * Fill an array with SectorOrder values. The ordering specified by "first" * will come first. Unused entries will be set to "unknown" and should be * ignored. * * "orderArray" must have kSectorOrderMax elements. */ /*static*/ void DiskImg::GetSectorOrderArray(SectorOrder* orderArray, SectorOrder first) { // init array for (int i = 0; i < kSectorOrderMax; i++) orderArray[i] = (SectorOrder) i; // pull the best-guess ordering to the front assert(orderArray[0] == kSectorOrderUnknown); orderArray[0] = first; orderArray[(int) first] = kSectorOrderUnknown; // don't bother checking CP/M sector order orderArray[kSectorOrderCPM] = kSectorOrderUnknown; } /* * Return a short string describing "format". * * These are semi-duplicated in ImageFormatDialog.cpp in CiderPress. */ /*static*/ const char* DiskImg::ToStringCommon(int format, const ToStringLookup* pTable, int tableSize) { for (int i = 0; i < tableSize; i++) { if (pTable[i].format == format) return pTable[i].str; } assert(false); return "(unknown)"; } /*static*/ const char* DiskImg::ToString(OuterFormat format) { static const ToStringLookup kOuterFormats[] = { { DiskImg::kOuterFormatUnknown, "Unknown format" }, { DiskImg::kOuterFormatNone, "(none)" }, { DiskImg::kOuterFormatCompress, "UNIX compress" }, { DiskImg::kOuterFormatGzip, "gzip" }, { DiskImg::kOuterFormatBzip2, "bzip2" }, { DiskImg::kOuterFormatZip, "Zip archive" }, }; return ToStringCommon(format, kOuterFormats, NELEM(kOuterFormats)); } /*static*/ const char* DiskImg::ToString(FileFormat format) { static const ToStringLookup kFileFormats[] = { { DiskImg::kFileFormatUnknown, "Unknown format" }, { DiskImg::kFileFormatUnadorned, "Unadorned raw data" }, { DiskImg::kFileFormat2MG, "2MG" }, { DiskImg::kFileFormatNuFX, "NuFX (ShrinkIt)" }, { DiskImg::kFileFormatDiskCopy42, "DiskCopy 4.2" }, { DiskImg::kFileFormatDiskCopy60, "DiskCopy 6.0" }, { DiskImg::kFileFormatDavex, "Davex volume image" }, { DiskImg::kFileFormatSim2eHDV, "Sim //e HDV" }, { DiskImg::kFileFormatTrackStar, "TrackStar image" }, { DiskImg::kFileFormatFDI, "FDI image" }, { DiskImg::kFileFormatDDD, "DDD" }, { DiskImg::kFileFormatDDDDeluxe, "DDDDeluxe" }, }; return ToStringCommon(format, kFileFormats, NELEM(kFileFormats)); }; /*static*/ const char* DiskImg::ToString(PhysicalFormat format) { static const ToStringLookup kPhysicalFormats[] = { { DiskImg::kPhysicalFormatUnknown, "Unknown format" }, { DiskImg::kPhysicalFormatSectors, "Sectors" }, { DiskImg::kPhysicalFormatNib525_6656, "Raw nibbles (6656-byte)" }, { DiskImg::kPhysicalFormatNib525_6384, "Raw nibbles (6384-byte)" }, { DiskImg::kPhysicalFormatNib525_Var, "Raw nibbles (variable len)" }, }; return ToStringCommon(format, kPhysicalFormats, NELEM(kPhysicalFormats)); }; /*static*/ const char* DiskImg::ToString(SectorOrder format) { static const ToStringLookup kSectorOrders[] = { { DiskImg::kSectorOrderUnknown, "Unknown ordering" }, { DiskImg::kSectorOrderProDOS, "ProDOS block ordering" }, { DiskImg::kSectorOrderDOS, "DOS sector ordering" }, { DiskImg::kSectorOrderCPM, "CP/M block ordering" }, { DiskImg::kSectorOrderPhysical, "Physical sector ordering" }, }; return ToStringCommon(format, kSectorOrders, NELEM(kSectorOrders)); }; /*static*/ const char* DiskImg::ToString(FSFormat format) { static const ToStringLookup kFSFormats[] = { { DiskImg::kFormatUnknown, "Unknown" }, { DiskImg::kFormatProDOS, "ProDOS" }, { DiskImg::kFormatDOS33, "DOS 3.3" }, { DiskImg::kFormatDOS32, "DOS 3.2" }, { DiskImg::kFormatPascal, "Pascal" }, { DiskImg::kFormatMacHFS, "HFS" }, { DiskImg::kFormatMacMFS, "MFS" }, { DiskImg::kFormatLisa, "Lisa" }, { DiskImg::kFormatCPM, "CP/M" }, { DiskImg::kFormatMSDOS, "MS-DOS FAT" }, { DiskImg::kFormatISO9660, "ISO-9660" }, { DiskImg::kFormatRDOS33, "RDOS 3.3 (16-sector)" }, { DiskImg::kFormatRDOS32, "RDOS 3.2 (13-sector)" }, { DiskImg::kFormatRDOS3, "RDOS 3 (cracked 13-sector)" }, { DiskImg::kFormatGenericDOSOrd, "Generic DOS sectors" }, { DiskImg::kFormatGenericProDOSOrd, "Generic ProDOS blocks" }, { DiskImg::kFormatGenericPhysicalOrd, "Generic raw sectors" }, { DiskImg::kFormatGenericCPMOrd, "Generic CP/M blocks" }, { DiskImg::kFormatUNIDOS, "UNIDOS (400K DOS x2)" }, { DiskImg::kFormatOzDOS, "OzDOS (400K DOS x2)" }, { DiskImg::kFormatCFFA4, "CFFA (4 or 6 partitions)" }, { DiskImg::kFormatCFFA8, "CFFA (8 partitions)" }, { DiskImg::kFormatMacPart, "Macintosh partitioned disk" }, { DiskImg::kFormatMicroDrive, "MicroDrive partitioned disk" }, { DiskImg::kFormatFocusDrive, "FocusDrive partitioned disk" }, }; return ToStringCommon(format, kFSFormats, NELEM(kFSFormats)); }; /* * strerror() equivalent for DiskImg errors. */ const char* DiskImgLib::DIStrError(DIError dierr) { if (dierr > 0) { const char* msg; msg = strerror(dierr); if (msg != NULL) return msg; } /* * BUG: this should be set up as per-thread storage in an MT environment. * I would be more inclined to worry about this if I was expecting * to hit "default:". So long as valid values are passed in, and the * switch statement is kept up to date, we should never have cause * to return this. * * An easier solution, should this present a problem for someone, would * be to have the function return NULL or "unknown error" when the * error value isn't recognized. I'd recommend leaving it as-is for * debug builds, though, as it's helpful to know *which* error is not * recognized. */ static char defaultMsg[32]; switch (dierr) { case kDIErrNone: return "(no error)"; case kDIErrAccessDenied: return "access denied"; case kDIErrVWAccessForbidden: return "for safety, write access to this volume is forbidden"; case kDIErrSharingViolation: return "file is already open and cannot be shared"; case kDIErrNoExclusiveAccess: return "couldn't get exclusive access"; case kDIErrWriteProtected: return "write protected"; case kDIErrCDROMNotSupported: return "access to CD-ROM drives is not supported"; case kDIErrASPIFailure: return "an ASPI request failed"; case kDIErrSPTIFailure: return "an SPTI request failed"; case kDIErrSCSIFailure: return "a SCSI request failed"; case kDIErrDeviceNotReady: return "device not ready"; case kDIErrFileNotFound: return "file not found"; case kDIErrForkNotFound: return "fork not found"; case kDIErrAlreadyOpen: return "an image is already open"; case kDIErrFileOpen: return "file is open"; case kDIErrNotReady: return "object not ready"; case kDIErrFileExists: return "file already exists"; case kDIErrDirectoryExists: return "directory already exists"; case kDIErrEOF: return "end of file reached"; case kDIErrReadFailed: return "read failed"; case kDIErrWriteFailed: return "write failed"; case kDIErrDataUnderrun: return "tried to read past end of file"; case kDIErrDataOverrun: return "tried to write past end of file"; case kDIErrGenericIO: return "I/O error"; case kDIErrOddLength: return "image size is wrong"; case kDIErrUnrecognizedFileFmt: return "not a recognized disk image format"; case kDIErrBadFileFormat: return "image file contents aren't in expected format"; case kDIErrUnsupportedFileFmt: return "file format not supported"; case kDIErrUnsupportedPhysicalFmt: return "physical format not supported"; case kDIErrUnsupportedFSFmt: return "filesystem type not supported"; case kDIErrBadOrdering: return "bad sector ordering"; case kDIErrFilesystemNotFound: return "specified filesystem not found"; case kDIErrUnsupportedAccess: return "the method of access used isn't supported for this image"; case kDIErrUnsupportedImageFeature: return "image file uses features that CiderPress doesn't support"; case kDIErrInvalidTrack: return "invalid track number"; case kDIErrInvalidSector: return "invalid sector number"; case kDIErrInvalidBlock: return "invalid block number"; case kDIErrInvalidIndex: return "invalid index number"; case kDIErrDirectoryLoop: return "disk directory structure has an infinite loop"; case kDIErrFileLoop: return "file structure has an infinite loop"; case kDIErrBadDiskImage: return "the filesystem on this image appears damaged"; case kDIErrBadFile: return "file structure appears damaged"; case kDIErrBadDirectory: return "a directory appears damaged"; case kDIErrBadPartition: return "bad partition"; case kDIErrFileArchive: return "this looks like a file archive, not a disk archive"; case kDIErrUnsupportedCompression: return "compression method not supported"; case kDIErrBadChecksum: return "checksum doesn't match, data may be corrupted"; case kDIErrBadCompressedData: return "the compressed data is corrupted"; case kDIErrBadArchiveStruct: return "archive may be damaged"; case kDIErrBadNibbleSectors: return "couldn't read sectors from this image"; case kDIErrSectorUnreadable: return "sector not readable"; case kDIErrInvalidDiskByte: return "found invalid nibble image disk byte"; case kDIErrBadRawData: return "couldn't convert raw data to nibble data"; case kDIErrInvalidFileName: return "invalid file name"; case kDIErrDiskFull: return "disk full"; case kDIErrVolumeDirFull: return "volume directory is full"; case kDIErrInvalidCreateReq: return "invalid disk image create request"; case kDIErrTooBig: return "size is larger than we can handle"; case kDIErrGeneric: return "DiskImg generic error"; case kDIErrInternal: return "DiskImg internal error"; case kDIErrMalloc: return "memory allocation failure"; case kDIErrInvalidArg: return "invalid argument"; case kDIErrNotSupported: return "feature not supported"; case kDIErrCancelled: return "cancelled by user"; case kDIErrNufxLibInitFailed: return "NufxLib initialization failed"; default: sprintf(defaultMsg, "(error=%d)", dierr); return defaultMsg; } } /* * High ASCII conversion table, from Technical Note PT515, * "Apple File Exchange Q&As". The table is available in a hopelessly * blurry PDF or a pair of GIFs created with small fonts, but I think I * have mostly captured it. */ /*static*/ const uint8_t DiskImg::kMacHighASCII[128+1] = "AACENOUaaaaaaceeeeiiiinooooouuuu" // 0x80 - 0x9f "tocL$oPBrct'.=AO%+<>YudsPpSaoOao" // 0xa0 - 0xbf "?!-vf=d<>. AAOOo--\"\"''/oyY/o<> f" // 0xc0 - 0xdf "|*,,%AEAEEIIIIOOaOUUUi^~-,**,\"? "; // 0xe0 - 0xff /* * Hack for Win32 systems. See Win32BlockIO.cpp for commentary. */ bool DiskImgLib::gAllowWritePhys0 = false; /*static*/ void DiskImg::SetAllowWritePhys0(bool val) { DiskImgLib::gAllowWritePhys0 = val; }