blob: a3b5e19b972e23be91f9c2fdca07e2af50ed0cfe [file] [log] [blame]
// ApmHandler.cpp
#include "StdAfx.h"
#include "../../../C/CpuArch.h"
#include "Common/ComTry.h"
#include "Common/IntToString.h"
#include "Common/MyString.h"
#include "Windows/PropVariant.h"
#include "../Common/LimitedStreams.h"
#include "../Common/ProgressUtils.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamUtils.h"
#include "../Compress/CopyCoder.h"
#define Get16(p) GetBe16(p)
#define Get32(p) GetBe32(p)
using namespace NWindows;
namespace NArchive {
namespace NApm {
struct CItem
{
UInt32 StartBlock;
UInt32 NumBlocks;
char Name[32];
char Type[32];
/*
UInt32 DataStartBlock;
UInt32 NumDataBlocks;
UInt32 Status;
UInt32 BootStartBlock;
UInt32 BootSize;
UInt32 BootAddr;
UInt32 BootEntry;
UInt32 BootChecksum;
char Processor[16];
*/
bool Parse(const Byte *p, UInt32 &numBlocksInMap)
{
if (p[0] != 0x50 || p[1] != 0x4D || p[2] != 0 || p[3] != 0)
return false;
numBlocksInMap = Get32(p + 4);
StartBlock = Get32(p + 8);
NumBlocks = Get32(p + 0xC);
memcpy(Name, p + 0x10, 32);
memcpy(Type, p + 0x30, 32);
/*
DataStartBlock = Get32(p + 0x50);
NumDataBlocks = Get32(p + 0x54);
Status = Get32(p + 0x58);
BootStartBlock = Get32(p + 0x5C);
BootSize = Get32(p + 0x60);
BootAddr = Get32(p + 0x64);
if (Get32(p + 0x68) != 0)
return false;
BootEntry = Get32(p + 0x6C);
if (Get32(p + 0x70) != 0)
return false;
BootChecksum = Get32(p + 0x74);
memcpy(Processor, p + 0x78, 16);
*/
return true;
}
};
class CHandler:
public IInArchive,
public IInArchiveGetStream,
public CMyUnknownImp
{
CMyComPtr<IInStream> _stream;
CRecordVector<CItem> _items;
int _blockSizeLog;
UInt32 _numBlocks;
HRESULT ReadTables(IInStream *stream);
UInt64 BlocksToBytes(UInt32 i) const { return (UInt64)i << _blockSizeLog; }
UInt64 GetItemSize(const CItem &item) { return BlocksToBytes(item.NumBlocks); }
public:
MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
INTERFACE_IInArchive(;)
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
};
static inline int GetLog(UInt32 num)
{
for (int i = 0; i < 31; i++)
if (((UInt32)1 << i) == num)
return i;
return -1;
}
HRESULT CHandler::ReadTables(IInStream *stream)
{
const UInt32 kSectorSize = 512;
Byte buf[kSectorSize];
{
RINOK(ReadStream_FALSE(stream, buf, kSectorSize));
if (buf[0] != 0x45 || buf[1] != 0x52)
return S_FALSE;
_blockSizeLog = GetLog(Get16(buf + 2));
if (_blockSizeLog < 9 || _blockSizeLog > 14)
return S_FALSE;
_numBlocks = Get32(buf + 4);
for (int i = 8; i < 16; i++)
if (buf[i] != 0)
return S_FALSE;
}
unsigned numSkips = (unsigned)1 << (_blockSizeLog - 9);
for (unsigned j = 1; j < numSkips; j++)
{
RINOK(ReadStream_FALSE(stream, buf, kSectorSize));
}
UInt32 numBlocksInMap = 0;
for (unsigned i = 0;;)
{
RINOK(ReadStream_FALSE(stream, buf, kSectorSize));
CItem item;
UInt32 numBlocksInMap2;
if (!item.Parse(buf, numBlocksInMap2))
return S_FALSE;
if (i == 0)
{
numBlocksInMap = numBlocksInMap2;
if (numBlocksInMap > (1 << 8))
return S_FALSE;
}
else if (numBlocksInMap2 != numBlocksInMap)
return S_FALSE;
UInt32 finish = item.StartBlock + item.NumBlocks;
if (finish < item.StartBlock)
return S_FALSE;
_numBlocks = MyMax(_numBlocks, finish);
_items.Add(item);
for (unsigned j = 1; j < numSkips; j++)
{
RINOK(ReadStream_FALSE(stream, buf, kSectorSize));
}
if (++i == numBlocksInMap)
break;
}
return S_OK;
}
STDMETHODIMP CHandler::Open(IInStream *stream,
const UInt64 * /* maxCheckStartPosition */,
IArchiveOpenCallback * /* openArchiveCallback */)
{
COM_TRY_BEGIN
Close();
RINOK(ReadTables(stream));
_stream = stream;
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::Close()
{
_items.Clear();
_stream.Release();
return S_OK;
}
STATPROPSTG kProps[] =
{
{ NULL, kpidPath, VT_BSTR},
{ NULL, kpidSize, VT_UI8},
{ NULL, kpidOffset, VT_UI8}
};
STATPROPSTG kArcProps[] =
{
{ NULL, kpidClusterSize, VT_UI4},
{ NULL, kpidPhySize, VT_UI8}
};
IMP_IInArchive_Props
IMP_IInArchive_ArcProps
static AString GetString(const char *s)
{
AString res;
for (int i = 0; i < 32 && s[i] != 0; i++)
res += s[i];
return res;
}
STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
switch(propID)
{
case kpidMainSubfile:
{
int mainIndex = -1;
for (int i = 0; i < _items.Size(); i++)
{
AString s = GetString(_items[i].Type);
if (s != "Apple_Free" &&
s != "Apple_partition_map")
{
if (mainIndex >= 0)
{
mainIndex = -1;
break;
}
mainIndex = i;
}
}
if (mainIndex >= 0)
prop = (UInt32)mainIndex;
break;
}
case kpidClusterSize: prop = (UInt32)1 << _blockSizeLog; break;
case kpidPhySize: prop = BlocksToBytes(_numBlocks); break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
*numItems = _items.Size();
return S_OK;
}
STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
COM_TRY_BEGIN
NCOM::CPropVariant prop;
const CItem &item = _items[index];
switch(propID)
{
case kpidPath:
{
AString s = GetString(item.Name);
if (s.IsEmpty())
{
char s2[32];
ConvertUInt32ToString(index, s2);
s = s2;
}
AString type = GetString(item.Type);
if (type == "Apple_HFS")
type = "hfs";
if (!type.IsEmpty())
{
s += '.';
s += type;
}
prop = s;
break;
}
case kpidSize:
case kpidPackSize:
prop = GetItemSize(item);
break;
case kpidOffset: prop = BlocksToBytes(item.StartBlock); break;
}
prop.Detach(value);
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
Int32 testMode, IArchiveExtractCallback *extractCallback)
{
COM_TRY_BEGIN
bool allFilesMode = (numItems == (UInt32)-1);
if (allFilesMode)
numItems = _items.Size();
if (numItems == 0)
return S_OK;
UInt64 totalSize = 0;
UInt32 i;
for (i = 0; i < numItems; i++)
totalSize += GetItemSize(_items[allFilesMode ? i : indices[i]]);
extractCallback->SetTotal(totalSize);
totalSize = 0;
NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
CLocalProgress *lps = new CLocalProgress;
CMyComPtr<ICompressProgressInfo> progress = lps;
lps->Init(extractCallback, false);
CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
CMyComPtr<ISequentialInStream> inStream(streamSpec);
streamSpec->SetStream(_stream);
for (i = 0; i < numItems; i++)
{
lps->InSize = totalSize;
lps->OutSize = totalSize;
RINOK(lps->SetCur());
CMyComPtr<ISequentialOutStream> outStream;
Int32 askMode = testMode ?
NExtract::NAskMode::kTest :
NExtract::NAskMode::kExtract;
Int32 index = allFilesMode ? i : indices[i];
const CItem &item = _items[index];
RINOK(extractCallback->GetStream(index, &outStream, askMode));
UInt64 size = GetItemSize(item);
totalSize += size;
if (!testMode && !outStream)
continue;
RINOK(extractCallback->PrepareOperation(askMode));
RINOK(_stream->Seek(BlocksToBytes(item.StartBlock), STREAM_SEEK_SET, NULL));
streamSpec->Init(size);
RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress));
outStream.Release();
RINOK(extractCallback->SetOperationResult(copyCoderSpec->TotalSize == size ?
NExtract::NOperationResult::kOK:
NExtract::NOperationResult::kDataError));
}
return S_OK;
COM_TRY_END
}
STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
{
COM_TRY_BEGIN
const CItem &item = _items[index];
return CreateLimitedInStream(_stream, BlocksToBytes(item.StartBlock), GetItemSize(item), stream);
COM_TRY_END
}
static IInArchive *CreateArc() { return new CHandler; }
static CArcInfo g_ArcInfo =
{ L"APM", L"", 0, 0xD4, { 0x50, 0x4D, 0, 0, 0, 0, 0 }, 7, false, CreateArc, 0 };
REGISTER_ARC(Apm)
}}