ciderpress/diskimg/UNIDOS.cpp

360 lines
11 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Implementation of DiskFSUNIDOS class.
*
* The "UNIDOS" filesystem doesn't actually hold files. Instead, it holds
* two 400K DOS 3.3 volumes on an 800K disk.
*
* We do have a test here for "wide" DOS 3.3, which is largely a clone of
* the standard DOS 3.3 test. The trick is that we have to adjust our
* detection to account for 32-sector tracks, and do so while the object
* is still in a state where it believes it has 16 sectors per track.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
/*
* ===========================================================================
* DiskFSUNIDOS
* ===========================================================================
*/
const int kExpectedNumBlocks = 1600;
const int kExpectedTracks = 50;
const int kExpectedSectors = 32;
const int kVTOCTrack = 17;
const int kVTOCSector = 0;
const int kSctSize = 256;
const int kCatalogEntrySize = 0x23; // length in bytes of catalog entries
const int kCatalogEntriesPerSect = 7; // #of entries per catalog sector
const int kMaxTSPairs = 0x7a; // 122 entries for 256-byte sectors
const int kTSOffset = 0x0c; // first T/S entry in a T/S list
const int kMaxTSIterations = 32;
const int kMaxCatalogIterations = 64;
/*
* Read a track/sector, adjusting for 32-sector disks being treated as
* if they were 16-sector.
*/
static DIError ReadTrackSectorAdjusted(DiskImg* pImg, int track, int sector,
int trackOffset, uint8_t* buf, DiskImg::SectorOrder imageOrder)
{
track += trackOffset;
track *= 2;
if (sector >= 16) {
track++;
sector -= 16;
}
return pImg->ReadTrackSectorSwapped(track, sector, buf, imageOrder,
DiskImg::kSectorOrderDOS);
}
/*
* Test for presence of 400K DOS 3.3 volumes.
*/
static DIError TestImageHalf(DiskImg* pImg, int trackOffset,
DiskImg::SectorOrder imageOrder, int* pGoodCount)
{
DIError dierr = kDIErrNone;
uint8_t sctBuf[kSctSize];
int numTracks, numSectors;
int catTrack, catSect;
int foundGood = 0;
int iterations = 0;
*pGoodCount = 0;
dierr = ReadTrackSectorAdjusted(pImg, kVTOCTrack, kVTOCSector,
trackOffset, sctBuf, imageOrder);
if (dierr != kDIErrNone)
goto bail;
catTrack = sctBuf[0x01];
catSect = sctBuf[0x02];
numTracks = sctBuf[0x34];
numSectors = sctBuf[0x35];
if (!(sctBuf[0x27] == kMaxTSPairs) ||
/*!(sctBuf[0x36] == 0 && sctBuf[0x37] == 1) ||*/ // bytes per sect
!(numTracks == kExpectedTracks) ||
!(numSectors == 32) ||
!(catTrack < numTracks && catSect < numSectors) ||
0)
{
LOGI(" UNI/Wide DOS header test failed");
dierr = kDIErrFilesystemNotFound;
goto bail;
}
foundGood++; // score one for a valid-looking VTOC
/*
* Walk through the catalog track to try to figure out ordering.
*/
while (catTrack != 0 && catSect != 0 &&
iterations < DiskFSDOS33::kMaxCatalogSectors)
{
dierr = ReadTrackSectorAdjusted(pImg, catTrack, catSect,
trackOffset, sctBuf, imageOrder);
if (dierr != kDIErrNone) {
dierr = kDIErrNone;
break; /* allow it if earlier stuff was okay */
}
if (catTrack == sctBuf[1] && catSect == sctBuf[2] +1)
foundGood++;
else if (catTrack == sctBuf[1] && catSect == sctBuf[2]) {
LOGI(" WideDOS detected self-reference on cat (%d,%d)",
catTrack, catSect);
break;
}
catTrack = sctBuf[1];
catSect = sctBuf[2];
iterations++; // watch for infinite loops
}
if (iterations >= DiskFSDOS33::kMaxCatalogSectors) {
dierr = kDIErrDirectoryLoop;
LOGI(" WideDOS directory links cause a loop");
goto bail;
}
LOGI(" WideDOS foundGood=%d off=%d swap=%d", foundGood,
trackOffset, imageOrder);
*pGoodCount = foundGood;
bail:
return dierr;
}
/*
* Test both of the DOS partitions.
*/
static DIError TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder,
int* pGoodCount)
{
DIError dierr;
int goodCount1, goodCount2;
*pGoodCount = 0;
LOGI(" UNIDOS checking first half (imageOrder=%d)", imageOrder);
dierr = TestImageHalf(pImg, 0, imageOrder, &goodCount1);
if (dierr != kDIErrNone)
return dierr;
LOGI(" UNIDOS checking second half (imageOrder=%d)", imageOrder);
dierr = TestImageHalf(pImg, kExpectedTracks, imageOrder, &goodCount2);
if (dierr != kDIErrNone)
return dierr;
if (goodCount1 > goodCount2)
*pGoodCount = goodCount1;
else
*pGoodCount = goodCount2;
return kDIErrNone;
}
/*
* Test to see if the image is a UNIDOS volume.
*/
/*static*/ DIError DiskFSUNIDOS::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* only on 800K disks (at the least, insist on numTracks being even) */
if (pImg->GetNumBlocks() != kExpectedNumBlocks)
return kDIErrFilesystemNotFound;
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
int bestCount = 0;
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
int goodCount = 0;
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImage(pImg, ordering[i], &goodCount) == kDIErrNone) {
if (goodCount > bestCount) {
bestCount = goodCount;
bestOrder = ordering[i];
}
}
}
if (bestCount >= 4 ||
(leniency == kLeniencyVery && bestCount >= 2))
{
LOGI(" WideDOS test: bestCount=%d for order=%d", bestCount, bestOrder);
assert(bestOrder != DiskImg::kSectorOrderUnknown);
*pOrder = bestOrder;
*pFormat = DiskImg::kFormatUNIDOS;
return kDIErrNone;
}
LOGI(" UNIDOS didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Test to see if the image is a 'wide' (32-sector) DOS3.3 volume, i.e.
* half of a UNIDOS volume (usually found embedded in ProDOS).
*
* Trying all possible formats is important here, because the wrong value for
* swap can return a "good" value of 7 (much less than the expected 30, but
* above a threshold of reasonableness).
*/
/*static*/ DIError DiskFSUNIDOS::TestWideFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder,
DiskImg::FSFormat* pFormat, FSLeniency leniency)
{
/* only on 400K "disks" */
if (pImg->GetNumBlocks() != kExpectedNumBlocks/2) {
LOGI(" WideDOS ignoring volume (numBlocks=%ld)",
pImg->GetNumBlocks());
return kDIErrFilesystemNotFound;
}
DiskImg::SectorOrder ordering[DiskImg::kSectorOrderMax];
DiskImg::GetSectorOrderArray(ordering, *pOrder);
DiskImg::SectorOrder bestOrder = DiskImg::kSectorOrderUnknown;
int bestCount = 0;
for (int i = 0; i < DiskImg::kSectorOrderMax; i++) {
int goodCount = 0;
if (ordering[i] == DiskImg::kSectorOrderUnknown)
continue;
if (TestImageHalf(pImg, 0, ordering[i], &goodCount) == kDIErrNone) {
if (goodCount > bestCount) {
bestCount = goodCount;
bestOrder = ordering[i];
}
}
}
if (bestCount >= 4 ||
(leniency == kLeniencyVery && bestCount >= 2))
{
LOGI(" UNI/Wide test: bestCount=%d for order=%d", bestCount, bestOrder);
assert(bestOrder != DiskImg::kSectorOrderUnknown);
*pOrder = bestOrder;
*pFormat = DiskImg::kFormatDOS33;
// up to the caller to adjust numTracks/numSectPerTrack
return kDIErrNone;
}
LOGI(" UNI/Wide didn't find valid FS");
return kDIErrFilesystemNotFound;
}
/*
* Set up our sub-volumes.
*/
DIError DiskFSUNIDOS::Initialize(void)
{
DIError dierr = kDIErrNone;
if (fScanForSubVolumes != kScanSubDisabled) {
dierr = OpenSubVolume(0);
if (dierr != kDIErrNone)
return dierr;
dierr = OpenSubVolume(1);
if (dierr != kDIErrNone)
return dierr;
} else {
LOGI(" UNIDOS not scanning for sub-volumes");
}
SetVolumeUsageMap();
return kDIErrNone;
}
/*
* Open up one of the DOS 3.3 sub-volumes.
*/
DIError DiskFSUNIDOS::OpenSubVolume(int idx)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = NULL;
DiskImg* pNewImg = NULL;
pNewImg = new DiskImg;
if (pNewImg == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(fpImg, kExpectedTracks * idx, 0,
kExpectedTracks * kExpectedSectors);
if (dierr != kDIErrNone) {
LOGI(" UNISub: OpenImage(%d,0,%d) failed (err=%d)",
kExpectedTracks * idx, kExpectedTracks * kExpectedSectors, dierr);
goto bail;
}
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" UNISub: analysis failed (err=%d)", dierr);
goto bail;
}
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" UNISub: unable to identify filesystem");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* open a DiskFS for the sub-image */
LOGI(" UNISub %d succeeded!", idx);
pNewFS = pNewImg->OpenAppropriateDiskFS();
if (pNewFS == NULL) {
LOGI(" UNISub: OpenAppropriateDiskFS failed");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
/* load the files from the sub-image */
dierr = pNewFS->Initialize(pNewImg, kInitFull);
if (dierr != kDIErrNone) {
LOGE(" UNISub: error %d reading list of files from disk", dierr);
goto bail;
}
/* if this really is DOS 3.3, override the "volume name" */
if (pNewImg->GetFSFormat() == DiskImg::kFormatDOS33) {
DiskFSDOS33* pDOS = (DiskFSDOS33*) pNewFS; /* eek, a downcast */
pDOS->SetDiskVolumeNum(idx+1);
}
/*
* Success, add it to the sub-volume list.
*/
AddSubVolumeToList(pNewImg, pNewFS);
bail:
if (dierr != kDIErrNone) {
delete pNewFS;
delete pNewImg;
}
return dierr;
}