ciderpress/diskimg/MicroDrive.cpp

398 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 uint32_t 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 {
uint16_t magic; // partition signature
uint16_t cylinders; // #of cylinders
uint16_t reserved1; // ??
uint16_t heads; // #of heads/cylinder
uint16_t sectors; // #of sectors/track
uint16_t reserved2; // ??
uint8_t numPart1; // #of partitions in first chunk
uint8_t numPart2; // #of partitions in second chunk
uint8_t reserved3[10]; // bytes 0x0e-0x17
uint16_t romVersion; // IIgs ROM01 or ROM03
uint8_t reserved4[6]; // bytes 0x1a-0x1f
uint32_t partitionStart1[kMaxNumParts]; // bytes 0x20-0x3f
uint32_t partitionLength1[kMaxNumParts]; // bytes 0x40-0x5f
uint8_t reserved5[32]; // bytes 0x60-0x7f
uint32_t partitionStart2[kMaxNumParts]; // bytes 0x80-0x9f
uint32_t partitionLength2[kMaxNumParts]; // bytes 0xa0-0xbf
uint8_t 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;
uint8_t 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 uint8_t* 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=%8d length=%d",
i, pMap->partitionStart1[i], pMap->partitionLength1[i]);
}
parts = pMap->numPart2;
assert(parts <= kMaxNumParts);
for (i = 0; i < parts; i++) {
LOGI(" %2d: startLBA=%8d length=%d",
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;
uint8_t 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;
LOGW(" MicroDrive failed opening sub-volume %d", idx);
dierr = CreatePlaceholder(startBlock, numBlocks, NULL, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
LOGE(" MicroDrive unable to create placeholder (err=%d)",
dierr);
// fall out with error
}
}
bail:
return dierr;
}