// 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 _stream; CRecordVector _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 copyCoder = copyCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(_stream); for (i = 0; i < numItems; i++) { lps->InSize = totalSize; lps->OutSize = totalSize; RINOK(lps->SetCur()); CMyComPtr 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) }}