mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-22 20:31:08 +00:00
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/
|
||||
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
|
||||
|
||||
|
@ -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
735
app/AppleSingleArchive.cpp
Normal 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
314
app/AppleSingleArchive.h
Normal 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*/
|
@ -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();
|
||||
}
|
||||
|
@ -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*/
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
84
app/Main.cpp
84
app/Main.cpp
@ -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);
|
||||
|
11
app/Main.h
11
app/Main.h
@ -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
|
||||
|
@ -15,7 +15,7 @@
|
||||
#define kAppMajorVersion 4
|
||||
#define kAppMinorVersion 0
|
||||
#define kAppBugVersion 0
|
||||
#define kAppDevString L"d2"
|
||||
#define kAppDevString L"d3"
|
||||
|
||||
/*
|
||||
* Windows application object.
|
||||
|
@ -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) {
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user