From 090797a76fb3435fe22cd7b1e7a6d4bee4a3d08e Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 9 Jan 2015 17:52:16 -0800 Subject: [PATCH] Change tabs to spaces, use Linux EOL No substantative changes (unless the tabs-to-spaces messed something up). --- linux/Convert.cpp | 982 ++++++++++++------------ linux/GetFile.cpp | 440 +++++------ linux/MDC.cpp | 1780 +++++++++++++++++++++---------------------- linux/MakeDisk.cpp | 776 +++++++++---------- linux/PackDDD.cpp | 1532 ++++++++++++++++++------------------- linux/SSTAsm.cpp | 650 ++++++++-------- linux/StringArray.h | 202 ++--- 7 files changed, 3181 insertions(+), 3181 deletions(-) diff --git a/linux/Convert.cpp b/linux/Convert.cpp index 9d96bfc..81d5941 100644 --- a/linux/Convert.cpp +++ b/linux/Convert.cpp @@ -1,491 +1,491 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * Convert from one image format to another. - */ -#include -#include -#include -#include -#include -#include "../diskimg/DiskImg.h" -#include "../nufxlib/NufxLib.h" - -using namespace DiskImgLib; - -#define nil NULL -#define ASSERT assert -#define NELEM(x) (sizeof(x) / sizeof((x)[0])) - -FILE* gLog = nil; -pid_t gPid = getpid(); - - -/* - * Handle a debug message from the DiskImg library. - */ -/*static*/ void -MsgHandler(const char* file, int line, const char* msg) -{ - ASSERT(file != nil); - ASSERT(msg != nil); - - fprintf(gLog, "%05u %s", gPid, msg); -} -/* - * Handle a global error message from the NufxLib library by shoving it - * through the DiskImgLib message function. - */ -NuResult -NufxErrorMsgHandler(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; -} - -/* - * Convert one disk image to another. - */ -DIError -Convert(const char* infile, const char* outfile) -{ - DIError dierr = kDIErrNone; - DiskImg srcImg, dstImg; - const char* storageName = nil; - - printf("Converting in='%s' out='%s'\n", infile, outfile); - - /* - * Prepare the source image. - */ - dierr = srcImg.OpenImage(infile, '/', true); - if (dierr != kDIErrNone) { - fprintf(stderr, "Unable to open disk image: %s.\n", - DiskImgLib::DIStrError(dierr)); - goto bail; - } - - dierr = srcImg.AnalyzeImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "Unable to determine source image format.\n"); - goto bail; - } - - if (!srcImg.GetHasBlocks() && !srcImg.GetHasSectors()) { - /* add nibble tracks someday */ - fprintf(stderr, - "Sorry, only block- or sector-addressable images allowed.\n"); - dierr = kDIErrUnsupportedPhysicalFmt; - goto bail; - } - if (srcImg.GetHasBlocks()) { - assert(srcImg.GetNumBlocks() > 0); - } else { - assert(srcImg.GetNumTracks() > 0); - } - - if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { - fprintf(stderr, "(QUERY) don't know sector order\n"); - dierr = kDIErrFilesystemNotFound; - goto bail; - } - - storageName = "MyHappyDisk"; - - /* force the access to be ProDOS-ordered */ - dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(), - DiskImg::kFormatGenericProDOSOrd, srcImg.GetSectorOrder()); - if (dierr != kDIErrNone) { - fprintf(stderr, "Couldn't switch to generic ProDOS: %s.\n", - DiskImgLib::DIStrError(dierr)); - goto bail; - } - - /* transfer the DOS volume num, if one was set */ - printf("DOS volume number set to %d\n", srcImg.GetDOSVolumeNum()); - dstImg.SetDOSVolumeNum(srcImg.GetDOSVolumeNum()); - - const DiskImg::NibbleDescr* pNibbleDescr; - pNibbleDescr = nil; - - /* - * Prepare the destination image. - * - * We *always* use DiskImg::kFormatGenericProDOSOrd here, because it - * must match up with what we selected above. - * - * We could enable "skipFormat" on all of these but the nibble images, - * but we go ahead and set it to "false" on all of them just for fun. - */ - switch (18) { - case 0: - /* 16-sector nibble image, by blocks */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatNib525_6656, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 1: - /* 16-sector nibble image, by tracks/sectors */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatNib525_6656, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - 35, 16, - false); - break; - case 2: - /* 16-sector NB2 nibble image, by tracks/sectors */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatNib525_6384, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - 35, 16, - false); - break; - case 3: - /* 13-sector nibble image, by tracks/sectors */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS32Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatNib525_6656, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - 35, 13, - false); - break; - case 4: - /* 16-sector nb2 image, by tracks/sectors */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatNib525_6384, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - 35, 16, - false); - break; - case 5: - /* sector image, by blocks, ProDOS order */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 6: - /* sector image, by blocks, DOS order */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 7: - /* sector image, by blocks, ProDOS order, Sim2e */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatSim2eHDV, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 8: - /* odd-length HUGE sector image, by blocks */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - 65535, - false); - break; - case 9: - /* sector image, by blocks, physical order, with gzip */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatGzip, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 10: - /* sector image, by blocks, ProDOS order, with gzip */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatGzip, - DiskImg::kFileFormatSim2eHDV, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 11: - /* sector image, by blocks, ProDOS order, 2MG */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormat2MG, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 12: - /* 16-sector nibble image, by tracks/sectors, 2MG */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormat2MG, - DiskImg::kPhysicalFormatNib525_6656, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - 35, 16, - false); - break; - case 13: - /* 16-sector nibble image, by tracks/sectors, 2MG, gzip */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatGzip, - DiskImg::kFileFormat2MG, - DiskImg::kPhysicalFormatNib525_6656, - pNibbleDescr, - DiskImg::kSectorOrderPhysical, - DiskImg::kFormatGenericProDOSOrd, - 35, 16, - false); - break; - case 14: - /* sector image, by blocks, for DC42 (800K only) */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatDiskCopy42, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 15: - /* sector image, by blocks, for NuFX */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatNuFX, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 16: - /* sector image, by blocks, for DDD */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatDDD, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 17: - /* sector image, by blocks, ProDOS order, stored in ZIP (.po.zip) */ - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatZip, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - srcImg.GetNumBlocks(), - false); - break; - case 18: - /* 13-sector nibble image, by tracks/sectors */ - pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); - dierr = dstImg.CreateImage(outfile, storageName, - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - pNibbleDescr, - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - 35, 13, - false); - break; - default: - fprintf(stderr, "UNEXPECTED NUMBER\n"); - abort(); - } - if (dierr != kDIErrNone) { - fprintf(stderr, "Couldn't create new image file '%s': %s.\n", - outfile, DiskImgLib::DIStrError(dierr)); - goto bail; - } - - /* - * Copy blocks or sectors from source to destination. - */ - if (srcImg.GetHasBlocks()) { - int numBlocks; - numBlocks = srcImg.GetNumBlocks(); - if (dstImg.GetNumBlocks() < srcImg.GetNumBlocks()) - numBlocks = dstImg.GetNumBlocks(); - printf("Copying %d blocks\n", numBlocks); - - unsigned char blkBuf[512]; - for (int block = 0; block < numBlocks; block++) { - dierr = srcImg.ReadBlock(block, blkBuf); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: ReadBlock failed (err=%d)\n", dierr); - goto bail; - } - dierr = dstImg.WriteBlock(block, blkBuf); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: WriteBlock failed (err=%d)\n", dierr); - goto bail; - } - } - } else { - int numTracks, numSectPerTrack; - numTracks = srcImg.GetNumTracks(); - numSectPerTrack = srcImg.GetNumSectPerTrack(); - if (dstImg.GetNumTracks() < srcImg.GetNumTracks()) - numTracks = dstImg.GetNumTracks(); - if (dstImg.GetNumSectPerTrack() < srcImg.GetNumSectPerTrack()) - numSectPerTrack = dstImg.GetNumSectPerTrack(); - printf("Copying %d tracks of %d sectors\n", numTracks, numSectPerTrack); - - unsigned char sctBuf[256]; - for (int track = 0; track < numTracks; track++) { - for (int sector = 0; sector < numSectPerTrack; sector++) { - dierr = srcImg.ReadTrackSector(track, sector, sctBuf); - if (dierr != kDIErrNone) { - fprintf(stderr, - "WARNING: ReadTrackSector failed on T=%d S=%d (err=%d)\n", - track, sector, dierr); - dierr = kDIErrNone; // allow bad blocks - memset(sctBuf, 0, sizeof(sctBuf)); - } - dierr = dstImg.WriteTrackSector(track, sector, sctBuf); - if (dierr != kDIErrNone) { - fprintf(stderr, - "ERROR: WriteBlock failed on T=%d S=%d (err=%d)\n", - track, sector, dierr); - goto bail; - } - } - } - } - - dierr = srcImg.CloseImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: srcImg close failed?!\n"); - goto bail; - } - - dierr = dstImg.CloseImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: dstImg close failed (err=%d)\n", dierr); - goto bail; - } - - assert(dierr == kDIErrNone); -bail: - return dierr; -} - -/* - * Process every argument. - */ -int -main(int argc, char** argv) -{ - const char* kLogFile = "iconv-log.txt"; - - if (argc != 3) { - fprintf(stderr, "%s: infile outfile\n", argv[0]); - exit(2); - } - - gLog = fopen(kLogFile, "w"); - if (gLog == nil) { - fprintf(stderr, "ERROR: unable to open log file\n"); - exit(1); - } - - printf("Image Converter for Linux v1.0\n"); - printf("Copyright (C) 2014 by faddenSoft. All rights reserved.\n"); - int32_t major, minor, bug; - Global::GetVersion(&major, &minor, &bug); - printf("Linked against DiskImg library v%d.%d.%d\n", - major, minor, bug); - printf("Log file is '%s'\n", kLogFile); - printf("\n"); - - Global::SetDebugMsgHandler(MsgHandler); - Global::AppInit(); - - NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler); - - Convert(argv[1], argv[2]); - - Global::AppCleanup(); - fclose(gLog); - - exit(0); -} - +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Convert from one image format to another. + */ +#include +#include +#include +#include +#include +#include "../diskimg/DiskImg.h" +#include "../nufxlib/NufxLib.h" + +using namespace DiskImgLib; + +#define nil NULL +#define ASSERT assert +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +FILE* gLog = nil; +pid_t gPid = getpid(); + + +/* + * Handle a debug message from the DiskImg library. + */ +/*static*/ void +MsgHandler(const char* file, int line, const char* msg) +{ + ASSERT(file != nil); + ASSERT(msg != nil); + + fprintf(gLog, "%05u %s", gPid, msg); +} +/* + * Handle a global error message from the NufxLib library by shoving it + * through the DiskImgLib message function. + */ +NuResult +NufxErrorMsgHandler(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; +} + +/* + * Convert one disk image to another. + */ +DIError +Convert(const char* infile, const char* outfile) +{ + DIError dierr = kDIErrNone; + DiskImg srcImg, dstImg; + const char* storageName = nil; + + printf("Converting in='%s' out='%s'\n", infile, outfile); + + /* + * Prepare the source image. + */ + dierr = srcImg.OpenImage(infile, '/', true); + if (dierr != kDIErrNone) { + fprintf(stderr, "Unable to open disk image: %s.\n", + DiskImgLib::DIStrError(dierr)); + goto bail; + } + + dierr = srcImg.AnalyzeImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "Unable to determine source image format.\n"); + goto bail; + } + + if (!srcImg.GetHasBlocks() && !srcImg.GetHasSectors()) { + /* add nibble tracks someday */ + fprintf(stderr, + "Sorry, only block- or sector-addressable images allowed.\n"); + dierr = kDIErrUnsupportedPhysicalFmt; + goto bail; + } + if (srcImg.GetHasBlocks()) { + assert(srcImg.GetNumBlocks() > 0); + } else { + assert(srcImg.GetNumTracks() > 0); + } + + if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { + fprintf(stderr, "(QUERY) don't know sector order\n"); + dierr = kDIErrFilesystemNotFound; + goto bail; + } + + storageName = "MyHappyDisk"; + + /* force the access to be ProDOS-ordered */ + dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(), + DiskImg::kFormatGenericProDOSOrd, srcImg.GetSectorOrder()); + if (dierr != kDIErrNone) { + fprintf(stderr, "Couldn't switch to generic ProDOS: %s.\n", + DiskImgLib::DIStrError(dierr)); + goto bail; + } + + /* transfer the DOS volume num, if one was set */ + printf("DOS volume number set to %d\n", srcImg.GetDOSVolumeNum()); + dstImg.SetDOSVolumeNum(srcImg.GetDOSVolumeNum()); + + const DiskImg::NibbleDescr* pNibbleDescr; + pNibbleDescr = nil; + + /* + * Prepare the destination image. + * + * We *always* use DiskImg::kFormatGenericProDOSOrd here, because it + * must match up with what we selected above. + * + * We could enable "skipFormat" on all of these but the nibble images, + * but we go ahead and set it to "false" on all of them just for fun. + */ + switch (18) { + case 0: + /* 16-sector nibble image, by blocks */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatNib525_6656, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 1: + /* 16-sector nibble image, by tracks/sectors */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatNib525_6656, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + 35, 16, + false); + break; + case 2: + /* 16-sector NB2 nibble image, by tracks/sectors */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatNib525_6384, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + 35, 16, + false); + break; + case 3: + /* 13-sector nibble image, by tracks/sectors */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS32Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatNib525_6656, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + 35, 13, + false); + break; + case 4: + /* 16-sector nb2 image, by tracks/sectors */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatNib525_6384, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + 35, 16, + false); + break; + case 5: + /* sector image, by blocks, ProDOS order */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 6: + /* sector image, by blocks, DOS order */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 7: + /* sector image, by blocks, ProDOS order, Sim2e */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatSim2eHDV, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 8: + /* odd-length HUGE sector image, by blocks */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + 65535, + false); + break; + case 9: + /* sector image, by blocks, physical order, with gzip */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatGzip, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 10: + /* sector image, by blocks, ProDOS order, with gzip */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatGzip, + DiskImg::kFileFormatSim2eHDV, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 11: + /* sector image, by blocks, ProDOS order, 2MG */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormat2MG, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 12: + /* 16-sector nibble image, by tracks/sectors, 2MG */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormat2MG, + DiskImg::kPhysicalFormatNib525_6656, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + 35, 16, + false); + break; + case 13: + /* 16-sector nibble image, by tracks/sectors, 2MG, gzip */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatGzip, + DiskImg::kFileFormat2MG, + DiskImg::kPhysicalFormatNib525_6656, + pNibbleDescr, + DiskImg::kSectorOrderPhysical, + DiskImg::kFormatGenericProDOSOrd, + 35, 16, + false); + break; + case 14: + /* sector image, by blocks, for DC42 (800K only) */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatDiskCopy42, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 15: + /* sector image, by blocks, for NuFX */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatNuFX, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 16: + /* sector image, by blocks, for DDD */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatDDD, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 17: + /* sector image, by blocks, ProDOS order, stored in ZIP (.po.zip) */ + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatZip, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + srcImg.GetNumBlocks(), + false); + break; + case 18: + /* 13-sector nibble image, by tracks/sectors */ + pNibbleDescr= DiskImg::GetStdNibbleDescr(DiskImg::kNibbleDescrDOS33Std); + dierr = dstImg.CreateImage(outfile, storageName, + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + pNibbleDescr, + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + 35, 13, + false); + break; + default: + fprintf(stderr, "UNEXPECTED NUMBER\n"); + abort(); + } + if (dierr != kDIErrNone) { + fprintf(stderr, "Couldn't create new image file '%s': %s.\n", + outfile, DiskImgLib::DIStrError(dierr)); + goto bail; + } + + /* + * Copy blocks or sectors from source to destination. + */ + if (srcImg.GetHasBlocks()) { + int numBlocks; + numBlocks = srcImg.GetNumBlocks(); + if (dstImg.GetNumBlocks() < srcImg.GetNumBlocks()) + numBlocks = dstImg.GetNumBlocks(); + printf("Copying %d blocks\n", numBlocks); + + unsigned char blkBuf[512]; + for (int block = 0; block < numBlocks; block++) { + dierr = srcImg.ReadBlock(block, blkBuf); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: ReadBlock failed (err=%d)\n", dierr); + goto bail; + } + dierr = dstImg.WriteBlock(block, blkBuf); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: WriteBlock failed (err=%d)\n", dierr); + goto bail; + } + } + } else { + int numTracks, numSectPerTrack; + numTracks = srcImg.GetNumTracks(); + numSectPerTrack = srcImg.GetNumSectPerTrack(); + if (dstImg.GetNumTracks() < srcImg.GetNumTracks()) + numTracks = dstImg.GetNumTracks(); + if (dstImg.GetNumSectPerTrack() < srcImg.GetNumSectPerTrack()) + numSectPerTrack = dstImg.GetNumSectPerTrack(); + printf("Copying %d tracks of %d sectors\n", numTracks, numSectPerTrack); + + unsigned char sctBuf[256]; + for (int track = 0; track < numTracks; track++) { + for (int sector = 0; sector < numSectPerTrack; sector++) { + dierr = srcImg.ReadTrackSector(track, sector, sctBuf); + if (dierr != kDIErrNone) { + fprintf(stderr, + "WARNING: ReadTrackSector failed on T=%d S=%d (err=%d)\n", + track, sector, dierr); + dierr = kDIErrNone; // allow bad blocks + memset(sctBuf, 0, sizeof(sctBuf)); + } + dierr = dstImg.WriteTrackSector(track, sector, sctBuf); + if (dierr != kDIErrNone) { + fprintf(stderr, + "ERROR: WriteBlock failed on T=%d S=%d (err=%d)\n", + track, sector, dierr); + goto bail; + } + } + } + } + + dierr = srcImg.CloseImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: srcImg close failed?!\n"); + goto bail; + } + + dierr = dstImg.CloseImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: dstImg close failed (err=%d)\n", dierr); + goto bail; + } + + assert(dierr == kDIErrNone); +bail: + return dierr; +} + +/* + * Process every argument. + */ +int +main(int argc, char** argv) +{ + const char* kLogFile = "iconv-log.txt"; + + if (argc != 3) { + fprintf(stderr, "%s: infile outfile\n", argv[0]); + exit(2); + } + + gLog = fopen(kLogFile, "w"); + if (gLog == nil) { + fprintf(stderr, "ERROR: unable to open log file\n"); + exit(1); + } + + printf("Image Converter for Linux v1.0\n"); + printf("Copyright (C) 2014 by faddenSoft. All rights reserved.\n"); + int32_t major, minor, bug; + Global::GetVersion(&major, &minor, &bug); + printf("Linked against DiskImg library v%d.%d.%d\n", + major, minor, bug); + printf("Log file is '%s'\n", kLogFile); + printf("\n"); + + Global::SetDebugMsgHandler(MsgHandler); + Global::AppInit(); + + NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler); + + Convert(argv[1], argv[2]); + + Global::AppCleanup(); + fclose(gLog); + + exit(0); +} + diff --git a/linux/GetFile.cpp b/linux/GetFile.cpp index ab4eb98..c3ca570 100644 --- a/linux/GetFile.cpp +++ b/linux/GetFile.cpp @@ -1,220 +1,220 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * Get a file from a disk image. - */ -#include -#include -#include -#include -#include -#include -#include -#include "../diskimg/DiskImg.h" - -using namespace DiskImgLib; - -#define nil NULL -#define NELEM(x) (sizeof(x) / sizeof((x)[0])) - -/* - * Globals. - */ -FILE* gLog = nil; -pid_t gPid = getpid(); - -/* - * Show usage info. - */ -void -Usage(const char* argv0) -{ - fprintf(stderr, "Usage: %s image-filename file\n", argv0); - - fprintf(stderr, "\n"); - fprintf(stderr, "The file will be written to stdout.\n"); -} - -/* - * Copy a file from "src" to "dst". - */ -int -CopyFile(A2FileDescr* src, FILE* dst) -{ - DIError dierr; - size_t actual; - char buf[4096]; - - while (1) { - dierr = src->Read(buf, sizeof(buf), &actual); - if (dierr != kDIErrNone) { - fprintf(stderr, "Error: read failed: %s\n", DIStrError(dierr)); - return -1; - } - - if (actual == 0) // EOF hit - break; - - fwrite(buf, 1, actual, dst); - } - - return 0; -} - -/* - * Extract the named file from the specified image. - */ -int -Process(const char* imageName, const char* wantedFileName) -{ - DIError dierr; - DiskImg diskImg; - DiskFS* pDiskFS = nil; - A2File* pFile = nil; - A2FileDescr* pDescr = nil; - int result = -1; - - /* open read-only */ - dierr = diskImg.OpenImage(imageName, '/', true); - if (dierr != kDIErrNone) { - fprintf(stderr, "Unable to open '%s': %s\n", imageName, - DIStrError(dierr)); - goto bail; - } - - /* figure out the format */ - dierr = diskImg.AnalyzeImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "Analysis of '%s' failed: %s\n", imageName, - DIStrError(dierr)); - goto bail; - } - - /* recognized? */ - if (diskImg.GetFSFormat() == DiskImg::kFormatUnknown || - diskImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) - { - fprintf(stderr, "Unable to identify filesystem on '%s'\n", imageName); - goto bail; - } - - /* create an appropriate DiskFS object */ - pDiskFS = diskImg.OpenAppropriateDiskFS(); - if (pDiskFS == nil) { - /* unknown FS should've been caught above! */ - assert(false); - fprintf(stderr, "Format of '%s' not recognized.\n", imageName); - goto bail; - } - - /* go ahead and load up volumes mounted inside volumes */ - pDiskFS->SetScanForSubVolumes(DiskFS::kScanSubEnabled); - - /* do a full scan */ - dierr = pDiskFS->Initialize(&diskImg, DiskFS::kInitFull); - if (dierr != kDIErrNone) { - fprintf(stderr, "Error reading list of files from disk: %s\n", - DIStrError(dierr)); - goto bail; - } - - /* - * Find the file. This comes out of a list of entries, so don't - * delete "pFile" when we're done. - */ - pFile = pDiskFS->GetFileByName(wantedFileName); - if (pFile == nil) { - fprintf(stderr, "File '%s' not found in '%s'\n", wantedFileName, - imageName); - goto bail; - } - - /* - * Open the file read-only. - */ - dierr = pFile->Open(&pDescr, true); - if (dierr != kDIErrNone) { - fprintf(stderr, "Error opening '%s': %s\n", wantedFileName, - DIStrError(dierr)); - goto bail; - } - - /* - * Copy the file to stdout. - */ - result = CopyFile(pDescr, stdout); - -bail: - if (pDescr != nil) { - pDescr->Close(); - //delete pDescr; -- don't do this (double free) - } - delete pDiskFS; - return result; -} - -/* - * Handle a debug message from the DiskImg library. - */ -/*static*/ void -MsgHandler(const char* file, int line, const char* msg) -{ - assert(file != nil); - assert(msg != nil); - -#ifdef _DEBUG - fprintf(gLog, "%05u %s", gPid, msg); -#endif -} - -/* - * Process args. - */ -int -main(int argc, char** argv) -{ -#ifdef _DEBUG - const char* kLogFile = "makedisk-log.txt"; - gLog = fopen(kLogFile, "w"); - if (gLog == nil) { - fprintf(stderr, "ERROR: unable to open log file\n"); - exit(1); - } -#endif - -#ifdef _DEBUG - fprintf(stderr, "Log file is '%s'\n", kLogFile); -#endif - - Global::SetDebugMsgHandler(MsgHandler); - Global::AppInit(); - - if (argc != 3) { - Usage(argv[0]); - exit(2); - } - - const char* imageName; - const char* getFileName; - - argv++; - imageName = *argv++; - getFileName = *argv++; - argc -= 2; - - if (Process(imageName, getFileName) == 0) - fprintf(stderr, "Success!\n"); - else - fprintf(stderr, "Failed.\n"); - - Global::AppCleanup(); -#ifdef _DEBUG - fclose(gLog); -#endif - - exit(0); -} - +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Get a file from a disk image. + */ +#include +#include +#include +#include +#include +#include +#include +#include "../diskimg/DiskImg.h" + +using namespace DiskImgLib; + +#define nil NULL +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +/* + * Globals. + */ +FILE* gLog = nil; +pid_t gPid = getpid(); + +/* + * Show usage info. + */ +void +Usage(const char* argv0) +{ + fprintf(stderr, "Usage: %s image-filename file\n", argv0); + + fprintf(stderr, "\n"); + fprintf(stderr, "The file will be written to stdout.\n"); +} + +/* + * Copy a file from "src" to "dst". + */ +int +CopyFile(A2FileDescr* src, FILE* dst) +{ + DIError dierr; + size_t actual; + char buf[4096]; + + while (1) { + dierr = src->Read(buf, sizeof(buf), &actual); + if (dierr != kDIErrNone) { + fprintf(stderr, "Error: read failed: %s\n", DIStrError(dierr)); + return -1; + } + + if (actual == 0) // EOF hit + break; + + fwrite(buf, 1, actual, dst); + } + + return 0; +} + +/* + * Extract the named file from the specified image. + */ +int +Process(const char* imageName, const char* wantedFileName) +{ + DIError dierr; + DiskImg diskImg; + DiskFS* pDiskFS = nil; + A2File* pFile = nil; + A2FileDescr* pDescr = nil; + int result = -1; + + /* open read-only */ + dierr = diskImg.OpenImage(imageName, '/', true); + if (dierr != kDIErrNone) { + fprintf(stderr, "Unable to open '%s': %s\n", imageName, + DIStrError(dierr)); + goto bail; + } + + /* figure out the format */ + dierr = diskImg.AnalyzeImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "Analysis of '%s' failed: %s\n", imageName, + DIStrError(dierr)); + goto bail; + } + + /* recognized? */ + if (diskImg.GetFSFormat() == DiskImg::kFormatUnknown || + diskImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) + { + fprintf(stderr, "Unable to identify filesystem on '%s'\n", imageName); + goto bail; + } + + /* create an appropriate DiskFS object */ + pDiskFS = diskImg.OpenAppropriateDiskFS(); + if (pDiskFS == nil) { + /* unknown FS should've been caught above! */ + assert(false); + fprintf(stderr, "Format of '%s' not recognized.\n", imageName); + goto bail; + } + + /* go ahead and load up volumes mounted inside volumes */ + pDiskFS->SetScanForSubVolumes(DiskFS::kScanSubEnabled); + + /* do a full scan */ + dierr = pDiskFS->Initialize(&diskImg, DiskFS::kInitFull); + if (dierr != kDIErrNone) { + fprintf(stderr, "Error reading list of files from disk: %s\n", + DIStrError(dierr)); + goto bail; + } + + /* + * Find the file. This comes out of a list of entries, so don't + * delete "pFile" when we're done. + */ + pFile = pDiskFS->GetFileByName(wantedFileName); + if (pFile == nil) { + fprintf(stderr, "File '%s' not found in '%s'\n", wantedFileName, + imageName); + goto bail; + } + + /* + * Open the file read-only. + */ + dierr = pFile->Open(&pDescr, true); + if (dierr != kDIErrNone) { + fprintf(stderr, "Error opening '%s': %s\n", wantedFileName, + DIStrError(dierr)); + goto bail; + } + + /* + * Copy the file to stdout. + */ + result = CopyFile(pDescr, stdout); + +bail: + if (pDescr != nil) { + pDescr->Close(); + //delete pDescr; -- don't do this (double free) + } + delete pDiskFS; + return result; +} + +/* + * Handle a debug message from the DiskImg library. + */ +/*static*/ void +MsgHandler(const char* file, int line, const char* msg) +{ + assert(file != nil); + assert(msg != nil); + +#ifdef _DEBUG + fprintf(gLog, "%05u %s", gPid, msg); +#endif +} + +/* + * Process args. + */ +int +main(int argc, char** argv) +{ +#ifdef _DEBUG + const char* kLogFile = "makedisk-log.txt"; + gLog = fopen(kLogFile, "w"); + if (gLog == nil) { + fprintf(stderr, "ERROR: unable to open log file\n"); + exit(1); + } +#endif + +#ifdef _DEBUG + fprintf(stderr, "Log file is '%s'\n", kLogFile); +#endif + + Global::SetDebugMsgHandler(MsgHandler); + Global::AppInit(); + + if (argc != 3) { + Usage(argv[0]); + exit(2); + } + + const char* imageName; + const char* getFileName; + + argv++; + imageName = *argv++; + getFileName = *argv++; + argc -= 2; + + if (Process(imageName, getFileName) == 0) + fprintf(stderr, "Success!\n"); + else + fprintf(stderr, "Failed.\n"); + + Global::AppCleanup(); +#ifdef _DEBUG + fclose(gLog); +#endif + + exit(0); +} + diff --git a/linux/MDC.cpp b/linux/MDC.cpp index b7e04cd..4eb50cd 100644 --- a/linux/MDC.cpp +++ b/linux/MDC.cpp @@ -1,890 +1,890 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * Drive diskimglib. Similar to MDC. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "zlib.h" -#include "../diskimg/DiskImg.h" -#include "../nufxlib/NufxLib.h" -#include "StringArray.h" - -using namespace DiskImgLib; - -#define nil NULL -#define ASSERT assert -#define NELEM(x) (sizeof(x) / sizeof((x)[0])) -typedef const char* LPCTSTR; - -#define UNIX_LIKE -#define HAVE_DIRENT_H // linux -#define MAX_PATH_LEN 1024 - -/* get a grip on this opendir/readdir stuff */ -#if defined(UNIX_LIKE) -# if defined(HAVE_DIRENT_H) -# include -# define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name)) - typedef struct dirent DIR_TYPE; -# elif defined(HAVE_SYS_DIR_H) -# include -# define DIR_NAME_LEN(direct) ((direct)->d_namlen) - typedef struct direct DIR_TYPE; -# elif defined(HAVE_NDIR_H) -# include -# define DIR_NAME_LEN(direct) ((direct)->d_namlen) - typedef struct direct DIR_TYPE; -# else -# error "Port this?" -# endif -#endif - -/* - * Globals. - */ -FILE* gLog = nil; -pid_t gPid = getpid(); - -struct Stats { - long numFiles; - long numDirectories; - long goodDiskImages; -} gStats = { 0 }; - -typedef struct ScanOpts { - FILE* outfp; -} ScanOpts; - -typedef enum RecordKind { - kRecordKindUnknown = 0, - kRecordKindDisk, - kRecordKindFile, - kRecordKindForkedFile, - kRecordKindDirectory, - kRecordKindVolumeDirectory, -} RecordKind; - - -//#define kFilenameExtDelim '.' /* separates extension from filename */ - -/* time_t values for bad dates */ -#define kDateNone ((time_t) -2) -#define kDateInvalid ((time_t) -1) // should match return from mktime() - -/* - * "buf" must hold at least 64 chars. - */ -void -FormatDate(time_t when, char* buf) -{ - if (when == kDateNone) { - strcpy(buf, "[No Date]"); - } else if (when == kDateInvalid) { - strcpy(buf, ""); - } else { - struct tm* ptm; - - ptm = localtime(&when); - strftime(buf, 64, "%d-%b-%y %H:%M", ptm); - } -} - -#if 0 -/* - * Find the filename component of a local pathname. Uses the fssep passed - * in. If the fssep is '\0' (as is the case for DOS 3.3), then the entire - * pathname is returned. - * - * Always returns a pointer to a string; never returns nil. - */ -const char* -FilenameOnly(const char* pathname, char fssep) -{ - const char* retstr; - const char* pSlash; - char* tmpStr = nil; - - ASSERT(pathname != nil); - if (fssep == '\0') { - retstr = pathname; - goto bail; - } - - pSlash = strrchr(pathname, fssep); - if (pSlash == nil) { - retstr = pathname; /* whole thing is the filename */ - goto bail; - } - - pSlash++; - if (*pSlash == '\0') { - if (strlen(pathname) < 2) { - retstr = pathname; /* the pathname is just "/"? Whatever */ - goto bail; - } - - /* some bonehead put an fssep on the very end; back up before it */ - /* (not efficient, but this should be rare, and I'm feeling lazy) */ - tmpStr = strdup(pathname); - tmpStr[strlen(pathname)-1] = '\0'; - pSlash = strrchr(tmpStr, fssep); - - if (pSlash == nil) { - retstr = pathname; /* just a filename with a '/' after it */ - goto bail; - } - - pSlash++; - if (*pSlash == '\0') { - retstr = pathname; /* I give up! */ - goto bail; - } - - retstr = pathname + (pSlash - tmpStr); - - } else { - retstr = pSlash; - } - -bail: - free(tmpStr); - return retstr; -} - -/* - * Return the filename extension found in a full pathname. - * - * An extension is the stuff following the last '.' in the filename. If - * there is nothing following the last '.', then there is no extension. - * - * Returns a pointer to the '.' preceding the extension, or nil if no - * extension was found. - * - * We guarantee that there is at least one character after the '.'. - */ -const char* -FindExtension(const char* pathname, char fssep) -{ - const char* pFilename; - const char* pExt; - - /* - * We have to isolate the filename so that we don't get excited - * about "/foo.bar/file". - */ - pFilename = FilenameOnly(pathname, fssep); - ASSERT(pFilename != nil); - pExt = strrchr(pFilename, kFilenameExtDelim); - - /* also check for "/blah/foo.", which doesn't count */ - if (pExt != nil && *(pExt+1) != '\0') - return pExt; - - return nil; -} -#endif - - -/* - * Analyze a file's characteristics. - */ -void -AnalyzeFile(const A2File* pFile, RecordKind* pRecordKind, - unsigned long* pTotalLen, unsigned long* pTotalCompLen) -{ - if (pFile->IsVolumeDirectory()) { - /* volume directory entry */ - ASSERT(pFile->GetRsrcLength() < 0); - *pRecordKind = kRecordKindVolumeDirectory; - *pTotalLen = pFile->GetDataLength(); - *pTotalCompLen = pFile->GetDataLength(); - } else if (pFile->IsDirectory()) { - /* directory entry */ - ASSERT(pFile->GetRsrcLength() < 0); - *pRecordKind = kRecordKindDirectory; - *pTotalLen = pFile->GetDataLength(); - *pTotalCompLen = pFile->GetDataLength(); - } else if (pFile->GetRsrcLength() >= 0) { - /* has resource fork */ - *pRecordKind = kRecordKindForkedFile; - *pTotalLen = pFile->GetDataLength() + pFile->GetRsrcLength(); - *pTotalCompLen = - pFile->GetDataSparseLength() + pFile->GetRsrcSparseLength(); - } else { - /* just data fork */ - *pRecordKind = kRecordKindFile; - *pTotalLen = pFile->GetDataLength(); - *pTotalCompLen = pFile->GetDataSparseLength(); - } -} - -/* - * Determine whether the access bits on the record make it a read-only - * file or not. - * - * Uses a simplified view of the access flags. - */ -bool -IsRecordReadOnly(int accessBits) -{ - if (accessBits == 0x21L || accessBits == 0x01L) - return true; - else - return false; -} - -/* ProDOS file type names; must be entirely in upper case */ -static const char gFileTypeNames[256][4] = { - "NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT", - "FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR", - "RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17", - "$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F", - "TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27", - "$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F", - "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37", - "$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F", - "DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47", - "$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F", - "GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN", - "HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN", - "$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67", - "$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV", - "$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77", - "$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F", - "$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87", - "$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F", - "$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97", - "$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F", - "WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7", - "$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF", - "SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF", - "NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC", - "PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV", - "FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF", - "$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI", - "SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF", - "LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7", - "$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAS", - "CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7", - "$F8", "OS ", "INT", "IVR", "BAS", "VAR", "REL", "SYS" -}; - -/* - * Return a pointer to the three-letter representation of the file type name. - * - * Note to self: code down below tests first char for '?'. - */ -/*static*/ const char* -GetFileTypeString(unsigned long fileType) -{ - if (fileType < NELEM(gFileTypeNames)) - return gFileTypeNames[fileType]; - else - return "???"; -} - -/* - * Sanitize a string. The Mac likes to stick control characters into - * things, e.g. ^C and ^M. - */ -static void -MacSanitize(char* str) -{ - while (*str != '\0') { - if (*str < 0x20 || *str >= 0x7f) - *str = '?'; - str++; - } -} - - -/* - * Load the contents of a DiskFS. - * - * Recursively handle sub-volumes. - */ -int -LoadDiskFSContents(DiskFS* pDiskFS, const char* volName, - ScanOpts* pScanOpts) -{ - static const char* kBlankFileName = ""; - DiskFS::SubVolume* pSubVol = nil; - A2File* pFile; - - ASSERT(pDiskFS != nil); - pFile = pDiskFS->GetNextFile(nil); - while (pFile != nil) { - char subVolName[128] = ""; - char dispName[128] = ""; - //CString subVolName, dispName; - RecordKind recordKind; - unsigned long totalLen, totalCompLen; - char tmpbuf[16]; - - AnalyzeFile(pFile, &recordKind, &totalLen, &totalCompLen); - if (recordKind == kRecordKindVolumeDirectory) { - /* skip these */ - pFile = pDiskFS->GetNextFile(pFile); - continue; - } - - /* prepend volName for sub-volumes; must be valid Win32 dirname */ - if (volName[0] != '\0') - snprintf(subVolName, sizeof(subVolName), "_%s", volName); - - const char* ccp = pFile->GetPathName(); - ASSERT(ccp != nil); - if (strlen(ccp) == 0) - ccp = kBlankFileName; - - if (subVolName[0] == '\0') - strcpy(dispName, ccp); - else { - snprintf(dispName, sizeof(dispName), "%s:%s", subVolName, ccp); - //dispName = subVolName; - //dispName += ':'; - //dispName += ccp; - } - ccp = dispName; - - int len = strlen(ccp); - if (len <= 32) { - fprintf(pScanOpts->outfp, "%c%-32.32s ", - IsRecordReadOnly(pFile->GetAccess()) ? '*' : ' ', - ccp); - } else { - fprintf(pScanOpts->outfp, "%c..%-30.30s ", - IsRecordReadOnly(pFile->GetAccess()) ? '*' : ' ', - ccp + len - 30); - } - switch (recordKind) { - case kRecordKindUnknown: - fprintf(pScanOpts->outfp, "%s- $%04X ", - GetFileTypeString(pFile->GetFileType()), - pFile->GetAuxType()); - break; - case kRecordKindDisk: - snprintf(tmpbuf, sizeof(tmpbuf), "%ldk", totalLen / 1024); - fprintf(pScanOpts->outfp, "Disk %-6s ", tmpbuf); - break; - case kRecordKindFile: - case kRecordKindForkedFile: - case kRecordKindDirectory: - if (pDiskFS->GetDiskImg()->GetFSFormat() == DiskImg::kFormatMacHFS) - { - if (recordKind != kRecordKindDirectory && - pFile->GetFileType() >= 0 && pFile->GetFileType() <= 0xff && - pFile->GetAuxType() >= 0 && pFile->GetAuxType() <= 0xffff) - { - /* ProDOS type embedded in HFS */ - fprintf(pScanOpts->outfp, "%s%c $%04X ", - GetFileTypeString(pFile->GetFileType()), - recordKind == kRecordKindForkedFile ? '+' : ' ', - pFile->GetAuxType()); - } else { - char typeStr[5]; - char creatorStr[5]; - unsigned long val; - - val = pFile->GetAuxType(); - creatorStr[0] = (unsigned char) (val >> 24); - creatorStr[1] = (unsigned char) (val >> 16); - creatorStr[2] = (unsigned char) (val >> 8); - creatorStr[3] = (unsigned char) val; - creatorStr[4] = '\0'; - - val = pFile->GetFileType(); - typeStr[0] = (unsigned char) (val >> 24); - typeStr[1] = (unsigned char) (val >> 16); - typeStr[2] = (unsigned char) (val >> 8); - typeStr[3] = (unsigned char) val; - typeStr[4] = '\0'; - - MacSanitize(creatorStr); - MacSanitize(typeStr); - - if (recordKind == kRecordKindDirectory) { - fprintf(pScanOpts->outfp, "DIR %-4s ", creatorStr); - } else { - fprintf(pScanOpts->outfp, "%-4s%c %-4s ", - typeStr, - pFile->GetRsrcLength() > 0 ? '+' : ' ', - creatorStr); - } - } - } else { - fprintf(pScanOpts->outfp, "%s%c $%04X ", - GetFileTypeString(pFile->GetFileType()), - recordKind == kRecordKindForkedFile ? '+' : ' ', - pFile->GetAuxType()); - } - break; - default: - ASSERT(0); - fprintf(pScanOpts->outfp, "ERROR "); - break; - } - - char date[64]; - if (pFile->GetModWhen() == 0) - FormatDate(kDateNone, date); - else - FormatDate(pFile->GetModWhen(), date); - fprintf(pScanOpts->outfp, "%-15s ", (LPCTSTR) date); - - const char* fmtStr; - switch (pFile->GetFSFormat()) { - case DiskImg::kFormatDOS33: - case DiskImg::kFormatDOS32: - case DiskImg::kFormatUNIDOS: - fmtStr = "DOS "; - break; - case DiskImg::kFormatProDOS: - fmtStr = "ProDOS"; - break; - case DiskImg::kFormatPascal: - fmtStr = "Pascal"; - break; - case DiskImg::kFormatMacHFS: - fmtStr = "HFS "; - break; - case DiskImg::kFormatCPM: - fmtStr = "CP/M "; - break; - case DiskImg::kFormatMSDOS: - fmtStr = "MS-DOS"; - break; - case DiskImg::kFormatRDOS33: - case DiskImg::kFormatRDOS32: - case DiskImg::kFormatRDOS3: - fmtStr = "RDOS "; - break; - default: - fmtStr = "??? "; - break; - } - if (pFile->GetQuality() == A2File::kQualityDamaged) - fmtStr = "BROKEN"; - - fprintf(pScanOpts->outfp, "%s ", fmtStr); - - -#if 0 - /* compute the percent size */ - if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen)) - fprintf(pScanOpts->outfp, "--- "); /* weird */ - else if (totalLen < totalCompLen) - fprintf(pScanOpts->outfp, ">100%% "); /* compression failed? */ - else { - sprintf(tmpbuf, "%02d%%", ComputePercent(totalCompLen, totalLen)); - fprintf(pScanOpts->outfp, "%4s ", tmpbuf); - } -#endif - - if (!totalLen && totalCompLen) - fprintf(pScanOpts->outfp, " ????"); /* weird */ - else - fprintf(pScanOpts->outfp, "%8ld", totalLen); - - fprintf(pScanOpts->outfp, "\n"); - - pFile = pDiskFS->GetNextFile(pFile); - } - - /* - * Load all sub-volumes. - */ - pSubVol = pDiskFS->GetNextSubVolume(nil); - while (pSubVol != nil) { - const char* subVolName; - int ret; - - subVolName = pSubVol->GetDiskFS()->GetVolumeName(); - if (subVolName == nil) - subVolName = "+++"; // could probably do better than this - - ret = LoadDiskFSContents(pSubVol->GetDiskFS(), subVolName, pScanOpts); - if (ret != 0) - return ret; - pSubVol = pDiskFS->GetNextSubVolume(pSubVol); - } - - return 0; -} - -/* - * Open a disk image and dump the contents. - * - * Returns 0 on success, nonzero on failure. - */ -int -ScanDiskImage(const char* pathName, ScanOpts* pScanOpts) -{ - ASSERT(pathName != nil); - ASSERT(pScanOpts != nil); - ASSERT(pScanOpts->outfp != nil); - - DIError dierr; - char errMsg[256] = ""; - DiskImg diskImg; - DiskFS* pDiskFS = nil; - - dierr = diskImg.OpenImage(pathName, '/', true); - if (dierr != kDIErrNone) { - snprintf(errMsg, sizeof(errMsg), "Unable to open '%s': %s", - pathName, DIStrError(dierr)); - goto bail; - } - - dierr = diskImg.AnalyzeImage(); - if (dierr != kDIErrNone) { - snprintf(errMsg, sizeof(errMsg), "Analysis of '%s' failed: %s", - pathName, DIStrError(dierr)); - goto bail; - } - - if (diskImg.GetFSFormat() == DiskImg::kFormatUnknown || - diskImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) - { - snprintf(errMsg, sizeof(errMsg), "Unable to identify filesystem on '%s'", - pathName); - goto bail; - } - - /* create an appropriate DiskFS object */ - pDiskFS = diskImg.OpenAppropriateDiskFS(); - if (pDiskFS == nil) { - /* unknown FS should've been caught above! */ - ASSERT(false); - snprintf(errMsg, sizeof(errMsg), "Format of '%s' not recognized.", - pathName); - goto bail; - } - - pDiskFS->SetScanForSubVolumes(DiskFS::kScanSubEnabled); - - /* object created; prep it */ - dierr = pDiskFS->Initialize(&diskImg, DiskFS::kInitFull); - if (dierr != kDIErrNone) { - snprintf(errMsg, sizeof(errMsg), - "Error reading list of files from disk: %s", DIStrError(dierr)); - goto bail; - } - - fprintf(pScanOpts->outfp, "File: %s\n", pathName); - - int kbytes; - if (pDiskFS->GetDiskImg()->GetHasBlocks()) - kbytes = pDiskFS->GetDiskImg()->GetNumBlocks() / 2; - else if (pDiskFS->GetDiskImg()->GetHasSectors()) - kbytes = (pDiskFS->GetDiskImg()->GetNumTracks() * - pDiskFS->GetDiskImg()->GetNumSectPerTrack()) / 4; - else - kbytes = 0; - fprintf(pScanOpts->outfp, "Disk: %s%s (%dKB)\n", pDiskFS->GetVolumeID(), - pDiskFS->GetFSDamaged() ? " [*]" : "", kbytes); - - fprintf(pScanOpts->outfp, - " Name Type Auxtyp Modified" - " Format Length\n"); - fprintf(pScanOpts->outfp, - "------------------------------------------------------" - "------------------------\n"); - if (LoadDiskFSContents(pDiskFS, "", pScanOpts) != 0) { - snprintf(errMsg, sizeof(errMsg), - "Failed while loading contents of '%s'.", pathName); - goto bail; - } - fprintf(pScanOpts->outfp, - "------------------------------------------------------" - "------------------------\n\n"); - - gStats.goodDiskImages++; - -bail: - delete pDiskFS; - - if (errMsg[0] != '\0') { - fprintf(pScanOpts->outfp, "Unable to process '%s'\n", pathName); - fprintf(pScanOpts->outfp, " %s\n\n", (LPCTSTR) errMsg); - return -1; - } else { - return 0; - } -} - - -/* - * Check a file's status. - * - * [ Someday we may want to modify this to handle symbolic links. ] - */ -int -CheckFileStatus(const char* pathname, struct stat* psb, bool* pExists, - bool* pIsReadable, bool* pIsDir) -{ - int result = 0; - int cc; - - assert(pathname != nil); - assert(psb != nil); - assert(pExists != nil); - assert(pIsReadable != nil); - assert(pIsDir != nil); - - *pExists = true; - *pIsReadable = true; - *pIsDir = false; - - cc = stat(pathname, psb); - if (cc) { - if (errno == ENOENT) - *pExists = false; - else - result = -1; // stat failed - goto bail; - } - - if (S_ISDIR(psb->st_mode)) - *pIsDir = true; - - /* - * Test if we can read this file. How do we do that? The easy but slow - * way is to call access(2), the harder way is to figure out - * what user/group we are and compare the appropriate file mode. - */ - if (access(pathname, R_OK) < 0) - *pIsReadable = false; - -bail: - return result; -} - - -/* forward decl */ -int ProcessFile(const char* pathname, ScanOpts* pScanOpts); - -/* - * UNIX-style recursive directory descent. Scan the contents of a directory. - * If a subdirectory is found, follow it; otherwise, call ProcessFile to - * handle the file. - */ -int -ProcessDirectory(const char* dirName, ScanOpts* pScanOpts) -{ - StringArray strArray; - int result = -1; - DIR* dirp = nil; - DIR_TYPE* entry; - char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */ - char fssep; - int len; - - assert(pScanOpts != nil); - assert(dirName != nil); - -#ifdef _DEBUG - fprintf(gLog, "+++ Processing directory '%s'\n", dirName); -#endif - - dirp = opendir(dirName); - if (dirp == nil) { - //err = errno ? errno : -1; - goto bail; - } - - fssep = '/'; - - /* could use readdir_r, but we don't care about reentrancy here */ - while ((entry = readdir(dirp)) != nil) { - /* skip the dotsies */ - if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) - continue; - - len = strlen(dirName); - if (len + DIR_NAME_LEN(entry) +2 > MAX_PATH_LEN) { - fprintf(stderr, "ERROR: Filename exceeds %d bytes: %s%c%s\n", - MAX_PATH_LEN, dirName, fssep, entry->d_name); - goto bail; - } - - /* form the new name, inserting an fssep if needed */ - strcpy(nbuf, dirName); - if (dirName[len-1] != fssep) - nbuf[len++] = fssep; - strcpy(nbuf+len, entry->d_name); - - strArray.Add(nbuf); - } - - /* sort the list, then process the files */ - strArray.Sort(StringArray::CmpAscendingAlpha); - for (int i = 0; i < strArray.GetCount(); i++) - (void) ProcessFile(strArray.GetEntry(i), pScanOpts); - - result = 0; - -bail: - if (dirp != nil) - (void)closedir(dirp); - return result; -} - -/* - * Process a file. - * - * Returns with an error if the file doesn't exist or isn't readable. - */ -int -ProcessFile(const char* pathname, ScanOpts* pScanOpts) -{ - int result = -1; - bool exists, isDir, isReadable; - struct stat sb; - - assert(pathname != nil); - assert(pScanOpts != nil); - -#ifdef _DEBUG - fprintf(gLog, "+++ Processing file or dir '%s'\n", pathname); -#endif - - if (CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir) != 0) { - fprintf(stderr, "ERROR: unexpected error while examining '%s'\n", - pathname); - goto bail; - } - - if (!exists) { - fprintf(stderr, "ERROR: couldn't find '%s'\n", pathname); - goto bail; - } - if (!isReadable) { - fprintf(stderr, "ERROR: file '%s' isn't readable\n", pathname); - goto bail; - } - - if (isDir) { - result = ProcessDirectory(pathname, pScanOpts); - gStats.numDirectories++; - } else { - result = ScanDiskImage(pathname, pScanOpts); - gStats.numFiles++; - } - -bail: - return result; -} - - -/* - * Handle a debug message from the DiskImg library. - */ -/*static*/ void -MsgHandler(const char* file, int line, const char* msg) -{ - ASSERT(file != nil); - ASSERT(msg != nil); - -#ifdef _DEBUG - fprintf(gLog, "%05u %s", gPid, msg); -#endif -} -/* - * Handle a global error message from the NufxLib library by shoving it - * through the DiskImgLib message function. - */ -NuResult -NufxErrorMsgHandler(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; -} - -/* - * Process every argument. - */ -int -main(int argc, char** argv) -{ - ScanOpts scanOpts; - scanOpts.outfp = stdout; - -#ifdef _DEBUG - const char* kLogFile = "mdc-log.txt"; - gLog = fopen(kLogFile, "w"); - if (gLog == nil) { - fprintf(stderr, "ERROR: unable to open log file\n"); - exit(1); - } -#endif - - int32_t major, minor, bug; - Global::GetVersion(&major, &minor, &bug); - - printf("MDC for Linux v3.0.0 (DiskImg library v%d.%d.%d)\n", - major, minor, bug); - printf("Copyright (C) 2006 by faddenSoft, LLC. All rights reserved.\n"); - printf("MDC is part of CiderPress, available from http://www.faddensoft.com/.\n"); - NuGetVersion(&major, &minor, &bug, nil, nil); - printf("Linked against NufxLib v%d.%d.%d and zlib version %s.\n", - major, minor, bug, zlibVersion()); - - if (argc == 1) { - fprintf(stderr, "\nUsage: mdc file ...\n"); - goto done; - } - -#ifdef _DEBUG - printf("Log file is '%s'\n", kLogFile); -#endif - printf("\n"); - - Global::SetDebugMsgHandler(MsgHandler); - Global::AppInit(); - - NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler); - - time_t start; - start = time(NULL); - printf("Run started at %.24s\n\n", ctime(&start)); - - while (--argc) { - ProcessFile(*++argv, &scanOpts); - } - - printf("Scan completed in %ld seconds:\n", time(NULL) - start); - printf(" Directories : %ld\n", gStats.numDirectories); - printf(" Files : %ld (%ld good disk images)\n", gStats.numFiles, - gStats.goodDiskImages); - - Global::AppCleanup(); - -done: -#ifdef _DEBUG - fclose(gLog); -#endif - - return 0; -} - +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Drive diskimglib. Similar to MDC. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "zlib.h" +#include "../diskimg/DiskImg.h" +#include "../nufxlib/NufxLib.h" +#include "StringArray.h" + +using namespace DiskImgLib; + +#define nil NULL +#define ASSERT assert +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) +typedef const char* LPCTSTR; + +#define UNIX_LIKE +#define HAVE_DIRENT_H // linux +#define MAX_PATH_LEN 1024 + +/* get a grip on this opendir/readdir stuff */ +#if defined(UNIX_LIKE) +# if defined(HAVE_DIRENT_H) +# include +# define DIR_NAME_LEN(dirent) ((int)strlen((dirent)->d_name)) + typedef struct dirent DIR_TYPE; +# elif defined(HAVE_SYS_DIR_H) +# include +# define DIR_NAME_LEN(direct) ((direct)->d_namlen) + typedef struct direct DIR_TYPE; +# elif defined(HAVE_NDIR_H) +# include +# define DIR_NAME_LEN(direct) ((direct)->d_namlen) + typedef struct direct DIR_TYPE; +# else +# error "Port this?" +# endif +#endif + +/* + * Globals. + */ +FILE* gLog = nil; +pid_t gPid = getpid(); + +struct Stats { + long numFiles; + long numDirectories; + long goodDiskImages; +} gStats = { 0 }; + +typedef struct ScanOpts { + FILE* outfp; +} ScanOpts; + +typedef enum RecordKind { + kRecordKindUnknown = 0, + kRecordKindDisk, + kRecordKindFile, + kRecordKindForkedFile, + kRecordKindDirectory, + kRecordKindVolumeDirectory, +} RecordKind; + + +//#define kFilenameExtDelim '.' /* separates extension from filename */ + +/* time_t values for bad dates */ +#define kDateNone ((time_t) -2) +#define kDateInvalid ((time_t) -1) // should match return from mktime() + +/* + * "buf" must hold at least 64 chars. + */ +void +FormatDate(time_t when, char* buf) +{ + if (when == kDateNone) { + strcpy(buf, "[No Date]"); + } else if (when == kDateInvalid) { + strcpy(buf, ""); + } else { + struct tm* ptm; + + ptm = localtime(&when); + strftime(buf, 64, "%d-%b-%y %H:%M", ptm); + } +} + +#if 0 +/* + * Find the filename component of a local pathname. Uses the fssep passed + * in. If the fssep is '\0' (as is the case for DOS 3.3), then the entire + * pathname is returned. + * + * Always returns a pointer to a string; never returns nil. + */ +const char* +FilenameOnly(const char* pathname, char fssep) +{ + const char* retstr; + const char* pSlash; + char* tmpStr = nil; + + ASSERT(pathname != nil); + if (fssep == '\0') { + retstr = pathname; + goto bail; + } + + pSlash = strrchr(pathname, fssep); + if (pSlash == nil) { + retstr = pathname; /* whole thing is the filename */ + goto bail; + } + + pSlash++; + if (*pSlash == '\0') { + if (strlen(pathname) < 2) { + retstr = pathname; /* the pathname is just "/"? Whatever */ + goto bail; + } + + /* some bonehead put an fssep on the very end; back up before it */ + /* (not efficient, but this should be rare, and I'm feeling lazy) */ + tmpStr = strdup(pathname); + tmpStr[strlen(pathname)-1] = '\0'; + pSlash = strrchr(tmpStr, fssep); + + if (pSlash == nil) { + retstr = pathname; /* just a filename with a '/' after it */ + goto bail; + } + + pSlash++; + if (*pSlash == '\0') { + retstr = pathname; /* I give up! */ + goto bail; + } + + retstr = pathname + (pSlash - tmpStr); + + } else { + retstr = pSlash; + } + +bail: + free(tmpStr); + return retstr; +} + +/* + * Return the filename extension found in a full pathname. + * + * An extension is the stuff following the last '.' in the filename. If + * there is nothing following the last '.', then there is no extension. + * + * Returns a pointer to the '.' preceding the extension, or nil if no + * extension was found. + * + * We guarantee that there is at least one character after the '.'. + */ +const char* +FindExtension(const char* pathname, char fssep) +{ + const char* pFilename; + const char* pExt; + + /* + * We have to isolate the filename so that we don't get excited + * about "/foo.bar/file". + */ + pFilename = FilenameOnly(pathname, fssep); + ASSERT(pFilename != nil); + pExt = strrchr(pFilename, kFilenameExtDelim); + + /* also check for "/blah/foo.", which doesn't count */ + if (pExt != nil && *(pExt+1) != '\0') + return pExt; + + return nil; +} +#endif + + +/* + * Analyze a file's characteristics. + */ +void +AnalyzeFile(const A2File* pFile, RecordKind* pRecordKind, + unsigned long* pTotalLen, unsigned long* pTotalCompLen) +{ + if (pFile->IsVolumeDirectory()) { + /* volume directory entry */ + ASSERT(pFile->GetRsrcLength() < 0); + *pRecordKind = kRecordKindVolumeDirectory; + *pTotalLen = pFile->GetDataLength(); + *pTotalCompLen = pFile->GetDataLength(); + } else if (pFile->IsDirectory()) { + /* directory entry */ + ASSERT(pFile->GetRsrcLength() < 0); + *pRecordKind = kRecordKindDirectory; + *pTotalLen = pFile->GetDataLength(); + *pTotalCompLen = pFile->GetDataLength(); + } else if (pFile->GetRsrcLength() >= 0) { + /* has resource fork */ + *pRecordKind = kRecordKindForkedFile; + *pTotalLen = pFile->GetDataLength() + pFile->GetRsrcLength(); + *pTotalCompLen = + pFile->GetDataSparseLength() + pFile->GetRsrcSparseLength(); + } else { + /* just data fork */ + *pRecordKind = kRecordKindFile; + *pTotalLen = pFile->GetDataLength(); + *pTotalCompLen = pFile->GetDataSparseLength(); + } +} + +/* + * Determine whether the access bits on the record make it a read-only + * file or not. + * + * Uses a simplified view of the access flags. + */ +bool +IsRecordReadOnly(int accessBits) +{ + if (accessBits == 0x21L || accessBits == 0x01L) + return true; + else + return false; +} + +/* ProDOS file type names; must be entirely in upper case */ +static const char gFileTypeNames[256][4] = { + "NON", "BAD", "PCD", "PTX", "TXT", "PDA", "BIN", "FNT", + "FOT", "BA3", "DA3", "WPF", "SOS", "$0D", "$0E", "DIR", + "RPD", "RPI", "AFD", "AFM", "AFR", "SCL", "PFS", "$17", + "$18", "ADB", "AWP", "ASP", "$1C", "$1D", "$1E", "$1F", + "TDM", "$21", "$22", "$23", "$24", "$25", "$26", "$27", + "$28", "$29", "8SC", "8OB", "8IC", "8LD", "P8C", "$2F", + "$30", "$31", "$32", "$33", "$34", "$35", "$36", "$37", + "$38", "$39", "$3A", "$3B", "$3C", "$3D", "$3E", "$3F", + "DIC", "OCR", "FTD", "$43", "$44", "$45", "$46", "$47", + "$48", "$49", "$4A", "$4B", "$4C", "$4D", "$4E", "$4F", + "GWP", "GSS", "GDB", "DRW", "GDP", "HMD", "EDU", "STN", + "HLP", "COM", "CFG", "ANM", "MUM", "ENT", "DVU", "FIN", + "$60", "$61", "$62", "$63", "$64", "$65", "$66", "$67", + "$68", "$69", "$6A", "BIO", "$6C", "TDR", "PRE", "HDV", + "$70", "$71", "$72", "$73", "$74", "$75", "$76", "$77", + "$78", "$79", "$7A", "$7B", "$7C", "$7D", "$7E", "$7F", + "$80", "$81", "$82", "$83", "$84", "$85", "$86", "$87", + "$88", "$89", "$8A", "$8B", "$8C", "$8D", "$8E", "$8F", + "$90", "$91", "$92", "$93", "$94", "$95", "$96", "$97", + "$98", "$99", "$9A", "$9B", "$9C", "$9D", "$9E", "$9F", + "WP ", "$A1", "$A2", "$A3", "$A4", "$A5", "$A6", "$A7", + "$A8", "$A9", "$AA", "GSB", "TDF", "BDF", "$AE", "$AF", + "SRC", "OBJ", "LIB", "S16", "RTL", "EXE", "PIF", "TIF", + "NDA", "CDA", "TOL", "DVR", "LDF", "FST", "$BE", "DOC", + "PNT", "PIC", "ANI", "PAL", "$C4", "OOG", "SCR", "CDV", + "FON", "FND", "ICN", "$CB", "$CC", "$CD", "$CE", "$CF", + "$D0", "$D1", "$D2", "$D3", "$D4", "MUS", "INS", "MDI", + "SND", "$D9", "$DA", "DBM", "$DC", "DDD", "$DE", "$DF", + "LBR", "$E1", "ATK", "$E3", "$E4", "$E5", "$E6", "$E7", + "$E8", "$E9", "$EA", "$EB", "$EC", "$ED", "R16", "PAS", + "CMD", "$F1", "$F2", "$F3", "$F4", "$F5", "$F6", "$F7", + "$F8", "OS ", "INT", "IVR", "BAS", "VAR", "REL", "SYS" +}; + +/* + * Return a pointer to the three-letter representation of the file type name. + * + * Note to self: code down below tests first char for '?'. + */ +/*static*/ const char* +GetFileTypeString(unsigned long fileType) +{ + if (fileType < NELEM(gFileTypeNames)) + return gFileTypeNames[fileType]; + else + return "???"; +} + +/* + * Sanitize a string. The Mac likes to stick control characters into + * things, e.g. ^C and ^M. + */ +static void +MacSanitize(char* str) +{ + while (*str != '\0') { + if (*str < 0x20 || *str >= 0x7f) + *str = '?'; + str++; + } +} + + +/* + * Load the contents of a DiskFS. + * + * Recursively handle sub-volumes. + */ +int +LoadDiskFSContents(DiskFS* pDiskFS, const char* volName, + ScanOpts* pScanOpts) +{ + static const char* kBlankFileName = ""; + DiskFS::SubVolume* pSubVol = nil; + A2File* pFile; + + ASSERT(pDiskFS != nil); + pFile = pDiskFS->GetNextFile(nil); + while (pFile != nil) { + char subVolName[128] = ""; + char dispName[128] = ""; + //CString subVolName, dispName; + RecordKind recordKind; + unsigned long totalLen, totalCompLen; + char tmpbuf[16]; + + AnalyzeFile(pFile, &recordKind, &totalLen, &totalCompLen); + if (recordKind == kRecordKindVolumeDirectory) { + /* skip these */ + pFile = pDiskFS->GetNextFile(pFile); + continue; + } + + /* prepend volName for sub-volumes; must be valid Win32 dirname */ + if (volName[0] != '\0') + snprintf(subVolName, sizeof(subVolName), "_%s", volName); + + const char* ccp = pFile->GetPathName(); + ASSERT(ccp != nil); + if (strlen(ccp) == 0) + ccp = kBlankFileName; + + if (subVolName[0] == '\0') + strcpy(dispName, ccp); + else { + snprintf(dispName, sizeof(dispName), "%s:%s", subVolName, ccp); + //dispName = subVolName; + //dispName += ':'; + //dispName += ccp; + } + ccp = dispName; + + int len = strlen(ccp); + if (len <= 32) { + fprintf(pScanOpts->outfp, "%c%-32.32s ", + IsRecordReadOnly(pFile->GetAccess()) ? '*' : ' ', + ccp); + } else { + fprintf(pScanOpts->outfp, "%c..%-30.30s ", + IsRecordReadOnly(pFile->GetAccess()) ? '*' : ' ', + ccp + len - 30); + } + switch (recordKind) { + case kRecordKindUnknown: + fprintf(pScanOpts->outfp, "%s- $%04X ", + GetFileTypeString(pFile->GetFileType()), + pFile->GetAuxType()); + break; + case kRecordKindDisk: + snprintf(tmpbuf, sizeof(tmpbuf), "%ldk", totalLen / 1024); + fprintf(pScanOpts->outfp, "Disk %-6s ", tmpbuf); + break; + case kRecordKindFile: + case kRecordKindForkedFile: + case kRecordKindDirectory: + if (pDiskFS->GetDiskImg()->GetFSFormat() == DiskImg::kFormatMacHFS) + { + if (recordKind != kRecordKindDirectory && + pFile->GetFileType() >= 0 && pFile->GetFileType() <= 0xff && + pFile->GetAuxType() >= 0 && pFile->GetAuxType() <= 0xffff) + { + /* ProDOS type embedded in HFS */ + fprintf(pScanOpts->outfp, "%s%c $%04X ", + GetFileTypeString(pFile->GetFileType()), + recordKind == kRecordKindForkedFile ? '+' : ' ', + pFile->GetAuxType()); + } else { + char typeStr[5]; + char creatorStr[5]; + unsigned long val; + + val = pFile->GetAuxType(); + creatorStr[0] = (unsigned char) (val >> 24); + creatorStr[1] = (unsigned char) (val >> 16); + creatorStr[2] = (unsigned char) (val >> 8); + creatorStr[3] = (unsigned char) val; + creatorStr[4] = '\0'; + + val = pFile->GetFileType(); + typeStr[0] = (unsigned char) (val >> 24); + typeStr[1] = (unsigned char) (val >> 16); + typeStr[2] = (unsigned char) (val >> 8); + typeStr[3] = (unsigned char) val; + typeStr[4] = '\0'; + + MacSanitize(creatorStr); + MacSanitize(typeStr); + + if (recordKind == kRecordKindDirectory) { + fprintf(pScanOpts->outfp, "DIR %-4s ", creatorStr); + } else { + fprintf(pScanOpts->outfp, "%-4s%c %-4s ", + typeStr, + pFile->GetRsrcLength() > 0 ? '+' : ' ', + creatorStr); + } + } + } else { + fprintf(pScanOpts->outfp, "%s%c $%04X ", + GetFileTypeString(pFile->GetFileType()), + recordKind == kRecordKindForkedFile ? '+' : ' ', + pFile->GetAuxType()); + } + break; + default: + ASSERT(0); + fprintf(pScanOpts->outfp, "ERROR "); + break; + } + + char date[64]; + if (pFile->GetModWhen() == 0) + FormatDate(kDateNone, date); + else + FormatDate(pFile->GetModWhen(), date); + fprintf(pScanOpts->outfp, "%-15s ", (LPCTSTR) date); + + const char* fmtStr; + switch (pFile->GetFSFormat()) { + case DiskImg::kFormatDOS33: + case DiskImg::kFormatDOS32: + case DiskImg::kFormatUNIDOS: + fmtStr = "DOS "; + break; + case DiskImg::kFormatProDOS: + fmtStr = "ProDOS"; + break; + case DiskImg::kFormatPascal: + fmtStr = "Pascal"; + break; + case DiskImg::kFormatMacHFS: + fmtStr = "HFS "; + break; + case DiskImg::kFormatCPM: + fmtStr = "CP/M "; + break; + case DiskImg::kFormatMSDOS: + fmtStr = "MS-DOS"; + break; + case DiskImg::kFormatRDOS33: + case DiskImg::kFormatRDOS32: + case DiskImg::kFormatRDOS3: + fmtStr = "RDOS "; + break; + default: + fmtStr = "??? "; + break; + } + if (pFile->GetQuality() == A2File::kQualityDamaged) + fmtStr = "BROKEN"; + + fprintf(pScanOpts->outfp, "%s ", fmtStr); + + +#if 0 + /* compute the percent size */ + if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen)) + fprintf(pScanOpts->outfp, "--- "); /* weird */ + else if (totalLen < totalCompLen) + fprintf(pScanOpts->outfp, ">100%% "); /* compression failed? */ + else { + sprintf(tmpbuf, "%02d%%", ComputePercent(totalCompLen, totalLen)); + fprintf(pScanOpts->outfp, "%4s ", tmpbuf); + } +#endif + + if (!totalLen && totalCompLen) + fprintf(pScanOpts->outfp, " ????"); /* weird */ + else + fprintf(pScanOpts->outfp, "%8ld", totalLen); + + fprintf(pScanOpts->outfp, "\n"); + + pFile = pDiskFS->GetNextFile(pFile); + } + + /* + * Load all sub-volumes. + */ + pSubVol = pDiskFS->GetNextSubVolume(nil); + while (pSubVol != nil) { + const char* subVolName; + int ret; + + subVolName = pSubVol->GetDiskFS()->GetVolumeName(); + if (subVolName == nil) + subVolName = "+++"; // could probably do better than this + + ret = LoadDiskFSContents(pSubVol->GetDiskFS(), subVolName, pScanOpts); + if (ret != 0) + return ret; + pSubVol = pDiskFS->GetNextSubVolume(pSubVol); + } + + return 0; +} + +/* + * Open a disk image and dump the contents. + * + * Returns 0 on success, nonzero on failure. + */ +int +ScanDiskImage(const char* pathName, ScanOpts* pScanOpts) +{ + ASSERT(pathName != nil); + ASSERT(pScanOpts != nil); + ASSERT(pScanOpts->outfp != nil); + + DIError dierr; + char errMsg[256] = ""; + DiskImg diskImg; + DiskFS* pDiskFS = nil; + + dierr = diskImg.OpenImage(pathName, '/', true); + if (dierr != kDIErrNone) { + snprintf(errMsg, sizeof(errMsg), "Unable to open '%s': %s", + pathName, DIStrError(dierr)); + goto bail; + } + + dierr = diskImg.AnalyzeImage(); + if (dierr != kDIErrNone) { + snprintf(errMsg, sizeof(errMsg), "Analysis of '%s' failed: %s", + pathName, DIStrError(dierr)); + goto bail; + } + + if (diskImg.GetFSFormat() == DiskImg::kFormatUnknown || + diskImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) + { + snprintf(errMsg, sizeof(errMsg), "Unable to identify filesystem on '%s'", + pathName); + goto bail; + } + + /* create an appropriate DiskFS object */ + pDiskFS = diskImg.OpenAppropriateDiskFS(); + if (pDiskFS == nil) { + /* unknown FS should've been caught above! */ + ASSERT(false); + snprintf(errMsg, sizeof(errMsg), "Format of '%s' not recognized.", + pathName); + goto bail; + } + + pDiskFS->SetScanForSubVolumes(DiskFS::kScanSubEnabled); + + /* object created; prep it */ + dierr = pDiskFS->Initialize(&diskImg, DiskFS::kInitFull); + if (dierr != kDIErrNone) { + snprintf(errMsg, sizeof(errMsg), + "Error reading list of files from disk: %s", DIStrError(dierr)); + goto bail; + } + + fprintf(pScanOpts->outfp, "File: %s\n", pathName); + + int kbytes; + if (pDiskFS->GetDiskImg()->GetHasBlocks()) + kbytes = pDiskFS->GetDiskImg()->GetNumBlocks() / 2; + else if (pDiskFS->GetDiskImg()->GetHasSectors()) + kbytes = (pDiskFS->GetDiskImg()->GetNumTracks() * + pDiskFS->GetDiskImg()->GetNumSectPerTrack()) / 4; + else + kbytes = 0; + fprintf(pScanOpts->outfp, "Disk: %s%s (%dKB)\n", pDiskFS->GetVolumeID(), + pDiskFS->GetFSDamaged() ? " [*]" : "", kbytes); + + fprintf(pScanOpts->outfp, + " Name Type Auxtyp Modified" + " Format Length\n"); + fprintf(pScanOpts->outfp, + "------------------------------------------------------" + "------------------------\n"); + if (LoadDiskFSContents(pDiskFS, "", pScanOpts) != 0) { + snprintf(errMsg, sizeof(errMsg), + "Failed while loading contents of '%s'.", pathName); + goto bail; + } + fprintf(pScanOpts->outfp, + "------------------------------------------------------" + "------------------------\n\n"); + + gStats.goodDiskImages++; + +bail: + delete pDiskFS; + + if (errMsg[0] != '\0') { + fprintf(pScanOpts->outfp, "Unable to process '%s'\n", pathName); + fprintf(pScanOpts->outfp, " %s\n\n", (LPCTSTR) errMsg); + return -1; + } else { + return 0; + } +} + + +/* + * Check a file's status. + * + * [ Someday we may want to modify this to handle symbolic links. ] + */ +int +CheckFileStatus(const char* pathname, struct stat* psb, bool* pExists, + bool* pIsReadable, bool* pIsDir) +{ + int result = 0; + int cc; + + assert(pathname != nil); + assert(psb != nil); + assert(pExists != nil); + assert(pIsReadable != nil); + assert(pIsDir != nil); + + *pExists = true; + *pIsReadable = true; + *pIsDir = false; + + cc = stat(pathname, psb); + if (cc) { + if (errno == ENOENT) + *pExists = false; + else + result = -1; // stat failed + goto bail; + } + + if (S_ISDIR(psb->st_mode)) + *pIsDir = true; + + /* + * Test if we can read this file. How do we do that? The easy but slow + * way is to call access(2), the harder way is to figure out + * what user/group we are and compare the appropriate file mode. + */ + if (access(pathname, R_OK) < 0) + *pIsReadable = false; + +bail: + return result; +} + + +/* forward decl */ +int ProcessFile(const char* pathname, ScanOpts* pScanOpts); + +/* + * UNIX-style recursive directory descent. Scan the contents of a directory. + * If a subdirectory is found, follow it; otherwise, call ProcessFile to + * handle the file. + */ +int +ProcessDirectory(const char* dirName, ScanOpts* pScanOpts) +{ + StringArray strArray; + int result = -1; + DIR* dirp = nil; + DIR_TYPE* entry; + char nbuf[MAX_PATH_LEN]; /* malloc might be better; this soaks stack */ + char fssep; + int len; + + assert(pScanOpts != nil); + assert(dirName != nil); + +#ifdef _DEBUG + fprintf(gLog, "+++ Processing directory '%s'\n", dirName); +#endif + + dirp = opendir(dirName); + if (dirp == nil) { + //err = errno ? errno : -1; + goto bail; + } + + fssep = '/'; + + /* could use readdir_r, but we don't care about reentrancy here */ + while ((entry = readdir(dirp)) != nil) { + /* skip the dotsies */ + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + len = strlen(dirName); + if (len + DIR_NAME_LEN(entry) +2 > MAX_PATH_LEN) { + fprintf(stderr, "ERROR: Filename exceeds %d bytes: %s%c%s\n", + MAX_PATH_LEN, dirName, fssep, entry->d_name); + goto bail; + } + + /* form the new name, inserting an fssep if needed */ + strcpy(nbuf, dirName); + if (dirName[len-1] != fssep) + nbuf[len++] = fssep; + strcpy(nbuf+len, entry->d_name); + + strArray.Add(nbuf); + } + + /* sort the list, then process the files */ + strArray.Sort(StringArray::CmpAscendingAlpha); + for (int i = 0; i < strArray.GetCount(); i++) + (void) ProcessFile(strArray.GetEntry(i), pScanOpts); + + result = 0; + +bail: + if (dirp != nil) + (void)closedir(dirp); + return result; +} + +/* + * Process a file. + * + * Returns with an error if the file doesn't exist or isn't readable. + */ +int +ProcessFile(const char* pathname, ScanOpts* pScanOpts) +{ + int result = -1; + bool exists, isDir, isReadable; + struct stat sb; + + assert(pathname != nil); + assert(pScanOpts != nil); + +#ifdef _DEBUG + fprintf(gLog, "+++ Processing file or dir '%s'\n", pathname); +#endif + + if (CheckFileStatus(pathname, &sb, &exists, &isReadable, &isDir) != 0) { + fprintf(stderr, "ERROR: unexpected error while examining '%s'\n", + pathname); + goto bail; + } + + if (!exists) { + fprintf(stderr, "ERROR: couldn't find '%s'\n", pathname); + goto bail; + } + if (!isReadable) { + fprintf(stderr, "ERROR: file '%s' isn't readable\n", pathname); + goto bail; + } + + if (isDir) { + result = ProcessDirectory(pathname, pScanOpts); + gStats.numDirectories++; + } else { + result = ScanDiskImage(pathname, pScanOpts); + gStats.numFiles++; + } + +bail: + return result; +} + + +/* + * Handle a debug message from the DiskImg library. + */ +/*static*/ void +MsgHandler(const char* file, int line, const char* msg) +{ + ASSERT(file != nil); + ASSERT(msg != nil); + +#ifdef _DEBUG + fprintf(gLog, "%05u %s", gPid, msg); +#endif +} +/* + * Handle a global error message from the NufxLib library by shoving it + * through the DiskImgLib message function. + */ +NuResult +NufxErrorMsgHandler(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; +} + +/* + * Process every argument. + */ +int +main(int argc, char** argv) +{ + ScanOpts scanOpts; + scanOpts.outfp = stdout; + +#ifdef _DEBUG + const char* kLogFile = "mdc-log.txt"; + gLog = fopen(kLogFile, "w"); + if (gLog == nil) { + fprintf(stderr, "ERROR: unable to open log file\n"); + exit(1); + } +#endif + + int32_t major, minor, bug; + Global::GetVersion(&major, &minor, &bug); + + printf("MDC for Linux v3.0.0 (DiskImg library v%d.%d.%d)\n", + major, minor, bug); + printf("Copyright (C) 2006 by faddenSoft, LLC. All rights reserved.\n"); + printf("MDC is part of CiderPress, available from http://www.faddensoft.com/.\n"); + NuGetVersion(&major, &minor, &bug, nil, nil); + printf("Linked against NufxLib v%d.%d.%d and zlib version %s.\n", + major, minor, bug, zlibVersion()); + + if (argc == 1) { + fprintf(stderr, "\nUsage: mdc file ...\n"); + goto done; + } + +#ifdef _DEBUG + printf("Log file is '%s'\n", kLogFile); +#endif + printf("\n"); + + Global::SetDebugMsgHandler(MsgHandler); + Global::AppInit(); + + NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler); + + time_t start; + start = time(NULL); + printf("Run started at %.24s\n\n", ctime(&start)); + + while (--argc) { + ProcessFile(*++argv, &scanOpts); + } + + printf("Scan completed in %ld seconds:\n", time(NULL) - start); + printf(" Directories : %ld\n", gStats.numDirectories); + printf(" Files : %ld (%ld good disk images)\n", gStats.numFiles, + gStats.goodDiskImages); + + Global::AppCleanup(); + +done: +#ifdef _DEBUG + fclose(gLog); +#endif + + return 0; +} + diff --git a/linux/MakeDisk.cpp b/linux/MakeDisk.cpp index 6b11341..38cb505 100644 --- a/linux/MakeDisk.cpp +++ b/linux/MakeDisk.cpp @@ -1,388 +1,388 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * Create a blank disk image, format it, and copy some files onto it. - */ -#include -#include -#include -#include -#include -#include -#include -#include "../diskimg/DiskImg.h" - -using namespace DiskImgLib; - -#define nil NULL -#define NELEM(x) (sizeof(x) / sizeof((x)[0])) - -/* - * Globals. - */ -FILE* gLog = nil; -pid_t gPid = getpid(); - -/* - * Show usage info. - */ -void -Usage(const char* argv0) -{ - fprintf(stderr, - "Usage: %s {dos|prodos|pascal} size image-filename.po input-file1 ...\n", - argv0); - - fprintf(stderr, "\n"); - fprintf(stderr, "Example: makedisk prodos 800k foo.po file1.txt file2.txt\n"); -} - - -/* - * Create a ProDOS-ordered disk image. - * - * Returns a DiskImg pointer on success, or nil on failure. - */ -DiskImg* -CreateDisk(const char* fileName, long blockCount) -{ - DIError dierr; - DiskImg* pDiskImg = nil; - - pDiskImg = new DiskImg; - dierr = pDiskImg->CreateImage( - fileName, - nil, // storageName - DiskImg::kOuterFormatNone, - DiskImg::kFileFormatUnadorned, - DiskImg::kPhysicalFormatSectors, - nil, // pNibbleDescr - DiskImg::kSectorOrderProDOS, - DiskImg::kFormatGenericProDOSOrd, - blockCount, - true); // no need to format the image - - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: CreateImage failed: %s\n", - DIStrError(dierr)); - delete pDiskImg; - pDiskImg = nil; - } - - return pDiskImg; -} - -/* - * Copy files to the disk. - */ -int -CopyFiles(DiskFS* pDiskFS, int argc, char** argv) -{ - DIError dierr; - DiskFS::CreateParms parms; - A2File* pNewFile; - - struct CreateParms { - const char* pathName; // full pathname - char fssep; - int storageType; // determines normal, subdir, or forked - long fileType; - long auxType; - int access; - time_t createWhen; - time_t modWhen; - }; - - - while (argc--) { - printf("+++ Adding '%s'\n", *argv); - - /* - * Use external pathname as internal pathname. This isn't quite - * right, since things like "../" will end up getting converted - * to something we don't want, but it'll do for now. - */ - parms.pathName = *argv; - parms.fssep = '/'; // UNIX fssep - parms.storageType = DiskFS::kStorageSeedling; // not forked, not dir - parms.fileType = 0; // NON - parms.auxType = 0; // $0000 - parms.access = DiskFS::kFileAccessUnlocked; - parms.createWhen = time(nil); - parms.modWhen = time(nil); - - /* - * Create a new, empty file. The "pNewFile" pointer does not belong - * to us, so we should not delete it later, or try to access it - * after the underlying file is deleted. - */ - dierr = pDiskFS->CreateFile(&parms, &pNewFile); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: unable to create '%s': %s\n", - *argv, DIStrError(dierr)); - return -1; - } - - /* - * Load the input file into memory. - */ - FILE* fp; - char* buf; - long len; - - fp = fopen(*argv, "r"); - if (fp == nil) { - fprintf(stderr, "ERROR: unable to open input file '%s': %s\n", - *argv, strerror(errno)); - return -1; - } - - if (fseek(fp, 0, SEEK_END) != 0) { - fprintf(stderr, "ERROR: unable to seek input file '%s': %s\n", - *argv, strerror(errno)); - fclose(fp); - return -1; - } - - len = ftell(fp); - rewind(fp); - - buf = new char[len]; - if (buf == nil) { - fprintf(stderr, "ERROR: unable to alloc %ld bytes\n", len); - fclose(fp); - return -1; - } - - if (fread(buf, len, 1, fp) != 1) { - fprintf(stderr, "ERROR: fread of %ld bytes from '%s' failed: %s\n", - len, *argv, strerror(errno)); - fclose(fp); - delete[] buf; - return -1; - } - fclose(fp); - - /* - * Write the buffer to the disk image. - * - * The A2FileDescr object is created by "Open" and deleted by - * "Close". - */ - A2FileDescr* pFD; - - dierr = pNewFile->Open(&pFD, true); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: unable to open new file '%s': %s\n", - pNewFile->GetPathName(), DIStrError(dierr)); - delete[] buf; - return -1; - } - - dierr = pFD->Write(buf, len); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: failed writing to '%s': %s\n", - pNewFile->GetPathName(), DIStrError(dierr)); - pFD->Close(); - pDiskFS->DeleteFile(pNewFile); - delete[] buf; - return -1; - } - delete[] buf; - - dierr = pFD->Close(); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: failed while closing '%s': %s\n", - pNewFile->GetPathName(), DIStrError(dierr)); - return -1; - } - - /* - * On to the next file. - */ - argv++; - } - - return 0; -} - -/* - * Process the request. - * - * Returns 0 on success, -1 on failure. - */ -int -Process(const char* formatName, const char* sizeStr, - const char* outputFileName, int argc, char** argv) -{ - DiskImg::FSFormat format; - long blockCount; - - if (strcasecmp(formatName, "dos") == 0) - format = DiskImg::kFormatDOS33; - else if (strcasecmp(formatName, "prodos") == 0) - format = DiskImg::kFormatProDOS; - else if (strcasecmp(formatName, "pascal") == 0) - format = DiskImg::kFormatPascal; - else { - fprintf(stderr, "ERROR: invalid format '%s'\n", formatName); - return -1; - } - - if (strcasecmp(sizeStr, "140k") == 0) - blockCount = 280; - else if (strcasecmp(sizeStr, "800k") == 0) - blockCount = 1600; - else { - blockCount = atoi(sizeStr); - if (blockCount <= 0 || blockCount > 65536) { - fprintf(stderr, "ERROR: invalid size '%s'\n", sizeStr); - return -1; - } - } - - if (access(outputFileName, F_OK) == 0) { - fprintf(stderr, "ERROR: output file '%s' already exists\n", - outputFileName); - return -1; - } - - assert(argc >= 1); - assert(*argv != nil); - - - const char* volName; - DiskImg* pDiskImg; - DiskFS* pDiskFS; - DIError dierr; - - /* - * Prepare the disk image file. - */ - pDiskImg = CreateDisk(outputFileName, blockCount); - if (pDiskImg == nil) - return -1; - - if (format == DiskImg::kFormatDOS33) - volName = "DOS"; // put DOS 3.3 in tracks 0-2 - else - volName = "TEST"; - dierr = pDiskImg->FormatImage(format, volName); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: unable to format disk: %s\n", - DIStrError(dierr)); - delete pDiskImg; - return -1; - } - - /* - * Prepare to access the image as a filesystem. - */ - pDiskFS = pDiskImg->OpenAppropriateDiskFS(false); - if (pDiskFS == nil) { - fprintf(stderr, "ERROR: unable to open appropriate DiskFS\n"); - delete pDiskImg; - return -1; - } - - dierr = pDiskFS->Initialize(pDiskImg, DiskFS::kInitFull); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: unable to initialize DiskFS: %s\n", - DIStrError(dierr)); - delete pDiskFS; - delete pDiskImg; - return -1; - } - - /* - * Copy the files over. - */ - if (CopyFiles(pDiskFS, argc, argv) != 0) { - delete pDiskFS; - delete pDiskImg; - return -1; - } - - /* - * Clean up. Note "CloseImage" isn't strictly necessary, but it gives - * us an opportunity to detect failures. - */ - delete pDiskFS; - - if (pDiskImg->CloseImage() != 0) { - fprintf(stderr, "WARNING: CloseImage failed: %s\n", - DIStrError(dierr)); - } - - delete pDiskImg; - return 0; -} - - -/* - * Handle a debug message from the DiskImg library. - */ -/*static*/ void -MsgHandler(const char* file, int line, const char* msg) -{ - assert(file != nil); - assert(msg != nil); - -#ifdef _DEBUG - fprintf(gLog, "%05u %s", gPid, msg); -#endif -} - -/* - * Process args. - */ -int -main(int argc, char** argv) -{ -#ifdef _DEBUG - const char* kLogFile = "makedisk-log.txt"; - gLog = fopen(kLogFile, "w"); - if (gLog == nil) { - fprintf(stderr, "ERROR: unable to open log file\n"); - exit(1); - } -#endif - -#ifdef _DEBUG - printf("Log file is '%s'\n", kLogFile); -#endif - - Global::SetDebugMsgHandler(MsgHandler); - Global::AppInit(); - - if (argc < 5) { - Usage(argv[0]); - exit(2); - } - - const char* formatName; - const char* sizeStr; - const char* outputFileName; - - argv++; - formatName = *argv++; - sizeStr = *argv++; - outputFileName = *argv++; - argc -= 4; - - if (Process(formatName, sizeStr, outputFileName, argc, argv) == 0) - fprintf(stderr, "Success!\n"); - else - fprintf(stderr, "Failed.\n"); - - Global::AppCleanup(); -#ifdef _DEBUG - fclose(gLog); -#endif - - exit(0); -} - +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Create a blank disk image, format it, and copy some files onto it. + */ +#include +#include +#include +#include +#include +#include +#include +#include "../diskimg/DiskImg.h" + +using namespace DiskImgLib; + +#define nil NULL +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +/* + * Globals. + */ +FILE* gLog = nil; +pid_t gPid = getpid(); + +/* + * Show usage info. + */ +void +Usage(const char* argv0) +{ + fprintf(stderr, + "Usage: %s {dos|prodos|pascal} size image-filename.po input-file1 ...\n", + argv0); + + fprintf(stderr, "\n"); + fprintf(stderr, "Example: makedisk prodos 800k foo.po file1.txt file2.txt\n"); +} + + +/* + * Create a ProDOS-ordered disk image. + * + * Returns a DiskImg pointer on success, or nil on failure. + */ +DiskImg* +CreateDisk(const char* fileName, long blockCount) +{ + DIError dierr; + DiskImg* pDiskImg = nil; + + pDiskImg = new DiskImg; + dierr = pDiskImg->CreateImage( + fileName, + nil, // storageName + DiskImg::kOuterFormatNone, + DiskImg::kFileFormatUnadorned, + DiskImg::kPhysicalFormatSectors, + nil, // pNibbleDescr + DiskImg::kSectorOrderProDOS, + DiskImg::kFormatGenericProDOSOrd, + blockCount, + true); // no need to format the image + + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: CreateImage failed: %s\n", + DIStrError(dierr)); + delete pDiskImg; + pDiskImg = nil; + } + + return pDiskImg; +} + +/* + * Copy files to the disk. + */ +int +CopyFiles(DiskFS* pDiskFS, int argc, char** argv) +{ + DIError dierr; + DiskFS::CreateParms parms; + A2File* pNewFile; + + struct CreateParms { + const char* pathName; // full pathname + char fssep; + int storageType; // determines normal, subdir, or forked + long fileType; + long auxType; + int access; + time_t createWhen; + time_t modWhen; + }; + + + while (argc--) { + printf("+++ Adding '%s'\n", *argv); + + /* + * Use external pathname as internal pathname. This isn't quite + * right, since things like "../" will end up getting converted + * to something we don't want, but it'll do for now. + */ + parms.pathName = *argv; + parms.fssep = '/'; // UNIX fssep + parms.storageType = DiskFS::kStorageSeedling; // not forked, not dir + parms.fileType = 0; // NON + parms.auxType = 0; // $0000 + parms.access = DiskFS::kFileAccessUnlocked; + parms.createWhen = time(nil); + parms.modWhen = time(nil); + + /* + * Create a new, empty file. The "pNewFile" pointer does not belong + * to us, so we should not delete it later, or try to access it + * after the underlying file is deleted. + */ + dierr = pDiskFS->CreateFile(&parms, &pNewFile); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: unable to create '%s': %s\n", + *argv, DIStrError(dierr)); + return -1; + } + + /* + * Load the input file into memory. + */ + FILE* fp; + char* buf; + long len; + + fp = fopen(*argv, "r"); + if (fp == nil) { + fprintf(stderr, "ERROR: unable to open input file '%s': %s\n", + *argv, strerror(errno)); + return -1; + } + + if (fseek(fp, 0, SEEK_END) != 0) { + fprintf(stderr, "ERROR: unable to seek input file '%s': %s\n", + *argv, strerror(errno)); + fclose(fp); + return -1; + } + + len = ftell(fp); + rewind(fp); + + buf = new char[len]; + if (buf == nil) { + fprintf(stderr, "ERROR: unable to alloc %ld bytes\n", len); + fclose(fp); + return -1; + } + + if (fread(buf, len, 1, fp) != 1) { + fprintf(stderr, "ERROR: fread of %ld bytes from '%s' failed: %s\n", + len, *argv, strerror(errno)); + fclose(fp); + delete[] buf; + return -1; + } + fclose(fp); + + /* + * Write the buffer to the disk image. + * + * The A2FileDescr object is created by "Open" and deleted by + * "Close". + */ + A2FileDescr* pFD; + + dierr = pNewFile->Open(&pFD, true); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: unable to open new file '%s': %s\n", + pNewFile->GetPathName(), DIStrError(dierr)); + delete[] buf; + return -1; + } + + dierr = pFD->Write(buf, len); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: failed writing to '%s': %s\n", + pNewFile->GetPathName(), DIStrError(dierr)); + pFD->Close(); + pDiskFS->DeleteFile(pNewFile); + delete[] buf; + return -1; + } + delete[] buf; + + dierr = pFD->Close(); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: failed while closing '%s': %s\n", + pNewFile->GetPathName(), DIStrError(dierr)); + return -1; + } + + /* + * On to the next file. + */ + argv++; + } + + return 0; +} + +/* + * Process the request. + * + * Returns 0 on success, -1 on failure. + */ +int +Process(const char* formatName, const char* sizeStr, + const char* outputFileName, int argc, char** argv) +{ + DiskImg::FSFormat format; + long blockCount; + + if (strcasecmp(formatName, "dos") == 0) + format = DiskImg::kFormatDOS33; + else if (strcasecmp(formatName, "prodos") == 0) + format = DiskImg::kFormatProDOS; + else if (strcasecmp(formatName, "pascal") == 0) + format = DiskImg::kFormatPascal; + else { + fprintf(stderr, "ERROR: invalid format '%s'\n", formatName); + return -1; + } + + if (strcasecmp(sizeStr, "140k") == 0) + blockCount = 280; + else if (strcasecmp(sizeStr, "800k") == 0) + blockCount = 1600; + else { + blockCount = atoi(sizeStr); + if (blockCount <= 0 || blockCount > 65536) { + fprintf(stderr, "ERROR: invalid size '%s'\n", sizeStr); + return -1; + } + } + + if (access(outputFileName, F_OK) == 0) { + fprintf(stderr, "ERROR: output file '%s' already exists\n", + outputFileName); + return -1; + } + + assert(argc >= 1); + assert(*argv != nil); + + + const char* volName; + DiskImg* pDiskImg; + DiskFS* pDiskFS; + DIError dierr; + + /* + * Prepare the disk image file. + */ + pDiskImg = CreateDisk(outputFileName, blockCount); + if (pDiskImg == nil) + return -1; + + if (format == DiskImg::kFormatDOS33) + volName = "DOS"; // put DOS 3.3 in tracks 0-2 + else + volName = "TEST"; + dierr = pDiskImg->FormatImage(format, volName); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: unable to format disk: %s\n", + DIStrError(dierr)); + delete pDiskImg; + return -1; + } + + /* + * Prepare to access the image as a filesystem. + */ + pDiskFS = pDiskImg->OpenAppropriateDiskFS(false); + if (pDiskFS == nil) { + fprintf(stderr, "ERROR: unable to open appropriate DiskFS\n"); + delete pDiskImg; + return -1; + } + + dierr = pDiskFS->Initialize(pDiskImg, DiskFS::kInitFull); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: unable to initialize DiskFS: %s\n", + DIStrError(dierr)); + delete pDiskFS; + delete pDiskImg; + return -1; + } + + /* + * Copy the files over. + */ + if (CopyFiles(pDiskFS, argc, argv) != 0) { + delete pDiskFS; + delete pDiskImg; + return -1; + } + + /* + * Clean up. Note "CloseImage" isn't strictly necessary, but it gives + * us an opportunity to detect failures. + */ + delete pDiskFS; + + if (pDiskImg->CloseImage() != 0) { + fprintf(stderr, "WARNING: CloseImage failed: %s\n", + DIStrError(dierr)); + } + + delete pDiskImg; + return 0; +} + + +/* + * Handle a debug message from the DiskImg library. + */ +/*static*/ void +MsgHandler(const char* file, int line, const char* msg) +{ + assert(file != nil); + assert(msg != nil); + +#ifdef _DEBUG + fprintf(gLog, "%05u %s", gPid, msg); +#endif +} + +/* + * Process args. + */ +int +main(int argc, char** argv) +{ +#ifdef _DEBUG + const char* kLogFile = "makedisk-log.txt"; + gLog = fopen(kLogFile, "w"); + if (gLog == nil) { + fprintf(stderr, "ERROR: unable to open log file\n"); + exit(1); + } +#endif + +#ifdef _DEBUG + printf("Log file is '%s'\n", kLogFile); +#endif + + Global::SetDebugMsgHandler(MsgHandler); + Global::AppInit(); + + if (argc < 5) { + Usage(argv[0]); + exit(2); + } + + const char* formatName; + const char* sizeStr; + const char* outputFileName; + + argv++; + formatName = *argv++; + sizeStr = *argv++; + outputFileName = *argv++; + argc -= 4; + + if (Process(formatName, sizeStr, outputFileName, argc, argv) == 0) + fprintf(stderr, "Success!\n"); + else + fprintf(stderr, "Failed.\n"); + + Global::AppCleanup(); +#ifdef _DEBUG + fclose(gLog); +#endif + + exit(0); +} + diff --git a/linux/PackDDD.cpp b/linux/PackDDD.cpp index 103759a..b56da53 100644 --- a/linux/PackDDD.cpp +++ b/linux/PackDDD.cpp @@ -1,766 +1,766 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * Pack DDD. - -The trouble with unpacking DOS DDD 2.x files: - -The files are stored as binary files with no length. DDD v2.0 stored -a copy of the length in sectors in the filename (e.g. "<397>"). This -means that, when CiderPress goes to extract or view the file, it just -sees an empty binary file. - -CiderPress could make an exception and assume that any binary file with -zero length and more than one sector allocated has a length equal to the -number of sectors times 256. This could cause problems for other things, -but it's probably pretty safe. However, we still don't have an accurate -idea of where the end of the file is. - -Knowing where the file ends is important because there is no identifying -information or checksum in a DDD file. The only way to know that it's a -DDD compressed disk is to try to unpack it and see if you end up at exactly -140K at the same time that you run out of input. Without knowing where the -file really ends, this test is much less certain. - -The only safe way to make this work would be to skip the automatic format -detection and tell CiderPress that the file is definitely DDD format. -There's currently no easy way to do that without complicating the user -interface. Filename extensions might be useful, but they're rare under -DOS 3.3, and I don't think the "<397>" convention is common to all versions -of DDD. - -Complicating the matter is that, if a DOS DDD file (type 'B') is converted -to ProDOS, the first 4 bytes will be stripped off. Without unpacking -the file and knowing to within a byte where it ends, there's no way to -automatically tell whether to start at byte 0 or byte 4. (DDD Pro files -have four bytes of garbage at the very start, probably in an attempt to -retain compatibility with the DOS version. Because it uses REL files the -4 bytes of extra DOS stuff aren't added when the files are copied around, -so this was a reasonably smart thing to do, but it complicates matters -for CiderPress because a file extracted from DOS and a file extracted -from ProDOS will come out differently due to the 4 bytes of type 'B' -gunk getting stripped. This can be avoided if the DOS file uses the 'R' -or 'S' file type.) - -All this would have been much easier if the DOS files had a length word. - -To unpack a file created by DOS DDD v2.x: - - Copy the file to a ProDOS disk, using something that guesses at the - actual length when one isn't provided (Copy ][+ 9.0 may work). - - Reduce the length to within a byte or two of the actual end of file. - Removing all but the last couple of trailing zero bytes usually does - the trick. - - Insert 4 bytes of garbage at the front of the file. My copy of DDD - Pro 1.1 seems to like 03 c9 bf d0. -Probably not worth the effort. - */ -#include -#include -#include -#include -#include -#include "../diskimg/DiskImg.h" -#include "../nufxlib/NufxLib.h" - -using namespace DiskImgLib; - -#define nil NULL -#define NELEM(x) (sizeof(x) / sizeof((x)[0])) - -FILE* gLog = nil; -pid_t gPid = getpid(); - -const int kTrackLen = 4096; -const int kNumSymbols = 256; -const int kNumFavorites = 20; -const int kRLEDelim = 0x97; // value MUST have high bit set -const int kNumTracks = 35; - -/* I suspect this is random garbage, but it's consistent for me */ -const unsigned long kDDDProSignature = 0xd0bfc903; - - -/* - * Class for getting and putting bits to and from a file. - */ -class BitBuffer { -public: - BitBuffer(void) : fFp(nil), fBits(0), fBitCount(0) {} - ~BitBuffer(void) {} - - void SetFile(FILE* fp) { fFp = fp; } - void PutBits(unsigned char bits, int numBits); - //void FlushBits(void); - unsigned char GetBits(int numBits); - - static unsigned char Reverse(unsigned char val); - -private: - FILE* fFp; - unsigned char fBits; - int fBitCount; -}; - -/* - * Add bits to the buffer. - * - * We roll the low bits out of "bits" and shift them to the left (in the - * reverse order in which they were passed in). As soon as we get 8 bits - * we flush. - */ -void -BitBuffer::PutBits(unsigned char bits, int numBits) -{ - assert(fBitCount >= 0 && fBitCount < 8); - assert(numBits > 0 && numBits <= 8); - assert(fFp != nil); - - while (numBits--) { - fBits = (fBits << 1) | (bits & 0x01); - fBitCount++; - - if (fBitCount == 8) { - putc(fBits, fFp); - fBitCount = 0; - } - - bits >>= 1; - } -} - -/* - * Get bits from the buffer. - * - * These come out in the order in which they appear in the file, which - * means that in some cases they will have to be reversed. - */ -unsigned char -BitBuffer::GetBits(int numBits) -{ - assert(fBitCount >= 0 && fBitCount < 8); - assert(numBits > 0 && numBits <= 8); - assert(fFp != nil); - - unsigned char retVal; - - if (fBitCount == 0) { - /* have no bits */ - fBits = getc(fFp); - fBitCount = 8; - } - - if (numBits <= fBitCount) { - /* just serve up what we've already got */ - retVal = fBits >> (8 - numBits); - fBits <<= numBits; - fBitCount -= numBits; - } else { - /* some old, some new; load what we have right-aligned */ - retVal = fBits >> (8 - fBitCount); - numBits -= fBitCount; - - fBits = getc(fFp); - fBitCount = 8; - - /* make room for the rest (also zeroes out the low bits) */ - retVal <<= numBits; - - /* add the high bits from the new byte */ - retVal |= fBits >> (8 - numBits); - fBits <<= numBits; - fBitCount -= numBits; - } - - return retVal; -} - -/* - * Utility function to reverse the order of bits in a byte. - */ -/*static*/ unsigned char -BitBuffer::Reverse(unsigned char val) -{ - int i; - unsigned char result = 0; // init to make compiler happy - - for (i = 0; i < 8; i++) { - result = (result << 1) + (val & 0x01); - val >>= 1; - } - - return result; -} - -#if 0 -/* - * Flush any remaining bits out. Call this at the very end. - */ -void -BitBuffer::FlushBits(void) -{ - if (fBitCount) { - fBits <<= 8 - fBitCount; - putc(fBits, fFp); - - fBitCount = 0; - } -} -#endif - - -/* - * Compute the #of times each byte appears in trackBuf. Runs of four - * bytes or longer are completely ignored. - * - * "trackBuf" holds kTrackLen bytes of data, and "freqCounts" holds - * kNumSymbols (256) unsigned shorts. - */ -void -ComputeFreqCounts(const unsigned char* trackBuf, unsigned short* freqCounts) -{ - const unsigned char* ucp; - int i; - - memset(freqCounts, 0, 256 * sizeof(unsigned short)); - - ucp = trackBuf; - for (i = 0; i < kTrackLen; i++, ucp++) { - if (i < (kTrackLen-3) && - *ucp == *(ucp+1) && - *ucp == *(ucp+2) && - *ucp == *(ucp+3)) - { - int runLen = 4; // DEBUG only - i += 3; - ucp += 3; - - while (*ucp == *(ucp+1) && i < kTrackLen) { - runLen++; - ucp++; - i++; - - if (runLen == 256) { - runLen = 0; - break; - } - } - - //printf("Found run of %d of 0x%02x\n", runLen, *ucp); - } else { - /* not a run, just update stats */ - freqCounts[*ucp]++; - } - } -} - -/* - * Find the 20 most frequently occurring symbols, in order. - * - * Modifies "freqCounts". - */ -void -ComputeFavorites(unsigned short* freqCounts, unsigned char* favorites) -{ - int i, fav; - - for (fav = 0; fav < kNumFavorites; fav++) { - unsigned short bestCount = 0; - unsigned char bestSym = 0; - - for (i = 0; i < kNumSymbols; i++) { - if (freqCounts[i] >= bestCount) { - bestSym = (unsigned char) i; - bestCount = freqCounts[i]; - } - } - - favorites[fav] = bestSym; - freqCounts[bestSym] = 0; - } - - //printf("FAVORITES: "); - //for (fav = 0; fav < kNumFavorites; fav++) - // printf("%02x ", favorites[fav]); - //printf("\n"); -} - -/* - * These are all odd, which when they're written in reverse order means - * they all have their hi bits set. - */ -static const unsigned char kFavoriteBitEnc[kNumFavorites] = { - 0x03, 0x09, 0x1f, 0x0f, 0x07, 0x1b, 0x0b, 0x0d, 0x15, 0x37, - 0x3d, 0x25, 0x05, 0xb1, 0x11, 0x21, 0x01, 0x57, 0x5d, 0x1d -}; -static const int kFavoriteBitEncLen[kNumFavorites] = { - 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, - 6, 6, 6, 6, 6, 6, 6, 7, 7, 7 -}; - - -/* - * Compress a track full of data. - */ -void -CompressTrack(const unsigned char* trackBuf, BitBuffer* pBitBuf) -{ - unsigned short freqCounts[kNumSymbols]; - unsigned char favorites[kNumFavorites]; - int i, fav; - - ComputeFreqCounts(trackBuf, freqCounts); - ComputeFavorites(freqCounts, favorites); - - /* write favorites */ - for (fav = 0; fav < kNumFavorites; fav++) - pBitBuf->PutBits(favorites[fav], 8); - - /* - * Compress track data. Store runs as { 0x97 char count }, where - * a count of zero means 256. - */ - const unsigned char* ucp = trackBuf; - for (i = 0; i < kTrackLen; i++, ucp++) { - if (i < (kTrackLen-3) && - *ucp == *(ucp+1) && - *ucp == *(ucp+2) && - *ucp == *(ucp+3)) - { - int runLen = 4; - i += 3; - ucp += 3; - - while (*ucp == *(ucp+1) && i < kTrackLen) { - runLen++; - ucp++; - i++; - - if (runLen == 256) { - runLen = 0; - break; - } - } - - pBitBuf->PutBits(kRLEDelim, 8); // note kRLEDelim has hi bit set - pBitBuf->PutBits(*ucp, 8); - pBitBuf->PutBits(runLen, 8); - - } else { - /* - * Not a run, see if it's one of our favorites. - */ - for (fav = 0; fav < kNumFavorites; fav++) { - if (*ucp == favorites[fav]) - break; - } - if (fav == kNumFavorites) { - /* just a plain byte */ - pBitBuf->PutBits(0x00, 1); - pBitBuf->PutBits(*ucp, 8); - } else { - /* found a favorite; leading hi bit is implied */ - pBitBuf->PutBits(kFavoriteBitEnc[fav], kFavoriteBitEncLen[fav]); - } - } - } -} - - - -/* - * Handle a debug message from the DiskImg library. - */ -/*static*/ void -MsgHandler(const char* file, int line, const char* msg) -{ - assert(file != nil); - assert(msg != nil); - - fprintf(gLog, "%05u %s", gPid, msg); -} -/* - * Handle a global error message from the NufxLib library by shoving it - * through the DiskImgLib message function. - */ -NuResult -NufxErrorMsgHandler(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; -} - -/* - * Pack a disk image with DDD. - */ -DIError -Pack(const char* infile, const char* outfile) -{ - DIError dierr = kDIErrNone; - DiskImg srcImg; - FILE* outfp = nil; - BitBuffer bitBuffer; - - printf("Packing in='%s' out='%s'\n", infile, outfile); - - /* - * Prepare the source image. - */ - dierr = srcImg.OpenImage(infile, '/', true); - if (dierr != kDIErrNone) { - fprintf(stderr, "Unable to open disk image: %s.\n", - DiskImgLib::DIStrError(dierr)); - goto bail; - } - - dierr = srcImg.AnalyzeImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "Unable to determine source image format.\n"); - goto bail; - } - - if (!srcImg.GetHasSectors()) { - fprintf(stderr, "Sorry, only sector-addressable images allowed.\n"); - dierr = kDIErrUnsupportedPhysicalFmt; - goto bail; - } - assert(srcImg.GetNumSectPerTrack() > 0); - - if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { - fprintf(stderr, "(QUERY) don't know sector order\n"); - dierr = kDIErrFilesystemNotFound; - goto bail; - } - - /* force the access to be DOS-ordered */ - dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(), - DiskImg::kFormatGenericDOSOrd, srcImg.GetSectorOrder()); - if (dierr != kDIErrNone) { - fprintf(stderr, "Couldn't switch to generic ProDOS: %s.\n", - DiskImgLib::DIStrError(dierr)); - goto bail; - } - - /* transfer the DOS volume num, if one was set */ - printf("DOS volume number set to %d\n", srcImg.GetDOSVolumeNum()); - if (srcImg.GetDOSVolumeNum() == DiskImg::kVolumeNumNotSet) - srcImg.SetDOSVolumeNum(kDefaultNibbleVolumeNum); - - /* - * Open the output file. - */ - outfp = fopen(outfile, "w"); - if (outfp == nil) { - perror("unable to open output file"); - dierr = kDIErrGeneric; - goto bail; - } - - /* write four zeroes to replace the DOS addr/len bytes */ - /* (let's write the apparent DDD Pro v1.1 signature instead) */ - putc(kDDDProSignature, outfp); - putc(kDDDProSignature >> 8, outfp); - putc(kDDDProSignature >> 16, outfp); - putc(kDDDProSignature >> 24, outfp); - - bitBuffer.SetFile(outfp); - - bitBuffer.PutBits(0x00, 3); - bitBuffer.PutBits(srcImg.GetDOSVolumeNum(), 8); - - /* - * Process all tracks. - */ - for (int track = 0; track < srcImg.GetNumTracks(); track++) { - unsigned char trackBuf[kTrackLen]; - - /* - * Read the track. - */ - for (int sector = 0; sector < srcImg.GetNumSectPerTrack(); sector++) { - dierr = srcImg.ReadTrackSector(track, sector, - trackBuf + sector * 256); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: ReadBlock failed (err=%d)\n", dierr); - goto bail; - } - } - - //printf("Got track %d (0x%02x %02x %02x %02x %02x %02x ...)\n", - // track, trackBuf[0], trackBuf[1], trackBuf[2], trackBuf[3], - // trackBuf[4], trackBuf[5]); - CompressTrack(trackBuf, &bitBuffer); - } - - /* write 8 bits of zeroes to flush remaining data out of buffer */ - bitBuffer.PutBits(0x00, 8); - - /* write another zero byte because that's what DDD Pro v1.1 does */ - long zero; - zero = 0; - fwrite(&zero, 1, 1, outfp); - - dierr = srcImg.CloseImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: srcImg close failed?!\n"); - goto bail; - } - - assert(dierr == kDIErrNone); -bail: - return dierr; -} - -/* - * This is the reverse of the kFavoriteBitEnc table. The bits are - * reversed and lack the high bit. - */ -static const unsigned char kFavoriteBitDec[kNumFavorites] = { - 0x04, 0x01, 0x0f, 0x0e, 0x0c, 0x0b, 0x0a, 0x06, 0x05, 0x1b, - 0x0f, 0x09, 0x08, 0x03, 0x02, 0x01, 0x00, 0x35, 0x1d, 0x1c -}; - -/* - * Unpack a single track. - * - * Returns "true" if all went well, "false" if something failed. - */ -bool -UnpackTrack(BitBuffer* pBitBuffer, unsigned char* trackBuf) -{ - unsigned char favorites[kNumFavorites]; - unsigned char val; - unsigned char* trackPtr; - int fav; - - /* - * Start by pulling our favorites out, in reverse order. - */ - for (fav = 0; fav < kNumFavorites; fav++) { - val = pBitBuffer->GetBits(8); - val = pBitBuffer->Reverse(val); - favorites[fav] = val; - } - - trackPtr = trackBuf; - - /* - * Keep pulling data out until the track is full. - */ - while (trackPtr < trackBuf + kTrackLen) { - val = pBitBuffer->GetBits(1); - if (!val) { - /* simple byte */ - val = pBitBuffer->GetBits(8); - val = pBitBuffer->Reverse(val); - *trackPtr++ = val; - } else { - /* try for a prefix match */ - int extraBits; - - val = pBitBuffer->GetBits(2); - - for (extraBits = 0; extraBits < 4; extraBits++) { - val = (val << 1) | pBitBuffer->GetBits(1); - int start, end; - - if (extraBits == 0) { - start = 0; - end = 2; - } else if (extraBits == 1) { - start = 2; - end = 9; - } else if (extraBits == 2) { - start = 9; - end = 17; - } else { - start = 17; - end = 20; - } - - while (start < end) { - if (val == kFavoriteBitDec[start]) { - /* winner! */ - *trackPtr++ = favorites[start]; - break; - } - start++; - } - if (start != end) - break; // we got it, break out of for loop - } - if (extraBits == 4) { - /* we didn't get it, this must be RLE */ - unsigned char rleChar; - int rleCount; - - (void) pBitBuffer->GetBits(1); // get last bit of 0x97 - val = pBitBuffer->GetBits(8); - rleChar = pBitBuffer->Reverse(val); - val = pBitBuffer->GetBits(8); - rleCount = pBitBuffer->Reverse(val); - printf("Found run of %d of 0x%02x\n", rleCount, rleChar); - - if (rleCount == 0) - rleCount = 256; - - /* make sure we won't overrun */ - if (trackPtr + rleCount > trackBuf + kTrackLen) { - printf("Overrun in RLE\n"); - return false; - } - while (rleCount--) - *trackPtr++ = rleChar; - } - } - } - - return true; -} - -/* - * Unpack a disk image compressed with DDD. - * - * The result is an unadorned DOS-ordered image. - */ -DIError -Unpack(const char* infile, const char* outfile) -{ - DIError dierr = kDIErrNone; - FILE* infp = nil; - FILE* outfp = nil; - BitBuffer bitBuffer; - unsigned char val; - - printf("Unpacking in='%s' out='%s'\n", infile, outfile); - - /* - * Open the input file. - */ - infp = fopen(infile, "r"); - if (infp == nil) { - perror("unable to open input file"); - dierr = kDIErrGeneric; - goto bail; - } - - /* - * Open the output file. - */ - outfp = fopen(outfile, "w"); - if (outfp == nil) { - perror("unable to open output file"); - dierr = kDIErrGeneric; - goto bail; - } - - /* read four zeroes to skip the DOS addr/len bytes */ - (void) getc(infp); - (void) getc(infp); - (void) getc(infp); - (void) getc(infp); - - bitBuffer.SetFile(infp); - - val = bitBuffer.GetBits(3); - if (val != 0) { - printf("HEY: this isn't a DDD II file (%d)\n", val); - dierr = kDIErrGeneric; - goto bail; - } - val = bitBuffer.GetBits(8); - val = bitBuffer.Reverse(val); - printf("GOT disk volume num = %d\n", val); - - for (int track = 0; track < kNumTracks; track++) { - unsigned char trackBuf[kTrackLen]; - - if (!UnpackTrack(&bitBuffer, trackBuf)) { - fprintf(stderr, "FAILED on track %d\n", track); - dierr = kDIErrBadCompressedData; - goto bail; - } - if (feof(infp) || ferror(infp)) { - fprintf(stderr, "Failure or EOF on input file\n"); - dierr = kDIErrBadCompressedData; - goto bail; - } - fwrite(trackBuf, 1, 4096, outfp); - } - - /* - * We should be within a byte or two of the end of the file. - */ - (void) getc(infp); - (void) getc(infp); - (void) getc(infp); - (void) getc(infp); - if (!feof(infp)) { - fprintf(stderr, "Looks like too much data in input file\n"); - dierr = kDIErrBadCompressedData; - goto bail; - } - - assert(dierr == kDIErrNone); -bail: - return dierr; -} - -/* - * Process every argument. - */ -int -main(int argc, char** argv) -{ -// const char* kLogFile = "iconv-log.txt"; - - if (argc != 3) { - fprintf(stderr, "%s: infile outfile\n", argv[0]); - exit(2); - } - - gLog = stdout; -// gLog = fopen(kLogFile, "w"); -// if (gLog == nil) { -// fprintf(stderr, "ERROR: unable to open log file\n"); -// exit(1); -// } - - printf("DDD Converter for Linux v1.0\n"); - printf("Copyright (C) 2003 by faddenSoft, LLC. All rights reserved.\n"); - int32_t major, minor, bug; - Global::GetVersion(&major, &minor, &bug); - printf("Linked against DiskImg library v%d.%d.%d\n", - major, minor, bug); -// printf("Log file is '%s'\n", kLogFile); - printf("\n"); - - Global::SetDebugMsgHandler(MsgHandler); - Global::AppInit(); - - NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler); - - int len = strlen(argv[2]); - if (len > 3 && strcasecmp(argv[2] + len - 3, ".do") == 0) { - Unpack(argv[1], argv[2]); - } else { - Pack(argv[1], argv[2]); - } - - Global::AppCleanup(); - fclose(gLog); - - exit(0); -} - +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Pack DDD. + +The trouble with unpacking DOS DDD 2.x files: + +The files are stored as binary files with no length. DDD v2.0 stored +a copy of the length in sectors in the filename (e.g. "<397>"). This +means that, when CiderPress goes to extract or view the file, it just +sees an empty binary file. + +CiderPress could make an exception and assume that any binary file with +zero length and more than one sector allocated has a length equal to the +number of sectors times 256. This could cause problems for other things, +but it's probably pretty safe. However, we still don't have an accurate +idea of where the end of the file is. + +Knowing where the file ends is important because there is no identifying +information or checksum in a DDD file. The only way to know that it's a +DDD compressed disk is to try to unpack it and see if you end up at exactly +140K at the same time that you run out of input. Without knowing where the +file really ends, this test is much less certain. + +The only safe way to make this work would be to skip the automatic format +detection and tell CiderPress that the file is definitely DDD format. +There's currently no easy way to do that without complicating the user +interface. Filename extensions might be useful, but they're rare under +DOS 3.3, and I don't think the "<397>" convention is common to all versions +of DDD. + +Complicating the matter is that, if a DOS DDD file (type 'B') is converted +to ProDOS, the first 4 bytes will be stripped off. Without unpacking +the file and knowing to within a byte where it ends, there's no way to +automatically tell whether to start at byte 0 or byte 4. (DDD Pro files +have four bytes of garbage at the very start, probably in an attempt to +retain compatibility with the DOS version. Because it uses REL files the +4 bytes of extra DOS stuff aren't added when the files are copied around, +so this was a reasonably smart thing to do, but it complicates matters +for CiderPress because a file extracted from DOS and a file extracted +from ProDOS will come out differently due to the 4 bytes of type 'B' +gunk getting stripped. This can be avoided if the DOS file uses the 'R' +or 'S' file type.) + +All this would have been much easier if the DOS files had a length word. + +To unpack a file created by DOS DDD v2.x: + - Copy the file to a ProDOS disk, using something that guesses at the + actual length when one isn't provided (Copy ][+ 9.0 may work). + - Reduce the length to within a byte or two of the actual end of file. + Removing all but the last couple of trailing zero bytes usually does + the trick. + - Insert 4 bytes of garbage at the front of the file. My copy of DDD + Pro 1.1 seems to like 03 c9 bf d0. +Probably not worth the effort. + */ +#include +#include +#include +#include +#include +#include "../diskimg/DiskImg.h" +#include "../nufxlib/NufxLib.h" + +using namespace DiskImgLib; + +#define nil NULL +#define NELEM(x) (sizeof(x) / sizeof((x)[0])) + +FILE* gLog = nil; +pid_t gPid = getpid(); + +const int kTrackLen = 4096; +const int kNumSymbols = 256; +const int kNumFavorites = 20; +const int kRLEDelim = 0x97; // value MUST have high bit set +const int kNumTracks = 35; + +/* I suspect this is random garbage, but it's consistent for me */ +const unsigned long kDDDProSignature = 0xd0bfc903; + + +/* + * Class for getting and putting bits to and from a file. + */ +class BitBuffer { +public: + BitBuffer(void) : fFp(nil), fBits(0), fBitCount(0) {} + ~BitBuffer(void) {} + + void SetFile(FILE* fp) { fFp = fp; } + void PutBits(unsigned char bits, int numBits); + //void FlushBits(void); + unsigned char GetBits(int numBits); + + static unsigned char Reverse(unsigned char val); + +private: + FILE* fFp; + unsigned char fBits; + int fBitCount; +}; + +/* + * Add bits to the buffer. + * + * We roll the low bits out of "bits" and shift them to the left (in the + * reverse order in which they were passed in). As soon as we get 8 bits + * we flush. + */ +void +BitBuffer::PutBits(unsigned char bits, int numBits) +{ + assert(fBitCount >= 0 && fBitCount < 8); + assert(numBits > 0 && numBits <= 8); + assert(fFp != nil); + + while (numBits--) { + fBits = (fBits << 1) | (bits & 0x01); + fBitCount++; + + if (fBitCount == 8) { + putc(fBits, fFp); + fBitCount = 0; + } + + bits >>= 1; + } +} + +/* + * Get bits from the buffer. + * + * These come out in the order in which they appear in the file, which + * means that in some cases they will have to be reversed. + */ +unsigned char +BitBuffer::GetBits(int numBits) +{ + assert(fBitCount >= 0 && fBitCount < 8); + assert(numBits > 0 && numBits <= 8); + assert(fFp != nil); + + unsigned char retVal; + + if (fBitCount == 0) { + /* have no bits */ + fBits = getc(fFp); + fBitCount = 8; + } + + if (numBits <= fBitCount) { + /* just serve up what we've already got */ + retVal = fBits >> (8 - numBits); + fBits <<= numBits; + fBitCount -= numBits; + } else { + /* some old, some new; load what we have right-aligned */ + retVal = fBits >> (8 - fBitCount); + numBits -= fBitCount; + + fBits = getc(fFp); + fBitCount = 8; + + /* make room for the rest (also zeroes out the low bits) */ + retVal <<= numBits; + + /* add the high bits from the new byte */ + retVal |= fBits >> (8 - numBits); + fBits <<= numBits; + fBitCount -= numBits; + } + + return retVal; +} + +/* + * Utility function to reverse the order of bits in a byte. + */ +/*static*/ unsigned char +BitBuffer::Reverse(unsigned char val) +{ + int i; + unsigned char result = 0; // init to make compiler happy + + for (i = 0; i < 8; i++) { + result = (result << 1) + (val & 0x01); + val >>= 1; + } + + return result; +} + +#if 0 +/* + * Flush any remaining bits out. Call this at the very end. + */ +void +BitBuffer::FlushBits(void) +{ + if (fBitCount) { + fBits <<= 8 - fBitCount; + putc(fBits, fFp); + + fBitCount = 0; + } +} +#endif + + +/* + * Compute the #of times each byte appears in trackBuf. Runs of four + * bytes or longer are completely ignored. + * + * "trackBuf" holds kTrackLen bytes of data, and "freqCounts" holds + * kNumSymbols (256) unsigned shorts. + */ +void +ComputeFreqCounts(const unsigned char* trackBuf, unsigned short* freqCounts) +{ + const unsigned char* ucp; + int i; + + memset(freqCounts, 0, 256 * sizeof(unsigned short)); + + ucp = trackBuf; + for (i = 0; i < kTrackLen; i++, ucp++) { + if (i < (kTrackLen-3) && + *ucp == *(ucp+1) && + *ucp == *(ucp+2) && + *ucp == *(ucp+3)) + { + int runLen = 4; // DEBUG only + i += 3; + ucp += 3; + + while (*ucp == *(ucp+1) && i < kTrackLen) { + runLen++; + ucp++; + i++; + + if (runLen == 256) { + runLen = 0; + break; + } + } + + //printf("Found run of %d of 0x%02x\n", runLen, *ucp); + } else { + /* not a run, just update stats */ + freqCounts[*ucp]++; + } + } +} + +/* + * Find the 20 most frequently occurring symbols, in order. + * + * Modifies "freqCounts". + */ +void +ComputeFavorites(unsigned short* freqCounts, unsigned char* favorites) +{ + int i, fav; + + for (fav = 0; fav < kNumFavorites; fav++) { + unsigned short bestCount = 0; + unsigned char bestSym = 0; + + for (i = 0; i < kNumSymbols; i++) { + if (freqCounts[i] >= bestCount) { + bestSym = (unsigned char) i; + bestCount = freqCounts[i]; + } + } + + favorites[fav] = bestSym; + freqCounts[bestSym] = 0; + } + + //printf("FAVORITES: "); + //for (fav = 0; fav < kNumFavorites; fav++) + // printf("%02x ", favorites[fav]); + //printf("\n"); +} + +/* + * These are all odd, which when they're written in reverse order means + * they all have their hi bits set. + */ +static const unsigned char kFavoriteBitEnc[kNumFavorites] = { + 0x03, 0x09, 0x1f, 0x0f, 0x07, 0x1b, 0x0b, 0x0d, 0x15, 0x37, + 0x3d, 0x25, 0x05, 0xb1, 0x11, 0x21, 0x01, 0x57, 0x5d, 0x1d +}; +static const int kFavoriteBitEncLen[kNumFavorites] = { + 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 6, 6, 6, 6, 7, 7, 7 +}; + + +/* + * Compress a track full of data. + */ +void +CompressTrack(const unsigned char* trackBuf, BitBuffer* pBitBuf) +{ + unsigned short freqCounts[kNumSymbols]; + unsigned char favorites[kNumFavorites]; + int i, fav; + + ComputeFreqCounts(trackBuf, freqCounts); + ComputeFavorites(freqCounts, favorites); + + /* write favorites */ + for (fav = 0; fav < kNumFavorites; fav++) + pBitBuf->PutBits(favorites[fav], 8); + + /* + * Compress track data. Store runs as { 0x97 char count }, where + * a count of zero means 256. + */ + const unsigned char* ucp = trackBuf; + for (i = 0; i < kTrackLen; i++, ucp++) { + if (i < (kTrackLen-3) && + *ucp == *(ucp+1) && + *ucp == *(ucp+2) && + *ucp == *(ucp+3)) + { + int runLen = 4; + i += 3; + ucp += 3; + + while (*ucp == *(ucp+1) && i < kTrackLen) { + runLen++; + ucp++; + i++; + + if (runLen == 256) { + runLen = 0; + break; + } + } + + pBitBuf->PutBits(kRLEDelim, 8); // note kRLEDelim has hi bit set + pBitBuf->PutBits(*ucp, 8); + pBitBuf->PutBits(runLen, 8); + + } else { + /* + * Not a run, see if it's one of our favorites. + */ + for (fav = 0; fav < kNumFavorites; fav++) { + if (*ucp == favorites[fav]) + break; + } + if (fav == kNumFavorites) { + /* just a plain byte */ + pBitBuf->PutBits(0x00, 1); + pBitBuf->PutBits(*ucp, 8); + } else { + /* found a favorite; leading hi bit is implied */ + pBitBuf->PutBits(kFavoriteBitEnc[fav], kFavoriteBitEncLen[fav]); + } + } + } +} + + + +/* + * Handle a debug message from the DiskImg library. + */ +/*static*/ void +MsgHandler(const char* file, int line, const char* msg) +{ + assert(file != nil); + assert(msg != nil); + + fprintf(gLog, "%05u %s", gPid, msg); +} +/* + * Handle a global error message from the NufxLib library by shoving it + * through the DiskImgLib message function. + */ +NuResult +NufxErrorMsgHandler(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; +} + +/* + * Pack a disk image with DDD. + */ +DIError +Pack(const char* infile, const char* outfile) +{ + DIError dierr = kDIErrNone; + DiskImg srcImg; + FILE* outfp = nil; + BitBuffer bitBuffer; + + printf("Packing in='%s' out='%s'\n", infile, outfile); + + /* + * Prepare the source image. + */ + dierr = srcImg.OpenImage(infile, '/', true); + if (dierr != kDIErrNone) { + fprintf(stderr, "Unable to open disk image: %s.\n", + DiskImgLib::DIStrError(dierr)); + goto bail; + } + + dierr = srcImg.AnalyzeImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "Unable to determine source image format.\n"); + goto bail; + } + + if (!srcImg.GetHasSectors()) { + fprintf(stderr, "Sorry, only sector-addressable images allowed.\n"); + dierr = kDIErrUnsupportedPhysicalFmt; + goto bail; + } + assert(srcImg.GetNumSectPerTrack() > 0); + + if (srcImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { + fprintf(stderr, "(QUERY) don't know sector order\n"); + dierr = kDIErrFilesystemNotFound; + goto bail; + } + + /* force the access to be DOS-ordered */ + dierr = srcImg.OverrideFormat(srcImg.GetPhysicalFormat(), + DiskImg::kFormatGenericDOSOrd, srcImg.GetSectorOrder()); + if (dierr != kDIErrNone) { + fprintf(stderr, "Couldn't switch to generic ProDOS: %s.\n", + DiskImgLib::DIStrError(dierr)); + goto bail; + } + + /* transfer the DOS volume num, if one was set */ + printf("DOS volume number set to %d\n", srcImg.GetDOSVolumeNum()); + if (srcImg.GetDOSVolumeNum() == DiskImg::kVolumeNumNotSet) + srcImg.SetDOSVolumeNum(kDefaultNibbleVolumeNum); + + /* + * Open the output file. + */ + outfp = fopen(outfile, "w"); + if (outfp == nil) { + perror("unable to open output file"); + dierr = kDIErrGeneric; + goto bail; + } + + /* write four zeroes to replace the DOS addr/len bytes */ + /* (let's write the apparent DDD Pro v1.1 signature instead) */ + putc(kDDDProSignature, outfp); + putc(kDDDProSignature >> 8, outfp); + putc(kDDDProSignature >> 16, outfp); + putc(kDDDProSignature >> 24, outfp); + + bitBuffer.SetFile(outfp); + + bitBuffer.PutBits(0x00, 3); + bitBuffer.PutBits(srcImg.GetDOSVolumeNum(), 8); + + /* + * Process all tracks. + */ + for (int track = 0; track < srcImg.GetNumTracks(); track++) { + unsigned char trackBuf[kTrackLen]; + + /* + * Read the track. + */ + for (int sector = 0; sector < srcImg.GetNumSectPerTrack(); sector++) { + dierr = srcImg.ReadTrackSector(track, sector, + trackBuf + sector * 256); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: ReadBlock failed (err=%d)\n", dierr); + goto bail; + } + } + + //printf("Got track %d (0x%02x %02x %02x %02x %02x %02x ...)\n", + // track, trackBuf[0], trackBuf[1], trackBuf[2], trackBuf[3], + // trackBuf[4], trackBuf[5]); + CompressTrack(trackBuf, &bitBuffer); + } + + /* write 8 bits of zeroes to flush remaining data out of buffer */ + bitBuffer.PutBits(0x00, 8); + + /* write another zero byte because that's what DDD Pro v1.1 does */ + long zero; + zero = 0; + fwrite(&zero, 1, 1, outfp); + + dierr = srcImg.CloseImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: srcImg close failed?!\n"); + goto bail; + } + + assert(dierr == kDIErrNone); +bail: + return dierr; +} + +/* + * This is the reverse of the kFavoriteBitEnc table. The bits are + * reversed and lack the high bit. + */ +static const unsigned char kFavoriteBitDec[kNumFavorites] = { + 0x04, 0x01, 0x0f, 0x0e, 0x0c, 0x0b, 0x0a, 0x06, 0x05, 0x1b, + 0x0f, 0x09, 0x08, 0x03, 0x02, 0x01, 0x00, 0x35, 0x1d, 0x1c +}; + +/* + * Unpack a single track. + * + * Returns "true" if all went well, "false" if something failed. + */ +bool +UnpackTrack(BitBuffer* pBitBuffer, unsigned char* trackBuf) +{ + unsigned char favorites[kNumFavorites]; + unsigned char val; + unsigned char* trackPtr; + int fav; + + /* + * Start by pulling our favorites out, in reverse order. + */ + for (fav = 0; fav < kNumFavorites; fav++) { + val = pBitBuffer->GetBits(8); + val = pBitBuffer->Reverse(val); + favorites[fav] = val; + } + + trackPtr = trackBuf; + + /* + * Keep pulling data out until the track is full. + */ + while (trackPtr < trackBuf + kTrackLen) { + val = pBitBuffer->GetBits(1); + if (!val) { + /* simple byte */ + val = pBitBuffer->GetBits(8); + val = pBitBuffer->Reverse(val); + *trackPtr++ = val; + } else { + /* try for a prefix match */ + int extraBits; + + val = pBitBuffer->GetBits(2); + + for (extraBits = 0; extraBits < 4; extraBits++) { + val = (val << 1) | pBitBuffer->GetBits(1); + int start, end; + + if (extraBits == 0) { + start = 0; + end = 2; + } else if (extraBits == 1) { + start = 2; + end = 9; + } else if (extraBits == 2) { + start = 9; + end = 17; + } else { + start = 17; + end = 20; + } + + while (start < end) { + if (val == kFavoriteBitDec[start]) { + /* winner! */ + *trackPtr++ = favorites[start]; + break; + } + start++; + } + if (start != end) + break; // we got it, break out of for loop + } + if (extraBits == 4) { + /* we didn't get it, this must be RLE */ + unsigned char rleChar; + int rleCount; + + (void) pBitBuffer->GetBits(1); // get last bit of 0x97 + val = pBitBuffer->GetBits(8); + rleChar = pBitBuffer->Reverse(val); + val = pBitBuffer->GetBits(8); + rleCount = pBitBuffer->Reverse(val); + printf("Found run of %d of 0x%02x\n", rleCount, rleChar); + + if (rleCount == 0) + rleCount = 256; + + /* make sure we won't overrun */ + if (trackPtr + rleCount > trackBuf + kTrackLen) { + printf("Overrun in RLE\n"); + return false; + } + while (rleCount--) + *trackPtr++ = rleChar; + } + } + } + + return true; +} + +/* + * Unpack a disk image compressed with DDD. + * + * The result is an unadorned DOS-ordered image. + */ +DIError +Unpack(const char* infile, const char* outfile) +{ + DIError dierr = kDIErrNone; + FILE* infp = nil; + FILE* outfp = nil; + BitBuffer bitBuffer; + unsigned char val; + + printf("Unpacking in='%s' out='%s'\n", infile, outfile); + + /* + * Open the input file. + */ + infp = fopen(infile, "r"); + if (infp == nil) { + perror("unable to open input file"); + dierr = kDIErrGeneric; + goto bail; + } + + /* + * Open the output file. + */ + outfp = fopen(outfile, "w"); + if (outfp == nil) { + perror("unable to open output file"); + dierr = kDIErrGeneric; + goto bail; + } + + /* read four zeroes to skip the DOS addr/len bytes */ + (void) getc(infp); + (void) getc(infp); + (void) getc(infp); + (void) getc(infp); + + bitBuffer.SetFile(infp); + + val = bitBuffer.GetBits(3); + if (val != 0) { + printf("HEY: this isn't a DDD II file (%d)\n", val); + dierr = kDIErrGeneric; + goto bail; + } + val = bitBuffer.GetBits(8); + val = bitBuffer.Reverse(val); + printf("GOT disk volume num = %d\n", val); + + for (int track = 0; track < kNumTracks; track++) { + unsigned char trackBuf[kTrackLen]; + + if (!UnpackTrack(&bitBuffer, trackBuf)) { + fprintf(stderr, "FAILED on track %d\n", track); + dierr = kDIErrBadCompressedData; + goto bail; + } + if (feof(infp) || ferror(infp)) { + fprintf(stderr, "Failure or EOF on input file\n"); + dierr = kDIErrBadCompressedData; + goto bail; + } + fwrite(trackBuf, 1, 4096, outfp); + } + + /* + * We should be within a byte or two of the end of the file. + */ + (void) getc(infp); + (void) getc(infp); + (void) getc(infp); + (void) getc(infp); + if (!feof(infp)) { + fprintf(stderr, "Looks like too much data in input file\n"); + dierr = kDIErrBadCompressedData; + goto bail; + } + + assert(dierr == kDIErrNone); +bail: + return dierr; +} + +/* + * Process every argument. + */ +int +main(int argc, char** argv) +{ +// const char* kLogFile = "iconv-log.txt"; + + if (argc != 3) { + fprintf(stderr, "%s: infile outfile\n", argv[0]); + exit(2); + } + + gLog = stdout; +// gLog = fopen(kLogFile, "w"); +// if (gLog == nil) { +// fprintf(stderr, "ERROR: unable to open log file\n"); +// exit(1); +// } + + printf("DDD Converter for Linux v1.0\n"); + printf("Copyright (C) 2003 by faddenSoft, LLC. All rights reserved.\n"); + int32_t major, minor, bug; + Global::GetVersion(&major, &minor, &bug); + printf("Linked against DiskImg library v%d.%d.%d\n", + major, minor, bug); +// printf("Log file is '%s'\n", kLogFile); + printf("\n"); + + Global::SetDebugMsgHandler(MsgHandler); + Global::AppInit(); + + NuSetGlobalErrorMessageHandler(NufxErrorMsgHandler); + + int len = strlen(argv[2]); + if (len > 3 && strcasecmp(argv[2] + len - 3, ".do") == 0) { + Unpack(argv[1], argv[2]); + } else { + Pack(argv[1], argv[2]); + } + + Global::AppCleanup(); + fclose(gLog); + + exit(0); +} + diff --git a/linux/SSTAsm.cpp b/linux/SSTAsm.cpp index 4c46de2..1bed6d9 100644 --- a/linux/SSTAsm.cpp +++ b/linux/SSTAsm.cpp @@ -1,325 +1,325 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * Reassemble SST disk images into a .NIB file. - */ -#include -#include -#include -#include -#include "../diskimg/DiskImg.h" - -using namespace DiskImgLib; - -#define nil NULL - - -#if 0 -inline int -ConvOddEven(unsigned char val1, unsigned char val2) -{ - return ((val1 & 0x55) << 1) | (val2 & 0x55); -} -#endif - - -const int kSSTNumTracks = 35; -const int kSSTTrackLen = 6656; // or 6384 for .NB2 - -/* - * Compute the destination file offset for a particular source track. The - * track number ranges from 0 to 69 inclusive. Sectors from two adjacent - * "cooked" tracks are combined into a single "raw nibbilized" track. - * - * The data is ordered like this: - * track 1 sector 15 --> track 1 sector 4 (12 sectors) - * track 0 sector 13 --> track 0 sector 0 (14 sectors) - * - * Total of 26 sectors, or $1a00 bytes. - */ -long -GetBufOffset(int track) -{ - assert(track >= 0 && track < kSSTNumTracks*2); - - long offset; - - if (track & 0x01) { - /* odd, use start of data */ - offset = (track / 2) * kSSTTrackLen; - } else { - /* even, start of data plus 12 sectors */ - offset = (track / 2) * kSSTTrackLen + 12 * 256; - } - - assert(offset >= 0 && offset < kSSTTrackLen * kSSTNumTracks); - - return offset; -} - -/* - * Copy 17.5 tracks of data from the SST image to a .NIB image. - * - * Data is stored in all 16 sectors of track 0, followed by the first - * 12 sectors of track 1, then on to track 2. Total of $1a00 bytes. - */ -int -LoadSSTData(DiskImg* pDiskImg, int seqNum, unsigned char* trackBuf) -{ - DIError dierr; - char sctBuf[256]; - int track, sector; - long bufOffset; - - for (track = 0; track < kSSTNumTracks; track++) { - int virtualTrack = track + (seqNum * kSSTNumTracks); - bufOffset = GetBufOffset(virtualTrack); - //fprintf(stderr, "USING offset=%ld (track=%d / %d)\n", - // bufOffset, track, virtualTrack); - - if (virtualTrack & 0x01) { - /* odd-numbered track, sectors 15-4 */ - for (sector = 15; sector >= 4; sector--) { - dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: on track=%d sector=%d\n", - track, sector); - return -1; - } - - memcpy(trackBuf + bufOffset, sctBuf, 256); - bufOffset += 256; - } - } else { - for (sector = 13; sector >= 0; sector--) { - dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: on track=%d sector=%d\n", - track, sector); - return -1; - } - - memcpy(trackBuf + bufOffset, sctBuf, 256); - bufOffset += 256; - } - } - } - -#if 0 - int i; - for (i = 0; (size_t) i < sizeof(trackBuf)-10; i++) { - if ((trackBuf[i] | 0x80) == 0xd5 && - (trackBuf[i+1] | 0x80) == 0xaa && - (trackBuf[i+2] | 0x80) == 0x96) - { - fprintf(stderr, "off=%5d vol=%d trk=%d sct=%d chk=%d\n", i, - ConvOddEven(trackBuf[i+3], trackBuf[i+4]), - ConvOddEven(trackBuf[i+5], trackBuf[i+6]), - ConvOddEven(trackBuf[i+7], trackBuf[i+8]), - ConvOddEven(trackBuf[i+9], trackBuf[i+10])); - i += 10; - if ((size_t)i < sizeof(trackBuf)-3) { - fprintf(stderr, " 0x%02x 0x%02x 0x%02x\n", - trackBuf[i+1], trackBuf[i+2], trackBuf[i+3]); - } - } - } -#endif - - return 0; -} - -/* - * Copy sectors from a single image. - */ -int -HandleSSTImage(const char* fileName, int seqNum, unsigned char* trackBuf) -{ - DIError dierr; - DiskImg diskImg; - int result = -1; - - fprintf(stderr, "Handling '%s'\n", fileName); - - dierr = diskImg.OpenImage(fileName, '/', true); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: unable to open '%s'\n", fileName); - goto bail; - } - - dierr = diskImg.AnalyzeImage(); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: image analysis failed\n"); - goto bail; - } - - if (diskImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { - fprintf(stderr, "ERROR: sector order not set\n"); - goto bail; - } - if (diskImg.GetFSFormat() != DiskImg::kFormatUnknown) { - fprintf(stderr, "WARNING: file format *was* recognized!\n"); - goto bail; - } - if (diskImg.GetNumTracks() != kSSTNumTracks || - diskImg.GetNumSectPerTrack() != 16) - { - fprintf(stderr, "ERROR: only 140K floppies can be SST inputs\n"); - goto bail; - } - - dierr = diskImg.OverrideFormat(diskImg.GetPhysicalFormat(), - DiskImg::kFormatGenericDOSOrd, diskImg.GetSectorOrder()); - if (dierr != kDIErrNone) { - fprintf(stderr, "ERROR: format override failed\n"); - goto bail; - } - - /* - * We have the image open successfully, now do something with it. - */ - result = LoadSSTData(&diskImg, seqNum, trackBuf); - -bail: - return result; -} - -/* - * Run through the data, adding 0x80 everywhere and re-aligning the - * tracks so that the big clump of sync bytes is at the end. - */ -int -ProcessTrackData(unsigned char* trackBuf) -{ - unsigned char* trackPtr; - int track; - - for (track = 0, trackPtr = trackBuf; track < kSSTNumTracks; - track++, trackPtr += kSSTTrackLen) - { - bool inRun; - int start = 0; - int longestStart = -1; - int count7f = 0; - int longest = -1; - int i; - - inRun = false; - for (i = 0; i < kSSTTrackLen; i++) { - if (trackPtr[i] == 0x7f) { - if (inRun) { - count7f++; - } else { - count7f = 1; - start = i; - inRun = true; - } - } else { - if (inRun) { - if (count7f > longest) { - longest = count7f; - longestStart = start; - } - inRun = false; - } else { - /* do nothing */ - } - } - - trackPtr[i] |= 0x80; - } - - - if (longest == -1) { - fprintf(stderr, "HEY: couldn't find any 0x7f in track %d\n", - track); - } else { - fprintf(stderr, "Found run of %d at %d in track %d\n", - longest, longestStart, track); - - int bkpt = longestStart + longest; - assert(bkpt < kSSTTrackLen); - - char oneTrack[kSSTTrackLen]; - memcpy(oneTrack, trackPtr, kSSTTrackLen); - - /* copy it back so sync bytes are at end of track */ - memcpy(trackPtr, oneTrack + bkpt, kSSTTrackLen - bkpt); - memcpy(trackPtr + (kSSTTrackLen - bkpt), oneTrack, bkpt); - } - } - - return 0; -} - -/* - * Read sectors from file1 and file2, and write them in the correct - * sequence to outfp. - */ -int -ReassembleSST(const char* file1, const char* file2, FILE* outfp) -{ - unsigned char* trackBuf = nil; - int result; - - trackBuf = new unsigned char[kSSTNumTracks * kSSTTrackLen]; - if (trackBuf == nil) { - fprintf(stderr, "ERROR: malloc failed\n"); - return -1; - } - - result = HandleSSTImage(file1, 0, trackBuf); - if (result != 0) - return result; - - result = HandleSSTImage(file2, 1, trackBuf); - if (result != 0) - return result; - - result = ProcessTrackData(trackBuf); - - fprintf(stderr, "Writing %d bytes\n", kSSTNumTracks * kSSTTrackLen); - fwrite(trackBuf, 1, kSSTNumTracks * kSSTTrackLen, outfp); - - delete[] trackBuf; - return result; -} - - -/* - * Handle a debug message from the DiskImg library. - */ -/*static*/ void -MsgHandler(const char* file, int line, const char* msg) -{ - assert(file != nil); - assert(msg != nil); - - fprintf(stderr, "%s", msg); -} - -/* - * Parse args, go. - */ -int -main(int argc, char** argv) -{ - int result; - - if (argc != 3) { - fprintf(stderr, "Usage: %s file1 file2 > outfile\n", argv[0]); - exit(2); - } - - Global::SetDebugMsgHandler(MsgHandler); - Global::AppInit(); - - result = ReassembleSST(argv[1], argv[2], stdout); - - Global::AppCleanup(); - exit(result != 0); -} - +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * Reassemble SST disk images into a .NIB file. + */ +#include +#include +#include +#include +#include "../diskimg/DiskImg.h" + +using namespace DiskImgLib; + +#define nil NULL + + +#if 0 +inline int +ConvOddEven(unsigned char val1, unsigned char val2) +{ + return ((val1 & 0x55) << 1) | (val2 & 0x55); +} +#endif + + +const int kSSTNumTracks = 35; +const int kSSTTrackLen = 6656; // or 6384 for .NB2 + +/* + * Compute the destination file offset for a particular source track. The + * track number ranges from 0 to 69 inclusive. Sectors from two adjacent + * "cooked" tracks are combined into a single "raw nibbilized" track. + * + * The data is ordered like this: + * track 1 sector 15 --> track 1 sector 4 (12 sectors) + * track 0 sector 13 --> track 0 sector 0 (14 sectors) + * + * Total of 26 sectors, or $1a00 bytes. + */ +long +GetBufOffset(int track) +{ + assert(track >= 0 && track < kSSTNumTracks*2); + + long offset; + + if (track & 0x01) { + /* odd, use start of data */ + offset = (track / 2) * kSSTTrackLen; + } else { + /* even, start of data plus 12 sectors */ + offset = (track / 2) * kSSTTrackLen + 12 * 256; + } + + assert(offset >= 0 && offset < kSSTTrackLen * kSSTNumTracks); + + return offset; +} + +/* + * Copy 17.5 tracks of data from the SST image to a .NIB image. + * + * Data is stored in all 16 sectors of track 0, followed by the first + * 12 sectors of track 1, then on to track 2. Total of $1a00 bytes. + */ +int +LoadSSTData(DiskImg* pDiskImg, int seqNum, unsigned char* trackBuf) +{ + DIError dierr; + char sctBuf[256]; + int track, sector; + long bufOffset; + + for (track = 0; track < kSSTNumTracks; track++) { + int virtualTrack = track + (seqNum * kSSTNumTracks); + bufOffset = GetBufOffset(virtualTrack); + //fprintf(stderr, "USING offset=%ld (track=%d / %d)\n", + // bufOffset, track, virtualTrack); + + if (virtualTrack & 0x01) { + /* odd-numbered track, sectors 15-4 */ + for (sector = 15; sector >= 4; sector--) { + dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: on track=%d sector=%d\n", + track, sector); + return -1; + } + + memcpy(trackBuf + bufOffset, sctBuf, 256); + bufOffset += 256; + } + } else { + for (sector = 13; sector >= 0; sector--) { + dierr = pDiskImg->ReadTrackSector(track, sector, sctBuf); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: on track=%d sector=%d\n", + track, sector); + return -1; + } + + memcpy(trackBuf + bufOffset, sctBuf, 256); + bufOffset += 256; + } + } + } + +#if 0 + int i; + for (i = 0; (size_t) i < sizeof(trackBuf)-10; i++) { + if ((trackBuf[i] | 0x80) == 0xd5 && + (trackBuf[i+1] | 0x80) == 0xaa && + (trackBuf[i+2] | 0x80) == 0x96) + { + fprintf(stderr, "off=%5d vol=%d trk=%d sct=%d chk=%d\n", i, + ConvOddEven(trackBuf[i+3], trackBuf[i+4]), + ConvOddEven(trackBuf[i+5], trackBuf[i+6]), + ConvOddEven(trackBuf[i+7], trackBuf[i+8]), + ConvOddEven(trackBuf[i+9], trackBuf[i+10])); + i += 10; + if ((size_t)i < sizeof(trackBuf)-3) { + fprintf(stderr, " 0x%02x 0x%02x 0x%02x\n", + trackBuf[i+1], trackBuf[i+2], trackBuf[i+3]); + } + } + } +#endif + + return 0; +} + +/* + * Copy sectors from a single image. + */ +int +HandleSSTImage(const char* fileName, int seqNum, unsigned char* trackBuf) +{ + DIError dierr; + DiskImg diskImg; + int result = -1; + + fprintf(stderr, "Handling '%s'\n", fileName); + + dierr = diskImg.OpenImage(fileName, '/', true); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: unable to open '%s'\n", fileName); + goto bail; + } + + dierr = diskImg.AnalyzeImage(); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: image analysis failed\n"); + goto bail; + } + + if (diskImg.GetSectorOrder() == DiskImg::kSectorOrderUnknown) { + fprintf(stderr, "ERROR: sector order not set\n"); + goto bail; + } + if (diskImg.GetFSFormat() != DiskImg::kFormatUnknown) { + fprintf(stderr, "WARNING: file format *was* recognized!\n"); + goto bail; + } + if (diskImg.GetNumTracks() != kSSTNumTracks || + diskImg.GetNumSectPerTrack() != 16) + { + fprintf(stderr, "ERROR: only 140K floppies can be SST inputs\n"); + goto bail; + } + + dierr = diskImg.OverrideFormat(diskImg.GetPhysicalFormat(), + DiskImg::kFormatGenericDOSOrd, diskImg.GetSectorOrder()); + if (dierr != kDIErrNone) { + fprintf(stderr, "ERROR: format override failed\n"); + goto bail; + } + + /* + * We have the image open successfully, now do something with it. + */ + result = LoadSSTData(&diskImg, seqNum, trackBuf); + +bail: + return result; +} + +/* + * Run through the data, adding 0x80 everywhere and re-aligning the + * tracks so that the big clump of sync bytes is at the end. + */ +int +ProcessTrackData(unsigned char* trackBuf) +{ + unsigned char* trackPtr; + int track; + + for (track = 0, trackPtr = trackBuf; track < kSSTNumTracks; + track++, trackPtr += kSSTTrackLen) + { + bool inRun; + int start = 0; + int longestStart = -1; + int count7f = 0; + int longest = -1; + int i; + + inRun = false; + for (i = 0; i < kSSTTrackLen; i++) { + if (trackPtr[i] == 0x7f) { + if (inRun) { + count7f++; + } else { + count7f = 1; + start = i; + inRun = true; + } + } else { + if (inRun) { + if (count7f > longest) { + longest = count7f; + longestStart = start; + } + inRun = false; + } else { + /* do nothing */ + } + } + + trackPtr[i] |= 0x80; + } + + + if (longest == -1) { + fprintf(stderr, "HEY: couldn't find any 0x7f in track %d\n", + track); + } else { + fprintf(stderr, "Found run of %d at %d in track %d\n", + longest, longestStart, track); + + int bkpt = longestStart + longest; + assert(bkpt < kSSTTrackLen); + + char oneTrack[kSSTTrackLen]; + memcpy(oneTrack, trackPtr, kSSTTrackLen); + + /* copy it back so sync bytes are at end of track */ + memcpy(trackPtr, oneTrack + bkpt, kSSTTrackLen - bkpt); + memcpy(trackPtr + (kSSTTrackLen - bkpt), oneTrack, bkpt); + } + } + + return 0; +} + +/* + * Read sectors from file1 and file2, and write them in the correct + * sequence to outfp. + */ +int +ReassembleSST(const char* file1, const char* file2, FILE* outfp) +{ + unsigned char* trackBuf = nil; + int result; + + trackBuf = new unsigned char[kSSTNumTracks * kSSTTrackLen]; + if (trackBuf == nil) { + fprintf(stderr, "ERROR: malloc failed\n"); + return -1; + } + + result = HandleSSTImage(file1, 0, trackBuf); + if (result != 0) + return result; + + result = HandleSSTImage(file2, 1, trackBuf); + if (result != 0) + return result; + + result = ProcessTrackData(trackBuf); + + fprintf(stderr, "Writing %d bytes\n", kSSTNumTracks * kSSTTrackLen); + fwrite(trackBuf, 1, kSSTNumTracks * kSSTTrackLen, outfp); + + delete[] trackBuf; + return result; +} + + +/* + * Handle a debug message from the DiskImg library. + */ +/*static*/ void +MsgHandler(const char* file, int line, const char* msg) +{ + assert(file != nil); + assert(msg != nil); + + fprintf(stderr, "%s", msg); +} + +/* + * Parse args, go. + */ +int +main(int argc, char** argv) +{ + int result; + + if (argc != 3) { + fprintf(stderr, "Usage: %s file1 file2 > outfile\n", argv[0]); + exit(2); + } + + Global::SetDebugMsgHandler(MsgHandler); + Global::AppInit(); + + result = ReassembleSST(argv[1], argv[2], stdout); + + Global::AppCleanup(); + exit(result != 0); +} + diff --git a/linux/StringArray.h b/linux/StringArray.h index 33feaf3..fdbb477 100644 --- a/linux/StringArray.h +++ b/linux/StringArray.h @@ -1,101 +1,101 @@ -/* - * CiderPress - * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. - * See the file LICENSE for distribution terms. - */ -/* - * An expandable array of strings. - */ -#ifndef __STRING_ARRAY__ -#define __STRING_ARRAY__ - -#include -#include -#include - -// -// This is a simple container for an array of strings. You can add strings -// to the list and sort them. -// -class StringArray { -public: - StringArray() - : mMax(0), mCurrent(0), mArray(NULL) - {} - virtual ~StringArray() - { - for (int i = 0; i < mCurrent; i++) - delete[] mArray[i]; - delete[] mArray; - } - - // - // Add a string. A copy of the string is made. - // - bool Add(const char* str) - { - if (mCurrent >= mMax) { - char** tmp; - - if (mMax == 0) - mMax = 16; - else - mMax *= 2; - - tmp = new char*[mMax]; - if (tmp == NULL) - return false; - - memcpy(tmp, mArray, mCurrent * sizeof(char*)); - delete[] mArray; - mArray = tmp; - } - - int len = strlen(str); - mArray[mCurrent] = new char[len+1]; - memcpy(mArray[mCurrent], str, len+1); - mCurrent++; - - return true; - } - - // - // Sort the array. Supply a sort function that takes two strings - // and returns <0, 0, or >0 if the first argument is less than, - // equal to, or greater than the second argument. (strcmp works.) - // - void Sort(int (*compare)(const void*, const void*)) - { - qsort(mArray, mCurrent, sizeof(char*), compare); - } - - // - // Use this as an argument to the sort routine. - // - static int CmpAscendingAlpha(const void* pstr1, const void* pstr2) - { - return strcmp(*(const char**)pstr1, *(const char**)pstr2); - } - - // - // Get the #of items in the array. - // - inline int GetCount(void) const { return mCurrent; } - - // - // Get entry N. - // - const char* GetEntry(int idx) const - { - if (idx < 0 || idx >= mCurrent) - return NULL; - return mArray[idx]; - } - -private: - int mMax; - int mCurrent; - char** mArray; -}; - -#endif /*__STRING_ARRAY__*/ +/* + * CiderPress + * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. + * See the file LICENSE for distribution terms. + */ +/* + * An expandable array of strings. + */ +#ifndef __STRING_ARRAY__ +#define __STRING_ARRAY__ + +#include +#include +#include + +// +// This is a simple container for an array of strings. You can add strings +// to the list and sort them. +// +class StringArray { +public: + StringArray() + : mMax(0), mCurrent(0), mArray(NULL) + {} + virtual ~StringArray() + { + for (int i = 0; i < mCurrent; i++) + delete[] mArray[i]; + delete[] mArray; + } + + // + // Add a string. A copy of the string is made. + // + bool Add(const char* str) + { + if (mCurrent >= mMax) { + char** tmp; + + if (mMax == 0) + mMax = 16; + else + mMax *= 2; + + tmp = new char*[mMax]; + if (tmp == NULL) + return false; + + memcpy(tmp, mArray, mCurrent * sizeof(char*)); + delete[] mArray; + mArray = tmp; + } + + int len = strlen(str); + mArray[mCurrent] = new char[len+1]; + memcpy(mArray[mCurrent], str, len+1); + mCurrent++; + + return true; + } + + // + // Sort the array. Supply a sort function that takes two strings + // and returns <0, 0, or >0 if the first argument is less than, + // equal to, or greater than the second argument. (strcmp works.) + // + void Sort(int (*compare)(const void*, const void*)) + { + qsort(mArray, mCurrent, sizeof(char*), compare); + } + + // + // Use this as an argument to the sort routine. + // + static int CmpAscendingAlpha(const void* pstr1, const void* pstr2) + { + return strcmp(*(const char**)pstr1, *(const char**)pstr2); + } + + // + // Get the #of items in the array. + // + inline int GetCount(void) const { return mCurrent; } + + // + // Get entry N. + // + const char* GetEntry(int idx) const + { + if (idx < 0 || idx >= mCurrent) + return NULL; + return mArray[idx]; + } + +private: + int mMax; + int mCurrent; + char** mArray; +}; + +#endif /*__STRING_ARRAY__*/