ciderpress/diskimg/MicroDrive.cpp

405 lines
13 KiB
C++

/*
* 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) {
LOGI(" MicroDrive partition signature not found in first part block");
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)
{
LOGI(" MicroDrive unreasonable partCount values %d/%d",
partCount1, partCount2);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
/* consider testing other fields */
// success!
LOGI(" MicroDrive partition map count = %d/%d", 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)
{
LOGI(" MicroDrive partition map:");
LOGI(" cyls=%d res1=%d heads=%d sects=%d",
pMap->cylinders, pMap->reserved1, pMap->heads, pMap->sectors);
LOGI(" res2=%d numPart1=%d numPart2=%d",
pMap->reserved2, pMap->numPart1, pMap->numPart2);
LOGI(" romVersion=ROM%02d", pMap->romVersion);
int i, parts;
parts = pMap->numPart1;
assert(parts <= kMaxNumParts);
for (i = 0; i < parts; i++) {
LOGI(" %2d: startLBA=%8ld length=%ld",
i, pMap->partitionStart1[i], pMap->partitionLength1[i]);
}
parts = pMap->numPart2;
assert(parts <= kMaxNumParts);
for (i = 0; i < parts; i++) {
LOGI(" %2d: startLBA=%8ld length=%ld",
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 = NULL;
DiskImg* pNewImg = NULL;
//bool tweaked = false;
LOGI("Adding %ld +%ld", startBlock, numBlocks);
if (startBlock > fpImg->GetNumBlocks()) {
LOGI("MicroDrive start block out of range (%ld vs %ld)",
startBlock, fpImg->GetNumBlocks());
return kDIErrBadPartition;
}
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
LOGI("MicroDrive partition too large (%ld vs %ld avail)",
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 == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewImg->OpenImage(fpImg, startBlock, numBlocks);
if (dierr != kDIErrNone) {
LOGI(" MicroDriveSub: OpenImage(%ld,%ld) failed (err=%d)",
startBlock, numBlocks, dierr);
goto bail;
}
//LOGI(" +++ CFFASub: new image has ro=%d (parent=%d)",
// pNewImg->GetReadOnly(), pImg->GetReadOnly());
/* figure out what the format is */
dierr = pNewImg->AnalyzeImage();
if (dierr != kDIErrNone) {
LOGI(" MicroDriveSub: analysis failed (err=%d)", dierr);
goto bail;
}
/* we allow unrecognized partitions */
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
LOGI(" MicroDriveSub (%ld,%ld): unable to identify filesystem",
startBlock, numBlocks);
DiskFSUnknown* pUnknownFS = new DiskFSUnknown;
if (pUnknownFS == NULL) {
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 */
LOGI(" MicroDriveSub (%ld,%ld) analyze succeeded!", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
if (pNewFS == NULL) {
LOGI(" MicroDriveSub: OpenAppropriateDiskFS failed");
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) {
LOGI(" MicroDriveSub: error %d reading list of files from disk", dierr);
goto bail;
}
/* add it to the list */
AddSubVolumeToList(pNewImg, pNewFS);
pNewImg = NULL;
pNewFS = NULL;
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;
}
LOGI(" FS didn't find valid MicroDrive");
return kDIErrFilesystemNotFound;
}
/*
* Prep the MicroDrive "container" for use.
*/
DIError
DiskFSMicroDrive::Initialize(void)
{
DIError dierr = kDIErrNone;
LOGI("MicroDrive initializing (scanForSub=%d)", 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 = NULL;
DiskImg* pNewImg = NULL;
LOGI(" MicroDrive failed opening sub-volume %d", idx);
dierr = CreatePlaceholder(startBlock, numBlocks, NULL, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGI(" MicroDrive unable to create placeholder (err=%d)",
dierr);
// fall out with error
}
}
bail:
return dierr;
}