mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-22 05:29:43 +00:00
791 lines
22 KiB
C++
791 lines
22 KiB
C++
/*
|
|
* CiderPress
|
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
|
* See the file LICENSE for distribution terms.
|
|
*/
|
|
/*
|
|
* AppleLink Compression Utility file support.
|
|
*
|
|
* This was adapted from the Binary II support, which has a mixed history,
|
|
* so this is a little scrambled in spots.
|
|
*/
|
|
#include "stdafx.h"
|
|
#include "ACUArchive.h"
|
|
#include "NufxArchive.h" // uses NuError
|
|
#include "Preferences.h"
|
|
#include "Main.h"
|
|
#include "Squeeze.h"
|
|
#include <errno.h>
|
|
|
|
/*
|
|
+00 2b Number of items in archive
|
|
+02 2b 0100
|
|
+04 5b "fZink"
|
|
+09 11b 0136 0000 0000 0000 0000 dd
|
|
|
|
+14
|
|
|
|
+00 1b ?? 00
|
|
+01 1b Compression type, 00=none, 03=sq
|
|
+02 2b ?? 0000 0000 0000 0000
|
|
+04 2b ?? 0a74 961f 7d85 af2c 2775 <- 0000 for dir (checksum?)
|
|
+06 2b ?? 0000 0000 0000 0000
|
|
+08 2b ?? 0000 0000 0000 0000
|
|
+0a 2b Storage size (in 512-byte blocks)
|
|
+0c 6b ?? 000000 000000 000000 000000
|
|
+12 4b Length of file in this archive (compressed or uncompressed)
|
|
+16 2b ProDOS file permissions
|
|
+18 2b ProDOS file type
|
|
+1a 4b ProDOS aux type
|
|
+1e ?? 0000
|
|
+20 1b ProDOS storage type (usually 02, 0d for dirs)
|
|
+21 ?? 00
|
|
+22 ?? 0000 0000
|
|
+26 4b Uncompressed file len
|
|
+2a 2b ProDOS date (create?)
|
|
+2c 2b ProDOS time
|
|
+2e 2b ProDOS date (mod?)
|
|
+30 2b ProDOS time
|
|
+32 2b Filename len
|
|
+34 2b ?? ac4a 2d02 for dir <- header checksum?
|
|
+36 FL Filename
|
|
+xx data start (dir has no data)
|
|
*/
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* AcuEntry
|
|
* ===========================================================================
|
|
*/
|
|
|
|
int AcuEntry::ExtractThreadToBuffer(int which, char** ppText, long* pLength,
|
|
CString* pErrMsg) const
|
|
{
|
|
NuError nerr;
|
|
ExpandBuffer expBuf;
|
|
char* dataBuf = NULL;
|
|
long len;
|
|
bool needAlloc = true;
|
|
int result = -1;
|
|
|
|
ASSERT(fpArchive != NULL);
|
|
ASSERT(fpArchive->fFp != NULL);
|
|
|
|
if (*ppText != NULL)
|
|
needAlloc = false;
|
|
|
|
if (which != kDataThread) {
|
|
*pErrMsg = "No such fork";
|
|
goto bail;
|
|
}
|
|
|
|
len = (long) GetUncompressedLen();
|
|
if (len == 0) {
|
|
if (needAlloc) {
|
|
*ppText = new char[1];
|
|
**ppText = '\0';
|
|
}
|
|
*pLength = 0;
|
|
result = IDOK;
|
|
goto bail;
|
|
}
|
|
|
|
SET_PROGRESS_BEGIN();
|
|
|
|
errno = 0;
|
|
if (fseek(fpArchive->fFp, fOffset, SEEK_SET) < 0) {
|
|
pErrMsg->Format(L"Unable to seek to offset %ld: %hs",
|
|
fOffset, strerror(errno));
|
|
goto bail;
|
|
}
|
|
|
|
if (GetSqueezed()) {
|
|
nerr = UnSqueeze(fpArchive->fFp, (unsigned long) GetCompressedLen(),
|
|
&expBuf, false, 0);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"File read failed: %hs", NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
char* unsqBuf = NULL;
|
|
long unsqLen = 0;
|
|
expBuf.SeizeBuffer(&unsqBuf, &unsqLen);
|
|
LOGI("Unsqueezed %ld bytes to %d",
|
|
(unsigned long) GetCompressedLen(), unsqLen);
|
|
if (unsqLen == 0) {
|
|
// some bonehead squeezed a zero-length file
|
|
delete[] unsqBuf;
|
|
ASSERT(*ppText == NULL);
|
|
LOGI("Handling zero-length squeezed file!");
|
|
if (needAlloc) {
|
|
*ppText = new char[1];
|
|
**ppText = '\0';
|
|
}
|
|
*pLength = 0;
|
|
} else {
|
|
if (needAlloc) {
|
|
/* just use the seized buffer */
|
|
*ppText = unsqBuf;
|
|
*pLength = unsqLen;
|
|
} else {
|
|
if (*pLength < unsqLen) {
|
|
pErrMsg->Format(L"buf size %ld too short (%ld)",
|
|
*pLength, unsqLen);
|
|
delete[] unsqBuf;
|
|
goto bail;
|
|
}
|
|
|
|
memcpy(*ppText, unsqBuf, unsqLen);
|
|
delete[] unsqBuf;
|
|
*pLength = unsqLen;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (needAlloc) {
|
|
dataBuf = new char[len];
|
|
if (dataBuf == NULL) {
|
|
pErrMsg->Format(L"allocation of %ld bytes failed", len);
|
|
goto bail;
|
|
}
|
|
} else {
|
|
if (*pLength < (long) len) {
|
|
pErrMsg->Format(L"buf size %ld too short (%ld)",
|
|
*pLength, len);
|
|
goto bail;
|
|
}
|
|
dataBuf = *ppText;
|
|
}
|
|
if (fread(dataBuf, len, 1, fpArchive->fFp) != 1) {
|
|
pErrMsg->Format(L"File read failed: %hs", strerror(errno));
|
|
goto bail;
|
|
}
|
|
|
|
if (needAlloc)
|
|
*ppText = dataBuf;
|
|
*pLength = len;
|
|
}
|
|
|
|
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 AcuEntry::ExtractThreadToFile(int which, FILE* outfp, ConvertEOL conv,
|
|
ConvertHighASCII convHA, CString* pErrMsg) const
|
|
{
|
|
NuError nerr;
|
|
long len;
|
|
int result = -1;
|
|
|
|
ASSERT(IDOK != -1 && IDCANCEL != -1);
|
|
if (which != kDataThread) {
|
|
*pErrMsg = L"No such fork";
|
|
goto bail;
|
|
}
|
|
|
|
len = (long) GetUncompressedLen();
|
|
if (len == 0) {
|
|
LOGI("Empty fork");
|
|
result = IDOK;
|
|
goto bail;
|
|
}
|
|
|
|
errno = 0;
|
|
if (fseek(fpArchive->fFp, fOffset, SEEK_SET) < 0) {
|
|
pErrMsg->Format(L"Unable to seek to offset %ld: %hs",
|
|
fOffset, strerror(errno));
|
|
goto bail;
|
|
}
|
|
|
|
SET_PROGRESS_BEGIN();
|
|
|
|
/*
|
|
* Generally speaking, anything in an ACU file is going to be small.
|
|
*
|
|
* To make life easy, we either unsqueeze the entire thing into a buffer
|
|
* and then write that, or we do a file-to-file copy of the specified
|
|
* number of bytes.
|
|
*/
|
|
if (GetSqueezed()) {
|
|
ExpandBuffer expBuf;
|
|
bool lastCR = false;
|
|
char* buf;
|
|
long uncLen;
|
|
|
|
nerr = UnSqueeze(fpArchive->fFp, (unsigned long) GetCompressedLen(),
|
|
&expBuf, false, 0);
|
|
if (nerr != kNuErrNone) {
|
|
pErrMsg->Format(L"File read failed: %hs", NuStrError(nerr));
|
|
goto bail;
|
|
}
|
|
|
|
expBuf.SeizeBuffer(&buf, &uncLen);
|
|
LOGI("Unsqueezed %ld bytes to %d", len, uncLen);
|
|
|
|
// some bonehead squeezed a zero-length file
|
|
if (uncLen == 0) {
|
|
ASSERT(buf == NULL);
|
|
LOGI("Handling zero-length squeezed file!");
|
|
result = IDOK;
|
|
goto bail;
|
|
}
|
|
|
|
int err = GenericEntry::WriteConvert(outfp, buf, uncLen, &conv,
|
|
&convHA, &lastCR);
|
|
if (err != 0) {
|
|
pErrMsg->Format(L"File write failed: %hs", strerror(err));
|
|
delete[] buf;
|
|
goto bail;
|
|
}
|
|
|
|
delete[] buf;
|
|
} else {
|
|
nerr = CopyData(outfp, conv, convHA, pErrMsg);
|
|
if (nerr != kNuErrNone) {
|
|
if (pErrMsg->IsEmpty()) {
|
|
pErrMsg->Format(L"Failed while copying data: %hs\n",
|
|
NuStrError(nerr));
|
|
}
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
result = IDOK;
|
|
|
|
bail:
|
|
SET_PROGRESS_END();
|
|
return result;
|
|
}
|
|
|
|
NuError AcuEntry::CopyData(FILE* outfp, ConvertEOL conv, ConvertHighASCII convHA,
|
|
CString* pMsg) const
|
|
{
|
|
NuError nerr = kNuErrNone;
|
|
const int kChunkSize = 8192;
|
|
char buf[kChunkSize];
|
|
bool lastCR = false;
|
|
long srcLen, dataRem;
|
|
|
|
srcLen = (long) GetUncompressedLen();
|
|
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 */
|
|
nerr = fpArchive->AcuRead(buf, chunkLen);
|
|
if (nerr != kNuErrNone) {
|
|
pMsg->Format(L"File read failed: %hs.", NuStrError(nerr));
|
|
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));
|
|
nerr = kNuErrGeneric;
|
|
goto bail;
|
|
}
|
|
|
|
dataRem -= chunkLen;
|
|
SET_PROGRESS_UPDATE(ComputePercent(srcLen - dataRem, srcLen));
|
|
}
|
|
|
|
bail:
|
|
return nerr;
|
|
}
|
|
|
|
|
|
NuError AcuEntry::TestEntry(CWnd* pMsgWnd)
|
|
{
|
|
NuError nerr = kNuErrNone;
|
|
CString errMsg;
|
|
long len;
|
|
int result = -1;
|
|
|
|
len = (long) GetUncompressedLen();
|
|
if (len == 0)
|
|
goto bail;
|
|
|
|
errno = 0;
|
|
if (fseek(fpArchive->fFp, fOffset, SEEK_SET) < 0) {
|
|
nerr = kNuErrGeneric;
|
|
errMsg.Format(L"Unable to seek to offset %ld: %hs\n",
|
|
fOffset, strerror(errno));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
|
|
if (GetSqueezed()) {
|
|
nerr = UnSqueeze(fpArchive->fFp, (unsigned long) GetCompressedLen(),
|
|
NULL, false, 0);
|
|
if (nerr != kNuErrNone) {
|
|
errMsg.Format(L"Unsqueeze failed: %hs.", NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
} else {
|
|
errno = 0;
|
|
if (fseek(fpArchive->fFp, fOffset + len, SEEK_SET) < 0) {
|
|
nerr = kNuErrGeneric;
|
|
errMsg.Format(L"Unable to seek to offset %ld (file truncated?): %hs\n",
|
|
fOffset, strerror(errno));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (SET_PROGRESS_UPDATE(100) == IDCANCEL)
|
|
nerr = kNuErrAborted;
|
|
|
|
bail:
|
|
return nerr;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* AcuArchive
|
|
* ===========================================================================
|
|
*/
|
|
|
|
/*static*/ CString AcuArchive::AppInit(void)
|
|
{
|
|
return L"";
|
|
}
|
|
|
|
GenericArchive::OpenResult AcuArchive::Open(const WCHAR* filename,
|
|
bool readOnly, CString* pErrMsg)
|
|
{
|
|
CString errMsg;
|
|
|
|
//fIsReadOnly = true; // ignore "readOnly"
|
|
|
|
errno = 0;
|
|
fFp = _wfopen(filename, L"rb");
|
|
if (fFp == NULL) {
|
|
errMsg.Format(L"Unable to open %ls: %hs.", filename, strerror(errno));
|
|
goto bail;
|
|
}
|
|
|
|
{
|
|
CWaitCursor waitc;
|
|
int result;
|
|
|
|
result = LoadContents();
|
|
if (result < 0) {
|
|
errMsg.Format(L"The file is not an ACU archive.");
|
|
goto bail;
|
|
} else if (result > 0) {
|
|
errMsg.Format(L"Failed while reading data from ACU archive.");
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
SetPathName(filename);
|
|
|
|
bail:
|
|
*pErrMsg = errMsg;
|
|
if (!errMsg.IsEmpty())
|
|
return kResultFailure;
|
|
else
|
|
return kResultSuccess;
|
|
}
|
|
|
|
CString AcuArchive::New(const WCHAR* /*filename*/, const void* /*options*/)
|
|
{
|
|
return L"Sorry, AppleLink Compression Utility files can't be created.";
|
|
}
|
|
|
|
|
|
long AcuArchive::GetCapability(Capability cap)
|
|
{
|
|
switch (cap) {
|
|
case kCapCanTest:
|
|
return true;
|
|
break;
|
|
case kCapCanRenameFullPath:
|
|
return true;
|
|
break;
|
|
case kCapCanRecompress:
|
|
return true;
|
|
break;
|
|
case kCapCanEditComment:
|
|
return false;
|
|
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 AcuArchive::LoadContents(void)
|
|
{
|
|
NuError nerr;
|
|
int numEntries;
|
|
|
|
ASSERT(fFp != NULL);
|
|
rewind(fFp);
|
|
|
|
/*
|
|
* Read the master header. In an ACU file this holds the number of
|
|
* files and a bunch of stuff that doesn't seem to change.
|
|
*/
|
|
if (ReadMasterHeader(&numEntries) != 0)
|
|
return -1;
|
|
|
|
while (numEntries) {
|
|
AcuFileEntry fileEntry;
|
|
|
|
nerr = ReadFileHeader(&fileEntry);
|
|
if (nerr != kNuErrNone)
|
|
return 1;
|
|
|
|
if (CreateEntry(&fileEntry) != 0)
|
|
return 1;
|
|
|
|
/* if file isn't empty, seek past it */
|
|
if (fileEntry.dataStorageLen) {
|
|
nerr = AcuSeek(fileEntry.dataStorageLen);
|
|
if (nerr != kNuErrNone)
|
|
return 1;
|
|
}
|
|
|
|
numEntries--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
CString AcuArchive::Reload(void)
|
|
{
|
|
fReloadFlag = true; // tell everybody that cached data is invalid
|
|
|
|
DeleteEntries();
|
|
if (LoadContents() != 0) {
|
|
return L"Reload failed.";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
int AcuArchive::ReadMasterHeader(int* pNumEntries)
|
|
{
|
|
AcuMasterHeader header;
|
|
unsigned char buf[kAcuMasterHeaderLen];
|
|
NuError nerr;
|
|
|
|
nerr = AcuRead(buf, kAcuMasterHeaderLen);
|
|
if (nerr != kNuErrNone)
|
|
return -1;
|
|
|
|
header.fileCount = buf[0x00] | buf[0x01] << 8;
|
|
header.unknown1 = buf[0x02] | buf[0x03] << 8;
|
|
memcpy(header.fZink, &buf[0x04], 5);
|
|
header.fZink[5] = '\0';
|
|
memcpy(header.unknown2, &buf[0x09], 11);
|
|
|
|
if (header.fileCount == 0 ||
|
|
header.unknown1 != 1 ||
|
|
strcmp((char*) header.fZink, "fZink") != 0)
|
|
{
|
|
LOGW("Not an ACU archive");
|
|
return -1;
|
|
}
|
|
|
|
LOGD("Looks like an ACU archive with %d entries", header.fileCount);
|
|
|
|
*pNumEntries = header.fileCount;
|
|
return 0;
|
|
}
|
|
|
|
NuError AcuArchive::ReadFileHeader(AcuFileEntry* pEntry)
|
|
{
|
|
NuError err = kNuErrNone;
|
|
unsigned char buf[kAcuEntryHeaderLen];
|
|
|
|
ASSERT(pEntry != NULL);
|
|
|
|
err = AcuRead(buf, kAcuEntryHeaderLen);
|
|
if (err != kNuErrNone)
|
|
goto bail;
|
|
|
|
// unknown at 00
|
|
pEntry->compressionType = buf[0x01];
|
|
// unknown at 02-03
|
|
pEntry->dataChecksum = buf[0x04] | buf[0x05] << 8; // not sure
|
|
// unknown at 06-09
|
|
pEntry->blockCount = buf[0x0a] | buf[0x0b] << 8;
|
|
// unknown at 0c-11
|
|
pEntry->dataStorageLen = buf[0x12] | buf [0x13] << 8 | buf[0x14] << 16 |
|
|
buf[0x15] << 24;
|
|
pEntry->access = buf[0x16] | buf[0x17] << 8;
|
|
pEntry->fileType = buf[0x18] | buf[0x19] << 8;
|
|
pEntry->auxType = buf[0x1a] | buf[0x1b] << 8;
|
|
// unknown at 1e-1f
|
|
pEntry->storageType = buf[0x20];
|
|
// unknown at 21-25
|
|
pEntry->dataEof = buf[0x26] | buf[0x27] << 8 | buf[0x28] << 16 |
|
|
buf[0x29] << 24;
|
|
pEntry->prodosModDate = buf[0x2a] | buf[0x2b] << 8;
|
|
pEntry->prodosModTime = buf[0x2c] | buf[0x2d] << 8;
|
|
AcuConvertDateTime(pEntry->prodosModDate, pEntry->prodosModTime,
|
|
&pEntry->modWhen);
|
|
pEntry->prodosCreateDate = buf[0x2e] | buf[0x2f] << 8;
|
|
pEntry->prodosCreateTime = buf[0x30] | buf[0x31] << 8;
|
|
AcuConvertDateTime(pEntry->prodosCreateDate, pEntry->prodosCreateTime,
|
|
&pEntry->createWhen);
|
|
pEntry->fileNameLen = buf[0x32] | buf[0x33] << 8;
|
|
pEntry->headerChecksum = buf[0x34] | buf[0x35] << 8; // not sure
|
|
|
|
/* read the filename */
|
|
if (pEntry->fileNameLen > kAcuMaxFileName) {
|
|
LOGI("GLITCH: filename is too long (%d bytes)",
|
|
pEntry->fileNameLen);
|
|
err = kNuErrGeneric;
|
|
goto bail;
|
|
}
|
|
if (!pEntry->fileNameLen) {
|
|
LOGI("GLITCH: filename missing");
|
|
err = kNuErrGeneric;
|
|
goto bail;
|
|
}
|
|
|
|
/* don't know if this is possible or not */
|
|
if (pEntry->storageType == 5) {
|
|
LOGI("HEY: EXTENDED FILE");
|
|
}
|
|
|
|
err = AcuRead(pEntry->fileName, pEntry->fileNameLen);
|
|
if (err != kNuErrNone)
|
|
goto bail;
|
|
pEntry->fileName[pEntry->fileNameLen] = '\0';
|
|
|
|
//DumpFileHeader(pEntry);
|
|
|
|
bail:
|
|
return err;
|
|
}
|
|
|
|
void AcuArchive::DumpFileHeader(const AcuFileEntry* pEntry)
|
|
{
|
|
time_t createWhen, modWhen;
|
|
CString createStr, modStr;
|
|
|
|
createWhen = NufxArchive::DateTimeToSeconds(&pEntry->createWhen);
|
|
modWhen = NufxArchive::DateTimeToSeconds(&pEntry->modWhen);
|
|
FormatDate(createWhen, &createStr);
|
|
FormatDate(modWhen, &modStr);
|
|
|
|
LOGI(" Header for file '%hs':", pEntry->fileName);
|
|
LOGI(" dataStorageLen=%d eof=%d blockCount=%d checksum=0x%04x",
|
|
pEntry->dataStorageLen, pEntry->dataEof, pEntry->blockCount,
|
|
pEntry->dataChecksum);
|
|
LOGI(" fileType=0x%02x auxType=0x%04x storageType=0x%02x access=0x%04x",
|
|
pEntry->fileType, pEntry->auxType, pEntry->storageType, pEntry->access);
|
|
LOGI(" created %ls, modified %ls",
|
|
(LPCWSTR) createStr, (LPCWSTR) modStr);
|
|
LOGI(" fileNameLen=%d headerChecksum=0x%04x",
|
|
pEntry->fileNameLen, pEntry->headerChecksum);
|
|
}
|
|
|
|
int AcuArchive::CreateEntry(const AcuFileEntry* pEntry)
|
|
{
|
|
const int kAcuFssep = '/';
|
|
NuError err = kNuErrNone;
|
|
AcuEntry* pNewEntry;
|
|
|
|
/*
|
|
* Create the new entry.
|
|
*/
|
|
pNewEntry = new AcuEntry(this);
|
|
pNewEntry->SetPathNameMOR(pEntry->fileName);
|
|
pNewEntry->SetFssep(kAcuFssep);
|
|
pNewEntry->SetFileType(pEntry->fileType);
|
|
pNewEntry->SetAuxType(pEntry->auxType);
|
|
pNewEntry->SetAccess(pEntry->access);
|
|
pNewEntry->SetCreateWhen(NufxArchive::DateTimeToSeconds(&pEntry->createWhen));
|
|
pNewEntry->SetModWhen(NufxArchive::DateTimeToSeconds(&pEntry->modWhen));
|
|
|
|
/* always ProDOS? */
|
|
pNewEntry->SetSourceFS(DiskImg::kFormatProDOS);
|
|
|
|
pNewEntry->SetHasDataFork(true);
|
|
pNewEntry->SetHasRsrcFork(false); // ?
|
|
if (IsDir(pEntry)) {
|
|
pNewEntry->SetRecordKind(GenericEntry::kRecordKindDirectory);
|
|
} else {
|
|
pNewEntry->SetRecordKind(GenericEntry::kRecordKindFile);
|
|
}
|
|
|
|
pNewEntry->SetCompressedLen(pEntry->dataStorageLen);
|
|
pNewEntry->SetDataForkLen(pEntry->dataEof);
|
|
|
|
if (pEntry->compressionType == kAcuCompNone) {
|
|
pNewEntry->SetFormatStr(L"Uncompr");
|
|
} else if (pEntry->compressionType == kAcuCompSqueeze) {
|
|
pNewEntry->SetFormatStr(L"Squeeze");
|
|
pNewEntry->SetSqueezed(true);
|
|
} else {
|
|
pNewEntry->SetFormatStr(L"(unknown)");
|
|
pNewEntry->SetSqueezed(false);
|
|
}
|
|
|
|
pNewEntry->SetOffset(ftell(fFp));
|
|
|
|
AddEntry(pNewEntry);
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* ACU functions
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool AcuArchive::IsDir(const AcuFileEntry* pEntry)
|
|
{
|
|
return (pEntry->storageType == 0x0d);
|
|
}
|
|
|
|
NuError AcuArchive::AcuRead(void* buf, size_t nbyte)
|
|
{
|
|
size_t result;
|
|
|
|
ASSERT(buf != NULL);
|
|
ASSERT(nbyte > 0);
|
|
ASSERT(fFp != NULL);
|
|
|
|
errno = 0;
|
|
result = fread(buf, 1, nbyte, fFp);
|
|
if (result != nbyte)
|
|
return errno ? (NuError)errno : kNuErrFileRead;
|
|
return kNuErrNone;
|
|
}
|
|
|
|
NuError AcuArchive::AcuSeek(long offset)
|
|
{
|
|
ASSERT(fFp != NULL);
|
|
ASSERT(offset > 0);
|
|
|
|
/*DBUG(("--- seeking forward %ld bytes\n", offset));*/
|
|
|
|
if (fseek(fFp, offset, SEEK_CUR) < 0)
|
|
return kNuErrFileSeek;
|
|
|
|
return kNuErrNone;
|
|
}
|
|
|
|
|
|
void AcuArchive::AcuConvertDateTime(uint16_t prodosDate,
|
|
uint16_t prodosTime, NuDateTime* pWhen)
|
|
{
|
|
pWhen->second = 0;
|
|
pWhen->minute = prodosTime & 0x3f;
|
|
pWhen->hour = (prodosTime >> 8) & 0x1f;
|
|
pWhen->day = (prodosDate & 0x1f) -1;
|
|
pWhen->month = ((prodosDate >> 5) & 0x0f) -1;
|
|
pWhen->year = (prodosDate >> 9) & 0x7f;
|
|
if (pWhen->year < 40)
|
|
pWhen->year += 100; /* P8 uses 0-39 for 2000-2039 */
|
|
pWhen->extra = 0;
|
|
pWhen->weekDay = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* ===========================================================================
|
|
* AcuArchive -- test files
|
|
* ===========================================================================
|
|
*/
|
|
|
|
bool AcuArchive::TestSelection(CWnd* pMsgWnd, SelectionSet* pSelSet)
|
|
{
|
|
// TODO: this is essentially copy & paste from NufxArchive::TestSelection().
|
|
// We can move the implementation to GenericArchive and just have an
|
|
// archive-specific TestEntry() function.
|
|
NuError nerr;
|
|
AcuEntry* pEntry;
|
|
CString errMsg;
|
|
bool retVal = false;
|
|
|
|
ASSERT(fFp != NULL);
|
|
|
|
LOGI("Testing %d entries", pSelSet->GetNumEntries());
|
|
|
|
SelectionEntry* pSelEntry = pSelSet->IterNext();
|
|
while (pSelEntry != NULL) {
|
|
pEntry = (AcuEntry*) pSelEntry->GetEntry();
|
|
|
|
LOGD(" Testing '%ls' (offset=%ld)", (LPCWSTR) pEntry->GetDisplayName(),
|
|
pEntry->GetOffset());
|
|
|
|
SET_PROGRESS_UPDATE2(0, pEntry->GetDisplayName(), NULL);
|
|
|
|
nerr = pEntry->TestEntry(pMsgWnd);
|
|
if (nerr != kNuErrNone) {
|
|
if (nerr == kNuErrAborted) {
|
|
CString title;
|
|
CheckedLoadString(&title, IDS_MB_APP_NAME);
|
|
errMsg = L"Cancelled.";
|
|
pMsgWnd->MessageBox(errMsg, title, MB_OK);
|
|
} else {
|
|
errMsg.Format(L"Failed while testing '%ls': %hs.",
|
|
(LPCWSTR) pEntry->GetPathNameUNI(), NuStrError(nerr));
|
|
ShowFailureMsg(pMsgWnd, errMsg, IDS_FAILED);
|
|
}
|
|
goto bail;
|
|
}
|
|
|
|
pSelEntry = pSelSet->IterNext();
|
|
}
|
|
|
|
/* show success message */
|
|
errMsg.Format(L"Tested %d file%ls, no errors found.",
|
|
pSelSet->GetNumEntries(),
|
|
pSelSet->GetNumEntries() == 1 ? L"" : L"s");
|
|
pMsgWnd->MessageBox(errMsg);
|
|
retVal = true;
|
|
|
|
bail:
|
|
SET_PROGRESS_END();
|
|
return retVal;
|
|
}
|