ciderpress/diskimg/ImageWrapper.cpp
Andy McFadden b97584eeb6 Update Linux build
Fix some %ld message in log messages, and update the Linux sample
code to match recent changes in NufxLib and DiskImgLib.

Also, bump MDC version to 3.0.0 to match Windows version.
2015-01-08 18:27:09 -08:00

2477 lines
75 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Code for handling disk image "wrappers", things like DiskCopy, 2MG, and
* SHK that surround a disk image.
*
* Returning with kDIErrBadChecksum from Test or Prep is taken as a sign
* that, while we have correctly identified the wrapper format, the contents
* of the file are corrupt, and the user needs to be told.
*
* Some formats, such as 2MG, include a DOS volume number. This is useful
* because DOS actually embeds the volume number in sector headers; the value
* stored in the VTOC is ignored by certain things (notably some games with
* trivial copy-protection). This value needs to be preserved. It's
* unclear how useful this will actually be; mostly we just want to preserve
* it when translating from one format to another.
*
* If a library (such as NufxLib) needs to read an actual file, it can
* (usually) pry the name out of the GFD.
*
* In general, it should be possible to write to any "wrapped" file that we
* can read from. For things like NuFX and DDD, this means we need to be
* able to re-compress the image file when we're done with it.
*/
#include "StdAfx.h"
#include "DiskImgPriv.h"
#include "TwoImg.h"
/*
* ===========================================================================
* 2MG (a/k/a 2IMG)
* ===========================================================================
*/
/*
* Test to see if this is a 2MG file.
*
* The easiest way to do that is to open up the header and see if
* it looks valid.
*/
/*static*/ DIError Wrapper2MG::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
TwoImgHeader header;
LOGI("Testing for 2MG");
// HEY: should test for wrappedLength > 2GB; if so, skip
pGFD->Rewind();
if (header.ReadHeader(pGFD, (long) wrappedLength) != 0)
return kDIErrGeneric;
LOGI("Looks like valid 2MG");
return kDIErrNone;
}
/*
* Get the header (again) and use it to locate the data.
*/
DIError Wrapper2MG::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly,
di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
TwoImgHeader header;
long offset;
LOGI("Prepping for 2MG");
pGFD->Rewind();
if (header.ReadHeader(pGFD, (long) wrappedLength) != 0)
return kDIErrGeneric;
offset = header.fDataOffset;
if (header.fFlags & TwoImgHeader::kDOSVolumeSet)
*pDiskVolNum = header.GetDOSVolumeNum();
*pLength = header.fDataLen;
*pPhysical = DiskImg::kPhysicalFormatSectors;
if (header.fImageFormat == TwoImgHeader::kImageFormatDOS)
*pOrder = DiskImg::kSectorOrderDOS;
else if (header.fImageFormat == TwoImgHeader::kImageFormatProDOS)
*pOrder = DiskImg::kSectorOrderProDOS;
else if (header.fImageFormat == TwoImgHeader::kImageFormatNibble) {
*pOrder = DiskImg::kSectorOrderPhysical;
if (*pLength == kTrackCount525 * kTrackLenNib525) {
LOGI(" Prepping for 6656-byte 2MG-NIB");
*pPhysical = DiskImg::kPhysicalFormatNib525_6656;
} else if (*pLength == kTrackCount525 * kTrackLenNb2525) {
LOGI(" Prepping for 6384-byte 2MG-NB2");
*pPhysical = DiskImg::kPhysicalFormatNib525_6384;
} else {
LOGI(" NIB 2MG with length=%ld rejected", (long) *pLength);
return kDIErrOddLength;
}
}
*ppNewGFD = new GFDGFD;
return ((GFDGFD*)*ppNewGFD)->Open(pGFD, offset, readOnly);
}
/*
* Initialize fields for a new file.
*/
DIError Wrapper2MG::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
TwoImgHeader header;
int cc;
switch (physical) {
case DiskImg::kPhysicalFormatNib525_6656:
if (length != kTrackLenNib525 * kTrackCount525) {
LOGI("Invalid 2MG nibble length %ld", (long) length);
return kDIErrInvalidArg;
}
header.InitHeader(TwoImgHeader::kImageFormatNibble, (long) length,
8 * kTrackCount525); // 8 blocks per track
break;
case DiskImg::kPhysicalFormatSectors:
if ((length % 512) != 0) {
LOGI("Invalid 2MG length %ld", (long) length);
return kDIErrInvalidArg;
}
if (order == DiskImg::kSectorOrderProDOS)
cc = header.InitHeader(TwoImgHeader::kImageFormatProDOS,
(long) length, (long) length / 512);
else if (order == DiskImg::kSectorOrderDOS)
cc = header.InitHeader(TwoImgHeader::kImageFormatDOS,
(long) length, (long) length / 512);
else {
LOGI("Invalid 2MG sector order %d", order);
return kDIErrInvalidArg;
}
if (cc != 0) {
LOGI("TwoImg InitHeader failed (len=%ld)", (long) length);
return kDIErrInvalidArg;
}
break;
default:
LOGI("Invalid 2MG physical %d", physical);
return kDIErrInvalidArg;
}
if (dosVolumeNum != DiskImg::kVolumeNumNotSet)
header.SetDOSVolumeNum(dosVolumeNum);
cc = header.WriteHeader(pWrapperGFD);
if (cc != 0) {
LOGI("ERROR: 2MG header write failed (cc=%d)", cc);
return kDIErrGeneric;
}
long footerLen = header.fCmtLen + header.fCreatorLen;
if (footerLen > 0) {
// This is currently impossible, which is good because the Seek call
// will fail if pWrapperGFD is a buffer.
assert(false);
pWrapperGFD->Seek(header.fDataOffset + length, kSeekSet);
header.WriteFooter(pWrapperGFD);
}
long offset = header.fDataOffset;
*pWrappedLength = length + offset + footerLen;
*pDataFD = new GFDGFD;
return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, offset, false);
}
/*
* We only use GFDGFD, so there's nothing to do here.
*
* If we want to support changing the comment field in an open image, we'd
* need to handle making the file longer or shorter here. Right now we
* just ignore everything that comes before or after the start of the data.
* Since there's no checksum, none of the header fields change, so we
* don't even deal with that.
*/
DIError Wrapper2MG::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
return kDIErrNone;
}
/*
* ===========================================================================
* SHK (ShrinkIt NuFX), also .SDK and .BXY
* ===========================================================================
*/
/*
* NOTE: this doesn't override the global error message callback because
* we expect it to be set by the application.
*/
/*
* Display error messages... or not.
*/
/*static*/ NuResult WrapperNuFX::ErrMsgHandler(NuArchive* /*pArchive*/,
void* vErrorMessage)
{
const NuErrorMessage* pErrorMessage = (const NuErrorMessage*) vErrorMessage;
if (pErrorMessage->isDebug) {
Global::PrintDebugMsg(pErrorMessage->file, pErrorMessage->line,
"[D] %s\n", pErrorMessage->message);
} else {
Global::PrintDebugMsg(pErrorMessage->file, pErrorMessage->line,
"%s\n", pErrorMessage->message);
}
return kNuOK;
}
/*
* Open a NuFX archive, and verify that it holds exactly one disk archive.
*
* On success, the NuArchive pointer and thread idx are set, and 0 is
* returned. Returns -1 on failure.
*/
/*static*/ DIError WrapperNuFX::OpenNuFX(const char* pathName, NuArchive** ppArchive,
NuThreadIdx* pThreadIdx, long* pLength, bool readOnly)
{
NuError nerr = kNuErrNone;
NuArchive* pArchive = NULL;
NuRecordIdx recordIdx;
NuAttr attr;
const NuRecord* pRecord;
const NuThread* pThread = NULL;
int idx;
LOGI("Opening file '%s' to test for NuFX", pathName);
/*
* Open the archive.
*/
if (readOnly) {
nerr = NuOpenRO(pathName, &pArchive);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to open archive (err=%d)", nerr);
goto bail;
}
} else {
char* tmpPath;
tmpPath = GenTempPath(pathName);
if (tmpPath == NULL) {
nerr = kNuErrInternal;
goto bail;
}
nerr = NuOpenRW(pathName, tmpPath, 0, &pArchive);
if (nerr != kNuErrNone) {
LOGI(" NuFX OpenRW failed (nerr=%d)", nerr);
nerr = kNuErrGeneric;
delete[] tmpPath;
goto bail;
}
delete[] tmpPath;
}
NuSetErrorMessageHandler(pArchive, ErrMsgHandler);
nerr = NuGetAttr(pArchive, kNuAttrNumRecords, &attr);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to get record count (err=%d)", nerr);
goto bail;
}
if (attr != 1) {
LOGI(" NuFX archive has %d entries, not disk-only", attr);
nerr = kNuErrGeneric;
if (attr > 1)
goto file_archive;
else
goto bail; // shouldn't get zero-count archives, but...
}
/* get the first record */
nerr = NuGetRecordIdxByPosition(pArchive, 0, &recordIdx);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to get first recordIdx (err=%d)", nerr);
goto bail;
}
nerr = NuGetRecord(pArchive, recordIdx, &pRecord);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to get first record (err=%d)", nerr);
goto bail;
}
/* find a disk image thread */
for (idx = 0; idx < (int)NuRecordGetNumThreads(pRecord); idx++) {
pThread = NuGetThread(pRecord, idx);
if (NuGetThreadID(pThread) == kNuThreadIDDiskImage)
break;
}
if (idx == (int)NuRecordGetNumThreads(pRecord)) {
LOGI(" NuFX no disk image found in first record");
nerr = kNuErrGeneric;
goto file_archive;
}
assert(pThread != NULL);
*pThreadIdx = pThread->threadIdx;
/*
* Don't allow zero-length disks.
*/
*pLength = pThread->actualThreadEOF;
if (!*pLength) {
LOGI(" NuFX length of disk image is bad (%ld)", *pLength);
nerr = kNuErrGeneric;
goto bail;
}
/*
* Success!
*/
assert(nerr == kNuErrNone);
*ppArchive = pArchive;
pArchive = NULL;
bail:
if (pArchive != NULL)
NuClose(pArchive);
if (nerr == kNuErrNone)
return kDIErrNone;
else if (nerr == kNuErrBadMHCRC || nerr == kNuErrBadRHCRC)
return kDIErrBadChecksum;
else
return kDIErrGeneric;
file_archive:
if (pArchive != NULL)
NuClose(pArchive);
return kDIErrFileArchive;
}
/*
* Load a disk image into memory.
*
* Allocates a buffer with the specified length and loads the desired
* thread into it.
*
* In an LZW-I compressed thread, the third byte of the compressed thread
* data is the disk volume number that P8 ShrinkIt would use when formatting
* the disk. In an LZW-II compressed thread, it's the first byte of the
* compressed data. Uncompressed disk images simply don't have the disk
* volume number in them. Until NufxLib provides a simple way to access
* this bit of loveliness, we're going to pretend it's not there.
*
* Returns 0 on success, -1 on error.
*/
DIError WrapperNuFX::GetNuFXDiskImage(NuArchive* pArchive, NuThreadIdx threadIdx,
long length, char** ppData)
{
NuError err;
NuDataSink* pDataSink = NULL;
uint8_t* buf = NULL;
assert(length > 0);
buf = new uint8_t[length];
if (buf == NULL)
return kDIErrMalloc;
/*
* Create a buffer and expand the disk image into it.
*/
err = NuCreateDataSinkForBuffer(true, kNuConvertOff, buf, length,
&pDataSink);
if (err != kNuErrNone) {
LOGI(" NuFX: unable to create data sink (err=%d)", err);
goto bail;
}
err = NuExtractThread(pArchive, threadIdx, pDataSink);
if (err != kNuErrNone) {
LOGI(" NuFX: unable to extract thread (err=%d)", err);
goto bail;
}
//err = kNuErrBadThreadCRC; goto bail; // debug test only
*ppData = (char*)buf;
bail:
NuFreeDataSink(pDataSink);
if (err != kNuErrNone) {
LOGI(" NuFX GetNuFXDiskImage returning after nuerr=%d", err);
delete[] buf;
}
if (err == kNuErrNone)
return kDIErrNone;
else if (err == kNuErrBadDataCRC || err == kNuErrBadThreadCRC)
return kDIErrBadChecksum;
else if (err == kNuErrBadData)
return kDIErrBadCompressedData;
else if (err == kNuErrBadFormat)
return kDIErrUnsupportedCompression;
else
return kDIErrGeneric;
}
/*
* Test to see if this is a single-record NuFX archive with a disk archive
* in it.
*/
/*static*/ DIError WrapperNuFX::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
DIError dierr;
NuArchive* pArchive = NULL;
NuThreadIdx threadIdx;
long length;
const char* imagePath;
imagePath = pGFD->GetPathName();
if (imagePath == NULL) {
LOGI("Can't test NuFX on non-file");
return kDIErrNotSupported;
}
LOGI("Testing for NuFX");
dierr = OpenNuFX(imagePath, &pArchive, &threadIdx, &length, true);
if (dierr != kDIErrNone)
return dierr;
/* success; throw away state in case they don't like us anyway */
assert(pArchive != NULL);
NuClose(pArchive);
return kDIErrNone;
}
/*
* Open the archive, extract the disk image into a memory buffer.
*/
DIError WrapperNuFX::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly,
di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
DIError dierr = kDIErrNone;
NuThreadIdx threadIdx;
GFDBuffer* pNewGFD = NULL;
char* buf = NULL;
long length = -1;
const char* imagePath;
imagePath = pGFD->GetPathName();
if (imagePath == NULL) {
assert(false); // should've been caught in Test
return kDIErrNotSupported;
}
pGFD->Close(); // don't hold the file open
dierr = OpenNuFX(imagePath, &fpArchive, &threadIdx, &length, readOnly);
if (dierr != kDIErrNone)
goto bail;
dierr = GetNuFXDiskImage(fpArchive, threadIdx, length, &buf);
if (dierr != kDIErrNone)
goto bail;
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, readOnly);
if (dierr != kDIErrNone)
goto bail;
buf = NULL; // now owned by pNewGFD;
/*
* Success!
*/
assert(dierr == kDIErrNone);
*ppNewGFD = pNewGFD;
*pLength = length;
*pPhysical = DiskImg::kPhysicalFormatSectors;
*pOrder = DiskImg::kSectorOrderProDOS;
LOGI(" NuFX is ready, threadIdx=%d", threadIdx);
fThreadIdx = threadIdx;
bail:
if (dierr != kDIErrNone) {
NuClose(fpArchive);
fpArchive = NULL;
delete pNewGFD;
delete buf;
}
return dierr;
}
/*
* Given a filename, create a suitable temp pathname.
*
* This is really the wrong place to be doing this -- the application
* should get to deal with this -- but it's not the end of the world
* if we handle it here. Add to wish list: fix NufxLib so that the
* temp file can be a memory buffer.
*
* Returns a string allocated with new[].
*/
/*static*/ char* WrapperNuFX::GenTempPath(const char* path)
{
static const char* kTmpTemplate = "DItmp_XXXXXX";
char* tmpPath;
assert(path != NULL);
assert(strlen(path) > 0);
tmpPath = new char[strlen(path) + 32];
if (tmpPath == NULL)
return NULL;
strcpy(tmpPath, path);
/* back up to the first thing that looks like it's an fssep */
char* cp;
cp = tmpPath + strlen(tmpPath);
while (--cp >= tmpPath) {
if (*cp == '/' || *cp == '\\' || *cp == ':')
break;
}
/* we either fell off the back end or found an fssep; advance */
cp++;
strcpy(cp, kTmpTemplate);
LOGI(" NuFX GenTempPath '%s' -> '%s'", path, tmpPath);
return tmpPath;
}
/*
* Initialize fields for a new file.
*
* "pWrapperGFD" will be fairly useless after this, because we're
* recreating the underlying file. (If it doesn't have an underlying
* file, then we're hosed.)
*/
DIError WrapperNuFX::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
assert(physical == DiskImg::kPhysicalFormatSectors);
assert(order == DiskImg::kSectorOrderProDOS);
DIError dierr = kDIErrNone;
NuArchive* pArchive;
const char* imagePath;
char* tmpPath = NULL;
uint8_t* buf = NULL;
NuError nerr;
/*
* Create the NuFX archive, stomping on the existing file. (This
* makes pWrapperGFD invalid, but such is life with NufxLib.)
*/
imagePath = pWrapperGFD->GetPathName();
if (imagePath == NULL) {
assert(false); // must not have an outer wrapper
dierr = kDIErrNotSupported;
goto bail;
}
pWrapperGFD->Close(); // don't hold the file open
tmpPath = GenTempPath(imagePath);
if (tmpPath == NULL) {
dierr = kDIErrInternal;
goto bail;
}
nerr = NuOpenRW(imagePath, tmpPath, kNuOpenCreat, &pArchive);
if (nerr != kNuErrNone) {
LOGI(" NuFX OpenRW failed (nerr=%d)", nerr);
dierr = kDIErrGeneric;
goto bail;
}
/*
* Create a blank chunk of memory for the image.
*/
assert(length > 0);
buf = new uint8_t[(int) length];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
GFDBuffer* pNewGFD;
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, false);
if (dierr != kDIErrNone) {
delete pNewGFD;
goto bail;
}
*pDataFD = pNewGFD;
buf = NULL; // now owned by pNewGFD;
/*
* Success! Set misc stuff.
*/
fThreadIdx = 0; // don't have one to overwrite
fpArchive = pArchive;
bail:
delete[] tmpPath;
delete[] buf;
return dierr;
}
/*
* Close the NuFX archive.
*/
DIError WrapperNuFX::CloseNuFX(void)
{
NuError nerr;
/* throw away any un-flushed changes so that "close" can't fail */
(void) NuAbort(fpArchive);
nerr = NuClose(fpArchive);
if (nerr != kNuErrNone) {
LOGI("WARNING: NuClose failed");
return kDIErrGeneric;
}
return kDIErrNone;
}
/*
* Write the data using the default compression method.
*
* Doesn't touch "pWrapperGFD" or "pWrappedLen". Could probably update
* "pWrappedLen", but that's really only useful if we have a gzip Outer
* that wants to know how much data we have. Because we don't write to
* pWrapperGFD, we can't have a gzip wrapper, so there's no point in
* updating it.
*/
DIError WrapperNuFX::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
NuError nerr = kNuErrNone;
NuFileDetails fileDetails;
NuRecordIdx recordIdx;
NuThreadIdx threadIdx;
NuDataSource* pDataSource = NULL;
if (fThreadIdx != 0) {
/*
* Mark the old record for deletion.
*/
nerr = NuGetRecordIdxByPosition(fpArchive, 0, &recordIdx);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to get first recordIdx (err=%d)", nerr);
goto bail;
}
nerr = NuDeleteRecord(fpArchive, recordIdx);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to delete first record (err=%d)", nerr);
goto bail;
}
}
assert((dataLen % 512) == 0);
nerr = NuSetValue(fpArchive, kNuValueDataCompression,
fCompressType + kNuCompressNone);
if (nerr != kNuErrNone) {
LOGI("WARNING: unable to set compression to format %d",
fCompressType);
nerr = kNuErrNone;
} else {
LOGI(" NuFX set compression to %d/%d", fCompressType,
fCompressType + kNuCompressNone);
}
/*
* Fill out the fileDetails record appropriately.
*/
memset(&fileDetails, 0, sizeof(fileDetails));
fileDetails.threadID = kNuThreadIDDiskImage;
if (fStorageName != NULL)
fileDetails.storageNameMOR = fStorageName; // TODO
else
fileDetails.storageNameMOR = "NEW.DISK";
fileDetails.fileSysID = kNuFileSysUnknown;
fileDetails.fileSysInfo = kDefaultStorageFssep;
fileDetails.storageType = 512;
fileDetails.extraType = (long) (dataLen / 512);
fileDetails.access = kNuAccessUnlocked;
time_t now;
now = time(NULL);
UNIXTimeToDateTime(&now, &fileDetails.archiveWhen);
UNIXTimeToDateTime(&now, &fileDetails.modWhen);
UNIXTimeToDateTime(&now, &fileDetails.createWhen);
/*
* Create the new record.
*/
nerr = NuAddRecord(fpArchive, &fileDetails, &recordIdx);
if (nerr != kNuErrNone) {
LOGI(" NuFX AddRecord failed (nerr=%d)", nerr);
goto bail;
}
/*
* Create a data source for the thread.
*
* We need to get the memory buffer from pDataGFD, which we do in
* a somewhat unwholesome manner. However, there's no other way to
* feed the data into NufxLib.
*/
nerr = NuCreateDataSourceForBuffer(kNuThreadFormatUncompressed, 0,
(const uint8_t*) ((GFDBuffer*) pDataGFD)->GetBuffer(),
0, (long) dataLen, NULL, &pDataSource);
if (nerr != kNuErrNone) {
LOGI(" NuFX unable to create NufxLib data source (nerr=%d)", nerr);
goto bail;
}
/*
* Add the thread.
*/
nerr = NuAddThread(fpArchive, recordIdx, kNuThreadIDDiskImage,
pDataSource, &threadIdx);
if (nerr != kNuErrNone) {
LOGI(" NuFX AddThread failed (nerr=%d)", nerr);
goto bail;
}
pDataSource = NULL; // now owned by NufxLib
LOGI(" NuFX added thread %d in record %d, flushing changes",
threadIdx, recordIdx);
/*
* Flush changes (does the actual compression).
*/
uint32_t status;
nerr = NuFlush(fpArchive, &status);
if (nerr != kNuErrNone) {
LOGI(" NuFX flush failed (nerr=%d, status=%u)", nerr, status);
goto bail;
}
/* update the threadID */
fThreadIdx = threadIdx;
bail:
NuFreeDataSource(pDataSource);
if (nerr != kNuErrNone)
return kDIErrGeneric;
return kDIErrNone;
}
/*
* Common NuFX utility function. This ought to be in NufxLib.
*/
void WrapperNuFX::UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime)
{
struct tm* ptm;
assert(pWhen != NULL);
assert(pDateTime != NULL);
ptm = localtime(pWhen);
pDateTime->second = ptm->tm_sec;
pDateTime->minute = ptm->tm_min;
pDateTime->hour = ptm->tm_hour;
pDateTime->day = ptm->tm_mday -1;
pDateTime->month = ptm->tm_mon;
pDateTime->year = ptm->tm_year;
pDateTime->extra = 0;
pDateTime->weekDay = ptm->tm_wday +1;
}
/*
* ===========================================================================
* DDD (DDD 2.1, DDD Pro)
* ===========================================================================
*/
/*
* There really isn't a way to test if the file is a DDD archive, except
* to try to unpack it. One thing we can do fairly quickly is look for
* runs of repeated bytes, which will be impossible in a DDD file because
* we compress runs of repeated bytes with RLE.
*/
/*static*/ DIError WrapperDDD::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
DIError dierr;
GenericFD* pNewGFD = NULL;
LOGI("Testing for DDD");
pGFD->Rewind();
dierr = CheckForRuns(pGFD);
if (dierr != kDIErrNone)
return dierr;
dierr = Unpack(pGFD, &pNewGFD, NULL);
delete pNewGFD;
return dierr;
}
/*
* Load a bunch of data and check it for repeated byte sequences that
* would be removed by RLE. Runs of 4 bytes or longer should have been
* stripped out. DDD adds a couple of zeroes onto the end, so to avoid
* special cases we assume that a run of 5 is okay, and only flunk the
* data when it gets to 6.
*
* One big exception: the "favorites" table isn't run-length encoded,
* and if the track is nothing but zeroes the entire thing will be
* filled with 0xff. So we allow runs of 0xff bytes.
*
* PROBLEM: some sequences, such as repeated d5aa, can turn into what looks
* like a run of bytes in the output. We can't assume that arbitrary
* sequences of bytes won't be repeated. It does appear that we can assume
* that 00 bytes won't be repeated, so we can still scan for a series of
* zeroes and reject the image if found (which should clear us for all
* uncompressed formats and any compressed format with a padded file header).
*
* The goal is to detect uncompressed data sources. The test for DDD
* should come after other compressed data formats.
*
* For speed we crank the data in 8K at a time and don't correctly handle
* the boundaries. We do, however, need to avoid scanning the last 256
* bytes of the file, because DOS DDD just fills it with junk, and it's
* possible that junk might have runs in it.
*/
/*static*/ DIError WrapperDDD::CheckForRuns(GenericFD* pGFD)
{
DIError dierr = kDIErrNone;
int kRunThreshold = 5;
uint8_t buf[8192];
size_t bufCount;
int runLen;
di_off_t fileLen;
int i;
dierr = pGFD->Seek(0, kSeekEnd);
if (dierr != kDIErrNone)
goto bail;
fileLen = pGFD->Tell();
pGFD->Rewind();
fileLen -= 256; // could be extra data from DOS DDD
while (fileLen) {
bufCount = (size_t) fileLen;
if (bufCount > sizeof(buf))
bufCount = sizeof(buf);
fileLen -= bufCount;
dierr = pGFD->Read(buf, bufCount);
if (dierr != kDIErrNone)
goto bail;
//LOGI(" DDD READ %d bytes", bufCount);
if (dierr != kDIErrNone) {
LOGI(" DDD CheckForRuns read failed (err=%d)", dierr);
return dierr;
}
runLen = 0;
for (i = 1; i < (int) bufCount; i++) {
if (buf[i] == 0 && buf[i] == buf[i-1]) {
runLen++;
if (runLen == kRunThreshold && buf[i] != 0xff) {
LOGI(" DDD found run of >= %d of 0x%02x, bailing",
runLen+1, buf[i]);
return kDIErrGeneric;
}
} else {
runLen = 0;
}
}
}
LOGI(" DDD CheckForRuns scan complete, no long runs found");
bail:
return dierr;
}
/*
* Prepping is much the same as testing, but we fill in a few more details.
*/
DIError WrapperDDD::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly,
di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
DIError dierr;
LOGI("Prepping for DDD");
assert(*ppNewGFD == NULL);
dierr = Unpack(pGFD, ppNewGFD, pDiskVolNum);
if (dierr != kDIErrNone)
return dierr;
*pLength = kNumTracks * kTrackLen;
*pPhysical = DiskImg::kPhysicalFormatSectors;
*pOrder = DiskImg::kSectorOrderDOS;
return dierr;
}
/*
* Unpack a compressed disk image from "pGFD" to a new memory buffer
* created in "*ppNewGFD".
*/
/*static*/ DIError WrapperDDD::Unpack(GenericFD* pGFD, GenericFD** ppNewGFD,
short* pDiskVolNum)
{
DIError dierr;
GFDBuffer* pNewGFD = NULL;
uint8_t* buf = NULL;
short diskVolNum;
pGFD->Rewind();
buf = new uint8_t[kNumTracks * kTrackLen];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
pNewGFD = new GFDBuffer;
if (pNewGFD == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewGFD->Open(buf, kNumTracks * kTrackLen, true, false, false);
if (dierr != kDIErrNone)
goto bail;
buf = NULL; // now owned by pNewGFD;
dierr = UnpackDisk(pGFD, pNewGFD, &diskVolNum);
if (dierr != kDIErrNone)
goto bail;
if (pDiskVolNum != NULL)
*pDiskVolNum = diskVolNum;
*ppNewGFD = pNewGFD;
pNewGFD = NULL; // now owned by caller
bail:
delete[] buf;
delete pNewGFD;
return dierr;
}
/*
* Initialize stuff for a new file. There's no file header or other
* goodies, so we leave "pWrapperGFD" and "pWrappedLength" alone.
*/
DIError WrapperDDD::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
assert(length == kNumTracks * kTrackLen);
assert(physical == DiskImg::kPhysicalFormatSectors);
assert(order == DiskImg::kSectorOrderDOS);
DIError dierr;
uint8_t* buf = NULL;
/*
* Create a blank chunk of memory for the image.
*/
buf = new uint8_t[(int) length];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
GFDBuffer* pNewGFD;
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, false);
if (dierr != kDIErrNone) {
delete pNewGFD;
goto bail;
}
*pDataFD = pNewGFD;
buf = NULL; // now owned by pNewGFD;
// can't set *pWrappedLength yet
if (dosVolumeNum != DiskImg::kVolumeNumNotSet)
fDiskVolumeNum = dosVolumeNum;
else
fDiskVolumeNum = kDefaultNibbleVolumeNum;
bail:
delete[] buf;
return dierr;
}
/*
* Compress the disk image.
*/
DIError WrapperDDD::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
DIError dierr;
assert(dataLen == kNumTracks * kTrackLen);
pDataGFD->Rewind();
dierr = PackDisk(pDataGFD, pWrapperGFD, fDiskVolumeNum);
if (dierr != kDIErrNone)
return dierr;
*pWrappedLen = pWrapperGFD->Tell();
LOGI(" DDD compressed from %d to %ld",
kNumTracks * kTrackLen, (long) *pWrappedLen);
return kDIErrNone;
}
/*
* ===========================================================================
* DiskCopy (primarily a Mac format)
* ===========================================================================
*/
/*
* DiskCopy 4.2 header, from FTN $e0/0005.
*
* All values are BIG-endian.
*/
const int kDC42NameLen = 64;
const int kDC42ChecksumOffset = 72; // where the checksum lives
const int kDC42DataOffset = 84; // header is always this long
const int kDC42PrivateMagic = 0x100;
const int kDC42FakeTagLen = 19200; // add a "fake" tag to match Mac
typedef struct DiskImgLib::DC42Header {
char diskName[kDC42NameLen+1]; // from pascal string
uint32_t dataSize; // in bytes
uint32_t tagSize;
uint32_t dataChecksum;
uint32_t tagChecksum;
uint8_t diskFormat; // should be 1 for 800K
uint8_t formatByte; // should be $24, sometimes $22
uint16_t privateWord; // must be 0x0100
// userData begins at +84
// tagData follows user data
} DC42Header;
/*
* Dump the contents of a DC42Header.
*/
/*static*/ void WrapperDiskCopy42::DumpHeader(const DC42Header* pHeader)
{
LOGI("--- header contents:");
LOGI("\tdiskName = '%s'", pHeader->diskName);
LOGI("\tdataSize = %d (%dK)", pHeader->dataSize,
pHeader->dataSize / 1024);
LOGI("\ttagSize = %d", pHeader->tagSize);
LOGI("\tdataChecksum = 0x%08x", pHeader->dataChecksum);
LOGI("\ttagChecksum = 0x%08x", pHeader->tagChecksum);
LOGI("\tdiskFormat = %d", pHeader->diskFormat);
LOGI("\tformatByte = 0x%02x", pHeader->formatByte);
LOGI("\tprivateWord = 0x%04x", pHeader->privateWord);
}
/*
* Init a DC42 header for an 800K ProDOS disk.
*/
void WrapperDiskCopy42::InitHeader(DC42Header* pHeader)
{
memset(pHeader, 0, sizeof(*pHeader));
if (fStorageName == NULL || strlen(fStorageName) == 0)
strcpy(pHeader->diskName, "-not a Macintosh disk");
else
strcpy(pHeader->diskName, fStorageName);
pHeader->dataSize = 819200;
pHeader->tagSize = kDC42FakeTagLen; // emulate Mac behavior
pHeader->dataChecksum = 0xffffffff; // fixed during Flush
pHeader->tagChecksum = 0x00000000; // 19200 zeroes
pHeader->diskFormat = 1;
pHeader->formatByte = 0x24;
pHeader->privateWord = kDC42PrivateMagic;
}
/*
* Read the header from a DC42 file and verify it.
*
* Returns 0 on success, -1 on error or invalid header.
*/
/*static*/ int WrapperDiskCopy42::ReadHeader(GenericFD* pGFD, DC42Header* pHeader)
{
uint8_t hdrBuf[kDC42DataOffset];
if (pGFD->Read(hdrBuf, kDC42DataOffset) != kDIErrNone)
return -1;
// test the Pascal length byte
if (hdrBuf[0] >= kDC42NameLen)
return -1;
memcpy(pHeader->diskName, &hdrBuf[1], hdrBuf[0]);
pHeader->diskName[hdrBuf[0]] = '\0';
pHeader->dataSize = GetLongBE(&hdrBuf[64]);
pHeader->tagSize = GetLongBE(&hdrBuf[68]);
pHeader->dataChecksum = GetLongBE(&hdrBuf[72]);
pHeader->tagChecksum = GetLongBE(&hdrBuf[76]);
pHeader->diskFormat = hdrBuf[80];
pHeader->formatByte = hdrBuf[81];
pHeader->privateWord = GetShortBE(&hdrBuf[82]);
if (pHeader->dataSize != 800 * 1024 ||
pHeader->diskFormat != 1 ||
(pHeader->formatByte != 0x22 && pHeader->formatByte != 0x24) ||
pHeader->privateWord != kDC42PrivateMagic)
{
return -1;
}
return 0;
}
/*
* Write the header for a DC42 file.
*/
DIError WrapperDiskCopy42::WriteHeader(GenericFD* pGFD, const DC42Header* pHeader)
{
uint8_t hdrBuf[kDC42DataOffset];
pGFD->Rewind();
memset(hdrBuf, 0, sizeof(hdrBuf));
/*
* Disks created on a Mac include the null byte in the count; not sure
* if this applies to volume labels or just the "not a Macintosh disk"
* magic string. To be safe, we only increment it if it starts with '-'.
* (Need access to a Macintosh to test this.)
*/
hdrBuf[0] = strlen(pHeader->diskName);
if (pHeader->diskName[0] == '-' && hdrBuf[0] < (kDC42NameLen-1))
hdrBuf[0]++;
memcpy(&hdrBuf[1], pHeader->diskName, hdrBuf[0]);
PutLongBE(&hdrBuf[64], pHeader->dataSize);
PutLongBE(&hdrBuf[68], pHeader->tagSize);
PutLongBE(&hdrBuf[72], pHeader->dataChecksum);
PutLongBE(&hdrBuf[76], pHeader->tagChecksum);
hdrBuf[80] = pHeader->diskFormat;
hdrBuf[81] = pHeader->formatByte;
PutShortBE(&hdrBuf[82], pHeader->privateWord);
return pGFD->Write(hdrBuf, kDC42DataOffset);
}
/*
* Check to see if this is a DiskCopy 4.2 image.
*
* The format doesn't really have a magic number, but if we're stringent
* about our interpretation of some of the header fields (e.g. we only
* recognize 800K disks) we should be okay.
*/
/*static*/ DIError WrapperDiskCopy42::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
DC42Header header;
LOGI("Testing for DiskCopy");
if (wrappedLength < 800 * 1024 + kDC42DataOffset)
return kDIErrGeneric;
pGFD->Rewind();
if (ReadHeader(pGFD, &header) != 0)
return kDIErrGeneric;
DumpHeader(&header);
return kDIErrNone;
}
/*
* Compute the funky DiskCopy checksum.
*
* Position "pGFD" at the start of data.
*/
/*static*/ DIError WrapperDiskCopy42::ComputeChecksum(GenericFD* pGFD, uint32_t* pChecksum)
{
DIError dierr = kDIErrNone;
uint8_t buf[512];
long dataRem = 800 * 1024 /*pHeader->dataSize*/;
uint32_t checksum;
assert(dataRem % sizeof(buf) == 0);
assert((sizeof(buf) & 0x01) == 0); // we take it two bytes at a time
checksum = 0;
while (dataRem) {
int i;
dierr = pGFD->Read(buf, sizeof(buf));
if (dierr != kDIErrNone) {
LOGI(" DC42 read failed, dataRem=%ld (err=%d)", dataRem, dierr);
return dierr;
}
for (i = 0; i < (int) sizeof(buf); i += 2) {
uint16_t val = GetShortBE(buf+i);
checksum += val;
if (checksum & 0x01)
checksum = checksum >> 1 | 0x80000000;
else
checksum = checksum >> 1;
}
dataRem -= sizeof(buf);
}
*pChecksum = checksum;
return dierr;
}
/*
* Prepare a DiskCopy image for use.
*/
DIError WrapperDiskCopy42::Prep(GenericFD* pGFD, di_off_t wrappedLength,
bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
DIError dierr;
DC42Header header;
LOGI("Prepping for DiskCopy 4.2");
pGFD->Rewind();
if (ReadHeader(pGFD, &header) != 0)
return kDIErrGeneric;
/*
* Verify checksum. File should already be seeked to appropriate place.
*/
uint32_t checksum;
dierr = ComputeChecksum(pGFD, &checksum);
if (dierr != kDIErrNone)
return dierr;
if (checksum != header.dataChecksum) {
LOGW(" DC42 checksum mismatch (got 0x%08x, expected 0x%08x)",
checksum, header.dataChecksum);
fBadChecksum = true;
//return kDIErrBadChecksum;
} else {
LOGD(" DC42 checksum matches!");
}
/* looks good! */
*pLength = header.dataSize;
*pPhysical = DiskImg::kPhysicalFormatSectors;
*pOrder = DiskImg::kSectorOrderProDOS;
*ppNewGFD = new GFDGFD;
return ((GFDGFD*)*ppNewGFD)->Open(pGFD, kDC42DataOffset, readOnly);
}
/*
* Initialize fields for a new file.
*/
DIError WrapperDiskCopy42::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
DIError dierr;
DC42Header header;
assert(length == 800 * 1024);
assert(physical == DiskImg::kPhysicalFormatSectors);
//assert(order == DiskImg::kSectorOrderProDOS);
InitHeader(&header); // set all but checksum
dierr = WriteHeader(pWrapperGFD, &header);
if (dierr != kDIErrNone) {
LOGI("ERROR: 2MG header write failed (err=%d)", dierr);
return dierr;
}
*pWrappedLength = length + kDC42DataOffset;
*pDataFD = new GFDGFD;
return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, kDC42DataOffset, false);
}
/*
* We only use GFDGFD, so there's no data to write. However, we do need
* to update the checksum, and append our "fake" tag section.
*/
DIError WrapperDiskCopy42::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
DIError dierr;
uint32_t checksum;
/* compute the data checksum */
dierr = pWrapperGFD->Seek(kDC42DataOffset, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
dierr = ComputeChecksum(pWrapperGFD, &checksum);
if (dierr != kDIErrNone) {
LOGI(" DC42 failed while computing checksum (err=%d)", dierr);
goto bail;
}
/* write it into the wrapper */
dierr = pWrapperGFD->Seek(kDC42ChecksumOffset, kSeekSet);
if (dierr != kDIErrNone)
goto bail;
dierr = WriteLongBE(pWrapperGFD, checksum);
if (dierr != kDIErrNone)
goto bail;
/* add the tag bytes */
dierr = pWrapperGFD->Seek(kDC42DataOffset + 800*1024, kSeekSet);
char* tmpBuf;
tmpBuf = new char[kDC42FakeTagLen];
if (tmpBuf == NULL)
return kDIErrMalloc;
memset(tmpBuf, 0, kDC42FakeTagLen);
dierr = pWrapperGFD->Write(tmpBuf, kDC42FakeTagLen, NULL);
delete[] tmpBuf;
if (dierr != kDIErrNone)
goto bail;
bail:
return dierr;
}
/*
* ===========================================================================
* Sim2eHDV (Sim2e virtual hard-drive images)
* ===========================================================================
*/
/*
// mkhdv.c
//
// Create a Hard Disk Volume File (.HDV) for simIIe
static int mkhdv(FILE *op, uint blocks)
{
byte sector[512];
byte data[15];
uint i;
memset(data, 0, sizeof(data));
memcpy(data, "SIMSYSTEM_HDV", 13);
data[13] = (blocks & 0xff);
data[14] = (blocks & 0xff00) >> 8;
fwrite(data, 1, sizeof(data), op);
memset(sector, 0, sizeof(sector));
for (i = 0; i < blocks; i++)
fwrite(sector, 1, sizeof(sector), op);
return 0;
}
*/
const int kSim2eHeaderLen = 15;
static const char* kSim2eID = "SIMSYSTEM_HDV";
/*
* Test for a virtual hard-drive image. This is either a "raw" unadorned
* image, or one with a 15-byte "SimIIe" header on it.
*/
DIError WrapperSim2eHDV::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
char buf[kSim2eHeaderLen];
LOGI("Testing for Sim2e HDV");
if (wrappedLength < 512 ||
((wrappedLength - kSim2eHeaderLen) % 4096) != 0)
{
return kDIErrGeneric;
}
pGFD->Rewind();
if (pGFD->Read(buf, sizeof(buf)) != kDIErrNone)
return kDIErrGeneric;
if (strncmp(buf, kSim2eID, strlen(kSim2eID)) == 0)
return kDIErrNone;
else
return kDIErrGeneric;
}
/*
* These are always ProDOS volumes.
*/
DIError WrapperSim2eHDV::Prep(GenericFD* pGFD, di_off_t wrappedLength,
bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
*pLength = wrappedLength - kSim2eHeaderLen;
*pPhysical = DiskImg::kPhysicalFormatSectors;
*pOrder = DiskImg::kSectorOrderProDOS;
*ppNewGFD = new GFDGFD;
return ((GFDGFD*)*ppNewGFD)->Open(pGFD, kSim2eHeaderLen, readOnly);
}
/*
* Initialize fields for a new file.
*/
DIError WrapperSim2eHDV::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
uint8_t header[kSim2eHeaderLen];
long blocks = (long) (length / 512);
assert(physical == DiskImg::kPhysicalFormatSectors);
assert(order == DiskImg::kSectorOrderProDOS);
if (blocks < 4 || blocks > 65536) {
LOGI(" Sim2e invalid blocks %ld", blocks);
return kDIErrInvalidArg;
}
if (blocks == 65536) // 32MB volumes are actually 31.9
blocks = 65535;
memcpy(header, kSim2eID, strlen(kSim2eID));
header[13] = (uint8_t) blocks;
header[14] = (uint8_t) ((blocks & 0xff00) >> 8);
DIError dierr = pWrapperGFD->Write(header, kSim2eHeaderLen);
if (dierr != kDIErrNone) {
LOGI(" Sim2eHDV header write failed (err=%d)", dierr);
return dierr;
}
*pWrappedLength = length + kSim2eHeaderLen;
*pDataFD = new GFDGFD;
return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, kSim2eHeaderLen, false);
}
/*
* We only use GFDGFD, so there's nothing to do here.
*/
DIError WrapperSim2eHDV::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
return kDIErrNone;
}
/*
* ===========================================================================
* TrackStar .app images
* ===========================================================================
*/
/*
* File format:
* $0000 track 0 data
* $1a00 track 1 data
* $3400 track 2 data
* ...
* $3f600 track 39 data
*
* Each track consists of:
* $0000 Text description of disk contents (same on every track), in low
* ASCII, padded out with spaces ($20)
* $002e Start of zeroed-out header field
* $0080 $00 (indicates end of data when reading from end??)
* $0081 Raw nibble data (hi bit set), written backwards
* $19fe Start offset of track data
*
* Take the start offset, add 128, and walk backward until you find a
* value with the high bit clear. If the start offset is zero, start
* scanning from $19fd backward. (This approach courtesty Gerald Ryckman.)
*
* My take: the "offset" actually indicates the length of data, and the
* $00 is there to simplify somebody's algorithm. If the offset is zero
* it means the track couldn't be analyzed successfully, so a raw dump has
* been provided. Tracks 35-39 on most Apple II disks have zero length,
* but occasionally one analyzes "successfully" with some horribly truncated
* length.
*
* I'm going to assert that byte $81 be zero and that nothing else has the
* high bit clear until you hit the end of valid data.
*
* Because the nibbles are stored in reverse order, it's easiest to unpack
* the tracks to local buffers, then re-pack them when saving the file.
*/
/*
* Test to see if this is a TrackStar 5.25" disk image.
*
* While the image format supports variable-length nibble tracks, it uses
* fixed-length fields to store them. Each track is stored in 6656 bytes,
* but has a 129-byte header and a 2-byte footer (max of 6525).
*
* Images may be 40-track (5.25") or 80-track (5.25" disk with half-track
* stepping). The latter is useful in some circumstances for handling
* copy-protected disks. We don't have a half-track interface, so we just
* ignore the odd-numbered tracks.
*
* There is currently no way for the API to set the number of tracks.
*/
/*static*/ DIError WrapperTrackStar::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
DIError dierr = kDIErrNone;
LOGI("Testing for TrackStar");
int numTracks;
/* check the length */
if (wrappedLength == 6656*40)
numTracks = 40;
else if (wrappedLength == 6656*80)
numTracks = 80;
else
return kDIErrGeneric;
LOGI(" Checking for %d-track image", numTracks);
/* verify each track */
uint8_t trackBuf[kFileTrackStorageLen];
pGFD->Rewind();
for (int trk = 0; trk < numTracks; trk++) {
dierr = pGFD->Read(trackBuf, sizeof(trackBuf));
if (dierr != kDIErrNone)
goto bail;
dierr = VerifyTrack(trk, trackBuf);
if (dierr != kDIErrNone)
goto bail;
}
LOGI(" TrackStar tracks verified");
bail:
return dierr;
}
/*
* Check the format.
*/
/*static*/ DIError WrapperTrackStar::VerifyTrack(int track, const uint8_t* trackBuf)
{
unsigned int dataLen;
if (trackBuf[0x80] != 0) {
LOGI(" TrackStar track=%d found nonzero at 129", track);
return kDIErrGeneric;
}
dataLen = GetShortLE(trackBuf + 0x19fe);
if (dataLen > kMaxTrackLen) {
LOGI(" TrackStar track=%d len=%d exceeds max (%d)",
track, dataLen, kMaxTrackLen);
return kDIErrGeneric;
}
if (dataLen == 0)
dataLen = kMaxTrackLen;
unsigned int i;
for (i = 0; i < dataLen; i++) {
if ((trackBuf[0x81 + i] & 0x80) == 0) {
LOGI(" TrackStar track=%d found invalid data 0x%02x at %d",
track, trackBuf[0x81+i], i);
return kDIErrGeneric;
}
}
if (track == 0) {
LOGI(" TrackStar msg='%s'", trackBuf);
}
return kDIErrNone;
}
/*
* Fill in some details.
*/
DIError WrapperTrackStar::Prep(GenericFD* pGFD, di_off_t wrappedLength,
bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
LOGI("Prepping for TrackStar");
DIError dierr = kDIErrNone;
if (wrappedLength == kFileTrackStorageLen * 40)
fImageTracks = 40;
else if (wrappedLength == kFileTrackStorageLen * 80)
fImageTracks = 80;
else
return kDIErrInternal;
dierr = Unpack(pGFD, ppNewGFD);
if (dierr != kDIErrNone)
return dierr;
*pLength = kTrackStarNumTracks * kTrackAllocSize;
*pPhysical = DiskImg::kPhysicalFormatNib525_Var;
*pOrder = DiskImg::kSectorOrderPhysical;
return dierr;
}
/*
* Unpack reverse-order nibbles from "pGFD" to a new memory buffer
* created in "*ppNewGFD".
*/
DIError WrapperTrackStar::Unpack(GenericFD* pGFD, GenericFD** ppNewGFD)
{
DIError dierr;
GFDBuffer* pNewGFD = NULL;
uint8_t* buf = NULL;
pGFD->Rewind();
buf = new uint8_t[kTrackStarNumTracks * kTrackAllocSize];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
pNewGFD = new GFDBuffer;
if (pNewGFD == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewGFD->Open(buf, kTrackStarNumTracks * kTrackAllocSize,
true, false, false);
if (dierr != kDIErrNone)
goto bail;
buf = NULL; // now owned by pNewGFD;
dierr = UnpackDisk(pGFD, pNewGFD);
if (dierr != kDIErrNone)
goto bail;
*ppNewGFD = pNewGFD;
pNewGFD = NULL; // now owned by caller
bail:
delete[] buf;
delete pNewGFD;
return dierr;
}
/*
* Unpack a TrackStar image. This is mainly just copying bytes around. The
* nibble code is perfectly happy with odd-sized tracks. However, we want
* to be able to find a particular track without having to do a lookup. So,
* we just block out 40 sets of 6656-byte tracks.
*
* The resultant image will always have 40 tracks. On an 80-track image
* we skip the odd ones.
*
* The bytes are stored in reverse order, so we need to unpack them to a
* separate buffer.
*
* This fills out "fNibbleTrackInfo".
*/
DIError WrapperTrackStar::UnpackDisk(GenericFD* pGFD, GenericFD* pNewGFD)
{
DIError dierr = kDIErrNone;
uint8_t inBuf[kFileTrackStorageLen];
uint8_t outBuf[kTrackAllocSize];
int i, trk;
assert(kTrackStarNumTracks <= kMaxNibbleTracks525);
pGFD->Rewind();
pNewGFD->Rewind();
/* we don't currently support half-tracks */
fNibbleTrackInfo.numTracks = kTrackStarNumTracks;
for (trk = 0; trk < kTrackStarNumTracks; trk++) {
unsigned int dataLen;
fNibbleTrackInfo.offset[trk] = trk * kTrackAllocSize;
/* these were verified earlier, so assume data is okay */
dierr = pGFD->Read(inBuf, sizeof(inBuf));
if (dierr != kDIErrNone)
goto bail;
dataLen = GetShortLE(inBuf + 0x19fe);
if (dataLen == 0)
dataLen = kMaxTrackLen;
assert(dataLen <= kMaxTrackLen);
assert(dataLen <= sizeof(outBuf));
fNibbleTrackInfo.length[trk] = dataLen;
memset(outBuf, 0x11, sizeof(outBuf));
for (i = 0; i < (int) dataLen; i++)
outBuf[i] = inBuf[128+dataLen-i];
pNewGFD->Write(outBuf, sizeof(outBuf));
if (fImageTracks == 2*kTrackStarNumTracks) {
/* skip the odd-numbered tracks */
dierr = pGFD->Read(inBuf, sizeof(inBuf));
if (dierr != kDIErrNone)
goto bail;
}
}
bail:
return dierr;
}
/*
* Initialize stuff for a new file. There's no file header or other
* goodies, so we leave "pWrapperGFD" and "pWrappedLength" alone.
*/
DIError WrapperTrackStar::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
assert(length == kTrackLenTrackStar525 * kTrackCount525 ||
length == kTrackLenTrackStar525 * kTrackStarNumTracks);
assert(physical == DiskImg::kPhysicalFormatNib525_Var);
assert(order == DiskImg::kSectorOrderPhysical);
DIError dierr;
uint8_t* buf = NULL;
int numTracks = (int) (length / kTrackLenTrackStar525);
int i;
/*
* Set up the track offset and length table. We use the maximum
* data length (kTrackLenTrackStar525) for each. The nibble write
* routine will alter the length field as appropriate.
*/
fNibbleTrackInfo.numTracks = numTracks;
assert(fNibbleTrackInfo.numTracks <= kMaxNibbleTracks525);
for (i = 0; i < numTracks; i++) {
fNibbleTrackInfo.offset[i] = kTrackLenTrackStar525 * i;
fNibbleTrackInfo.length[i] = kTrackLenTrackStar525;
}
/*
* Create a blank chunk of memory for the image.
*/
buf = new uint8_t[(int) length];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
GFDBuffer* pNewGFD;
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, false);
if (dierr != kDIErrNone) {
delete pNewGFD;
goto bail;
}
*pDataFD = pNewGFD;
buf = NULL; // now owned by pNewGFD;
// can't set *pWrappedLength yet
bail:
delete[] buf;
return dierr;
}
/*
* Write the stored data into TrackStar format.
*
* The source data is in "pDataGFD" in a layout described by fNibbleTrackInfo.
* We need to create the new file in "pWrapperGFD".
*/
DIError WrapperTrackStar::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
DIError dierr = kDIErrNone;
assert(dataLen == kTrackLenTrackStar525 * kTrackCount525 ||
dataLen == kTrackLenTrackStar525 * kTrackStarNumTracks);
assert(kTrackLenTrackStar525 <= kMaxTrackLen);
pDataGFD->Rewind();
uint8_t writeBuf[kFileTrackStorageLen];
uint8_t dataBuf[kTrackLenTrackStar525];
int track, trackLen;
for (track = 0; track < kTrackStarNumTracks; track++) {
if (track < fNibbleTrackInfo.numTracks) {
dierr = pDataGFD->Read(dataBuf, kTrackLenTrackStar525);
if (dierr != kDIErrNone)
goto bail;
trackLen = fNibbleTrackInfo.length[track];
assert(fNibbleTrackInfo.offset[track] == kTrackLenTrackStar525 * track);
} else {
LOGI(" TrackStar faking track %d", track);
memset(dataBuf, 0xff, sizeof(dataBuf));
trackLen = kMaxTrackLen;
}
memset(writeBuf, 0x80, sizeof(writeBuf)); // not strictly necessary
memset(writeBuf, 0x20, kCommentFieldLen);
memset(writeBuf+kCommentFieldLen, 0x00, 0x81-kCommentFieldLen);
const char* comment;
if (fStorageName != NULL && *fStorageName != '\0')
comment = fStorageName;
else
comment = "(created by CiderPress)";
if (strlen(comment) > kCommentFieldLen)
memcpy(writeBuf, comment, kCommentFieldLen);
else
memcpy(writeBuf, comment, strlen(comment));
int i;
for (i = 0; i < trackLen; i++) {
// If we write a value here with the high bit clear, we will
// reject the file when we try to open it. So, we force the
// high bit on here, on the assumption that the nibble data
// we've been handled is otherwise good.
//writeBuf[0x81+i] = dataBuf[trackLen - i -1];
writeBuf[0x81+i] = dataBuf[trackLen - i -1] | 0x80;
}
if (trackLen == kMaxTrackLen)
PutShortLE(writeBuf + 0x19fe, 0);
else
PutShortLE(writeBuf + 0x19fe, (uint16_t) trackLen);
dierr = pWrapperGFD->Write(writeBuf, sizeof(writeBuf));
if (dierr != kDIErrNone)
goto bail;
}
*pWrappedLen = pWrapperGFD->Tell();
assert(*pWrappedLen == kFileTrackStorageLen * kTrackStarNumTracks);
bail:
return dierr;
}
void WrapperTrackStar::SetNibbleTrackLength(int track, int length)
{
assert(track >= 0);
assert(length > 0 && length <= kMaxTrackLen);
assert(track < fNibbleTrackInfo.numTracks);
LOGI(" TrackStar: set length of track %d to %d", track, length);
fNibbleTrackInfo.length[track] = length;
}
/*
* ===========================================================================
* FDI (Formatted Disk Image) format
* ===========================================================================
*/
/*
* The format is described in detail in documents on the "disk2fdi" web site.
*
* FDI is currently unique in that it can (and often does) store nibble
* images of 3.5" disks. Rather than add an understanding of nibblized
* 3.5" disks to DiskImg, I've chosen to present it as a simple 800K
* ProDOS disk image. The only flaw in the scheme is that we have to
* keep track of the bad blocks in a parallel data structure.
*/
/*static*/ const char* WrapperFDI::kFDIMagic = "Formatted Disk Image file\r\n";
/*
* Test to see if this is an FDI disk image.
*/
/*static*/ DIError WrapperFDI::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
DIError dierr = kDIErrNone;
uint8_t headerBuf[kMinHeaderLen];
FDIHeader hdr;
LOGI("Testing for FDI");
pGFD->Rewind();
dierr = pGFD->Read(headerBuf, sizeof(headerBuf));
if (dierr != kDIErrNone)
goto bail;
UnpackHeader(headerBuf, &hdr);
if (strcmp(hdr.signature, kFDIMagic) != 0) {
LOGI("FDI: FDI signature not found");
return kDIErrGeneric;
}
if (hdr.version < kMinVersion) {
LOGI("FDI: bad version 0x%.04x", hdr.version);
return kDIErrGeneric;
}
bail:
return dierr;
}
/*
* Unpack a 512-byte buffer with the FDI header into its components.
*/
/*static*/ void WrapperFDI::UnpackHeader(const uint8_t* headerBuf, FDIHeader* pHdr)
{
memcpy(pHdr->signature, &headerBuf[0], kSignatureLen);
pHdr->signature[kSignatureLen] = '\0';
memcpy(pHdr->creator, &headerBuf[27], kCreatorLen);
pHdr->creator[kCreatorLen] = '\0';
memcpy(pHdr->comment, &headerBuf[59], kCommentLen);
pHdr->comment[kCommentLen] = '\0';
pHdr->version = GetShortBE(&headerBuf[140]);
pHdr->lastTrack = GetShortBE(&headerBuf[142]);
pHdr->lastHead = headerBuf[144];
pHdr->type = headerBuf[145];
pHdr->rotSpeed = headerBuf[146];
pHdr->flags = headerBuf[147];
pHdr->tpi = headerBuf[148];
pHdr->headWidth = headerBuf[149];
pHdr->reserved = GetShortBE(&headerBuf[150]);
}
/*
* Dump the contents of an FDI header.
*/
/*static*/ void WrapperFDI::DumpHeader(const FDIHeader* pHdr)
{
static const char* kTypes[] = {
"8\"", "5.25\"", "3.5\"", "3\""
};
static const char* kTPI[] = {
"48", "67", "96", "100", "135", "192"
};
LOGI(" FDI header contents:");
LOGI(" signature: '%s'", pHdr->signature);
LOGI(" creator : '%s'", pHdr->creator);
LOGI(" comment : '%s'", pHdr->comment);
LOGI(" version : %d.%d", pHdr->version >> 8, pHdr->version & 0xff);
LOGI(" lastTrack: %d", pHdr->lastTrack);
LOGI(" lastHead : %d", pHdr->lastHead);
LOGI(" type : %d (%s)", pHdr->type,
(/*pHdr->type >= 0 &&*/ pHdr->type < NELEM(kTypes)) ?
kTypes[pHdr->type] : "???");
LOGI(" rotSpeed : %drpm", pHdr->rotSpeed + 128);
LOGI(" flags : 0x%02x", pHdr->flags);
LOGI(" tpi : %d (%s)", pHdr->tpi,
(/*pHdr->tpi >= 0 &&*/ pHdr->tpi < NELEM(kTPI)) ?
kTPI[pHdr->tpi] : "???");
LOGI(" headWidth: %d (%s)", pHdr->headWidth,
(/*pHdr->headWidth >= 0 &&*/ pHdr->headWidth < NELEM(kTPI)) ?
kTPI[pHdr->headWidth] : "???");
LOGI(" reserved : %d", pHdr->reserved);
}
/*
* Unpack the disk to heap storage.
*/
DIError WrapperFDI::Prep(GenericFD* pGFD, di_off_t wrappedLength, bool readOnly,
di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
LOGI("Prepping for FDI");
DIError dierr = kDIErrNone;
FDIHeader hdr;
pGFD->Rewind();
dierr = pGFD->Read(fHeaderBuf, sizeof(fHeaderBuf));
if (dierr != kDIErrNone)
goto bail;
UnpackHeader(fHeaderBuf, &hdr);
if (strcmp(hdr.signature, kFDIMagic) != 0)
return kDIErrGeneric;
DumpHeader(&hdr);
/*
* There are two formats that we're interested in, 3.5" and 5.25". They
* are handled differently within CiderPress, so we split here.
*
* Sometimes disk2fdi finds extra tracks. No Apple II hardware ever
* went past 40 on 5.25" disks, but we'll humor the software and allow
* images with up to 50. Ditto for 3.5" disks, which should always
* have 80 double-sided tracks.
*/
if (hdr.type == kDiskType525) {
LOGI("FDI: decoding 5.25\" disk");
if (hdr.lastHead != 0 || hdr.lastTrack >= kMaxNibbleTracks525 + 10) {
LOGI("FDI: bad params head=%d ltrack=%d",
hdr.lastHead, hdr.lastTrack);
dierr = kDIErrUnsupportedImageFeature;
goto bail;
}
if (hdr.lastTrack >= kMaxNibbleTracks525) {
LOGI("FDI: reducing hdr.lastTrack from %d to %d",
hdr.lastTrack, kMaxNibbleTracks525-1);
hdr.lastTrack = kMaxNibbleTracks525-1;
}
/*
* Unpack to a series of variable-length nibble tracks. The data
* goes into ppNewGFD, and a table of track info goes into
* fNibbleTrackInfo.
*/
dierr = Unpack525(pGFD, ppNewGFD, hdr.lastTrack+1, hdr.lastHead+1);
if (dierr != kDIErrNone)
return dierr;
*pLength = kMaxNibbleTracks525 * kTrackAllocSize;
*pPhysical = DiskImg::kPhysicalFormatNib525_Var;
*pOrder = DiskImg::kSectorOrderPhysical;
} else if (hdr.type == kDiskType35) {
LOGI("FDI: decoding 3.5\" disk");
if (hdr.lastHead != 1 || hdr.lastTrack >= kMaxNibbleTracks35 + 10) {
LOGI("FDI: bad params head=%d ltrack=%d",
hdr.lastHead, hdr.lastTrack);
dierr = kDIErrUnsupportedImageFeature;
goto bail;
}
if (hdr.lastTrack >= kMaxNibbleTracks35) {
LOGI("FDI: reducing hdr.lastTrack from %d to %d",
hdr.lastTrack, kMaxNibbleTracks35-1);
hdr.lastTrack = kMaxNibbleTracks35-1;
}
/*
* Unpack to 800K of 512-byte ProDOS-order blocks, with a
* "bad block" map.
*/
dierr = Unpack35(pGFD, ppNewGFD, hdr.lastTrack+1, hdr.lastHead+1,
ppBadBlockMap);
if (dierr != kDIErrNone)
return dierr;
*pLength = 800 * 1024;
*pPhysical = DiskImg::kPhysicalFormatSectors;
*pOrder = DiskImg::kSectorOrderProDOS;
} else {
LOGI("FDI: unsupported disk type");
dierr = kDIErrUnsupportedImageFeature;
goto bail;
}
bail:
return dierr;
}
/*
* Unpack pulse timing values to nibbles.
*/
DIError WrapperFDI::Unpack525(GenericFD* pGFD, GenericFD** ppNewGFD, int numCyls,
int numHeads)
{
DIError dierr = kDIErrNone;
GFDBuffer* pNewGFD = NULL;
uint8_t* buf = NULL;
int numTracks;
numTracks = numCyls * numHeads;
if (numTracks < kMaxNibbleTracks525)
numTracks = kMaxNibbleTracks525;
pGFD->Rewind();
buf = new uint8_t[numTracks * kTrackAllocSize];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
pNewGFD = new GFDBuffer;
if (pNewGFD == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewGFD->Open(buf, numTracks * kTrackAllocSize,
true, false, false);
if (dierr != kDIErrNone)
goto bail;
buf = NULL; // now owned by pNewGFD;
dierr = UnpackDisk525(pGFD, pNewGFD, numCyls, numHeads);
if (dierr != kDIErrNone)
goto bail;
*ppNewGFD = pNewGFD;
pNewGFD = NULL; // now owned by caller
bail:
delete[] buf;
delete pNewGFD;
return dierr;
}
/*
* Unpack pulse timing values to fully-decoded blocks.
*/
DIError WrapperFDI::Unpack35(GenericFD* pGFD, GenericFD** ppNewGFD, int numCyls,
int numHeads, LinearBitmap** ppBadBlockMap)
{
DIError dierr = kDIErrNone;
GFDBuffer* pNewGFD = NULL;
uint8_t* buf = NULL;
pGFD->Rewind();
buf = new uint8_t[800 * 1024];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
pNewGFD = new GFDBuffer;
if (pNewGFD == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = pNewGFD->Open(buf, 800 * 1024, true, false, false);
if (dierr != kDIErrNone)
goto bail;
buf = NULL; // now owned by pNewGFD;
*ppBadBlockMap = new LinearBitmap(1600);
if (*ppBadBlockMap == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
dierr = UnpackDisk35(pGFD, pNewGFD, numCyls, numHeads, *ppBadBlockMap);
if (dierr != kDIErrNone)
goto bail;
*ppNewGFD = pNewGFD;
pNewGFD = NULL; // now owned by caller
bail:
delete[] buf;
delete pNewGFD;
return dierr;
}
/*
* Initialize stuff for a new file. There's no file header or other
* goodies, so we leave "pWrapperGFD" and "pWrappedLength" alone.
*/
DIError WrapperFDI::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
DIError dierr = kDIErrGeneric; // not yet
#if 0
uint8_t* buf = NULL;
int numTracks = (int) (length / kTrackLenTrackStar525);
int i;
assert(length == kTrackLenTrackStar525 * kTrackCount525 ||
length == kTrackLenTrackStar525 * kTrackStarNumTracks);
assert(physical == DiskImg::kPhysicalFormatNib525_Var);
assert(order == DiskImg::kSectorOrderPhysical);
/*
* Set up the track offset and length table. We use the maximum
* data length (kTrackLenTrackStar525) for each. The nibble write
* routine will alter the length field as appropriate.
*/
fNibbleTrackInfo.numTracks = numTracks;
assert(fNibbleTrackInfo.numTracks <= kMaxNibbleTracks);
for (i = 0; i < numTracks; i++) {
fNibbleTrackInfo.offset[i] = kTrackLenTrackStar525 * i;
fNibbleTrackInfo.length[i] = kTrackLenTrackStar525;
}
/*
* Create a blank chunk of memory for the image.
*/
buf = new uint8_t[(int) length];
if (buf == NULL) {
dierr = kDIErrMalloc;
goto bail;
}
GFDBuffer* pNewGFD;
pNewGFD = new GFDBuffer;
dierr = pNewGFD->Open(buf, length, true, false, false);
if (dierr != kDIErrNone) {
delete pNewGFD;
goto bail;
}
*pDataFD = pNewGFD;
buf = NULL; // now owned by pNewGFD;
// can't set *pWrappedLength yet
bail:
delete[] buf;
#endif
return dierr;
}
/*
* Write the stored data into FDI format.
*
* The source data is in "pDataGFD" in a layout described by fNibbleTrackInfo.
* We need to create the new file in "pWrapperGFD".
*/
DIError WrapperFDI::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
DIError dierr = kDIErrGeneric; // not yet
#if 0
assert(dataLen == kTrackLenTrackStar525 * kTrackCount525 ||
dataLen == kTrackLenTrackStar525 * kTrackStarNumTracks);
assert(kTrackLenTrackStar525 <= kMaxTrackLen);
pDataGFD->Rewind();
uint8_t writeBuf[kFileTrackStorageLen];
uint8_t dataBuf[kTrackLenTrackStar525];
int track, trackLen;
for (track = 0; track < kTrackStarNumTracks; track++) {
if (track < fNibbleTrackInfo.numTracks) {
dierr = pDataGFD->Read(dataBuf, kTrackLenTrackStar525);
if (dierr != kDIErrNone)
goto bail;
trackLen = fNibbleTrackInfo.length[track];
assert(fNibbleTrackInfo.offset[track] == kTrackLenTrackStar525 * track);
} else {
LOGI(" TrackStar faking track %d", track);
memset(dataBuf, 0xff, sizeof(dataBuf));
trackLen = kMaxTrackLen;
}
memset(writeBuf, 0x80, sizeof(writeBuf)); // not strictly necessary
memset(writeBuf, 0x20, kCommentFieldLen);
memset(writeBuf+kCommentFieldLen, 0x00, 0x81-kCommentFieldLen);
const char* comment;
if (fStorageName != NULL && *fStorageName != '\0')
comment = fStorageName;
else
comment = "(created by CiderPress)";
if (strlen(comment) > kCommentFieldLen)
memcpy(writeBuf, comment, kCommentFieldLen);
else
memcpy(writeBuf, comment, strlen(comment));
int i;
for (i = 0; i < trackLen; i++)
writeBuf[0x81+i] = dataBuf[trackLen - i -1];
if (trackLen == kMaxTrackLen)
PutShortLE(writeBuf + 0x19fe, 0);
else
PutShortLE(writeBuf + 0x19fe, (uint16_t) trackLen);
dierr = pWrapperGFD->Write(writeBuf, sizeof(writeBuf));
if (dierr != kDIErrNone)
goto bail;
}
*pWrappedLen = pWrapperGFD->Tell();
assert(*pWrappedLen == kFileTrackStorageLen * kTrackStarNumTracks);
bail:
#endif
return dierr;
}
void WrapperFDI::SetNibbleTrackLength(int track, int length)
{
assert(false); // not yet
#if 0
assert(track >= 0);
assert(length > 0 && length <= kMaxTrackLen);
assert(track < fNibbleTrackInfo.numTracks);
LOGI(" FDI: set length of track %d to %d", track, length);
fNibbleTrackInfo.length[track] = length;
#endif
}
/*
* ===========================================================================
* Unadorned nibble format
* ===========================================================================
*/
/*
* See if this is unadorned nibble format.
*/
/*static*/ DIError WrapperUnadornedNibble::Test(GenericFD* pGFD,
di_off_t wrappedLength)
{
LOGI("Testing for unadorned nibble");
/* test length */
if (wrappedLength != kTrackCount525 * kTrackLenNib525 &&
wrappedLength != kTrackCount525 * kTrackLenNb2525)
{
return kDIErrGeneric;
}
/* quick scan for invalid data */
const int kScanSize = 512;
uint8_t buf[kScanSize];
pGFD->Rewind();
if (pGFD->Read(buf, sizeof(buf)) != kDIErrNone)
return kDIErrGeneric;
/*
* Make sure this is a nibble image and not just a ProDOS volume that
* happened to get the right number of blocks. The primary test is
* for < 0x80 since there's no way that can be valid, even on a track
* full of junk.
*/
for (int i = 0; i < kScanSize; i++) {
if (buf[i] < 0x80) {
LOGD(" Disqualifying len=%ld from nibble, byte=0x%02x",
(long) wrappedLength, buf[i]);
return kDIErrGeneric;
} else if (buf[i] < 0x96) {
LOGD(" Warning: funky byte 0x%02x in file", buf[i]);
}
}
return kDIErrNone;
}
/*
* Prepare unadorned nibble for use. Not much to do here.
*/
DIError WrapperUnadornedNibble::Prep(GenericFD* pGFD, di_off_t wrappedLength,
bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
LOGI("Prep for unadorned nibble");
if (wrappedLength == kTrackCount525 * kTrackLenNib525) {
LOGI(" Prepping for 6656-byte NIB");
*pPhysical = DiskImg::kPhysicalFormatNib525_6656;
} else if (wrappedLength == kTrackCount525 * kTrackLenNb2525) {
LOGI(" Prepping for 6384-byte NB2");
*pPhysical = DiskImg::kPhysicalFormatNib525_6384;
} else {
LOGI(" Unexpected wrappedLength %ld for unadorned nibble",
(long) wrappedLength);
assert(false);
}
*pLength = wrappedLength;
*pOrder = DiskImg::kSectorOrderPhysical;
*ppNewGFD = new GFDGFD;
return ((GFDGFD*)*ppNewGFD)->Open(pGFD, 0, readOnly);
}
/*
* Initialize fields for a new file.
*/
DIError WrapperUnadornedNibble::Create(di_off_t length,
DiskImg::PhysicalFormat physical, DiskImg::SectorOrder order,
short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
LOGI("Create unadorned nibble");
*pWrappedLength = length;
*pDataFD = new GFDGFD;
return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, 0, false);
}
/*
* We only use GFDGFD, so there's nothing to do here.
*/
DIError WrapperUnadornedNibble::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
return kDIErrNone;
}
/*
* ===========================================================================
* Unadorned sectors
* ===========================================================================
*/
/*
* See if this is unadorned sector format. The only way we can really tell
* is by looking at the file size.
*
* The only requirement is that it be a multiple of 512 bytes. This holds
* for all ProDOS volumes and all floppy disk images. We also need to test
* for 13-sector ".d13" images.
*
* It also holds for 35-track 6656-byte unadorned nibble images, so we need
* to test for them first.
*/
/*static*/ DIError WrapperUnadornedSector::Test(GenericFD* pGFD, di_off_t wrappedLength)
{
LOGI("Testing for unadorned sector (wrappedLength=%ld/%u)",
(long) (wrappedLength >> 32), (uint32_t) wrappedLength);
if (wrappedLength >= 1536 && (wrappedLength % 512) == 0)
return kDIErrNone;
if (wrappedLength == kD13Length) // 13-sector image?
return kDIErrNone;
return kDIErrGeneric;
}
/*
* Prepare unadorned sector for use. Not much to do here.
*/
DIError WrapperUnadornedSector::Prep(GenericFD* pGFD, di_off_t wrappedLength,
bool readOnly, di_off_t* pLength, DiskImg::PhysicalFormat* pPhysical,
DiskImg::SectorOrder* pOrder, short* pDiskVolNum,
LinearBitmap** ppBadBlockMap, GenericFD** ppNewGFD)
{
LOGI("Prepping for unadorned sector");
assert(wrappedLength > 0);
*pLength = wrappedLength;
*pPhysical = DiskImg::kPhysicalFormatSectors;
//*pOrder = undetermined
*ppNewGFD = new GFDGFD;
return ((GFDGFD*)*ppNewGFD)->Open(pGFD, 0, readOnly);
}
/*
* Initialize fields for a new file.
*/
DIError WrapperUnadornedSector::Create(di_off_t length, DiskImg::PhysicalFormat physical,
DiskImg::SectorOrder order, short dosVolumeNum, GenericFD* pWrapperGFD,
di_off_t* pWrappedLength, GenericFD** pDataFD)
{
LOGI("Create unadorned sector");
*pWrappedLength = length;
*pDataFD = new GFDGFD;
return ((GFDGFD*)*pDataFD)->Open(pWrapperGFD, 0, false);
}
/*
* We only use GFDGFD, so there's nothing to do here.
*/
DIError WrapperUnadornedSector::Flush(GenericFD* pWrapperGFD, GenericFD* pDataGFD,
di_off_t dataLen, di_off_t* pWrappedLen)
{
return kDIErrNone;
}