mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-22 05:29:43 +00:00
b97584eeb6
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.
2477 lines
75 KiB
C++
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;
|
|
}
|