/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * The "MicroDrive" DiskFS is a container class for multiple ProDOS and HFS * volumes. It represents a partitioned disk device, such as a hard * drive or CF card, that has been formatted for use with ///SHH Systeme's * MicroDrive card for the Apple II. */ #include "StdAfx.h" #include "DiskImgPriv.h" const int kBlkSize = 512; const int kPartMapBlock = 0; // partition map lives here const unsigned int kPartSizeMask = 0x00ffffff; /* * Format of partition map. It resides in the first 256 bytes of block 0. * All values are in little-endian order. * * The layout was discovered through reverse-engineering. Additional notes: * From Joachim Lange: Below, this is the configuration block as it is used in all MicroDrive cards. Please verify that my ID shortcut can be found at offset 0, otherwise the partition info is not valid. Most of the other parms are not useful, some are historic and not useful anymore. As a second security measure, verify that the first partition starts at absolute block 256. This is also a fixed value used in all MicroDrive cards. Of course the partition size is not two bytes long but three (not four), the 4th byte is used for switching drives in a two-drive configuration. So, for completeness, when reading partition sizes, perform a partitionLength[..] & 0x00FFFFFF, or at least issue a warning that something may be wrong. The offset (partitionStart) could reach into the 4th byte. I have attached the config block in a zip file because the mailer would probably re-format the source text. */ const int kMaxNumParts = 8; typedef struct DiskFSMicroDrive::PartitionMap { unsigned short magic; // partition signature unsigned short cylinders; // #of cylinders unsigned short reserved1; // ?? unsigned short heads; // #of heads/cylinder unsigned short sectors; // #of sectors/track unsigned short reserved2; // ?? unsigned char numPart1; // #of partitions in first chunk unsigned char numPart2; // #of partitions in second chunk unsigned char reserved3[10]; // bytes 0x0e-0x17 unsigned short romVersion; // IIgs ROM01 or ROM03 unsigned char reserved4[6]; // bytes 0x1a-0x1f unsigned long partitionStart1[kMaxNumParts]; // bytes 0x20-0x3f unsigned long partitionLength1[kMaxNumParts]; // bytes 0x40-0x5f unsigned char reserved5[32]; // bytes 0x60-0x7f unsigned long partitionStart2[kMaxNumParts]; // bytes 0x80-0x9f unsigned long partitionLength2[kMaxNumParts]; // bytes 0xa0-0xbf unsigned char padding[320]; } PartitionMap; /* * Figure out if this is a MicroDrive partition. * * The "imageOrder" parameter has no use here, because (in the current * version) embedded parent volumes are implicitly ProDOS-ordered. */ /*static*/ DIError DiskFSMicroDrive::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder) { DIError dierr = kDIErrNone; unsigned char blkBuf[kBlkSize]; int partCount1, partCount2; assert(sizeof(PartitionMap) == kBlkSize); /* * See if block 0 is a MicroDrive partition map. */ dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder, DiskImg::kSectorOrderProDOS); if (dierr != kDIErrNone) goto bail; if (GetShortLE(&blkBuf[0x00]) != kPartitionSignature) { WMSG0(" MicroDrive partition signature not found in first part block\n"); dierr = kDIErrFilesystemNotFound; goto bail; } /* might assert that partCount2 be zero unless partCount1 == 8? */ partCount1 = blkBuf[0x0c]; partCount2 = blkBuf[0x0d]; if (partCount1 == 0 || partCount1 > kMaxNumParts || partCount2 > kMaxNumParts) { WMSG2(" MicroDrive unreasonable partCount values %d/%d\n", partCount1, partCount2); dierr = kDIErrFilesystemNotFound; goto bail; } /* consider testing other fields */ // success! WMSG2(" MicroDrive partition map count = %d/%d\n", partCount1, partCount2); bail: return dierr; } /* * Unpack a partition map block into a partition map data structure. */ /*static*/ void DiskFSMicroDrive::UnpackPartitionMap(const unsigned char* buf, PartitionMap* pMap) { pMap->magic = GetShortLE(&buf[0x00]); pMap->cylinders = GetShortLE(&buf[0x02]); pMap->reserved1 = GetShortLE(&buf[0x04]); pMap->heads = GetShortLE(&buf[0x06]); pMap->sectors = GetShortLE(&buf[0x08]); pMap->reserved2 = GetShortLE(&buf[0x0a]); pMap->numPart1 = buf[0x0c]; pMap->numPart2 = buf[0x0d]; memcpy(pMap->reserved3, &buf[0x0e], sizeof(pMap->reserved3)); pMap->romVersion = GetShortLE(&buf[0x18]); memcpy(pMap->reserved4, &buf[0x1a], sizeof(pMap->reserved4)); for (int i = 0; i < kMaxNumParts; i++) { pMap->partitionStart1[i] = GetLongLE(&buf[0x20] + i * 4); pMap->partitionLength1[i] = GetLongLE(&buf[0x40] + i * 4) & kPartSizeMask; pMap->partitionStart2[i] = GetLongLE(&buf[0x80] + i * 4); pMap->partitionLength2[i] = GetLongLE(&buf[0xa0] + i * 4) & kPartSizeMask; } memcpy(pMap->reserved5, &buf[0x60], sizeof(pMap->reserved5)); memcpy(pMap->padding, &buf[0x80], sizeof(pMap->padding)); } /* * Debug: dump the contents of the partition map. */ /*static*/ void DiskFSMicroDrive::DumpPartitionMap(const PartitionMap* pMap) { WMSG0(" MicroDrive partition map:\n"); WMSG4(" cyls=%d res1=%d heads=%d sects=%d\n", pMap->cylinders, pMap->reserved1, pMap->heads, pMap->sectors); WMSG3(" res2=%d numPart1=%d numPart2=%d\n", pMap->reserved2, pMap->numPart1, pMap->numPart2); WMSG1(" romVersion=ROM%02d\n", pMap->romVersion); int i, parts; parts = pMap->numPart1; assert(parts <= kMaxNumParts); for (i = 0; i < parts; i++) { WMSG3(" %2d: startLBA=%8ld length=%ld\n", i, pMap->partitionStart1[i], pMap->partitionLength1[i]); } parts = pMap->numPart2; assert(parts <= kMaxNumParts); for (i = 0; i < parts; i++) { WMSG3(" %2d: startLBA=%8ld length=%ld\n", i+8, pMap->partitionStart2[i], pMap->partitionLength2[i]); } } /* * Open up a sub-volume. */ DIError DiskFSMicroDrive::OpenSubVolume(long startBlock, long numBlocks) { DIError dierr = kDIErrNone; DiskFS* pNewFS = nil; DiskImg* pNewImg = nil; //bool tweaked = false; WMSG2("Adding %ld +%ld\n", startBlock, numBlocks); if (startBlock > fpImg->GetNumBlocks()) { WMSG2("MicroDrive start block out of range (%ld vs %ld)\n", startBlock, fpImg->GetNumBlocks()); return kDIErrBadPartition; } if (startBlock + numBlocks > fpImg->GetNumBlocks()) { WMSG2("MicroDrive partition too large (%ld vs %ld avail)\n", numBlocks, fpImg->GetNumBlocks() - startBlock); fpImg->AddNote(DiskImg::kNoteInfo, "Reduced partition from %ld blocks to %ld.\n", numBlocks, fpImg->GetNumBlocks() - startBlock); numBlocks = fpImg->GetNumBlocks() - startBlock; //tweaked = true; } pNewImg = new DiskImg; if (pNewImg == nil) { dierr = kDIErrMalloc; goto bail; } dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks); if (dierr != kDIErrNone) { WMSG3(" MicroDriveSub: OpenImage(%ld,%ld) failed (err=%d)\n", startBlock, numBlocks, dierr); goto bail; } //WMSG2(" +++ CFFASub: new image has ro=%d (parent=%d)\n", // pNewImg->GetReadOnly(), pImg->GetReadOnly()); /* figure out what the format is */ dierr = pNewImg->AnalyzeImage(); if (dierr != kDIErrNone) { WMSG1(" MicroDriveSub: analysis failed (err=%d)\n", dierr); goto bail; } /* we allow unrecognized partitions */ if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown || pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown) { WMSG2(" MicroDriveSub (%ld,%ld): unable to identify filesystem\n", startBlock, numBlocks); DiskFSUnknown* pUnknownFS = new DiskFSUnknown; if (pUnknownFS == nil) { dierr = kDIErrInternal; goto bail; } //pUnknownFS->SetVolumeInfo((const char*)pMap->pmParType); pNewFS = pUnknownFS; //pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s' type='%s'.", // pMap->pmPartName, pMap->pmParType); } else { /* open a DiskFS for the sub-image */ WMSG2(" MicroDriveSub (%ld,%ld) analyze succeeded!\n", startBlock, numBlocks); pNewFS = pNewImg->OpenAppropriateDiskFS(true); if (pNewFS == nil) { WMSG0(" MicroDriveSub: OpenAppropriateDiskFS failed\n"); dierr = kDIErrUnsupportedFSFmt; goto bail; } } /* we encapsulate arbitrary stuff, so encourage child to scan */ pNewFS->SetScanForSubVolumes(kScanSubEnabled); /* * Load the files from the sub-image. When doing our initial tests, * or when loading data for the volume copier, we don't want to dig * into our sub-volumes, just figure out what they are and where. * * If "initialize" fails, the sub-volume won't get added to the list. * It's important that a failure at this stage doesn't cause the whole * thing to fall over. */ InitMode initMode; if (GetScanForSubVolumes() == kScanSubContainerOnly) initMode = kInitHeaderOnly; else initMode = kInitFull; dierr = pNewFS->Initialize(pNewImg, initMode); if (dierr != kDIErrNone) { WMSG1(" MicroDriveSub: error %d reading list of files from disk", dierr); goto bail; } /* add it to the list */ AddSubVolumeToList(pNewImg, pNewFS); pNewImg = nil; pNewFS = nil; bail: delete pNewFS; delete pNewImg; return dierr; } /* * Check to see if this is a MicroDrive volume. */ /*static*/ DIError DiskFSMicroDrive::TestFS(DiskImg* pImg, DiskImg::SectorOrder* pOrder, DiskImg::FSFormat* pFormat, FSLeniency leniency) { if (pImg->GetNumBlocks() < kMinInterestingBlocks) return kDIErrFilesystemNotFound; if (pImg->GetIsEmbedded()) // don't look for partitions inside return kDIErrFilesystemNotFound; /* assume ProDOS -- shouldn't matter, since it's embedded */ if (TestImage(pImg, DiskImg::kSectorOrderProDOS) == kDIErrNone) { *pFormat = DiskImg::kFormatMicroDrive; *pOrder = DiskImg::kSectorOrderProDOS; return kDIErrNone; } WMSG0(" FS didn't find valid MicroDrive\n"); return kDIErrFilesystemNotFound; } /* * Prep the MicroDrive "container" for use. */ DIError DiskFSMicroDrive::Initialize(void) { DIError dierr = kDIErrNone; WMSG1("MicroDrive initializing (scanForSub=%d)\n", fScanForSubVolumes); /* seems pointless *not* to, but we just do what we're told */ if (fScanForSubVolumes != kScanSubDisabled) { dierr = FindSubVolumes(); if (dierr != kDIErrNone) return dierr; } /* blank out the volume usage map */ SetVolumeUsageMap(); return dierr; } /* * Find the various sub-volumes and open them. */ DIError DiskFSMicroDrive::FindSubVolumes(void) { DIError dierr = kDIErrNone; unsigned char buf[kBlkSize]; PartitionMap map; int i; dierr = fpImg->ReadBlock(kPartMapBlock, buf); if (dierr != kDIErrNone) goto bail; UnpackPartitionMap(buf, &map); DumpPartitionMap(&map); /* first part of the table */ for (i = 0; i < map.numPart1; i++) { dierr = OpenVol(i, map.partitionStart1[i], map.partitionLength1[i]); if (dierr != kDIErrNone) goto bail; } /* second part of the table */ for (i = 0; i < map.numPart2; i++) { dierr = OpenVol(i + kMaxNumParts, map.partitionStart2[i], map.partitionLength2[i]); if (dierr != kDIErrNone) goto bail; } bail: return dierr; } /* * Open the volume. If it fails, open a placeholder instead. (If *that* * fails, return with an error.) */ DIError DiskFSMicroDrive::OpenVol(int idx, long startBlock, long numBlocks) { DIError dierr; dierr = OpenSubVolume(startBlock, numBlocks); if (dierr != kDIErrNone) { if (dierr == kDIErrCancelled) goto bail; DiskFS* pNewFS = nil; DiskImg* pNewImg = nil; WMSG1(" MicroDrive failed opening sub-volume %d\n", idx); dierr = CreatePlaceholder(startBlock, numBlocks, NULL, NULL, &pNewImg, &pNewFS); if (dierr == kDIErrNone) { AddSubVolumeToList(pNewImg, pNewFS); } else { WMSG1(" MicroDrive unable to create placeholder (err=%d)\n", dierr); // fall out with error } } bail: return dierr; }