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:
parent
84ba460957
commit
5946481b4e
|
@ -4,10 +4,10 @@ faddenSoft
|
||||||
http://www.faddensoft.com/
|
http://www.faddensoft.com/
|
||||||
CiderPress
|
CiderPress
|
||||||
http://a2ciderpress.com/
|
http://a2ciderpress.com/
|
||||||
4.0.0d2
|
4.0.0d3
|
||||||
41989
|
42015
|
||||||
C:\DATA\faddenSoft\fs.ico
|
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\ReadMe.txt
|
||||||
C:\Src\CiderPress\DIST\License.txt
|
C:\Src\CiderPress\DIST\License.txt
|
||||||
|
|
||||||
|
@ -355,7 +355,7 @@ FALSE
|
||||||
4095
|
4095
|
||||||
|
|
||||||
|
|
||||||
Setup400d2.exe
|
Setup400d3.exe
|
||||||
|
|
||||||
FALSE
|
FALSE
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ public:
|
||||||
/*
|
/*
|
||||||
* Perform one-time initialization. There really isn't any for us.
|
* 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);
|
static CString AppInit(void);
|
||||||
|
|
||||||
|
@ -96,17 +96,17 @@ public:
|
||||||
/*
|
/*
|
||||||
* Finish instantiating an AcuArchive object by creating a new archive.
|
* 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 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 CString Reload(void) override;
|
||||||
virtual bool IsReadOnly(void) const override { return true; };
|
virtual bool IsReadOnly(void) const override { return true; };
|
||||||
virtual bool IsModified(void) const override { return false; }
|
virtual bool IsModified(void) const override { return false; }
|
||||||
virtual void GetDescription(CString* pStr) const override
|
virtual void GetDescription(CString* pStr) const override
|
||||||
{ *pStr = "AppleLink ACU"; }
|
{ *pStr = L"AppleLink ACU"; }
|
||||||
virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
|
virtual bool BulkAdd(ActionProgressDialog* pActionProgress,
|
||||||
const AddFilesDialog* pAddOpts) override
|
const AddFilesDialog* pAddOpts) override
|
||||||
{ ASSERT(false); return false; }
|
{ ASSERT(false); return false; }
|
||||||
|
@ -126,10 +126,10 @@ public:
|
||||||
{ ASSERT(false); return false; }
|
{ ASSERT(false); return false; }
|
||||||
virtual CString TestVolumeName(const DiskFS* pDiskFS,
|
virtual CString TestVolumeName(const DiskFS* pDiskFS,
|
||||||
const WCHAR* newName) const override
|
const WCHAR* newName) const override
|
||||||
{ ASSERT(false); return "!"; }
|
{ ASSERT(false); return L"!"; }
|
||||||
virtual CString TestPathName(const GenericEntry* pGenericEntry,
|
virtual CString TestPathName(const GenericEntry* pGenericEntry,
|
||||||
const CString& basePath, const CString& newName, char newFssep) const override
|
const CString& basePath, const CString& newName, char newFssep) const override
|
||||||
{ ASSERT(false); return "!"; }
|
{ ASSERT(false); return L"!"; }
|
||||||
virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
|
virtual bool RecompressSelection(CWnd* pMsgWnd, SelectionSet* pSelSet,
|
||||||
const RecompressOptionsDialog* pRecompOpts) override
|
const RecompressOptionsDialog* pRecompOpts) override
|
||||||
{ ASSERT(false); return false; }
|
{ ASSERT(false); return false; }
|
||||||
|
@ -165,7 +165,7 @@ private:
|
||||||
{ ASSERT(false); }
|
{ ASSERT(false); }
|
||||||
virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf,
|
virtual CString XferFile(LocalFileDetails* pDetails, uint8_t** pDataBuf,
|
||||||
long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override
|
long dataLen, uint8_t** pRsrcBuf, long rsrcLen) override
|
||||||
{ ASSERT(false); return "!"; }
|
{ ASSERT(false); return L"!"; }
|
||||||
virtual void XferAbort(CWnd* pMsgWnd) override
|
virtual void XferAbort(CWnd* pMsgWnd) override
|
||||||
{ ASSERT(false); }
|
{ ASSERT(false); }
|
||||||
virtual void XferFinish(CWnd* pMsgWnd) override
|
virtual void XferFinish(CWnd* pMsgWnd) override
|
||||||
|
@ -237,8 +237,8 @@ private:
|
||||||
/*
|
/*
|
||||||
* Load the contents of the archive.
|
* Load the contents of the archive.
|
||||||
*
|
*
|
||||||
* Returns 0 on success, < 0 if this is not an ACU archive > 0 if this appears
|
* Returns 0 on success, < 0 if this is not an ACU archive, or > 0 if
|
||||||
* to be an ACU archive but it's damaged.
|
* this appears to be an ACU archive but it's damaged.
|
||||||
*/
|
*/
|
||||||
int LoadContents(void);
|
int LoadContents(void);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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*/
|
|
@ -395,3 +395,23 @@ BOOL AcuArchiveInfoDialog::OnInitDialog(void)
|
||||||
|
|
||||||
return ArchiveInfoDialog::OnInitDialog();
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include "DiskArchive.h"
|
#include "DiskArchive.h"
|
||||||
#include "BnyArchive.h"
|
#include "BnyArchive.h"
|
||||||
#include "AcuArchive.h"
|
#include "AcuArchive.h"
|
||||||
|
#include "AppleSingleArchive.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This is an abstract base class for the archive info dialogs. There is
|
* This is an abstract base class for the archive info dialogs. There is
|
||||||
|
@ -130,4 +131,21 @@ private:
|
||||||
AcuArchive* fpArchive;
|
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*/
|
#endif /*APP_ARCHIVEINFODIALOG_H*/
|
||||||
|
|
|
@ -1226,6 +1226,20 @@ BEGIN
|
||||||
CTEXT ">count<",IDC_PROGRESS_COUNTER_COUNT,7,19,172,8
|
CTEXT ">count<",IDC_PROGRESS_COUNTER_COUNT,7,19,172,8
|
||||||
END
|
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
|
TOPMARGIN, 7
|
||||||
BOTTOMMARGIN, 50
|
BOTTOMMARGIN, 50
|
||||||
END
|
END
|
||||||
|
|
||||||
|
IDD_ARCHIVEINFO_APPLESINGLE, DIALOG
|
||||||
|
BEGIN
|
||||||
|
LEFTMARGIN, 7
|
||||||
|
RIGHTMARGIN, 302
|
||||||
|
TOPMARGIN, 7
|
||||||
|
END
|
||||||
END
|
END
|
||||||
#endif // APSTUDIO_INVOKED
|
#endif // APSTUDIO_INVOKED
|
||||||
|
|
||||||
|
|
|
@ -419,6 +419,7 @@ public:
|
||||||
kArchiveNuFX,
|
kArchiveNuFX,
|
||||||
kArchiveBNY,
|
kArchiveBNY,
|
||||||
kArchiveACU,
|
kArchiveACU,
|
||||||
|
kArchiveAppleSingle,
|
||||||
kArchiveDiskImage,
|
kArchiveDiskImage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -768,6 +769,38 @@ public:
|
||||||
*/
|
*/
|
||||||
static void UNIXTimeToDateTime(const time_t* pWhen, NuDateTime *pDateTime);
|
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:
|
protected:
|
||||||
/*
|
/*
|
||||||
* Delete the "entries" list.
|
* Delete the "entries" list.
|
||||||
|
|
86
app/Main.cpp
86
app/Main.cpp
|
@ -14,6 +14,7 @@
|
||||||
#include "DiskArchive.h"
|
#include "DiskArchive.h"
|
||||||
#include "BNYArchive.h"
|
#include "BNYArchive.h"
|
||||||
#include "ACUArchive.h"
|
#include "ACUArchive.h"
|
||||||
|
#include "AppleSingleArchive.h"
|
||||||
#include "ArchiveInfoDialog.h"
|
#include "ArchiveInfoDialog.h"
|
||||||
#include "PrefsDialog.h"
|
#include "PrefsDialog.h"
|
||||||
#include "EnterRegDialog.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
|
* 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
|
* 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.
|
* 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[] =
|
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[] =
|
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[] =
|
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[] =
|
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|";
|
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[] =
|
const WCHAR MainWindow::kOpenAll[] =
|
||||||
L"All Files (*.*)|*.*|";
|
L"All Files" /* (*.*)*/ L"|*.*|";
|
||||||
const WCHAR MainWindow::kOpenEnd[] =
|
const WCHAR MainWindow::kOpenEnd[] =
|
||||||
L"|";
|
L"|";
|
||||||
|
|
||||||
|
@ -61,6 +87,7 @@ static const struct {
|
||||||
{ L"bny", kFilterIndexBinaryII },
|
{ L"bny", kFilterIndexBinaryII },
|
||||||
{ L"bqy", kFilterIndexBinaryII },
|
{ L"bqy", kFilterIndexBinaryII },
|
||||||
{ L"acu", kFilterIndexACU },
|
{ L"acu", kFilterIndexACU },
|
||||||
|
{ L"as", kFilterIndexAppleSingle },
|
||||||
{ L"dsk", kFilterIndexDiskImage },
|
{ L"dsk", kFilterIndexDiskImage },
|
||||||
{ L"po", kFilterIndexDiskImage },
|
{ L"po", kFilterIndexDiskImage },
|
||||||
{ L"do", kFilterIndexDiskImage },
|
{ L"do", kFilterIndexDiskImage },
|
||||||
|
@ -80,6 +107,7 @@ static const struct {
|
||||||
const WCHAR MainWindow::kModeNuFX[] = L"nufx";
|
const WCHAR MainWindow::kModeNuFX[] = L"nufx";
|
||||||
const WCHAR MainWindow::kModeBinaryII[] = L"bin2";
|
const WCHAR MainWindow::kModeBinaryII[] = L"bin2";
|
||||||
const WCHAR MainWindow::kModeACU[] = L"acu";
|
const WCHAR MainWindow::kModeACU[] = L"acu";
|
||||||
|
const WCHAR MainWindow::kModeAppleSingle[] = L"as";
|
||||||
const WCHAR MainWindow::kModeDiskImage[] = L"disk";
|
const WCHAR MainWindow::kModeDiskImage[] = L"disk";
|
||||||
|
|
||||||
|
|
||||||
|
@ -403,6 +431,8 @@ void MainWindow::ProcessCommandLine(void)
|
||||||
filterIndex = kFilterIndexBinaryII;
|
filterIndex = kFilterIndexBinaryII;
|
||||||
else if (wcsicmp(argv[i], kModeACU) == 0)
|
else if (wcsicmp(argv[i], kModeACU) == 0)
|
||||||
filterIndex = kFilterIndexACU;
|
filterIndex = kFilterIndexACU;
|
||||||
|
else if (wcsicmp(argv[i], kModeAppleSingle) == 0)
|
||||||
|
filterIndex = kFilterIndexAppleSingle;
|
||||||
else if (wcsicmp(argv[i], kModeDiskImage) == 0)
|
else if (wcsicmp(argv[i], kModeDiskImage) == 0)
|
||||||
filterIndex = kFilterIndexDiskImage;
|
filterIndex = kFilterIndexDiskImage;
|
||||||
else {
|
else {
|
||||||
|
@ -1143,7 +1173,7 @@ void MainWindow::OnFileNewArchive(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
bail:
|
bail:
|
||||||
LOGI("--- OnFileNewArchive done");
|
LOGD("--- OnFileNewArchive done");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::OnFileOpen(void)
|
void MainWindow::OnFileOpen(void)
|
||||||
|
@ -1153,13 +1183,15 @@ void MainWindow::OnFileOpen(void)
|
||||||
CString openFilters;
|
CString openFilters;
|
||||||
CString saveFolder;
|
CString saveFolder;
|
||||||
|
|
||||||
/* set up filters; the order is significant */
|
/* set up filters; the order must match enum FilterIndex */
|
||||||
openFilters = kOpenNuFX;
|
openFilters = kOpenNuFX;
|
||||||
openFilters += kOpenBinaryII;
|
openFilters += kOpenBinaryII;
|
||||||
openFilters += kOpenACU;
|
openFilters += kOpenACU;
|
||||||
|
openFilters += kOpenAppleSingle;
|
||||||
openFilters += kOpenDiskImage;
|
openFilters += kOpenDiskImage;
|
||||||
openFilters += kOpenAll;
|
openFilters += kOpenAll;
|
||||||
openFilters += kOpenEnd;
|
openFilters += kOpenEnd;
|
||||||
|
LOGD("filters: '%ls'", openFilters);
|
||||||
CFileDialog dlg(TRUE, L"shk", NULL,
|
CFileDialog dlg(TRUE, L"shk", NULL,
|
||||||
OFN_FILEMUSTEXIST, openFilters, this);
|
OFN_FILEMUSTEXIST, openFilters, this);
|
||||||
|
|
||||||
|
@ -1288,8 +1320,11 @@ void MainWindow::OnFileArchiveInfo(void)
|
||||||
case GenericArchive::kArchiveACU:
|
case GenericArchive::kArchiveACU:
|
||||||
pDlg = new AcuArchiveInfoDialog((AcuArchive*) fpOpenArchive, this);
|
pDlg = new AcuArchiveInfoDialog((AcuArchive*) fpOpenArchive, this);
|
||||||
break;
|
break;
|
||||||
|
case GenericArchive::kArchiveAppleSingle:
|
||||||
|
pDlg = new AppleSingleArchiveInfoDialog((AppleSingleArchive*) fpOpenArchive, this);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOGI("Unexpected archive type %d", fpOpenArchive->GetArchiveKind());
|
LOGW("Unexpected archive type %d", fpOpenArchive->GetArchiveKind());
|
||||||
ASSERT(false);
|
ASSERT(false);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -1484,11 +1519,19 @@ void MainWindow::HandleDoubleClick(void)
|
||||||
TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeACU);
|
TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeACU);
|
||||||
handled = true;
|
handled = true;
|
||||||
} else
|
} 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 &&
|
if (fileType == 0x64496d67 && auxType == 0x64437079 &&
|
||||||
pEntry->GetUncompressedLen() == 819284)
|
pEntry->GetUncompressedLen() == 819284)
|
||||||
{
|
{
|
||||||
/* type is dImg, creator is dCpy, length is 800K + DC stuff */
|
/* 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);
|
TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeDiskImage);
|
||||||
handled = true;
|
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.
|
* 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
|
* In general, though, if we don't recognize the extension, it's
|
||||||
* probably a disk image.
|
* 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) {
|
if (filterIndex == kFilterIndexGeneric) {
|
||||||
int i;
|
int i;
|
||||||
|
@ -1906,6 +1959,19 @@ try_again:
|
||||||
goto bail;
|
goto bail;
|
||||||
}
|
}
|
||||||
} else
|
} 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) {
|
if (filterIndex == kFilterIndexDiskImage) {
|
||||||
/* try various disk image formats */
|
/* try various disk image formats */
|
||||||
ASSERT(!createFile);
|
ASSERT(!createFile);
|
||||||
|
|
11
app/Main.h
11
app/Main.h
|
@ -25,13 +25,14 @@
|
||||||
#define WMU_LATE_INIT (WM_USER+0)
|
#define WMU_LATE_INIT (WM_USER+0)
|
||||||
#define WMU_START (WM_USER+1) // used by ActionProgressDialog
|
#define WMU_START (WM_USER+1) // used by ActionProgressDialog
|
||||||
|
|
||||||
typedef enum {
|
enum FilterIndex {
|
||||||
kFilterIndexNuFX = 1,
|
kFilterIndexNuFX = 1,
|
||||||
kFilterIndexBinaryII = 2,
|
kFilterIndexBinaryII = 2,
|
||||||
kFilterIndexACU = 3,
|
kFilterIndexACU = 3,
|
||||||
kFilterIndexDiskImage = 4,
|
kFilterIndexAppleSingle = 4,
|
||||||
kFilterIndexGeneric = 5, // *.* filter used
|
kFilterIndexDiskImage = 5,
|
||||||
} FilterIndex;
|
kFilterIndexGeneric = 6 // *.* filter used
|
||||||
|
};
|
||||||
|
|
||||||
struct FileCollectionEntry; // fwd
|
struct FileCollectionEntry; // fwd
|
||||||
|
|
||||||
|
@ -247,6 +248,7 @@ public:
|
||||||
static const WCHAR kOpenNuFX[];
|
static const WCHAR kOpenNuFX[];
|
||||||
static const WCHAR kOpenBinaryII[];
|
static const WCHAR kOpenBinaryII[];
|
||||||
static const WCHAR kOpenACU[];
|
static const WCHAR kOpenACU[];
|
||||||
|
static const WCHAR kOpenAppleSingle[];
|
||||||
static const WCHAR kOpenDiskImage[];
|
static const WCHAR kOpenDiskImage[];
|
||||||
static const WCHAR kOpenAll[];
|
static const WCHAR kOpenAll[];
|
||||||
static const WCHAR kOpenEnd[];
|
static const WCHAR kOpenEnd[];
|
||||||
|
@ -255,6 +257,7 @@ private:
|
||||||
static const WCHAR kModeNuFX[];
|
static const WCHAR kModeNuFX[];
|
||||||
static const WCHAR kModeBinaryII[];
|
static const WCHAR kModeBinaryII[];
|
||||||
static const WCHAR kModeACU[];
|
static const WCHAR kModeACU[];
|
||||||
|
static const WCHAR kModeAppleSingle[];
|
||||||
static const WCHAR kModeDiskImage[];
|
static const WCHAR kModeDiskImage[];
|
||||||
|
|
||||||
// Command handlers
|
// Command handlers
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
#define kAppMajorVersion 4
|
#define kAppMajorVersion 4
|
||||||
#define kAppMinorVersion 0
|
#define kAppMinorVersion 0
|
||||||
#define kAppBugVersion 0
|
#define kAppBugVersion 0
|
||||||
#define kAppDevString L"d2"
|
#define kAppDevString L"d3"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Windows application object.
|
* Windows application object.
|
||||||
|
|
|
@ -357,7 +357,7 @@ void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
|
||||||
SetHasDataFork(true);
|
SetHasDataFork(true);
|
||||||
SetDataForkLen(pThread->actualThreadEOF);
|
SetDataForkLen(pThread->actualThreadEOF);
|
||||||
} else {
|
} else {
|
||||||
LOGI("WARNING: ignoring second disk image / data fork");
|
LOGW("WARNING: ignoring second disk image / data fork");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (threadID == kNuThreadIDRsrcFork) {
|
if (threadID == kNuThreadIDRsrcFork) {
|
||||||
|
@ -365,7 +365,7 @@ void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
|
||||||
SetHasRsrcFork(true);
|
SetHasRsrcFork(true);
|
||||||
SetRsrcForkLen(pThread->actualThreadEOF);
|
SetRsrcForkLen(pThread->actualThreadEOF);
|
||||||
} else {
|
} else {
|
||||||
LOGI("WARNING: ignoring second data fork");
|
LOGW("WARNING: ignoring second data fork");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (threadID == kNuThreadIDDiskImage) {
|
if (threadID == kNuThreadIDDiskImage) {
|
||||||
|
@ -373,7 +373,7 @@ void NufxEntry::AnalyzeRecord(const NuRecord* pRecord)
|
||||||
SetHasDiskImage(true);
|
SetHasDiskImage(true);
|
||||||
SetDataForkLen(pThread->actualThreadEOF);
|
SetDataForkLen(pThread->actualThreadEOF);
|
||||||
} else {
|
} else {
|
||||||
LOGI("WARNING: ignoring second disk image / data fork");
|
LOGW("WARNING: ignoring second disk image / data fork");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (threadID == kNuThreadIDComment) {
|
if (threadID == kNuThreadIDComment) {
|
||||||
|
|
|
@ -160,6 +160,7 @@
|
||||||
<ClInclude Include="ACUArchive.h" />
|
<ClInclude Include="ACUArchive.h" />
|
||||||
<ClInclude Include="AddClashDialog.h" />
|
<ClInclude Include="AddClashDialog.h" />
|
||||||
<ClInclude Include="AddFilesDialog.h" />
|
<ClInclude Include="AddFilesDialog.h" />
|
||||||
|
<ClInclude Include="AppleSingleArchive.h" />
|
||||||
<ClInclude Include="ArchiveInfoDialog.h" />
|
<ClInclude Include="ArchiveInfoDialog.h" />
|
||||||
<ClInclude Include="BasicImport.h" />
|
<ClInclude Include="BasicImport.h" />
|
||||||
<ClInclude Include="BNYArchive.h" />
|
<ClInclude Include="BNYArchive.h" />
|
||||||
|
@ -258,6 +259,7 @@
|
||||||
<ClCompile Include="ACUArchive.cpp" />
|
<ClCompile Include="ACUArchive.cpp" />
|
||||||
<ClCompile Include="AddClashDialog.cpp" />
|
<ClCompile Include="AddClashDialog.cpp" />
|
||||||
<ClCompile Include="AddFilesDialog.cpp" />
|
<ClCompile Include="AddFilesDialog.cpp" />
|
||||||
|
<ClCompile Include="AppleSingleArchive.cpp" />
|
||||||
<ClCompile Include="ArchiveInfoDialog.cpp" />
|
<ClCompile Include="ArchiveInfoDialog.cpp" />
|
||||||
<ClCompile Include="BasicImport.cpp" />
|
<ClCompile Include="BasicImport.cpp" />
|
||||||
<ClCompile Include="BNYArchive.cpp" />
|
<ClCompile Include="BNYArchive.cpp" />
|
||||||
|
|
|
@ -197,6 +197,9 @@
|
||||||
<ClInclude Include="targetver.h">
|
<ClInclude Include="targetver.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="AppleSingleArchive.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="Graphics\binary2.ico">
|
<Image Include="Graphics\binary2.ico">
|
||||||
|
@ -414,5 +417,8 @@
|
||||||
<ClCompile Include="VolumeCopyDialog.cpp">
|
<ClCompile Include="VolumeCopyDialog.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="AppleSingleArchive.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -68,6 +68,7 @@
|
||||||
#define IDD_IMPORT_BAS 189
|
#define IDD_IMPORT_BAS 189
|
||||||
#define IDD_PASTE_SPECIAL 190
|
#define IDD_PASTE_SPECIAL 190
|
||||||
#define IDD_PROGRESS_COUNTER 191
|
#define IDD_PROGRESS_COUNTER 191
|
||||||
|
#define IDD_ARCHIVEINFO_APPLESINGLE 192
|
||||||
#define IDC_NUFXLIB_VERS_TEXT 1001
|
#define IDC_NUFXLIB_VERS_TEXT 1001
|
||||||
#define IDC_CONTENT_LIST 1002
|
#define IDC_CONTENT_LIST 1002
|
||||||
#define IDC_COL_PATHNAME 1005
|
#define IDC_COL_PATHNAME 1005
|
||||||
|
@ -567,7 +568,7 @@
|
||||||
//
|
//
|
||||||
#ifdef APSTUDIO_INVOKED
|
#ifdef APSTUDIO_INVOKED
|
||||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
#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_COMMAND_VALUE 40102
|
||||||
#define _APS_NEXT_CONTROL_VALUE 1454
|
#define _APS_NEXT_CONTROL_VALUE 1454
|
||||||
#define _APS_NEXT_SYMED_VALUE 102
|
#define _APS_NEXT_SYMED_VALUE 102
|
||||||
|
|
Loading…
Reference in New Issue