mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-26 17:49:21 +00:00
405 lines
12 KiB
C++
405 lines
12 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) {
|
||
|
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;
|
||
|
}
|