/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Implementation of DiskFSRDOS class. */ #include "StdAfx.h" #include "DiskImgPriv.h" /* * =========================================================================== * DiskFSRDOS * =========================================================================== */ const int kSctSize = 256; const int kCatTrack = 1; const int kNumCatSectors = 11; // 0 through 10 const int kDirectoryEntryLen = 32; const int kNumDirEntryPerSect = (256 / kDirectoryEntryLen); // 8 /* * See if this looks like a RDOS volume. * * There are three variants: * RDOS32 (e.g. ComputerAmbush.nib): * 13-sector disk * sector (1,0) starts with "RDOS 2" * sector (1,12) has catalog code, CHAIN in (1,11) * uses "physical" ordering * NOTE: track 0 may be unreadable with RDOS 3.2 NibbleDescr * RDOS33 (e.g. disk #199): * 16-sector disk * sector (1,0) starts with "RDOS 3" * sector (1,12) has catalog code * uses "ProDOS" ordering * RDOS3 (e.g. disk #108): * 16-sector disk, but only 13 sectors of each track are used * sector (1,0) starts with "RDOS 2" * sector (0,1) has catalog code * uses "physical" orering * * In all cases: * catalog found on (1,0) through (1,10) * * The initial value of "pFormatFound" is ignored, because we can reliably * detect which variant we're looking at. */ static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder, DiskImg::FSFormat* pFormatFound) { DIError dierr = kDIErrNone; uint8_t sctBuf[kSctSize]; if (pImg->GetNumSectPerTrack() == 13) { /* must be a nibble image; check it for RDOS 3.2 */ dierr = pImg->ReadTrackSectorSwapped(kCatTrack, 0, sctBuf, imageOrder, DiskImg::kSectorOrderPhysical); if (dierr != kDIErrNone) goto bail; } else if (pImg->GetNumSectPerTrack() == 16) { /* could be RDOS3 or RDOS 3.3 */ dierr = pImg->ReadTrackSectorSwapped(kCatTrack, 0, sctBuf, imageOrder, DiskImg::kSectorOrderPhysical); if (dierr != kDIErrNone) goto bail; } else { LOGI(" RDOS neither 13 nor 16 sector, bailing"); goto bail; } /* check for RDOS string and correct #of blocks */ if (!( sctBuf[0] == 'R'+0x80 && sctBuf[1] == 'D'+0x80 && sctBuf[2] == 'O'+0x80 && sctBuf[3] == 'S'+0x80 && sctBuf[4] == ' '+0x80) || !(sctBuf[25] == 26 || sctBuf[25] == 32)) { LOGI(" RDOS no signature found on (%d,0)", kCatTrack); dierr = kDIErrGeneric; goto bail; } /* * Guess at the format based on the first catalog entry, which usually * begins "RDOS 2.0", "RDOS 2.1", or "RDOS 3.3". */ if (pImg->GetNumSectPerTrack() == 13) { *pFormatFound = DiskImg::kFormatRDOS32; } else { if (sctBuf[5] == '2'+0x80) *pFormatFound = DiskImg::kFormatRDOS3; else *pFormatFound = DiskImg::kFormatRDOS33; } /* * The above came from sector 0, which doesn't help us figure out the * sector ordering. Look for the catalog code. */ { int track, sector, offset; uint8_t orMask; static const char* kCompare = ""; DiskImg::SectorOrder order; if (*pFormatFound == DiskImg::kFormatRDOS32 || *pFormatFound == DiskImg::kFormatRDOS3) { track = 1; sector = 12; offset = 0xa2; orMask = 0x80; order = DiskImg::kSectorOrderPhysical; } else { track = 0; sector = 1; offset = 0x98; orMask = 0; order = DiskImg::kSectorOrderProDOS; } dierr = pImg->ReadTrackSectorSwapped(track, sector, sctBuf, imageOrder, order); if (dierr != kDIErrNone) goto bail; int i; for (i = strlen(kCompare)-1; i >= 0; i--) { if (sctBuf[offset+i] != ((uint8_t)kCompare[i] | orMask)) break; } if (i >= 0) { dierr = kDIErrGeneric; goto bail; } LOGI(" RDOS found '%s' signature (order=%d)", kCompare, imageOrder); } dierr = kDIErrNone; bail: return dierr; } /* * Common RDOS test code. */ /*static*/ DIError DiskFSRDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, DiskImg::FSFormat* pFormat, FSLeniency leniency) { if (!pImg->GetHasSectors()) { LOGI(" RDOS - image doesn't have sectors, not trying"); return kDIErrFilesystemNotFound; } if (pImg->GetNumTracks() != 35) { LOGI(" RDOS - not a 35-track disk, not trying"); return kDIErrFilesystemNotFound; } DiskImg::FSFormat formatFound; DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax]; DiskImg::GetSectorOrderArray(ordering, *pOrder); for (int i = 0; i < DiskImg::kSectorOrderMax; i++) { if (ordering[i] == DiskImg::kSectorOrderUnknown) continue; if (TestImage(pImg, ordering[i], &formatFound) == kDIErrNone) { *pFormat = formatFound; *pOrder = ordering[i]; //*pFormat = DiskImg::kFormatXXX; return kDIErrNone; } } LOGI(" RDOS didn't find valid FS"); return kDIErrFilesystemNotFound; } #if 0 /* * Test to see if the image is an RDOS 3.3 disk. */ /*static*/ DIError DiskFSRDOS::TestFS33(DiskImg* pImg, DiskImg::SectorOrder* pOrder, FSLeniency leniency) { DIError dierr; DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown; dierr = TestCommon(pImg, pOrder, leniency, &formatFound); if (dierr != kDIErrNone) return dierr; if (formatFound != DiskImg::kFormatRDOS33) { LOGI(" RDOS found RDOS but wrong type"); return kDIErrFilesystemNotFound; } return kDIErrNone; } /* * Test to see if the image is an RDOS 3.2 disk. */ /*static*/ DIError DiskFSRDOS::TestFS32(DiskImg* pImg, DiskImg::SectorOrder* pOrder, FSLeniency leniency) { DIError dierr; DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown; dierr = TestCommon(pImg, pOrder, leniency, &formatFound); if (dierr != kDIErrNone) return dierr; if (formatFound != DiskImg::kFormatRDOS32) { LOGI(" RDOS found RDOS but wrong type"); return kDIErrFilesystemNotFound; } return kDIErrNone; } /* * Test to see if the image is an RDOS 3 (cracked 3.2) disk. */ /*static*/ DIError DiskFSRDOS::TestFS3(DiskImg* pImg, DiskImg::SectorOrder* pOrder, FSLeniency leniency) { DIError dierr; DiskImg::FSFormat formatFound = DiskImg::kFormatUnknown; dierr = TestCommon(pImg, pOrder, leniency, &formatFound); if (dierr != kDIErrNone) return dierr; if (formatFound != DiskImg::kFormatRDOS3) { LOGI(" RDOS found RDOS but wrong type"); return kDIErrFilesystemNotFound; } return kDIErrNone; } #endif /* * Get things rolling. * * Since we're assured that this is a valid disk, errors encountered from here * on out must be handled somehow, possibly by claiming that the disk is * completely full and has no files on it. */ DIError DiskFSRDOS::Initialize(void) { DIError dierr = kDIErrNone; const char* volStr; switch (GetDiskImg()->GetFSFormat()) { case DiskImg::kFormatRDOS33: volStr = "RDOS 3.3"; fOurSectPerTrack = 16; break; case DiskImg::kFormatRDOS32: volStr = "RDOS 3.2"; fOurSectPerTrack = 13; break; case DiskImg::kFormatRDOS3: volStr = "RDOS 3"; fOurSectPerTrack = 13; break; default: assert(false); return kDIErrInternal; } assert(strlen(volStr) < sizeof(fVolumeName)); strcpy(fVolumeName, volStr); dierr = ReadCatalog(); if (dierr != kDIErrNone) goto bail; fVolumeUsage.Create(fpImg->GetNumTracks(), fOurSectPerTrack); dierr = ScanFileUsage(); if (dierr != kDIErrNone) { /* this might not be fatal; just means that *some* files are bad */ goto bail; } fVolumeUsage.Dump(); //A2File* pFile; //pFile = GetNextFile(NULL); //while (pFile != NULL) { // pFile->Dump(); // pFile = GetNextFile(pFile); //} bail: return dierr; } /* * Read the catalog from the disk. * * To make life easy we slurp the whole thing into memory. */ DIError DiskFSRDOS::ReadCatalog(void) { DIError dierr = kDIErrNone; uint8_t* dir = NULL; uint8_t* dirPtr; int track, sector; dir = new uint8_t[kSctSize * kNumCatSectors]; if (dir == NULL) { dierr = kDIErrMalloc; goto bail; } track = kCatTrack; dirPtr = dir; for (sector = 0; sector < kNumCatSectors; sector++) { dierr = fpImg->ReadTrackSector(track, sector, dirPtr); if (dierr != kDIErrNone) goto bail; dirPtr += kSctSize; } int i; A2FileRDOS* pFile; dirPtr = dir; for (i = 0; i < kNumCatSectors * kNumDirEntryPerSect; i++, dirPtr += kDirectoryEntryLen) { if (dirPtr[0] == 0x80 || dirPtr[24] == 0xa0) // deleted file continue; if (dirPtr[24] == 0x00) // unused entry; must be at end of catalog break; pFile = new A2FileRDOS(this); memcpy(pFile->fRawFileName, dirPtr, A2FileRDOS::kMaxFileName); pFile->fRawFileName[A2FileRDOS::kMaxFileName] = '\0'; memcpy(pFile->fFileName, dirPtr, A2FileRDOS::kMaxFileName); pFile->fFileName[A2FileRDOS::kMaxFileName] = '\0'; pFile->FixFilename(); switch (dirPtr[24]) { case 'A'+0x80: pFile->fFileType = A2FileRDOS::kTypeApplesoft; break; case 'B'+0x80: pFile->fFileType = A2FileRDOS::kTypeBinary; break; case 'T'+0x80: pFile->fFileType = A2FileRDOS::kTypeText; break; // 0x00 is end of catalog, ' '+0x80 is deleted file, both handled above default: pFile->fFileType = A2FileRDOS::kTypeUnknown; break; } pFile->fNumSectors = dirPtr[25]; pFile->fLoadAddr = GetShortLE(&dirPtr[26]); pFile->fLength = GetShortLE(&dirPtr[28]); pFile->fStartSector = GetShortLE(&dirPtr[30]); if (pFile->fStartSector + pFile->fNumSectors > fpImg->GetNumTracks() * fOurSectPerTrack) { LOGI(" RDOS invalid start/count (%d + %d) (max %ld) '%s'", pFile->fStartSector, pFile->fNumSectors, fpImg->GetNumBlocks(), pFile->fFileName); pFile->fStartSector = pFile->fNumSectors = 0; pFile->fLength = 0; pFile->SetQuality(A2File::kQualityDamaged); } AddFileToList(pFile); } bail: delete[] dir; return dierr; } /* * Create the volume usage map. Since RDOS volumes have neither * in-use maps nor index blocks, this is pretty straightforward. */ DIError DiskFSRDOS::ScanFileUsage(void) { int track, sector, block, count; A2FileRDOS* pFile; pFile = (A2FileRDOS*) GetNextFile(NULL); while (pFile != NULL) { block = pFile->fStartSector; count = pFile->fNumSectors; while (count--) { track = block / fOurSectPerTrack; sector = block % fOurSectPerTrack; SetSectorUsage(track, sector, VolumeUsage::kChunkPurposeUserData); block++; } pFile = (A2FileRDOS*) GetNextFile(pFile); } return kDIErrNone; } /* * Update an entry in the usage map. */ void DiskFSRDOS::SetSectorUsage(long track, long sector, VolumeUsage::ChunkPurpose purpose) { VolumeUsage::ChunkState cstate; fVolumeUsage.GetChunkState(track, sector, &cstate); if (cstate.isUsed) { cstate.purpose = VolumeUsage::kChunkPurposeConflict; LOGI(" RDOS conflicting uses for sct=(%ld,%ld)", track, sector); } else { cstate.isUsed = true; cstate.isMarkedUsed = true; cstate.purpose = purpose; } fVolumeUsage.SetChunkState(track, sector, &cstate); } /* * =========================================================================== * A2FileRDOS * =========================================================================== */ /* * Convert RDOS file type to ProDOS file type. */ uint32_t A2FileRDOS::GetFileType(void) const { uint32_t retval; switch (fFileType) { case kTypeText: retval = 0x04; break; // TXT case kTypeApplesoft: retval = 0xfc; break; // BAS case kTypeBinary: retval = 0x06; break; // BIN case kTypeUnknown: default: retval = 0x00; break; // NON } return retval; } /* * Dump the contents of the A2File structure. */ void A2FileRDOS::Dump(void) const { LOGI("A2FileRDOS '%s' (type=%d)", fFileName, fFileType); LOGI(" start=%d num=%d len=%d addr=0x%04x", fStartSector, fNumSectors, fLength, fLoadAddr); } /* * "Fix" an RDOS filename. Convert DOS-ASCII to normal ASCII, and strip * trailing spaces. * * It's possible that RDOS 3.3 forces the filename to high-ASCII, because * one disk (#938A) has a file left by the crackers whose name is in * low-ASCII. The inverse-mode correction turns it into punctuation, but * I don't see a good way around it. Or any particular need to fix it. */ void A2FileRDOS::FixFilename(void) { DiskFSDOS33::LowerASCII((uint8_t*)fFileName, kMaxFileName); TrimTrailingSpaces(fFileName); } /* * Trim the spaces off the end of a filename. * * Assumes the filename has already been converted to low ASCII. */ void A2FileRDOS::TrimTrailingSpaces(char* filename) { char* lastspc = filename + strlen(filename); assert(*lastspc == '\0'); while (--lastspc) { if (*lastspc != ' ') break; } *(lastspc+1) = '\0'; } /* * Not a whole lot to do, since there's no fancy index blocks. */ DIError A2FileRDOS::Open(A2FileDescr** ppOpenFile, bool readOnly, bool rsrcFork /*=false*/) { if (fpOpenFile != NULL) return kDIErrAlreadyOpen; if (rsrcFork) return kDIErrForkNotFound; assert(readOnly == true); A2FDRDOS* pOpenFile = new A2FDRDOS(this); pOpenFile->fOffset = 0; //fOpen = true; fpOpenFile = pOpenFile; *ppOpenFile = pOpenFile; pOpenFile = NULL; return kDIErrNone; } /* * Returns the raw filename. * * If a pointer to a size_t is passed in, it will be filled with the * raw filename length. */ const char* A2FileRDOS::GetRawFileName(size_t* size = NULL) const { if (size) { *size = strlen(fRawFileName); } return fRawFileName; } /* * =========================================================================== * A2FDRDOS * =========================================================================== */ /* * Read a chunk of data from the current offset. */ DIError A2FDRDOS::Read(void* buf, size_t len, size_t* pActual) { LOGD(" RDOS reading %lu bytes from '%s' (offset=%ld)", (unsigned long) len, fpFile->GetPathName(), (long) fOffset); //if (!fOpen) // return kDIErrNotReady; A2FileRDOS* pFile = (A2FileRDOS*) fpFile; /* don't allow them to read past the end of the file */ if (fOffset + (long)len > pFile->fLength) { if (pActual == NULL) return kDIErrDataUnderrun; len = (size_t) (pFile->fLength - fOffset); } if (pActual != NULL) *pActual = len; long incrLen = len; DIError dierr = kDIErrNone; uint8_t sctBuf[kSctSize]; long block = pFile->fStartSector + (long) (fOffset / kSctSize); int bufOffset = (int) (fOffset % kSctSize); // (& 0xff) int ourSectPerTrack = GetOurSectPerTrack(); size_t thisCount; if (len == 0) { ///* one block allocated for empty file */ //SetLastBlock(block, true); return kDIErrNone; } assert(pFile->fLength != 0); while (len) { assert(block >= pFile->fStartSector && block < pFile->fStartSector + pFile->fNumSectors); dierr = pFile->GetDiskFS()->GetDiskImg()->ReadTrackSector(block / ourSectPerTrack, block % ourSectPerTrack, sctBuf); if (dierr != kDIErrNone) { LOGI(" RDOS error reading file '%s'", pFile->fFileName); return dierr; } thisCount = kSctSize - bufOffset; if (thisCount > len) thisCount = len; memcpy(buf, sctBuf + bufOffset, thisCount); len -= thisCount; buf = (char*)buf + thisCount; bufOffset = 0; block++; } fOffset += incrLen; return dierr; } /* * Write data at the current offset. */ DIError A2FDRDOS::Write(const void* buf, size_t len, size_t* pActual) { //if (!fOpen) // return kDIErrNotReady; return kDIErrNotSupported; } /* * Seek to a new offset. */ DIError A2FDRDOS::Seek(di_off_t offset, DIWhence whence) { //if (!fOpen) // return kDIErrNotReady; long fileLen = ((A2FileRDOS*) fpFile)->fLength; switch (whence) { case kSeekSet: if (offset < 0 || offset > fileLen) return kDIErrInvalidArg; fOffset = offset; break; case kSeekEnd: if (offset > 0 || offset < -fileLen) return kDIErrInvalidArg; fOffset = fileLen + offset; break; case kSeekCur: if (offset < -fOffset || offset >= (fileLen - fOffset)) { return kDIErrInvalidArg; } fOffset += offset; break; default: assert(false); return kDIErrInvalidArg; } assert(fOffset >= 0 && fOffset <= fileLen); return kDIErrNone; } /* * Return current offset. */ di_off_t A2FDRDOS::Tell(void) { //if (!fOpen) // return kDIErrNotReady; return fOffset; } /* * Release file state, such as it is. */ DIError A2FDRDOS::Close(void) { fpFile->CloseDescr(this); return kDIErrNone; } /* * Return the #of sectors/blocks in the file. */ long A2FDRDOS::GetSectorCount(void) const { //if (!fOpen) // return kDIErrNotReady; return ((A2FileRDOS*) fpFile)->fNumSectors; } long A2FDRDOS::GetBlockCount(void) const { //if (!fOpen) // return kDIErrNotReady; return ((A2FileRDOS*) fpFile)->fNumSectors / 2; } /* * Return the Nth track/sector in this file. */ DIError A2FDRDOS::GetStorage(long sectorIdx, long* pTrack, long* pSector) const { //if (!fOpen) // return kDIErrNotReady; A2FileRDOS* pFile = (A2FileRDOS*) fpFile; long rdosBlock = pFile->fStartSector + sectorIdx; int ourSectPerTrack = GetOurSectPerTrack(); if (rdosBlock >= pFile->fStartSector + pFile->fNumSectors) return kDIErrInvalidIndex; *pTrack = rdosBlock / ourSectPerTrack; *pSector = rdosBlock % ourSectPerTrack; return kDIErrNone; } /* * Return the Nth 512-byte block in this file. */ DIError A2FDRDOS::GetStorage(long blockIdx, long* pBlock) const { //if (!fOpen) // return kDIErrNotReady; A2FileRDOS* pFile = (A2FileRDOS*) fpFile; long rdosBlock = pFile->fStartSector + blockIdx*2; if (rdosBlock >= pFile->fStartSector + pFile->fNumSectors) return kDIErrInvalidIndex; *pBlock = rdosBlock / 2; if (pFile->GetDiskFS()->GetDiskImg()->GetHasBlocks()) { assert(*pBlock < pFile->GetDiskFS()->GetDiskImg()->GetNumBlocks()); } return kDIErrNone; }