ciderpress/diskimg/FocusDrive.cpp
2007-03-27 17:47:10 +00:00

363 lines
9.8 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* This is a container for the Parsons Engineering FocusDrive.
*
* The format was reverse-engineered by Ranger Harke.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
const int kBlkSize = 512;
const int kPartMapBlock = 0; // partition map lives here
const int kMaxPartitions = 30; // max allowed partitions on a drive
const int kPartNameStart = 1; // partition names start here (2 blocks)
const int kPartNameLen = 32; // max length of partition name
static const char* kSignature = "Parsons Engin.";
const int kSignatureLen = 14;
/*
* Format of partition map. It resides in the first 256 bytes of block 0.
* All values are in little-endian order.
*
* We also make space here for the partition names, which live on blocks 1+2.
*/
typedef struct DiskFSFocusDrive::PartitionMap {
unsigned char signature[kSignatureLen];
unsigned char unknown1;
unsigned char partCount; // could be ushort, combined w/unknown1
unsigned char unknown2[16];
struct Entry {
unsigned long startBlock;
unsigned long blockCount;
unsigned long unknown1;
unsigned long unknown2;
unsigned char name[kPartNameLen+1];
} entry[kMaxPartitions];
} PartitionMap;
/*
* Figure out if this is a FocusDrive partition.
*
* The "imageOrder" parameter has no use here, because (in the current
* version) embedded parent volumes are implicitly ProDOS-ordered.
*/
/*static*/ DIError
DiskFSFocusDrive::TestImage(DiskImg* pImg, DiskImg::SectorOrder imageOrder)
{
DIError dierr = kDIErrNone;
unsigned char blkBuf[kBlkSize];
int partCount;
/*
* See if block 0 is a FocusDrive partition map.
*/
dierr = pImg->ReadBlockSwapped(kPartMapBlock, blkBuf, imageOrder,
DiskImg::kSectorOrderProDOS);
if (dierr != kDIErrNone)
goto bail;
if (memcmp(blkBuf, kSignature, kSignatureLen) != 0) {
WMSG0(" FocusDrive partition signature not found in first part block\n");
dierr = kDIErrFilesystemNotFound;
goto bail;
}
partCount = blkBuf[0x0f];
if (partCount == 0 || partCount > kMaxPartitions) {
WMSG1(" FocusDrive partition count looks bad (%d)\n", partCount);
dierr = kDIErrFilesystemNotFound;
goto bail;
}
// success!
WMSG1(" Looks like FocusDrive with %d partitions\n", partCount);
bail:
return dierr;
}
/*
* Unpack a partition map block into a partition map data structure.
*/
/*static*/ void
DiskFSFocusDrive::UnpackPartitionMap(const unsigned char* buf,
const unsigned char* nameBuf, PartitionMap* pMap)
{
const unsigned char* ptr;
const unsigned char* namePtr;
int i;
memcpy(pMap->signature, &buf[0x00], kSignatureLen);
pMap->unknown1 = buf[0x0e];
pMap->partCount = buf[0x0f];
memcpy(pMap->unknown2, &buf[0x10], 16);
ptr = &buf[0x20];
namePtr = &nameBuf[kPartNameLen]; // not sure what first 32 bytes are
for (i = 0; i < kMaxPartitions; i++) {
pMap->entry[i].startBlock = GetLongLE(ptr);
pMap->entry[i].blockCount = GetLongLE(ptr+4);
pMap->entry[i].unknown1 = GetLongLE(ptr+8);
pMap->entry[i].unknown2 = GetLongLE(ptr+12);
memcpy(pMap->entry[i].name, namePtr, kPartNameLen);
pMap->entry[i].name[kPartNameLen] = '\0';
ptr += 0x10;
namePtr += kPartNameLen;
}
assert(ptr == buf + kBlkSize);
}
/*
* Debug: dump the contents of the partition map.
*/
/*static*/ void
DiskFSFocusDrive::DumpPartitionMap(const PartitionMap* pMap)
{
int i;
WMSG1(" FocusDrive partition map (%d partitions):\n", pMap->partCount);
for (i = 0; i < pMap->partCount; i++) {
WMSG4(" %2d: %8ld %8ld '%s'\n", i, pMap->entry[i].startBlock,
pMap->entry[i].blockCount, pMap->entry[i].name);
}
}
/*
* Open up a sub-volume.
*/
DIError
DiskFSFocusDrive::OpenSubVolume(long startBlock, long numBlocks,
const char* name)
{
DIError dierr = kDIErrNone;
DiskFS* pNewFS = nil;
DiskImg* pNewImg = nil;
//bool tweaked = false;
WMSG2("Adding %ld +%ld\n", startBlock, numBlocks);
if (startBlock > fpImg->GetNumBlocks()) {
WMSG2("FocusDrive start block out of range (%ld vs %ld)\n",
startBlock, fpImg->GetNumBlocks());
return kDIErrBadPartition;
}
if (startBlock + numBlocks > fpImg->GetNumBlocks()) {
WMSG2("FocusDrive 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(" FocusDriveSub: 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(" FocusDriveSub: analysis failed (err=%d)\n", dierr);
goto bail;
}
/* we allow unrecognized partitions */
if (pNewImg->GetFSFormat() == DiskImg::kFormatUnknown ||
pNewImg->GetSectorOrder() == DiskImg::kSectorOrderUnknown)
{
WMSG2(" FocusDriveSub (%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;
} else {
/* open a DiskFS for the sub-image */
WMSG2(" FocusDriveSub (%ld,%ld) analyze succeeded!\n", startBlock, numBlocks);
pNewFS = pNewImg->OpenAppropriateDiskFS(true);
if (pNewFS == nil) {
WMSG0(" FocusDriveSub: OpenAppropriateDiskFS failed\n");
dierr = kDIErrUnsupportedFSFmt;
goto bail;
}
}
pNewImg->AddNote(DiskImg::kNoteInfo, "Partition name='%s'.", name);
/* 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(" FocusDriveSub: 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 FocusDrive volume.
*/
/*static*/ DIError
DiskFSFocusDrive::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::kFormatFocusDrive;
*pOrder = DiskImg::kSectorOrderProDOS;
return kDIErrNone;
}
WMSG0(" FS didn't find valid FocusDrive\n");
return kDIErrFilesystemNotFound;
}
/*
* Prep the FocusDrive "container" for use.
*/
DIError
DiskFSFocusDrive::Initialize(void)
{
DIError dierr = kDIErrNone;
WMSG1("FocusDrive 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
DiskFSFocusDrive::FindSubVolumes(void)
{
DIError dierr = kDIErrNone;
unsigned char buf[kBlkSize];
unsigned char nameBuf[kBlkSize*2];
PartitionMap map;
int i;
dierr = fpImg->ReadBlock(kPartMapBlock, buf);
if (dierr != kDIErrNone)
goto bail;
dierr = fpImg->ReadBlock(kPartNameStart, nameBuf);
if (dierr != kDIErrNone)
goto bail;
dierr = fpImg->ReadBlock(kPartNameStart+1, nameBuf+kBlkSize);
if (dierr != kDIErrNone)
goto bail;
UnpackPartitionMap(buf, nameBuf, &map);
DumpPartitionMap(&map);
for (i = 0; i < map.partCount; i++) {
dierr = OpenVol(i, map.entry[i].startBlock, map.entry[i].blockCount,
(const char*)map.entry[i].name);
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
DiskFSFocusDrive::OpenVol(int idx, long startBlock, long numBlocks,
const char* name)
{
DIError dierr;
dierr = OpenSubVolume(startBlock, numBlocks, name);
if (dierr != kDIErrNone) {
if (dierr == kDIErrCancelled)
goto bail;
DiskFS* pNewFS = nil;
DiskImg* pNewImg = nil;
WMSG1(" FocusDrive failed opening sub-volume %d\n", idx);
dierr = CreatePlaceholder(startBlock, numBlocks, name, NULL,
&pNewImg, &pNewFS);
if (dierr == kDIErrNone) {
AddSubVolumeToList(pNewImg, pNewFS);
} else {
WMSG1(" FocusDrive unable to create placeholder (err=%d)\n",
dierr);
// fall out with error
}
}
bail:
return dierr;
}