2015-01-12 07:02:35 +00:00
|
|
|
/*
|
|
|
|
* 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);
|
2015-01-13 21:21:43 +00:00
|
|
|
if (rsrcLen > 0) { // could do ">=" to preserve empty resource forks
|
2015-01-12 07:02:35 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-01-13 21:21:43 +00:00
|
|
|
|
|
|
|
// This doesn't matter, since we only have the file name, but it keeps
|
|
|
|
// the entry from getting a weird default.
|
|
|
|
pNewEntry->SetFssep(':');
|
2015-01-12 07:02:35 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2015-01-13 02:06:38 +00:00
|
|
|
}
|