Add AppleSingle support

This handles version 1 and 2, and copes with the broken files
created by the Mac OS X "applesingle" command-line tool (which is
unable to decode the broken files it creates).

I get the sense that many AppleSingle files don't end with ".AS", so
the filespec includes "*.*" as well.

Some AppleSingle files don't include a filename.  In that case, we
use the file's name as the entry name, minus any ".as" extension.
The current implementation doesn't convert from Unicode to Mac OS
Roman, so non-ASCII characters are mishandled unless the file was
generated by GS/ShrinkIt.  (We assume version 1 AppleSingle files
use MOR name strings.)

Also, version bump to 4.0.0d3.
This commit is contained in:
Andy McFadden 2015-01-11 23:02:35 -08:00
parent 84ba460957
commit 5946481b4e
15 changed files with 1251 additions and 32 deletions

View File

@ -4,10 +4,10 @@ faddenSoft
http://www.faddensoft.com/
CiderPress
http://a2ciderpress.com/
4.0.0d2
41989
4.0.0d3
42015
C:\DATA\faddenSoft\fs.ico
Copyright © 2014 CiderPress project authors. All rights reserved.
Copyright © 2015 CiderPress project authors. All rights reserved.
C:\Src\CiderPress\DIST\ReadMe.txt
C:\Src\CiderPress\DIST\License.txt
@ -355,7 +355,7 @@ FALSE
4095
Setup400d2.exe
Setup400d3.exe
FALSE

View File

@ -81,7 +81,7 @@ public:
/*
* Perform one-time initialization. There really isn't any for us.
*
* Returns 0 on success, nonzero on error.
* Returns an error string on failure.
*/
static CString AppInit(void);
@ -96,17 +96,17 @@ public:
/*
* Finish instantiating an AcuArchive object by creating a new archive.
*
* Returns an error string on failure, or "" on success.
* This isn't implemented, and will always return an error.
*/
virtual CString New(const WCHAR* filename, const void* options) override;
virtual CString Flush(void) override { return ""; }
virtual CString Flush(void) override { return L""; }
virtual CString Reload(void) override;
virtual bool IsReadOnly(void) const override { return true; };
virtual bool IsModified(void) const override { return false; }
virtual void GetDescription(CString* pStr) const override
{ *pStr = "AppleLink ACU"; }
{ *pStr = L"AppleLink ACU"; }
virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) override
{ ASSERT(false); return false; }
@ -126,10 +126,10 @@ public:
{ ASSERT(false); return false; }
virtual CString TestVolumeName(const DiskFS* pDiskFS,
const WCHAR* newName) const override
{ ASSERT(false); return "!"; }
{ ASSERT(false); return L"!"; }
virtual CString TestPathName(const GenericEntry* pGenericEntry,
const CString& basePath, const CString& newName, char newFssep) const override
{ ASSERT(false); return "!"; }
{ ASSERT(false); return L"!"; }
virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
const RecompressOptionsDialog* pRecompOpts) override
{ ASSERT(false); return false; }
@ -165,7 +165,7 @@ private:
{ ASSERT(false); }
virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf,
long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override
{ ASSERT(false); return "!"; }
{ ASSERT(false); return L"!"; }
virtual void XferAbort(CWnd* pMsgWnd) override
{ ASSERT(false); }
virtual void XferFinish(CWnd* pMsgWnd) override
@ -237,8 +237,8 @@ private:
/*
* Load the contents of the archive.
*
* Returns 0 on success, < 0 if this is not an ACU archive > 0 if this appears
* to be an ACU archive but it's damaged.
* Returns 0 on success, < 0 if this is not an ACU archive, or > 0 if
* this appears to be an ACU archive but it's damaged.
*/
int LoadContents(void);

735
app/AppleSingleArchive.cpp Normal file
View File

@ -0,0 +1,735 @@
/*
* CiderPress
* Copyright (C) 2015 by faddenSoft. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
#include "stdafx.h"
#include "AppleSingleArchive.h"
#include "NufxArchive.h" // using date/time function
#include "Preferences.h"
#include "Main.h"
#include <errno.h>
/*
* ===========================================================================
* AppleSingleEntry
* ===========================================================================
*/
int AppleSingleEntry::ExtractThreadToBuffer(int which, char** ppText,
long* pLength, CString* pErrMsg) const
{
ExpandBuffer expBuf;
char* dataBuf = NULL;
bool needAlloc = true;
int result = -1;
ASSERT(fpArchive != NULL);
ASSERT(fpArchive->fFp != NULL);
if (*ppText != NULL)
needAlloc = false;
long offset, length;
if (which == kDataThread && fDataOffset >= 0) {
offset = fDataOffset;
length = (long) GetDataForkLen();
} else if (which == kRsrcThread && fRsrcOffset >= 0) {
offset = fRsrcOffset;
length = (long) GetRsrcForkLen();
} else {
*pErrMsg = "No such fork";
goto bail;
}
SET_PROGRESS_BEGIN();
errno = 0;
if (fseek(fpArchive->fFp, offset, SEEK_SET) < 0) {
pErrMsg->Format(L"Unable to seek to offset %ld: %hs",
fDataOffset, strerror(errno));
goto bail;
}
if (needAlloc) {
dataBuf = new char[length];
if (dataBuf == NULL) {
pErrMsg->Format(L"allocation of %ld bytes failed", length);
goto bail;
}
} else {
if (*pLength < length) {
pErrMsg->Format(L"buf size %ld too short (%ld)",
*pLength, length);
goto bail;
}
dataBuf = *ppText;
}
if (length > 0) {
if (fread(dataBuf, length, 1, fpArchive->fFp) != 1) {
pErrMsg->Format(L"File read failed: %hs", strerror(errno));
goto bail;
}
}
if (needAlloc)
*ppText = dataBuf;
*pLength = length;
result = IDOK;
bail:
if (result == IDOK) {
SET_PROGRESS_END();
ASSERT(pErrMsg->IsEmpty());
} else {
ASSERT(result == IDCANCEL || !pErrMsg->IsEmpty());
if (needAlloc) {
delete[] dataBuf;
ASSERT(*ppText == NULL);
}
}
return result;
}
int AppleSingleEntry::ExtractThreadToFile(int which, FILE* outfp,
ConvertEOL conv, ConvertHighASCII convHA, CString* pErrMsg) const
{
int result = -1;
ASSERT(IDOK != -1 && IDCANCEL != -1);
long offset, length;
if (which == kDataThread && fDataOffset >= 0) {
offset = fDataOffset;
length = (long) GetDataForkLen();
} else if (which == kRsrcThread && fRsrcOffset >= 0) {
offset = fRsrcOffset;
length = (long) GetRsrcForkLen();
} else {
*pErrMsg = "No such fork";
goto bail;
}
if (length == 0) {
LOGD("Empty fork");
result = IDOK;
goto bail;
}
errno = 0;
if (fseek(fpArchive->fFp, offset, SEEK_SET) < 0) {
pErrMsg->Format(L"Unable to seek to offset %ld: %hs",
fDataOffset, strerror(errno));
goto bail;
}
SET_PROGRESS_BEGIN();
if (CopyData(length, outfp, conv, convHA, pErrMsg) != 0) {
if (pErrMsg->IsEmpty()) {
*pErrMsg = L"Failed while copying data.";
}
goto bail;
}
result = IDOK;
bail:
SET_PROGRESS_END();
return result;
}
int AppleSingleEntry::CopyData(long srcLen, FILE* outfp, ConvertEOL conv,
ConvertHighASCII convHA, CString* pMsg) const
{
int err = 0;
const int kChunkSize = 65536;
char* buf = new char[kChunkSize];
bool lastCR = false;
long dataRem;
ASSERT(srcLen > 0); // empty files should've been caught earlier
/*
* Loop until all data copied.
*/
dataRem = srcLen;
while (dataRem) {
int chunkLen;
if (dataRem > kChunkSize) {
chunkLen = kChunkSize;
} else {
chunkLen = dataRem;
}
/* read a chunk from the source file */
size_t result = fread(buf, 1, chunkLen, fpArchive->fFp);
if (result != chunkLen) {
pMsg->Format(L"File read failed: %hs.", strerror(errno));
err = -1;
goto bail;
}
/* write chunk to destination file */
int err = GenericEntry::WriteConvert(outfp, buf, chunkLen, &conv,
&convHA, &lastCR);
if (err != 0) {
pMsg->Format(L"File write failed: %hs.", strerror(err));
err = -1;
goto bail;
}
dataRem -= chunkLen;
SET_PROGRESS_UPDATE(ComputePercent(srcLen - dataRem, srcLen));
}
bail:
delete[] buf;
return err;
}
/*
* ===========================================================================
* AppleSingleArchive
* ===========================================================================
*/
/*static*/ CString AppleSingleArchive::AppInit(void)
{
return L"";
}
GenericArchive::OpenResult AppleSingleArchive::Open(const WCHAR* filename,
bool readOnly, CString* pErrMsg)
{
CString errMsg;
errno = 0;
fFp = _wfopen(filename, L"rb");
if (fFp == NULL) {
errMsg.Format(L"Unable to open %ls: %hs.", filename, strerror(errno));
goto bail;
}
// Set this before calling LoadContents() -- we may need to use it as
// the name of the archived file.
SetPathName(filename);
{
CWaitCursor waitc;
int result;
result = LoadContents();
if (result < 0) {
errMsg.Format(L"The file is not an AppleSingle archive.");
goto bail;
} else if (result > 0) {
errMsg.Format(L"Failed while reading data from AppleSingle file.");
goto bail;
}
}
bail:
*pErrMsg = errMsg;
if (!errMsg.IsEmpty())
return kResultFailure;
else
return kResultSuccess;
}
CString AppleSingleArchive::New(const WCHAR* /*filename*/, const void* /*options*/)
{
return L"Sorry, AppleSingle files can't be created.";
}
long AppleSingleArchive::GetCapability(Capability cap)
{
switch (cap) {
case kCapCanTest: return false; break;
case kCapCanRenameFullPath: return false; break;
case kCapCanRecompress: return false; break;
case kCapCanEditComment: return true; break;
case kCapCanAddDisk: return false; break;
case kCapCanConvEOLOnAdd: return false; break;
case kCapCanCreateSubdir: return false; break;
case kCapCanRenameVolume: return false; break;
default:
ASSERT(false);
return -1;
break;
}
}
int AppleSingleArchive::LoadContents(void)
{
ASSERT(fFp != NULL);
rewind(fFp);
/*
* Read the file header.
*/
uint8_t headerBuf[kHeaderLen];
if (fread(headerBuf, 1, kHeaderLen, fFp) != kHeaderLen) {
return -1; // probably not AppleSingle
}
if (headerBuf[1] == 0x05) {
// big-endian (spec-compliant)
fIsBigEndian = true;
fHeader.magic = Get32BE(&headerBuf[0]);
fHeader.version = Get32BE(&headerBuf[4]);
fHeader.numEntries = Get16BE(&headerBuf[8 + kHomeFileSystemLen]);
} else {
// little-endian (Mac OS X generated)
fIsBigEndian = false;
fHeader.magic = Get32LE(&headerBuf[0]);
fHeader.version = Get32LE(&headerBuf[4]);
fHeader.numEntries = Get16LE(&headerBuf[8 + kHomeFileSystemLen]);
}
memcpy(fHeader.homeFileSystem, &headerBuf[8], kHomeFileSystemLen);
fHeader.homeFileSystem[kHomeFileSystemLen] = '\0';
if (fHeader.magic != kMagicNumber) {
LOGD("File does not have AppleSingle magic number");
return -1;
}
if (fHeader.version != kVersion1 && fHeader.version != kVersion2) {
LOGI("AS file has unrecognized version number 0x%08x", fHeader.version);
return -1;
}
/*
* Read the entries (a table of contents). There are at most 65535
* entries, so we don't need to worry about capping it at a "reasonable"
* size.
*/
size_t totalEntryLen = fHeader.numEntries * kEntryLen;
uint8_t* entryBuf = new uint8_t[totalEntryLen];
if (fread(entryBuf, 1, totalEntryLen, fFp) != totalEntryLen) {
LOGW("Unable to read entry list from AS file (err=%d)", errno);
delete[] entryBuf;
return 1;
}
fEntries = new TOCEntry[fHeader.numEntries];
const uint8_t* ptr = entryBuf;
for (size_t i = 0; i < fHeader.numEntries; i++, ptr += kEntryLen) {
if (fIsBigEndian) {
fEntries[i].entryId = Get32BE(ptr);
fEntries[i].offset = Get32BE(ptr + 4);
fEntries[i].length = Get32BE(ptr + 8);
} else {
fEntries[i].entryId = Get32LE(ptr);
fEntries[i].offset = Get32LE(ptr + 4);
fEntries[i].length = Get32LE(ptr + 8);
}
}
delete[] entryBuf;
/*
* Make sure the file actually has everything.
*/
if (!CheckFileLength()) {
return 1;
}
/*
* Walk through the TOC entries, using them to fill out the fields in an
* AppleSingleEntry class.
*/
if (!CreateEntry()) {
return 1;
}
DumpArchive();
return 0;
}
bool AppleSingleArchive::CheckFileLength()
{
// Find the biggest offset+length.
uint64_t maxPosn = 0;
for (size_t i = 0; i < fHeader.numEntries; i++) {
uint64_t end = (uint64_t) fEntries[i].offset + fEntries[i].length;
if (maxPosn < end) {
maxPosn = end;
}
}
fseek(fFp, 0, SEEK_END);
long fileLen = ftell(fFp);
if (fileLen < 0) {
LOGW("Unable to determine file length");
return false;
}
if (maxPosn > (uint64_t) fileLen) {
LOGW("AS max=%llu, file len is only %ld", maxPosn, fileLen);
return false;
}
return true;
}
bool AppleSingleArchive::CreateEntry()
{
AppleSingleEntry* pNewEntry = new AppleSingleEntry(this);
uint32_t dataLen = 0, rsrcLen = 0;
bool haveInfo = false;
bool hasFileName = false;
for (size_t i = 0; i < fHeader.numEntries; i++) {
const TOCEntry* pToc = &fEntries[i];
switch (pToc->entryId) {
case kIdDataFork:
if (pNewEntry->GetHasDataFork()) {
LOGW("Found two data forks in AppleSingle");
return false;
}
dataLen = pToc->length;
pNewEntry->SetHasDataFork(true);
pNewEntry->SetDataOffset(pToc->offset);
pNewEntry->SetDataForkLen(pToc->length);
break;
case kIdResourceFork:
if (pNewEntry->GetHasRsrcFork()) {
LOGW("Found two rsrc forks in AppleSingle");
return false;
}
rsrcLen = pToc->length;
pNewEntry->SetHasRsrcFork(true);
pNewEntry->SetRsrcOffset(pToc->offset);
pNewEntry->SetRsrcForkLen(pToc->length);
break;
case kIdRealName:
hasFileName = HandleRealName(pToc, pNewEntry);
break;
case kIdComment:
// We could handle this, but I don't think this is widely used.
break;
case kIdFileInfo:
HandleFileInfo(pToc, pNewEntry);
break;
case kIdFileDatesInfo:
HandleFileDatesInfo(pToc, pNewEntry);
break;
case kIdFinderInfo:
if (!haveInfo) {
HandleFinderInfo(pToc, pNewEntry);
}
break;
case kIdProDOSFileInfo:
// this take precedence over Finder info
haveInfo = HandleProDOSFileInfo(pToc, pNewEntry);
break;
case kIdBWIcon:
case kIdColorIcon:
case kIdMacintoshFileInfo:
case kIdMSDOSFileInfo:
case kIdShortName:
case kIdAFPFileInfo:
case kIdDirectoryId:
// We're not interested in these.
break;
default:
LOGD("Ignoring entry with type=%u", pToc->entryId);
break;
}
}
pNewEntry->SetCompressedLen(dataLen + rsrcLen);
if (rsrcLen > 0) {
pNewEntry->SetRecordKind(GenericEntry::kRecordKindForkedFile);
} else {
pNewEntry->SetRecordKind(GenericEntry::kRecordKindFile);
}
pNewEntry->SetFormatStr(L"Uncompr");
// If there wasn't a file name, use the AppleSingle file's name, minus
// any ".as" extension.
if (!hasFileName) {
CString fileName(PathName::FilenameOnly(GetPathName(), '\\'));
if (fileName.GetLength() > 3 &&
fileName.Right(3).CompareNoCase(L".as") == 0) {
fileName = fileName.Left(fileName.GetLength() - 3);
}
// TODO: convert UTF-16 Unicode to MOR
CStringA fileNameA(fileName);
pNewEntry->SetPathNameMOR(fileNameA);
}
AddEntry(pNewEntry);
return true;
}
bool AppleSingleArchive::HandleRealName(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry)
{
if (tocEntry->length > 1024) {
// this is a single file name, not a full path
LOGW("Ignoring excessively long filename (%u)", tocEntry->length);
return false;
}
(void) fseek(fFp, tocEntry->offset, SEEK_SET);
char* buf = new char[tocEntry->length + 1];
if (fread(buf, 1, tocEntry->length, fFp) != tocEntry->length) {
LOGW("failed reading file name");
delete[] buf;
return false;
}
buf[tocEntry->length] = '\0';
if (fHeader.version == kVersion1) {
// filename is in Mac OS Roman format already
pEntry->SetPathNameMOR(buf);
} else {
// filename is in UTF-8-encoded Unicode
// TODO: convert UTF-8 to MOR, dropping invalid characters
pEntry->SetPathNameMOR(buf);
}
delete[] buf;
return true;
}
bool AppleSingleArchive::HandleFileInfo(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry)
{
if (strcmp(fHeader.homeFileSystem, "ProDOS ") != 0) {
LOGD("Ignoring file info for filesystem '%s'", fHeader.homeFileSystem);
return false;
}
const int kEntrySize = 16;
if (tocEntry->length != kEntrySize) {
LOGW("Bad length on ProDOS File Info (%d)", tocEntry->length);
return false;
}
(void) fseek(fFp, tocEntry->offset, SEEK_SET);
uint8_t buf[kEntrySize];
if (fread(buf, 1, kEntrySize, fFp) != kEntrySize) {
LOGW("failed reading ProDOS File Info");
return false;
}
uint16_t createDate, createTime, modDate, modTime, access, fileType;
uint32_t auxType;
if (fIsBigEndian) {
createDate = Get16BE(buf);
createTime = Get16BE(buf + 2);
modDate = Get16BE(buf + 4);
modTime = Get16BE(buf + 6);
access = Get16BE(buf + 8);
fileType = Get16BE(buf + 10);
auxType = Get32BE(buf + 12);
} else {
createDate = Get16LE(buf);
createTime = Get16LE(buf + 2);
modDate = Get16LE(buf + 4);
modTime = Get16LE(buf + 6);
access = Get16LE(buf + 8);
fileType = Get16LE(buf + 10);
auxType = Get32LE(buf + 12);
}
pEntry->SetAccess(access);
pEntry->SetFileType(fileType);
pEntry->SetAuxType(auxType);
pEntry->SetCreateWhen(ConvertProDOSDateTime(createDate, createTime));
pEntry->SetModWhen(ConvertProDOSDateTime(modDate, modTime));
return true;
}
bool AppleSingleArchive::HandleFileDatesInfo(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry)
{
const int kEntrySize = 16;
if (tocEntry->length != kEntrySize) {
LOGW("Bad length on File Dates info (%d)", tocEntry->length);
return false;
}
(void) fseek(fFp, tocEntry->offset, SEEK_SET);
uint8_t buf[kEntrySize];
if (fread(buf, 1, kEntrySize, fFp) != kEntrySize) {
LOGW("failed reading File Dates info");
return false;
}
int32_t createDate, modDate;
if (fIsBigEndian) {
createDate = Get32BE(buf);
modDate = Get32BE(buf + 4);
// ignore backup date and access date
} else {
createDate = Get32LE(buf);
modDate = Get32LE(buf + 4);
}
// Number of seconds between Jan 1 1970 and Jan 1 2000, computed with
// Linux mktime(). Does not include leap-seconds.
//
const int32_t kTimeOffset = 946684800;
// The Mac OS X applesingle tool is creating entries with some pretty
// wild values, so we have to range-check them here or the Windows
// time conversion method gets bent out of shape.
//
// TODO: these are screwy enough that I'm just going to ignore them.
// If it turns out I'm holding it wrong we can re-enable it.
time_t tmpTime = (time_t) createDate + kTimeOffset;
if (tmpTime >= 0 && tmpTime <= 0xffffffffLL) {
//pEntry->SetCreateWhen(tmpTime);
}
tmpTime = (time_t) modDate + kTimeOffset;
if (tmpTime >= 0 && tmpTime <= 0xffffffffLL) {
//pEntry->SetModWhen(tmpTime);
}
return false;
}
bool AppleSingleArchive::HandleProDOSFileInfo(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry)
{
const int kEntrySize = 8;
uint16_t access, fileType;
uint32_t auxType;
if (tocEntry->length != kEntrySize) {
LOGW("Bad length on ProDOS file info (%d)", tocEntry->length);
return false;
}
(void) fseek(fFp, tocEntry->offset, SEEK_SET);
uint8_t buf[kEntrySize];
if (fread(buf, 1, kEntrySize, fFp) != kEntrySize) {
LOGW("failed reading ProDOS info");
return false;
}
if (fIsBigEndian) {
access = Get16BE(buf);
fileType = Get16BE(buf + 2);
auxType = Get32BE(buf + 4);
} else {
access = Get16LE(buf);
fileType = Get16LE(buf + 2);
auxType = Get32LE(buf + 4);
}
pEntry->SetAccess(access);
pEntry->SetFileType(fileType);
pEntry->SetAuxType(auxType);
return true;
}
bool AppleSingleArchive::HandleFinderInfo(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry)
{
const int kEntrySize = 32;
const int kPdosType = 0x70646f73; // 'pdos'
uint32_t creator, macType;
if (tocEntry->length != kEntrySize) {
LOGW("Bad length on Finder info (%d)", tocEntry->length);
return false;
}
(void) fseek(fFp, tocEntry->offset, SEEK_SET);
uint8_t buf[kEntrySize];
if (fread(buf, 1, kEntrySize, fFp) != kEntrySize) {
LOGW("failed reading Finder info");
return false;
}
// These values are stored big-endian even on Mac OS X.
macType = Get32BE(buf);
creator = Get32BE(buf + 4);
if (creator == kPdosType && (macType >> 24) == 'p') {
pEntry->SetFileType((macType >> 16) & 0xff);
pEntry->SetAuxType(macType & 0xffff);
} else {
pEntry->SetFileType(macType);
pEntry->SetAuxType(creator);
}
return true;
}
CString AppleSingleArchive::Reload(void)
{
fReloadFlag = true; // tell everybody that cached data is invalid
DeleteEntries();
if (LoadContents() != 0) {
return L"Reload failed.";
}
return "";
}
CString AppleSingleArchive::GetInfoString()
{
CString str;
if (fHeader.version == kVersion1) {
str += "Version 1, ";
} else {
str += "Version 2, ";
}
if (fIsBigEndian) {
str += "big endian";
} else {
str += "little endian";
}
return str;
}
/*
* ===========================================================================
* Utility functions
* ===========================================================================
*/
time_t AppleSingleArchive::ConvertProDOSDateTime(uint16_t prodosDate,
uint16_t prodosTime)
{
NuDateTime ndt;
ndt.second = 0;
ndt.minute = prodosTime & 0x3f;
ndt.hour = (prodosTime >> 8) & 0x1f;
ndt.day = (prodosDate & 0x1f) -1;
ndt.month = ((prodosDate >> 5) & 0x0f) -1;
ndt.year = (prodosDate >> 9) & 0x7f;
if (ndt.year < 40)
ndt.year += 100; /* P8 uses 0-39 for 2000-2039 */
ndt.extra = 0;
ndt.weekDay = 0;
return NufxArchive::DateTimeToSeconds(&ndt);
}
void AppleSingleArchive::DumpArchive()
{
LOGI("AppleSingleArchive: %hs magic=0x%08x, version=%08x, entries=%u",
fIsBigEndian ? "BE" : "LE", fHeader.magic, fHeader.version,
fHeader.numEntries);
LOGI(" homeFileSystem='%hs'", fHeader.homeFileSystem);
for (size_t i = 0; i < fHeader.numEntries; i++) {
LOGI(" %2u: id=%u off=%u len=%u", i,
fEntries[i].entryId, fEntries[i].offset, fEntries[i].length);
}
}

314
app/AppleSingleArchive.h Normal file
View File

@ -0,0 +1,314 @@
/*
* CiderPress
* Copyright (C) 2015 by faddenSoft. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* AppleSingle support. This format provides a way to package a single
* forked file into an ordinary file.
*
* To create a test file from Mac OS X using NuLib2 v3.0 or later:
* - extract a forked file with "nulib2 xe <archive.shk> <file>"
* - rename the type-preservation header off of <file>'s data fork
* - combine the forks with "cat <file>#nnnnr > <file>/..namedfork/rsrc"
* - use "xattr -l <file>" to confirm that the file has a resource fork
* and the FinderInfo with the ProDOS file type
* - use "applesingle encode <file>" to create <file>.as
*
* The tool does not create a spec-compliant AppleSingle file. The v2
* spec is mildly ambiguous, but the Apple II file type note says,
* "...which is stored reverse as $00 $05 $16 $00". It appears that
* someone decided to generate little-endian AppleSingle files, and you
* have to use the magic number to figure out which end is which.
* FWIW, the Linux "file" command only recognizes the big-endian form.
*
* Perhaps unsurprisingly, the "applesingle" tool is not able to decode the
* files it creates -- but it can handle files GS/ShrinkIt creates.
*
* The GS/ShrinkIt "create AppleSingle" function creates a version 1 file
* with Mac OS Roman filenames. The Mac OS X tool creates a version 2 file
* with UTF-8-encoded Unicode filenames. We will treat the name
* accordingly, though it's possible there are v2 files with MOR strings.
*/
#ifndef APP_APPLESINGLEARCHIVE_H
#define APP_APPLESINGLEARCHIVE_H
#include "GenericArchive.h"
class AppleSingleArchive;
/*
* AppleSingle files only have one entry, so making this a separate class
* is just in keeping with the overall structure.
*/
class AppleSingleEntry : public GenericEntry {
public:
AppleSingleEntry(AppleSingleArchive* pArchive) :
fpArchive(pArchive), fDataOffset(-1), fRsrcOffset(-1) {}
virtual ~AppleSingleEntry(void) {}
virtual int ExtractThreadToBuffer(int which, char** ppText, long* pLength,
CString* pErrMsg) const override;
virtual int ExtractThreadToFile(int which, FILE* outfp, ConvertEOL conv,
ConvertHighASCII convHA, CString* pErrMsg) const override;
// doesn't matter
virtual long GetSelectionSerial(void) const override { return -1; }
virtual bool GetFeatureFlag(Feature feature) const override {
if (feature == kFeatureHasFullAccess ||
feature == kFeatureHFSTypes)
{
return true;
} else {
return false;
}
}
void SetDataOffset(long offset) { fDataOffset = offset; }
void SetRsrcOffset(long offset) { fRsrcOffset = offset; }
private:
/*
* Copy data from the seeked archive to outfp, possibly converting EOL along
* the way.
*/
int CopyData(long srcLen, FILE* outfp, ConvertEOL conv,
ConvertHighASCII convHA, CString* pMsg) const;
AppleSingleArchive* fpArchive; // holds FILE* for archive
long fDataOffset;
long fRsrcOffset;
};
/*
* AppleSingle archive definition.
*/
class AppleSingleArchive : public GenericArchive {
public:
AppleSingleArchive(void) : fFp(NULL), fEntries(NULL), fIsBigEndian(false) {}
virtual ~AppleSingleArchive(void) {
(void) Close();
delete[] fEntries;
}
/*
* Perform one-time initialization. There really isn't any for us.
*
* Returns an error string on failure.
*/
static CString AppInit(void);
/*
* Open an AppleSingle archive.
*
* Returns an error string on failure, or "" on success.
*/
virtual OpenResult Open(const WCHAR* filename, bool readOnly,
CString* pErrMsg) override;
/*
* Create a new AppleSingleArchive instance.
*
* This isn't implemented, and will always return an error.
*/
virtual CString New(const WCHAR* filename, const void* options) override;
virtual CString Flush(void) override { return L""; }
virtual CString Reload(void) override;
virtual bool IsReadOnly(void) const override { return true; };
virtual bool IsModified(void) const override { return false; }
virtual void GetDescription(CString* pStr) const override
{ *pStr = L"AppleSingle"; }
virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) override
{ ASSERT(false); return false; }
virtual bool AddDisk(ActionProgressDialog* pActionProgress,
const AddFilesDialog* pAddOpts) override
{ ASSERT(false); return false; }
virtual bool CreateSubdir(CWnd* pMsgWnd, GenericEntry* pParentEntry,
const WCHAR* newName) override
{ ASSERT(false); return false; }
virtual bool TestSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override
{ ASSERT(false); return false; }
virtual bool DeleteSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override
{ ASSERT(false); return false; }
virtual bool RenameSelection(CWnd* pMsgWnd, SelectionSet* pSelSet) override
{ ASSERT(false); return false; }
virtual bool RenameVolume(CWnd* pMsgWnd, DiskFS* pDiskFS,
const WCHAR* newName) override
{ ASSERT(false); return false; }
virtual CString TestVolumeName(const DiskFS* pDiskFS,
const WCHAR* newName) const override
{ ASSERT(false); return L"!"; }
virtual CString TestPathName(const GenericEntry* pGenericEntry,
const CString& basePath, const CString& newName, char newFssep) const override
{ ASSERT(false); return L"!"; }
virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
const RecompressOptionsDialog* pRecompOpts) override
{ ASSERT(false); return false; }
virtual XferStatus XferSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
ActionProgressDialog* pActionProgress,
const XferFileOptions* pXferOpts) override
{ ASSERT(false); return kXferFailed; }
virtual bool GetComment(CWnd* pMsgWnd, const GenericEntry* pEntry,
CString* pStr) override
{ ASSERT(false); return false; }
virtual bool SetComment(CWnd* pMsgWnd, GenericEntry* pEntry,
const CString& str) override
{ ASSERT(false); return false; }
virtual bool DeleteComment(CWnd* pMsgWnd, GenericEntry* pEntry) override
{ ASSERT(false); return false; }
virtual bool SetProps(CWnd* pMsgWnd, GenericEntry* pEntry,
const FileProps* pProps) override
{ ASSERT(false); return false; }
virtual void PreferencesChanged(void) override {}
virtual long GetCapability(Capability cap) override;
// Generate a string for the "archive info" dialog.
CString GetInfoString();
friend class AppleSingleEntry;
private:
// File header. "homeFileSystem" became all-zero "filler" in v2.
static const int kHomeFileSystemLen = 16;
static const int kMagicNumber = 0x00051600;
static const int kVersion1 = 0x00010000;
static const int kVersion2 = 0x00020000;
struct FileHeader {
uint32_t magic;
uint32_t version;
char homeFileSystem[kHomeFileSystemLen + 1];
uint16_t numEntries;
};
static const size_t kHeaderLen = 4 + 4 + kHomeFileSystemLen + 2;
// Array of these, just past the file header.
struct TOCEntry {
uint32_t entryId;
uint32_t offset;
uint32_t length;
};
static const size_t kEntryLen = 4 + 4 + 4;
// predefined values for entryId
enum {
kIdDataFork = 1,
kIdResourceFork = 2,
kIdRealName = 3,
kIdComment = 4,
kIdBWIcon = 5,
kIdColorIcon = 6,
kIdFileInfo = 7, // version 1 only
kIdFileDatesInfo = 8, // version 2 only
kIdFinderInfo = 9,
kIdMacintoshFileInfo = 10, // here and below are version 2 only
kIdProDOSFileInfo = 11,
kIdMSDOSFileInfo = 12,
kIdShortName = 13,
kIdAFPFileInfo = 14,
kIdDirectoryId = 15
};
virtual CString Close(void) {
if (fFp != NULL) {
fclose(fFp);
fFp = NULL;
}
return L"";
}
virtual void XferPrepare(const XferFileOptions* pXferOpts) override
{ ASSERT(false); }
virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf,
long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override
{ ASSERT(false); return L"!"; }
virtual void XferAbort(CWnd* pMsgWnd) override
{ ASSERT(false); }
virtual void XferFinish(CWnd* pMsgWnd) override
{ ASSERT(false); }
virtual ArchiveKind GetArchiveKind(void) override { return kArchiveAppleSingle; }
virtual NuError DoAddFile(const AddFilesDialog* pAddOpts,
LocalFileDetails* pDetails) override
{ ASSERT(false); return kNuErrGeneric; }
/*
* Loads the contents of the archive.
*
* Returns 0 on success, < 0 if this is not an AppleSingle file, or
* > 0 if this appears to be an AppleSingle file but it's damaged.
*/
int LoadContents();
/*
* Confirms that the file is big enough to hold all of the entries
* listed in the table of contents.
*/
bool CheckFileLength();
/*
* Creates our one and only AppleSingleEntry instance by walking through
* the various bits of info.
*/
bool CreateEntry();
/*
* Reads the "real name" chunk, converting the character set to
* Mac OS Roman if necessary. (If we wanted to be a general AppleSingle
* tool we wouldn't do that... but we're not.)
*/
bool HandleRealName(const TOCEntry* tocEntry, AppleSingleEntry* pEntry);
/*
* Reads the version 1 File Info chunk, which is OS-specific. The data
* layout is determined by the "home file system" string in the header.
*
* We only really want to find a ProDOS chunk. The Macintosh chunk doesn't
* have the file type in it.
*
* This will set the access, file type, aux type, create date/time, and
* modification date/time.
*/
bool HandleFileInfo(const TOCEntry* tocEntry, AppleSingleEntry* pEntry);
/*
* Reads the version 2 File Dates Info chunk, which provides various
* dates as 32-bit seconds since Jan 1 2000 UTC. Nothing else uses
* this, making it equally inconvenient on all systems.
*/
bool HandleFileDatesInfo(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry);
/*
* Reads a ProDOS file info block, using the values to set the access,
* file type, and aux type fields.
*/
bool HandleProDOSFileInfo(const TOCEntry* tocEntry,
AppleSingleEntry* pEntry);
/*
* Reads a Finder info block, using the values to set the file type and
* aux type.
*/
bool HandleFinderInfo(const TOCEntry* tocEntry, AppleSingleEntry* pEntry);
/*
* Convert from ProDOS compact date format to time_t (time in seconds
* since Jan 1 1970 UTC).
*/
time_t ConvertProDOSDateTime(uint16_t prodosDate, uint16_t prodosTime);
void DumpArchive();
FILE* fFp;
bool fIsBigEndian;
FileHeader fHeader;
TOCEntry* fEntries;
};
#endif /*APP_APPLESINGLEARCHIVE_H*/

View File

@ -395,3 +395,23 @@ BOOL AcuArchiveInfoDialog::OnInitDialog(void)
return ArchiveInfoDialog::OnInitDialog();
}
/*
* ===========================================================================
* AppleSingleArchiveInfoDialog
* ===========================================================================
*/
BOOL AppleSingleArchiveInfoDialog::OnInitDialog(void)
{
CWnd* pWnd;
ASSERT(fpArchive != NULL);
pWnd = GetDlgItem(IDC_AI_FILENAME);
pWnd->SetWindowText(fpArchive->GetPathName());
pWnd = GetDlgItem(IDC_AIBNY_RECORDS);
pWnd->SetWindowText(fpArchive->GetInfoString());
return ArchiveInfoDialog::OnInitDialog();
}

View File

@ -15,6 +15,7 @@
#include "DiskArchive.h"
#include "BnyArchive.h"
#include "AcuArchive.h"
#include "AppleSingleArchive.h"
/*
* This is an abstract base class for the archive info dialogs. There is
@ -130,4 +131,21 @@ private:
AcuArchive* fpArchive;
};
/*
* AppleSingle archive info.
*/
class AppleSingleArchiveInfoDialog : public ArchiveInfoDialog {
public:
AppleSingleArchiveInfoDialog(AppleSingleArchive* pArchive, CWnd* pParentWnd = NULL) :
fpArchive(pArchive),
ArchiveInfoDialog(IDD_ARCHIVEINFO_APPLESINGLE, pParentWnd)
{}
virtual ~AppleSingleArchiveInfoDialog(void) {}
private:
virtual BOOL OnInitDialog(void) override;
AppleSingleArchive* fpArchive;
};
#endif /*APP_ARCHIVEINFODIALOG_H*/

View File

@ -1226,6 +1226,20 @@ BEGIN
CTEXT ">count<",IDC_PROGRESS_COUNTER_COUNT,7,19,172,8
END
IDD_ARCHIVEINFO_APPLESINGLE DIALOGEX 0, 0, 320, 74
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "AppleSingle File"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "Done",IDOK,158,53,50,14
LTEXT "Filename:",IDC_STATIC,7,18,68,8,0,WS_EX_RIGHT
LTEXT "Info:",IDC_STATIC,7,29,68,8,0,WS_EX_RIGHT
GROUPBOX "Archive Info",IDC_STATIC,6,7,307,36
LTEXT "<filename>",IDC_AI_FILENAME,84,18,218,8
LTEXT "<info>",IDC_AIBNY_RECORDS,84,29,218,8
PUSHBUTTON "Help",IDHELP,105,53,50,14
END
/////////////////////////////////////////////////////////////////////////////
//
@ -1807,6 +1821,13 @@ BEGIN
TOPMARGIN, 7
BOTTOMMARGIN, 50
END
IDD_ARCHIVEINFO_APPLESINGLE, DIALOG
BEGIN
LEFTMARGIN, 7
RIGHTMARGIN, 302
TOPMARGIN, 7
END
END
#endif // APSTUDIO_INVOKED

View File

@ -419,6 +419,7 @@ public:
kArchiveNuFX,
kArchiveBNY,
kArchiveACU,
kArchiveAppleSingle,
kArchiveDiskImage,
};
@ -768,6 +769,38 @@ public:
*/
static void UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime);
/*
* Reads a 16-bit big-endian value from a buffer. Does not guard
* against buffer overrun.
*/
static uint16_t Get16BE(const uint8_t* ptr) {
return ptr[1] | (ptr[0] << 8);
}
/*
* Reads a 32-bit big-endian value from a buffer. Does not guard
* against buffer overrun.
*/
static uint32_t Get32BE(const uint8_t* ptr) {
return ptr[3] | (ptr[2] << 8) | (ptr[1] << 16) | (ptr[0] << 24);
}
/*
* Reads a 16-bit little-endian value from a buffer. Does not guard
* against buffer overrun.
*/
static uint16_t Get16LE(const uint8_t* ptr) {
return ptr[0] | (ptr[1] << 8);
}
/*
* Reads a 32-bit little-endian value from a buffer. Does not guard
* against buffer overrun.
*/
static uint32_t Get32LE(const uint8_t* ptr) {
return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
}
protected:
/*
* Delete the "entries" list.

View File

@ -14,6 +14,7 @@
#include "DiskArchive.h"
#include "BNYArchive.h"
#include "ACUArchive.h"
#include "AppleSingleArchive.h"
#include "ArchiveInfoDialog.h"
#include "PrefsDialog.h"
#include "EnterRegDialog.h"
@ -35,18 +36,43 @@ static const WCHAR kMainWindowClassName[] = L"faddenSoft.CiderPress.4";
* Filters for the "open file" command. In some cases a file may be opened
* in more than one format, so it's necessary to keep track of what the
* file filter was set to when the file was opened.
*
* With Vista-style dialogs, the second part of the string (the filespec)
* will sometimes be included in the pop-up. Sometimes not. It's
* deterministic but I haven't been able to figure out what the pattern is --
* it's not simply length of a given filter or of the entire string, or based
* on the presence of certain characters. The filter works correctly, so it
* doesn't seem to be malformed. It's just ugly to have the open dialog
* popup show an enormous, redundant filter string.
*
* I tried substituting '\0' for '|' and placing the string directly into
* the dialog; no change.
*
* CFileDialog::ApplyOFNToShellDialog in {VisualStudio}\VC\atlmfc\src\mfc\dlgfile.cpp
* appears to be doing the parsing. Single-stepping through the code shows
* that it's working fine, so something later on is choosing to merge
* pszName and pszSpec when generating the pop-up menu.
*
* The good news is that if I exclude the list of extensions from the name
* section, the popup will (so far) always includes the spec. The bad news
* is that this means we won't display the list of extensions on WinXP, which
* uses the older style of dialog. We could switch from public constants to
* a function that generates the filter based on a bit mask and the current
* OS version, but that might be more trouble than it's worth.
*/
const WCHAR MainWindow::kOpenNuFX[] =
L"ShrinkIt Archives (.shk .sdk .bxy .sea .bse)|*.shk;*.sdk;*.bxy;*.sea;*.bse|";
L"ShrinkIt Archives" /* (.shk .sdk .bxy .sea .bse)*/ L"|*.shk;*.sdk;*.bxy;*.sea;*.bse|";
const WCHAR MainWindow::kOpenBinaryII[] =
L"Binary II Archives (.bny .bqy .bxy)|*.bny;*.bqy;*.bxy|";
L"Binary II Archives" /* (.bny .bqy .bxy)*/ L"|*.bny;*.bqy;*.bxy|";
const WCHAR MainWindow::kOpenACU[] =
L"ACU Archives (.acu)|*.acu|";
L"ACU Archives" /* (.acu)*/ L"|*.acu|";
const WCHAR MainWindow::kOpenAppleSingle[] =
L"AppleSingle files" /* (.as *.*)*/ L"|*.as;*.*|";
const WCHAR MainWindow::kOpenDiskImage[] =
L"Disk Images (.shk .sdk .dsk .po .do .d13 .2mg .img .nib .nb2 .raw .hdv .dc .dc6 .ddd .app .fdi .iso .gz .zip)|"
L"Disk Images" /* (.shk .sdk .dsk .po .do .d13 .2mg .img .nib .nb2 .raw .hdv .dc .dc6 .ddd .app .fdi .iso .gz .zip)*/ L"|"
L"*.shk;*.sdk;*.dsk;*.po;*.do;*.d13;*.2mg;*.img;*.nib;*.nb2;*.raw;*.hdv;*.dc;*.dc6;*.ddd;*.app;*.fdi;*.iso;*.gz;*.zip|";
const WCHAR MainWindow::kOpenAll[] =
L"All Files (*.*)|*.*|";
L"All Files" /* (*.*)*/ L"|*.*|";
const WCHAR MainWindow::kOpenEnd[] =
L"|";
@ -61,6 +87,7 @@ static const struct {
{ L"bny", kFilterIndexBinaryII },
{ L"bqy", kFilterIndexBinaryII },
{ L"acu", kFilterIndexACU },
{ L"as", kFilterIndexAppleSingle },
{ L"dsk", kFilterIndexDiskImage },
{ L"po", kFilterIndexDiskImage },
{ L"do", kFilterIndexDiskImage },
@ -80,6 +107,7 @@ static const struct {
const WCHAR MainWindow::kModeNuFX[] = L"nufx";
const WCHAR MainWindow::kModeBinaryII[] = L"bin2";
const WCHAR MainWindow::kModeACU[] = L"acu";
const WCHAR MainWindow::kModeAppleSingle[] = L"as";
const WCHAR MainWindow::kModeDiskImage[] = L"disk";
@ -403,6 +431,8 @@ void MainWindow::ProcessCommandLine(void)
filterIndex = kFilterIndexBinaryII;
else if (wcsicmp(argv[i], kModeACU) == 0)
filterIndex = kFilterIndexACU;
else if (wcsicmp(argv[i], kModeAppleSingle) == 0)
filterIndex = kFilterIndexAppleSingle;
else if (wcsicmp(argv[i], kModeDiskImage) == 0)
filterIndex = kFilterIndexDiskImage;
else {
@ -1143,7 +1173,7 @@ void MainWindow::OnFileNewArchive(void)
}
bail:
LOGI("--- OnFileNewArchive done");
LOGD("--- OnFileNewArchive done");
}
void MainWindow::OnFileOpen(void)
@ -1153,13 +1183,15 @@ void MainWindow::OnFileOpen(void)
CString openFilters;
CString saveFolder;
/* set up filters; the order is significant */
/* set up filters; the order must match enum FilterIndex */
openFilters = kOpenNuFX;
openFilters += kOpenBinaryII;
openFilters += kOpenACU;
openFilters += kOpenAppleSingle;
openFilters += kOpenDiskImage;
openFilters += kOpenAll;
openFilters += kOpenEnd;
LOGD("filters: '%ls'", openFilters);
CFileDialog dlg(TRUE, L"shk", NULL,
OFN_FILEMUSTEXIST, openFilters, this);
@ -1288,8 +1320,11 @@ void MainWindow::OnFileArchiveInfo(void)
case GenericArchive::kArchiveACU:
pDlg = new AcuArchiveInfoDialog((AcuArchive*) fpOpenArchive, this);
break;
case GenericArchive::kArchiveAppleSingle:
pDlg = new AppleSingleArchiveInfoDialog((AppleSingleArchive*) fpOpenArchive, this);
break;
default:
LOGI("Unexpected archive type %d", fpOpenArchive->GetArchiveKind());
LOGW("Unexpected archive type %d", fpOpenArchive->GetArchiveKind());
ASSERT(false);
return;
};
@ -1484,11 +1519,19 @@ void MainWindow::HandleDoubleClick(void)
TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeACU);
handled = true;
} else
if ((ext != NULL && (
wcsicmp(ext, L".as") == 0)) ||
(fileType == 0xe0 && auxType == 0x0001))
{
LOGI(" Guessing AppleSingle");
TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeAppleSingle);
handled = true;
} else
if (fileType == 0x64496d67 && auxType == 0x64437079 &&
pEntry->GetUncompressedLen() == 819284)
{
/* type is dImg, creator is dCpy, length is 800K + DC stuff */
LOGI(" Looks like a disk image");
LOGI(" Looks like a DiskCopy disk image");
TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeDiskImage);
handled = true;
}
@ -1864,6 +1907,16 @@ int MainWindow::LoadArchive(const WCHAR* fileName, const WCHAR* extension,
* up here, and maybe do a little "open it up and see" stuff as well.
* In general, though, if we don't recognize the extension, it's
* probably a disk image.
*
* TODO: different idea: always pass the file to each of the different
* archive handlers, which will provide an "is this your file" method.
* If the filter matches, open according to the filter. If it doesn't,
* open it according to whatever stepped up to claim it. Consider
* altering the UI to offer a disambiguation dialog that shows all the
* things it could possibly be (though that might be annoying if it comes
* up every time on e.g. .SDK files). The ultimate goal is to avoid
* saying, "I can't open that", when we actually could if the filter was
* set to the right thing.
*/
if (filterIndex == kFilterIndexGeneric) {
int i;
@ -1906,6 +1959,19 @@ try_again:
goto bail;
}
} else
if (filterIndex == kFilterIndexAppleSingle) {
/* try AppleSingle and nothing else */
ASSERT(!createFile);
LOGD(" Trying AppleSingle");
pOpenArchive = new AppleSingleArchive;
openResult = pOpenArchive->Open(fileName, readOnly, &errStr);
if (openResult != GenericArchive::kResultSuccess) {
if (!errStr.IsEmpty())
ShowFailureMsg(this, errStr, IDS_FAILED);
result = -1;
goto bail;
}
} else
if (filterIndex == kFilterIndexDiskImage) {
/* try various disk image formats */
ASSERT(!createFile);

View File

@ -25,13 +25,14 @@
#define WMU_LATE_INIT (WM_USER+0)
#define WMU_START (WM_USER+1) // used by ActionProgressDialog
typedef enum {
enum FilterIndex {
kFilterIndexNuFX = 1,
kFilterIndexBinaryII = 2,
kFilterIndexACU = 3,
kFilterIndexDiskImage = 4,
kFilterIndexGeneric = 5, // *.* filter used
} FilterIndex;
kFilterIndexAppleSingle = 4,
kFilterIndexDiskImage = 5,
kFilterIndexGeneric = 6 // *.* filter used
};
struct FileCollectionEntry; // fwd
@ -247,6 +248,7 @@ public:
static const WCHAR kOpenNuFX[];
static const WCHAR kOpenBinaryII[];
static const WCHAR kOpenACU[];
static const WCHAR kOpenAppleSingle[];
static const WCHAR kOpenDiskImage[];
static const WCHAR kOpenAll[];
static const WCHAR kOpenEnd[];
@ -255,6 +257,7 @@ private:
static const WCHAR kModeNuFX[];
static const WCHAR kModeBinaryII[];
static const WCHAR kModeACU[];
static const WCHAR kModeAppleSingle[];
static const WCHAR kModeDiskImage[];
// Command handlers

View File

@ -15,7 +15,7 @@
#define kAppMajorVersion 4
#define kAppMinorVersion 0
#define kAppBugVersion 0
#define kAppDevString L"d2"
#define kAppDevString L"d3"
/*
* Windows application object.

View File

@ -357,7 +357,7 @@ void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
SetHasDataFork(true);
SetDataForkLen(pThread->actualThreadEOF);
} else {
LOGI("WARNING: ignoring second disk image / data fork");
LOGW("WARNING: ignoring second disk image / data fork");
}
}
if (threadID == kNuThreadIDRsrcFork) {
@ -365,7 +365,7 @@ void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
SetHasRsrcFork(true);
SetRsrcForkLen(pThread->actualThreadEOF);
} else {
LOGI("WARNING: ignoring second data fork");
LOGW("WARNING: ignoring second data fork");
}
}
if (threadID == kNuThreadIDDiskImage) {
@ -373,7 +373,7 @@ void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
SetHasDiskImage(true);
SetDataForkLen(pThread->actualThreadEOF);
} else {
LOGI("WARNING: ignoring second disk image / data fork");
LOGW("WARNING: ignoring second disk image / data fork");
}
}
if (threadID == kNuThreadIDComment) {

View File

@ -160,6 +160,7 @@
<ClInclude Include="ACUArchive.h" />
<ClInclude Include="AddClashDialog.h" />
<ClInclude Include="AddFilesDialog.h" />
<ClInclude Include="AppleSingleArchive.h" />
<ClInclude Include="ArchiveInfoDialog.h" />
<ClInclude Include="BasicImport.h" />
<ClInclude Include="BNYArchive.h" />
@ -258,6 +259,7 @@
<ClCompile Include="ACUArchive.cpp" />
<ClCompile Include="AddClashDialog.cpp" />
<ClCompile Include="AddFilesDialog.cpp" />
<ClCompile Include="AppleSingleArchive.cpp" />
<ClCompile Include="ArchiveInfoDialog.cpp" />
<ClCompile Include="BasicImport.cpp" />
<ClCompile Include="BNYArchive.cpp" />

View File

@ -197,6 +197,9 @@
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AppleSingleArchive.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Image Include="Graphics\binary2.ico">
@ -414,5 +417,8 @@
<ClCompile Include="VolumeCopyDialog.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AppleSingleArchive.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -68,6 +68,7 @@
#define IDD_IMPORT_BAS 189
#define IDD_PASTE_SPECIAL 190
#define IDD_PROGRESS_COUNTER 191
#define IDD_ARCHIVEINFO_APPLESINGLE 192
#define IDC_NUFXLIB_VERS_TEXT 1001
#define IDC_CONTENT_LIST 1002
#define IDC_COL_PATHNAME 1005
@ -567,7 +568,7 @@
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 192
#define _APS_NEXT_RESOURCE_VALUE 193
#define _APS_NEXT_COMMAND_VALUE 40102
#define _APS_NEXT_CONTROL_VALUE 1454
#define _APS_NEXT_SYMED_VALUE 102