diff options
Diffstat (limited to 'CPP/7zip/Archive/HfsHandler.cpp')
-rw-r--r-- | CPP/7zip/Archive/HfsHandler.cpp | 1874 |
1 files changed, 1874 insertions, 0 deletions
diff --git a/CPP/7zip/Archive/HfsHandler.cpp b/CPP/7zip/Archive/HfsHandler.cpp new file mode 100644 index 00000000..ca1370d8 --- /dev/null +++ b/CPP/7zip/Archive/HfsHandler.cpp @@ -0,0 +1,1874 @@ +// HfsHandler.cpp + +#include "StdAfx.h" + +#include "../../../C/CpuArch.h" + +#include "../../Common/ComTry.h" +#include "../../Common/MyString.h" + +#include "../../Windows/PropVariant.h" + +#include "../Common/LimitedStreams.h" +#include "../Common/RegisterArc.h" +#include "../Common/StreamObjects.h" +#include "../Common/StreamUtils.h" + +#include "../Compress/ZlibDecoder.h" + +/* if HFS_SHOW_ALT_STREAMS is defined, the handler will show attribute files + and resource forks. In most cases it looks useless. So we disable it. */ + +// #define HFS_SHOW_ALT_STREAMS + +#define Get16(p) GetBe16(p) +#define Get32(p) GetBe32(p) +#define Get64(p) GetBe64(p) + +namespace NArchive { +namespace NHfs { + +static const wchar_t *kResFileName = L"rsrc"; // L"com.apple.ResourceFork"; + +struct CExtent +{ + UInt32 Pos; + UInt32 NumBlocks; +}; + +struct CIdExtents +{ + UInt32 ID; + UInt32 StartBlock; + CRecordVector<CExtent> Extents; +}; + +struct CFork +{ + UInt64 Size; + UInt32 NumBlocks; + // UInt32 ClumpSize; + CRecordVector<CExtent> Extents; + + CFork(): Size(0), NumBlocks(0) {} + + void Parse(const Byte *p); + + bool IsEmpty() const { return Size == 0 && NumBlocks == 0 && Extents.Size() == 0; } + + UInt32 Calc_NumBlocks_from_Extents() const; + bool Check_NumBlocks() const; + + bool Check_Size_with_NumBlocks(unsigned blockSizeLog) const + { + return Size <= ((UInt64)NumBlocks << blockSizeLog); + } + + bool IsOk(unsigned blockSizeLog) const + { + // we don't check cases with extra (empty) blocks in last extent + return Check_NumBlocks() && Check_Size_with_NumBlocks(blockSizeLog); + } + + bool Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id); + bool UpgradeAndTest(const CObjectVector<CIdExtents> &items, UInt32 id, unsigned blockSizeLog) + { + if (!Upgrade(items, id)) + return false; + return IsOk(blockSizeLog); + } +}; + +static const unsigned kNumFixedExtents = 8; + +void CFork::Parse(const Byte *p) +{ + Extents.Clear(); + Size = Get64(p); + // ClumpSize = Get32(p + 8); + NumBlocks = Get32(p + 12); + p += 16; + for (unsigned i = 0; i < kNumFixedExtents; i++, p += 8) + { + CExtent e; + e.Pos = Get32(p); + e.NumBlocks = Get32(p + 4); + if (e.NumBlocks != 0) + Extents.Add(e); + } +} + +UInt32 CFork::Calc_NumBlocks_from_Extents() const +{ + UInt32 num = 0; + FOR_VECTOR (i, Extents) + { + num += Extents[i].NumBlocks; + } + return num; +} + +bool CFork::Check_NumBlocks() const +{ + UInt32 num = 0; + FOR_VECTOR (i, Extents) + { + UInt32 next = num + Extents[i].NumBlocks; + if (next < num) + return false; + num = next; + } + return num == NumBlocks; +} + +struct CIdIndexPair +{ + UInt32 ID; + int Index; + + int Compare(const CIdIndexPair &a) const; +}; + +#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } + +int CIdIndexPair::Compare(const CIdIndexPair &a) const +{ + RINOZ(MyCompare(ID, a.ID)); + return MyCompare(Index, a.Index); +} + +static int FindItemIndex(const CRecordVector<CIdIndexPair> &items, UInt32 id) +{ + unsigned left = 0, right = items.Size(); + while (left != right) + { + unsigned mid = (left + right) / 2; + UInt32 midVal = items[mid].ID; + if (id == midVal) + return items[mid].Index; + if (id < midVal) + right = mid; + else + left = mid + 1; + } + return -1; +} + +static int Find_in_IdExtents(const CObjectVector<CIdExtents> &items, UInt32 id) +{ + unsigned left = 0, right = items.Size(); + while (left != right) + { + unsigned mid = (left + right) / 2; + UInt32 midVal = items[mid].ID; + if (id == midVal) + return mid; + if (id < midVal) + right = mid; + else + left = mid + 1; + } + return -1; +} + +bool CFork::Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id) +{ + int index = Find_in_IdExtents(items, id); + if (index < 0) + return true; + const CIdExtents &item = items[index]; + if (Calc_NumBlocks_from_Extents() != item.StartBlock) + return false; + Extents += item.Extents; + return true; +} + + +struct CVolHeader +{ + Byte Header[2]; + UInt16 Version; + // UInt32 Attr; + // UInt32 LastMountedVersion; + // UInt32 JournalInfoBlock; + + UInt32 CTime; + UInt32 MTime; + // UInt32 BackupTime; + // UInt32 CheckedTime; + + UInt32 NumFiles; + UInt32 NumFolders; + unsigned BlockSizeLog; + UInt32 NumBlocks; + UInt32 NumFreeBlocks; + + // UInt32 WriteCount; + // UInt32 FinderInfo[8]; + // UInt64 VolID; + + UInt64 GetPhySize() const { return (UInt64)NumBlocks << BlockSizeLog; } + UInt64 GetFreeSize() const { return (UInt64)NumFreeBlocks << BlockSizeLog; } + bool IsHfsX() const { return Version > 4; } +}; + +inline void HfsTimeToFileTime(UInt32 hfsTime, FILETIME &ft) +{ + UInt64 v = ((UInt64)3600 * 24 * (365 * 303 + 24 * 3) + hfsTime) * 10000000; + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); +} + +enum ERecordType +{ + RECORD_TYPE_FOLDER = 1, + RECORD_TYPE_FILE, + RECORD_TYPE_FOLDER_THREAD, + RECORD_TYPE_FILE_THREAD +}; + +struct CItem +{ + UString Name; + + UInt32 ParentID; + + UInt16 Type; + UInt16 FileMode; + // UInt16 Flags; + // UInt32 Valence; + UInt32 ID; + UInt32 CTime; + UInt32 MTime; + // UInt32 AttrMTime; + UInt32 ATime; + // UInt32 BackupDate; + + /* + UInt32 OwnerID; + UInt32 GroupID; + Byte AdminFlags; + Byte OwnerFlags; + union + { + UInt32 iNodeNum; + UInt32 LinkCount; + UInt32 RawDevice; + } special; + + UInt32 FileType; + UInt32 FileCreator; + UInt16 FinderFlags; + UInt16 Point[2]; + */ + + CFork DataFork; + CFork ResourceFork; + + // for compressed attribute + UInt64 UnpackSize; + size_t DataPos; + UInt32 PackSize; + unsigned Method; + bool UseAttr; + bool UseInlineData; + + CItem(): UseAttr(false), UseInlineData(false) {} + bool IsDir() const { return Type == RECORD_TYPE_FOLDER; } + const CFork &GetFork(bool isResource) const { return (CFork & )*(isResource ? &ResourceFork: &DataFork ); } +}; + +struct CAttr +{ + UInt32 ID; + UInt32 Size; + size_t Pos; + UString Name; +}; + +struct CRef +{ + unsigned ItemIndex; + int AttrIndex; + int Parent; + bool IsResource; + + bool IsAltStream() const { return IsResource || AttrIndex >= 0; } + CRef(): AttrIndex(-1), Parent(-1), IsResource(false) {} +}; + +class CDatabase +{ + HRESULT ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream); + HRESULT LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray); + HRESULT LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress); + HRESULT LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress); + bool Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip); +public: + CRecordVector<CRef> Refs; + CObjectVector<CItem> Items; + CObjectVector<CAttr> Attrs; + + CByteBuffer AttrBuf; + + CVolHeader Header; + bool HeadersError; + bool ThereAreAltStreams; + // bool CaseSensetive; + UString ResFileName; + + UInt64 PhySize; + + void Clear() + { + PhySize = 0; + HeadersError = false; + ThereAreAltStreams = false; + // CaseSensetive = false; + Refs.Clear(); + Items.Clear(); + Attrs.Clear(); + AttrBuf.Free(); + } + + UInt64 Get_UnpackSize_of_Ref(const CRef &ref) const + { + if (ref.AttrIndex >= 0) + return Attrs[ref.AttrIndex].Size; + const CItem &item = Items[ref.ItemIndex]; + if (item.IsDir()) + return 0; + if (item.UseAttr) + return item.UnpackSize; + return item.GetFork(ref.IsResource).Size; + } + + void GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const; + HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *progress); +}; + +enum +{ + kHfsID_Root = 1, + kHfsID_RootFolder = 2, + kHfsID_ExtentsFile = 3, + kHfsID_CatalogFile = 4, + kHfsID_BadBlockFile = 5, + kHfsID_AllocationFile = 6, + kHfsID_StartupFile = 7, + kHfsID_AttributesFile = 8, + kHfsID_RepairCatalogFile = 14, + kHfsID_BogusExtentFile = 15, + kHfsID_FirstUserCatalogNode = 16 +}; + +void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const +{ + unsigned len = 0; + const unsigned kNumLevelsMax = (1 << 10); + int cur = index; + unsigned i; + + for (i = 0; i < kNumLevelsMax; i++) + { + const CRef &ref = Refs[cur]; + const UString *s; + + if (ref.IsResource) + s = &ResFileName; + else if (ref.AttrIndex >= 0) + s = &Attrs[ref.AttrIndex].Name; + else + s = &Items[ref.ItemIndex].Name; + + len += s->Len(); + len++; + cur = ref.Parent; + if (cur < 0) + break; + } + + len--; + wchar_t *p = path.AllocBstr(len); + p[len] = 0; + cur = index; + + for (;;) + { + const CRef &ref = Refs[cur]; + const UString *s; + wchar_t delimChar = L':'; + + if (ref.IsResource) + s = &ResFileName; + else if (ref.AttrIndex >= 0) + s = &Attrs[ref.AttrIndex].Name; + else + { + delimChar = WCHAR_PATH_SEPARATOR; + s = &Items[ref.ItemIndex].Name; + } + + unsigned curLen = s->Len(); + len -= curLen; + + const wchar_t *src = (const wchar_t *)*s; + wchar_t *dest = p + len; + for (unsigned j = 0; j < curLen; j++) + dest[j] = src[j]; + + if (len == 0) + break; + p[--len] = delimChar; + cur = ref.Parent; + } +} + +// Actually we read all blocks. It can be larger than fork.Size + +HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream) +{ + if (fork.NumBlocks >= Header.NumBlocks) + return S_FALSE; + size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog; + if ((totalSize >> Header.BlockSizeLog) != fork.NumBlocks) + return S_FALSE; + buf.Alloc(totalSize); + UInt32 curBlock = 0; + FOR_VECTOR (i, fork.Extents) + { + if (curBlock >= fork.NumBlocks) + return S_FALSE; + const CExtent &e = fork.Extents[i]; + if (e.Pos > Header.NumBlocks || + e.NumBlocks > fork.NumBlocks - curBlock || + e.NumBlocks > Header.NumBlocks - e.Pos) + return S_FALSE; + RINOK(inStream->Seek((UInt64)e.Pos << Header.BlockSizeLog, STREAM_SEEK_SET, NULL)); + RINOK(ReadStream_FALSE(inStream, + (Byte *)buf + ((size_t)curBlock << Header.BlockSizeLog), + (size_t)e.NumBlocks << Header.BlockSizeLog)); + curBlock += e.NumBlocks; + } + return S_OK; +} + +static const unsigned kNodeDescriptor_Size = 14; + +struct CNodeDescriptor +{ + UInt32 fLink; + // UInt32 bLink; + Byte Kind; + // Byte Height; + unsigned NumRecords; + + bool CheckNumRecords(unsigned nodeSizeLog) + { + return (kNodeDescriptor_Size + ((UInt32)NumRecords + 1) * 2 <= ((UInt32)1 << nodeSizeLog)); + } + void Parse(const Byte *p); +}; + +void CNodeDescriptor::Parse(const Byte *p) +{ + fLink = Get32(p); + // bLink = Get32(p + 4); + Kind = p[8]; + // Height = p[9]; + NumRecords = Get16(p + 10); +} + +struct CHeaderRec +{ + // UInt16 TreeDepth; + // UInt32 RootNode; + // UInt32 LeafRecords; + UInt32 FirstLeafNode; + // UInt32 LastLeafNode; + unsigned NodeSizeLog; + // UInt16 MaxKeyLength; + UInt32 TotalNodes; + // UInt32 FreeNodes; + // UInt16 Reserved1; + // UInt32 ClumpSize; + // Byte BtreeType; + // Byte KeyCompareType; + // UInt32 Attributes; + // UInt32 Reserved3[16]; + + HRESULT Parse(const Byte *p); +}; + +HRESULT CHeaderRec::Parse(const Byte *p) +{ + // TreeDepth = Get16(p); + // RootNode = Get32(p + 2); + // LeafRecords = Get32(p + 6); + FirstLeafNode = Get32(p + 0xA); + // LastLeafNode = Get32(p + 0xE); + UInt32 nodeSize = Get16(p + 0x12); + + unsigned i; + for (i = 9; ((UInt32)1 << i) != nodeSize; i++) + if (i == 16) + return S_FALSE; + NodeSizeLog = i; + + // MaxKeyLength = Get16(p + 0x14); + TotalNodes = Get32(p + 0x16); + // FreeNodes = Get32(p + 0x1A); + // Reserved1 = Get16(p + 0x1E); + // ClumpSize = Get32(p + 0x20); + // BtreeType = p[0x24]; + // KeyCompareType = p[0x25]; + // Attributes = Get32(p + 0x26); + /* + for (int i = 0; i < 16; i++) + Reserved3[i] = Get32(p + 0x2A + i * 4); + */ + return S_OK; +} + + +static const Byte kNodeType_Leaf = 0xFF; +// static const Byte kNodeType_Index = 0; +// static const Byte kNodeType_Header = 1; +// static const Byte kNodeType_Mode = 2; + +static const Byte kExtentForkType_Data = 0; +static const Byte kExtentForkType_Resource = 0xFF; + +/* It loads data extents from Extents Overflow File + Most dmg installers are not fragmented. So there are no extents in Overflow File. */ + +HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray) +{ + if (fork.NumBlocks == 0) + return S_OK; + CByteBuffer buf; + RINOK(ReadFile(fork, buf, inStream)); + const Byte *p = (const Byte *)buf; + + // CNodeDescriptor nodeDesc; + // nodeDesc.Parse(p); + CHeaderRec hr; + RINOK(hr.Parse(p + kNodeDescriptor_Size)); + + if ((buf.Size() >> hr.NodeSizeLog) < hr.TotalNodes) + return S_FALSE; + + UInt32 node = hr.FirstLeafNode; + if (node == 0) + return S_OK; + + CByteBuffer usedBuf(hr.TotalNodes); + memset(usedBuf, 0, hr.TotalNodes); + + while (node != 0) + { + if (node >= hr.TotalNodes || usedBuf[node] != 0) + return S_FALSE; + usedBuf[node] = 1; + + size_t nodeOffset = (size_t)node << hr.NodeSizeLog; + CNodeDescriptor desc; + desc.Parse(p + nodeOffset); + if (!desc.CheckNumRecords(hr.NodeSizeLog)) + return S_FALSE; + if (desc.Kind != kNodeType_Leaf) + return S_FALSE; + + UInt32 endBlock = 0; + + for (unsigned i = 0; i < desc.NumRecords; i++) + { + UInt32 nodeSize = (UInt32)1 << hr.NodeSizeLog; + UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); + UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); + if (offs > nodeSize || offsNext > nodeSize) + return S_FALSE; + UInt32 recSize = offsNext - offs; + const unsigned kKeyLen = 10; + + if (recSize != 2 + kKeyLen + kNumFixedExtents * 8) + return S_FALSE; + + const Byte *r = p + nodeOffset + offs; + if (Get16(r) != kKeyLen) + return S_FALSE; + + Byte forkType = r[2]; + unsigned forkTypeIndex; + if (forkType == kExtentForkType_Data) + forkTypeIndex = 0; + else if (forkType == kExtentForkType_Resource) + forkTypeIndex = 1; + else + continue; + CObjectVector<CIdExtents> &overflowExtents = overflowExtentsArray[forkTypeIndex]; + + UInt32 id = Get32(r + 4); + UInt32 startBlock = Get32(r + 8); + r += 2 + kKeyLen; + + bool needNew = true; + + if (overflowExtents.Size() != 0) + { + CIdExtents &e = overflowExtents.Back(); + if (e.ID == id) + { + if (endBlock != startBlock) + return S_FALSE; + needNew = false; + } + } + + if (needNew) + { + CIdExtents &e = overflowExtents.AddNew(); + e.ID = id; + e.StartBlock = startBlock; + endBlock = startBlock; + } + + CIdExtents &e = overflowExtents.Back(); + + for (unsigned k = 0; k < kNumFixedExtents; k++, r += 8) + { + CExtent ee; + ee.Pos = Get32(r); + ee.NumBlocks = Get32(r + 4); + if (ee.NumBlocks != 0) + { + e.Extents.Add(ee); + endBlock += ee.NumBlocks; + } + } + } + + node = desc.fLink; + } + return S_OK; +} + +static void LoadName(const Byte *data, unsigned len, UString &dest) +{ + wchar_t *p = dest.GetBuffer(len); + unsigned i; + for (i = 0; i < len; i++) + p[i] = Get16(data + i * 2); + p[i] = 0; + dest.ReleaseBuffer(); +} + +static bool IsNameEqualTo(const Byte *data, const char *name) +{ + for (unsigned i = 0;; i++) + { + char c = name[i]; + if (c == 0) + return true; + if (Get16(data + i * 2) != (Byte)c) + return false; + } +} + +static const UInt32 kAttrRecordType_Inline = 0x10; +// static const UInt32 kAttrRecordType_Fork = 0x20; +// static const UInt32 kAttrRecordType_Extents = 0x30; + +HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress) +{ + if (fork.NumBlocks == 0) + return S_OK; + + RINOK(ReadFile(fork, AttrBuf, inStream)); + const Byte *p = (const Byte *)AttrBuf; + + // CNodeDescriptor nodeDesc; + // nodeDesc.Parse(p); + CHeaderRec hr; + RINOK(hr.Parse(p + kNodeDescriptor_Size)); + + // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); + + if ((AttrBuf.Size() >> hr.NodeSizeLog) < hr.TotalNodes) + return S_FALSE; + + UInt32 node = hr.FirstLeafNode; + if (node == 0) + return S_OK; + + CByteBuffer usedBuf(hr.TotalNodes); + memset(usedBuf, 0, hr.TotalNodes); + + CFork resFork; + + while (node != 0) + { + if (node >= hr.TotalNodes || usedBuf[node] != 0) + return S_FALSE; + usedBuf[node] = 1; + + size_t nodeOffset = (size_t)node << hr.NodeSizeLog; + CNodeDescriptor desc; + desc.Parse(p + nodeOffset); + if (!desc.CheckNumRecords(hr.NodeSizeLog)) + return S_FALSE; + if (desc.Kind != kNodeType_Leaf) + return S_FALSE; + + for (unsigned i = 0; i < desc.NumRecords; i++) + { + UInt32 nodeSize = (1 << hr.NodeSizeLog); + UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); + UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); + UInt32 recSize = offsNext - offs; + if (offs >= nodeSize + || offsNext >= nodeSize + || offsNext < offs) + return S_FALSE; + + const unsigned kHeadSize = 14; + if (recSize < kHeadSize) + return S_FALSE; + + const Byte *r = p + nodeOffset + offs; + UInt32 keyLen = Get16(r); + + // UInt16 pad = Get16(r + 2); + UInt32 fileID = Get32(r + 4); + unsigned startBlock = Get32(r + 8); + if (startBlock != 0) + { + // that case is still unsupported + HeadersError = true; + continue; + } + unsigned nameLen = Get16(r + 12); + + if (keyLen + 2 > recSize || + keyLen != kHeadSize - 2 + nameLen * 2) + return S_FALSE; + r += kHeadSize; + recSize -= kHeadSize; + + const Byte *name = r; + r += nameLen * 2; + recSize -= nameLen * 2; + + if (recSize < 4) + return S_FALSE; + + UInt32 recordType = Get32(r); + if (recordType != kAttrRecordType_Inline) + { + // Probably only kAttrRecordType_Inline now is used in real HFS files + HeadersError = true; + continue; + } + + const UInt32 kRecordHeaderSize = 16; + if (recSize < kRecordHeaderSize) + return S_FALSE; + UInt32 dataSize = Get32(r + 12); + + r += kRecordHeaderSize; + recSize -= kRecordHeaderSize; + + if (recSize < dataSize) + return S_FALSE; + + CAttr &attr = Attrs.AddNew(); + attr.ID = fileID; + attr.Pos = nodeOffset + offs + 2 + keyLen + kRecordHeaderSize; + attr.Size = dataSize; + LoadName(name, nameLen, attr.Name); + + if (progress && (i & 0xFFF) == 0) + { + UInt64 numFiles = 0; + RINOK(progress->SetCompleted(&numFiles, NULL)); + } + } + + node = desc.fLink; + } + return S_OK; +} + +static const UInt32 kMethod_Attr = 3; // data stored in attribute file +static const UInt32 kMethod_Resource = 4; // data stored in resource fork + +bool CDatabase::Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip) +{ + skip = false; + if (attr.Name != L"com.apple.decmpfs") + return true; + if (item.UseAttr || !item.DataFork.IsEmpty()) + return false; + + const UInt32 k_decmpfs_headerSize = 16; + UInt32 dataSize = attr.Size; + if (dataSize < k_decmpfs_headerSize) + return false; + const Byte *r = AttrBuf + attr.Pos; + if (GetUi32(r) != 0x636D7066) // magic == "fpmc" + return false; + item.Method = GetUi32(r + 4); + item.UnpackSize = GetUi64(r + 8); + dataSize -= k_decmpfs_headerSize; + r += k_decmpfs_headerSize; + if (item.Method == kMethod_Resource) + { + if (dataSize != 0) + return false; + item.UseAttr = true; + } + else if (item.Method == kMethod_Attr) + { + if (dataSize == 0) + return false; + Byte b = r[0]; + if ((b & 0xF) == 0xF) + { + dataSize--; + if (item.UnpackSize > dataSize) + return false; + item.DataPos = attr.Pos + k_decmpfs_headerSize + 1; + item.PackSize = dataSize; + item.UseAttr = true; + item.UseInlineData = true; + } + else + { + item.DataPos = attr.Pos + k_decmpfs_headerSize; + item.PackSize = dataSize; + item.UseAttr = true; + } + } + else + return false; + skip = true; + return true; +} + +HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress) +{ + unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles); + Items.ClearAndReserve(reserveSize); + Refs.ClearAndReserve(reserveSize); + + CRecordVector<CIdIndexPair> IdToIndexMap; + IdToIndexMap.ClearAndReserve(reserveSize); + + CByteBuffer buf; + RINOK(ReadFile(fork, buf, inStream)); + const Byte *p = (const Byte *)buf; + + // CNodeDescriptor nodeDesc; + // nodeDesc.Parse(p); + CHeaderRec hr; + hr.Parse(p + kNodeDescriptor_Size); + + // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); + + if ((buf.Size() >> hr.NodeSizeLog) < hr.TotalNodes) + return S_FALSE; + + CByteBuffer usedBuf(hr.TotalNodes); + memset(usedBuf, 0, hr.TotalNodes); + + CFork resFork; + + UInt32 node = hr.FirstLeafNode; + UInt32 numFiles = 0; + UInt32 numFolders = 0; + + while (node != 0) + { + if (node >= hr.TotalNodes || usedBuf[node] != 0) + return S_FALSE; + usedBuf[node] = 1; + + size_t nodeOffset = (size_t)node << hr.NodeSizeLog; + CNodeDescriptor desc; + desc.Parse(p + nodeOffset); + if (!desc.CheckNumRecords(hr.NodeSizeLog)) + return S_FALSE; + if (desc.Kind != kNodeType_Leaf) + return S_FALSE; + + for (unsigned i = 0; i < desc.NumRecords; i++) + { + UInt32 nodeSize = (1 << hr.NodeSizeLog); + UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); + UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); + UInt32 recSize = offsNext - offs; + if (offs >= nodeSize + || offs >= nodeSize + || offsNext < offs + || recSize < 6) + return S_FALSE; + + const Byte *r = p + nodeOffset + offs; + UInt32 keyLen = Get16(r); + UInt32 parentID = Get32(r + 2); + if (keyLen < 6 || (keyLen & 1) != 0 || keyLen + 2 > recSize) + return S_FALSE; + r += 6; + recSize -= 6; + keyLen -= 6; + + unsigned nameLen = Get16(r); + if (nameLen * 2 != (unsigned)keyLen) + return S_FALSE; + r += 2; + recSize -= 2; + + r += nameLen * 2; + recSize -= nameLen * 2; + + if (recSize < 2) + return S_FALSE; + UInt16 type = Get16(r); + + if (type != RECORD_TYPE_FOLDER && + type != RECORD_TYPE_FILE) + continue; + + const unsigned kBasicRecSize = 0x58; + if (recSize < kBasicRecSize) + return S_FALSE; + + CItem &item = Items.AddNew(); + item.ParentID = parentID; + item.Type = type; + // item.Flags = Get16(r + 2); + // item.Valence = Get32(r + 4); + item.ID = Get32(r + 8); + { + const Byte *name = r - (nameLen * 2); + LoadName(name, nameLen, item.Name); + if (item.Name.Len() <= 1) + { + if (item.Name.IsEmpty() && nameLen == 21) + { + if (GetUi32(name) == 0 && + GetUi32(name + 4) == 0 && + IsNameEqualTo(name + 8, "HFS+ Private Data")) + { + // it's folder for "Hard Links" files + item.Name = L"[HFS+ Private Data]"; + } + } + + // Some dmg files have ' ' folder item. + if (item.Name.IsEmpty() || item.Name[0] == L' ') + item.Name = L"[]"; + } + } + + item.CTime = Get32(r + 0xC); + item.MTime = Get32(r + 0x10); + // item.AttrMTime = Get32(r + 0x14); + item.ATime = Get32(r + 0x18); + // item.BackupDate = Get32(r + 0x1C); + + /* + item.OwnerID = Get32(r + 0x20); + item.GroupID = Get32(r + 0x24); + item.AdminFlags = r[0x28]; + item.OwnerFlags = r[0x29]; + item.FileMode = Get16(r + 0x2A); + item.special.iNodeNum = Get16(r + 0x2C); // or .linkCount + item.FileType = Get32(r + 0x30); + item.FileCreator = Get32(r + 0x34); + item.FinderFlags = Get16(r + 0x38); + item.Point[0] = Get16(r + 0x3A); // v + item.Point[1] = Get16(r + 0x3C); // h + */ + + // const refIndex = Refs.Size(); + CIdIndexPair pair; + pair.ID = item.ID; + pair.Index = Items.Size() - 1; + IdToIndexMap.Add(pair); + + recSize -= kBasicRecSize; + r += kBasicRecSize; + if (item.IsDir()) + { + numFolders++; + if (recSize != 0) + return S_FALSE; + } + else + { + numFiles++; + const unsigned kForkRecSize = 16 + kNumFixedExtents * 8; + if (recSize != kForkRecSize * 2) + return S_FALSE; + + item.DataFork.Parse(r); + + if (!item.DataFork.UpgradeAndTest(overflowExtentsArray[0], item.ID, Header.BlockSizeLog)) + HeadersError = true; + + item.ResourceFork.Parse(r + kForkRecSize); + if (!item.ResourceFork.IsEmpty()) + { + if (!item.ResourceFork.UpgradeAndTest(overflowExtentsArray[1], item.ID, Header.BlockSizeLog)) + HeadersError = true; + ThereAreAltStreams = true; + } + } + if (progress && (Items.Size() & 0xFFF) == 0) + { + UInt64 numItems = Items.Size(); + RINOK(progress->SetCompleted(&numItems, NULL)); + } + } + node = desc.fLink; + } + + if (Header.NumFiles != numFiles || + Header.NumFolders + 1 != numFolders) + HeadersError = true; + + IdToIndexMap.Sort2(); + { + for (unsigned i = 1; i < IdToIndexMap.Size(); i++) + if (IdToIndexMap[i - 1].ID == IdToIndexMap[i].ID) + return S_FALSE; + } + + + CBoolArr skipAttr(Attrs.Size()); + { + for (unsigned i = 0; i < Attrs.Size(); i++) + skipAttr[i] = false; + } + + { + FOR_VECTOR (i, Attrs) + { + const CAttr &attr = Attrs[i]; + + int itemIndex = FindItemIndex(IdToIndexMap, attr.ID); + if (itemIndex < 0) + { + HeadersError = true; + continue; + } + if (!Parse_decmpgfs(attr, Items[itemIndex], skipAttr[i])) + HeadersError = true; + } + } + + IdToIndexMap.ClearAndReserve(Items.Size()); + + { + FOR_VECTOR (i, Items) + { + const CItem &item = Items[i]; + + CIdIndexPair pair; + pair.ID = item.ID; + pair.Index = Refs.Size(); + IdToIndexMap.Add(pair); + + CRef ref; + ref.ItemIndex = i; + Refs.Add(ref); + + #ifdef HFS_SHOW_ALT_STREAMS + + if (item.ResourceFork.IsEmpty()) + continue; + if (item.UseAttr && item.Method == kMethod_Resource) + continue; + CRef resRef; + resRef.ItemIndex = i; + resRef.IsResource = true; + resRef.Parent = Refs.Size() - 1; + Refs.Add(resRef); + + #endif + } + } + + IdToIndexMap.Sort2(); + + { + FOR_VECTOR (i, Refs) + { + CRef &ref = Refs[i]; + if (ref.IsResource) + continue; + CItem &item = Items[ref.ItemIndex]; + ref.Parent = FindItemIndex(IdToIndexMap, item.ParentID); + if (ref.Parent >= 0) + { + if (!Items[Refs[ref.Parent].ItemIndex].IsDir()) + { + ref.Parent = -1; + HeadersError = true; + } + } + } + } + + #ifdef HFS_SHOW_ALT_STREAMS + { + FOR_VECTOR (i, Attrs) + { + if (skipAttr[i]) + continue; + const CAttr &attr = Attrs[i]; + + int refIndex = FindItemIndex(IdToIndexMap, attr.ID); + if (refIndex < 0) + { + HeadersError = true; + continue; + } + + CRef ref; + ref.AttrIndex = i; + ref.Parent = refIndex; + ref.ItemIndex = Refs[refIndex].ItemIndex; + Refs.Add(ref); + } + } + #endif + + return S_OK; +} + +static const unsigned kHeaderPadSize = (1 << 10); + +HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress) +{ + Clear(); + static const unsigned kHeaderSize = kHeaderPadSize + 512; + Byte buf[kHeaderSize]; + RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); + { + for (unsigned i = 0; i < kHeaderPadSize; i++) + if (buf[i] != 0) + return S_FALSE; + } + const Byte *p = buf + kHeaderPadSize; + CVolHeader &h = Header; + + h.Header[0] = p[0]; + h.Header[1] = p[1]; + if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X')) + return S_FALSE; + h.Version = Get16(p + 2); + if (h.Version < 4 || h.Version > 5) + return S_FALSE; + + // h.Attr = Get32(p + 4); + // h.LastMountedVersion = Get32(p + 8); + // h.JournalInfoBlock = Get32(p + 0xC); + + h.CTime = Get32(p + 0x10); + h.MTime = Get32(p + 0x14); + // h.BackupTime = Get32(p + 0x18); + // h.CheckedTime = Get32(p + 0x1C); + + h.NumFiles = Get32(p + 0x20); + h.NumFolders = Get32(p + 0x24); + + if (h.NumFolders > ((UInt32)1 << 29) || + h.NumFiles > ((UInt32)1 << 30)) + return S_FALSE; + if (progress) + { + UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1; + RINOK(progress->SetTotal(&numFiles, NULL)); + } + + UInt32 blockSize = Get32(p + 0x28); + + { + unsigned i; + for (i = 9; ((UInt32)1 << i) != blockSize; i++) + if (i == 31) + return S_FALSE; + h.BlockSizeLog = i; + } + + h.NumBlocks = Get32(p + 0x2C); + h.NumFreeBlocks = Get32(p + 0x30); + + /* + h.NextCalatlogNodeID = Get32(p + 0x40); + h.WriteCount = Get32(p + 0x44); + for (i = 0; i < 6; i++) + h.FinderInfo[i] = Get32(p + 0x50 + i * 4); + h.VolID = Get64(p + 0x68); + */ + + /* + UInt64 endPos; + RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); + if ((endPos >> h.BlockSizeLog) < h.NumBlocks) + return S_FALSE; + */ + + ResFileName = kResFileName; + + CFork extentsFork, catalogFork, attrFork; + // allocationFork.Parse(p + 0x70 + 0x50 * 0); + extentsFork.Parse(p + 0x70 + 0x50 * 1); + catalogFork.Parse(p + 0x70 + 0x50 * 2); + attrFork.Parse (p + 0x70 + 0x50 * 3); + // startupFork.Parse(p + 0x70 + 0x50 * 4); + + CObjectVector<CIdExtents> overflowExtents[2]; + if (!extentsFork.IsOk(Header.BlockSizeLog)) + HeadersError = true; + else + { + HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents); + if (res == S_FALSE) + HeadersError = true; + else if (res != S_OK) + return res; + } + + if (!catalogFork.UpgradeAndTest(overflowExtents[0], kHfsID_CatalogFile, Header.BlockSizeLog)) + return S_FALSE; + + if (!attrFork.UpgradeAndTest(overflowExtents[0], kHfsID_AttributesFile, Header.BlockSizeLog)) + HeadersError = true; + else + { + if (attrFork.Size != 0) + RINOK(LoadAttrs(attrFork, inStream, progress)); + } + + RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress)); + + PhySize = Header.GetPhySize(); + return S_OK; +} + + + +class CHandler: + public IInArchive, + public IArchiveGetRawProps, + public IInArchiveGetStream, + public CMyUnknownImp, + public CDatabase +{ + CMyComPtr<IInStream> _stream; + + HRESULT GetForkStream(const CFork &fork, ISequentialInStream **stream); + + HRESULT ExtractZlibFile( + ISequentialOutStream *realOutStream, + const CItem &item, + NCompress::NZlib::CDecoder *_zlibDecoderSpec, + CByteBuffer &buf, + UInt64 progressStart, + IArchiveExtractCallback *extractCallback); +public: + MY_UNKNOWN_IMP3(IInArchive, IArchiveGetRawProps, IInArchiveGetStream) + INTERFACE_IInArchive(;) + INTERFACE_IArchiveGetRawProps(;) + STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); +}; + +static const Byte kProps[] = +{ + kpidPath, + kpidIsDir, + kpidSize, + kpidPackSize, + kpidCTime, + kpidMTime, + kpidATime, + kpidPosixAttrib +}; + +static const Byte kArcProps[] = +{ + kpidMethod, + kpidClusterSize, + kpidFreeSpace, + kpidCTime, + kpidMTime +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + +static void HfsTimeToProp(UInt32 hfsTime, NWindows::NCOM::CPropVariant &prop) +{ + FILETIME ft; + HfsTimeToFileTime(hfsTime, ft); + prop = ft; +} + +STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + switch (propID) + { + case kpidExtension: prop = Header.IsHfsX() ? "hfsx" : "hfs"; break; + case kpidMethod: prop = Header.IsHfsX() ? "HFSX" : "HFS+"; break; + case kpidPhySize: prop = PhySize; break; + case kpidClusterSize: prop = (UInt32)1 << Header.BlockSizeLog; break; + case kpidFreeSpace: prop = (UInt64)Header.GetFreeSize(); break; + case kpidMTime: HfsTimeToProp(Header.MTime, prop); break; + case kpidCTime: + { + FILETIME localFt, ft; + HfsTimeToFileTime(Header.CTime, localFt); + if (LocalFileTimeToFileTime(&localFt, &ft)) + prop = ft; + break; + } + case kpidIsTree: prop = true; break; + case kpidErrorFlags: + { + UInt32 flags = 0; + if (HeadersError) flags |= kpv_ErrorFlags_HeadersError; + if (flags != 0) + prop = flags; + break; + } + case kpidIsAltStream: prop = ThereAreAltStreams; break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +{ + *numProps = 0; + return S_OK; +} + +STDMETHODIMP CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID) +{ + *name = NULL; + *propID = 0; + return S_OK; +} + +STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +{ + const CRef &ref = Refs[index]; + *parentType = ref.IsAltStream() ? + NParentType::kAltStream : + NParentType::kDir; + *parent = (UInt32)(Int32)ref.Parent; + return S_OK; +} + +STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +{ + *data = NULL; + *dataSize = 0; + *propType = 0; + #ifdef MY_CPU_LE + if (propID == kpidName) + { + const CRef &ref = Refs[index]; + const UString *s; + if (ref.IsResource) + s = &ResFileName; + else if (ref.AttrIndex >= 0) + s = &Attrs[ref.AttrIndex].Name; + else + s = &Items[ref.ItemIndex].Name; + *data = (const wchar_t *)(*s); + *dataSize = (s->Len() + 1) * sizeof(wchar_t); + *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE; + return S_OK; + } + #endif + return S_OK; +} + +STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + const CRef &ref = Refs[index]; + const CItem &item = Items[ref.ItemIndex]; + switch (propID) + { + case kpidPath: GetItemPath(index, prop); break; + case kpidName: + const UString *s; + if (ref.IsResource) + s = &ResFileName; + else if (ref.AttrIndex >= 0) + s = &Attrs[ref.AttrIndex].Name; + else + s = &item.Name; + prop = *s; + break; + case kpidPackSize: + { + UInt64 size; + if (ref.AttrIndex >= 0) + size = Attrs[ref.AttrIndex].Size; + else if (item.IsDir()) + break; + else if (item.UseAttr) + { + if (item.Method == kMethod_Resource) + size = item.ResourceFork.NumBlocks << Header.BlockSizeLog; + else + size = item.PackSize; + } + else + size = item.GetFork(ref.IsResource).NumBlocks << Header.BlockSizeLog; + prop = size; + break; + } + case kpidSize: + { + UInt64 size; + if (ref.AttrIndex >= 0) + size = Attrs[ref.AttrIndex].Size; + else if (item.IsDir()) + break; + else if (item.UseAttr) + size = item.UnpackSize; + else + size = item.GetFork(ref.IsResource).Size; + prop = size; + break; + } + case kpidIsDir: prop = item.IsDir(); break; + case kpidIsAltStream: prop = ref.IsAltStream(); break; + + case kpidCTime: HfsTimeToProp(item.CTime, prop); break; + case kpidMTime: HfsTimeToProp(item.MTime, prop); break; + case kpidATime: HfsTimeToProp(item.ATime, prop); break; + + case kpidPosixAttrib: if (ref.AttrIndex < 0) prop = (UInt32)item.FileMode; break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +STDMETHODIMP CHandler::Open(IInStream *inStream, + const UInt64 * /* maxCheckStartPosition */, + IArchiveOpenCallback *callback) +{ + COM_TRY_BEGIN + Close(); + RINOK(Open2(inStream, callback)); + _stream = inStream; + return S_OK; + COM_TRY_END +} + +STDMETHODIMP CHandler::Close() +{ + _stream.Release(); + Clear(); + return S_OK; +} + +static const UInt32 kCompressionBlockSize = 1 << 16; + +HRESULT CHandler::ExtractZlibFile( + ISequentialOutStream *outStream, + const CItem &item, + NCompress::NZlib::CDecoder *_zlibDecoderSpec, + CByteBuffer &buf, + UInt64 progressStart, + IArchiveExtractCallback *extractCallback) +{ + CMyComPtr<ISequentialInStream> inStream; + const CFork &fork = item.ResourceFork; + RINOK(GetForkStream(fork, &inStream)); + const unsigned kHeaderSize = 0x100 + 8; + RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); + UInt32 dataPos = Get32(buf); + UInt32 mapPos = Get32(buf + 4); + UInt32 dataSize = Get32(buf + 8); + UInt32 mapSize = Get32(buf + 12); + + const UInt32 kResMapSize = 50; + + if (mapSize != kResMapSize + || dataPos + dataSize != mapPos + || mapPos + mapSize != fork.Size) + return S_FALSE; + + UInt32 dataSize2 = Get32(buf + 0x100); + if (4 + dataSize2 != dataSize || dataSize2 < 8) + return S_FALSE; + + UInt32 numBlocks = GetUi32(buf + 0x100 + 4); + if (((dataSize2 - 4) >> 3) < numBlocks) + return S_FALSE; + if (item.UnpackSize > (UInt64)numBlocks * kCompressionBlockSize) + return S_FALSE; + + if (item.UnpackSize + kCompressionBlockSize < (UInt64)numBlocks * kCompressionBlockSize) + return S_FALSE; + + UInt32 tableSize = (numBlocks << 3); + + CByteBuffer tableBuf(tableSize); + + RINOK(ReadStream_FALSE(inStream, tableBuf, tableSize)); + + UInt32 prev = 4 + tableSize; + + UInt32 i; + for (i = 0; i < numBlocks; i++) + { + UInt32 offset = GetUi32(tableBuf + i * 8); + UInt32 size = GetUi32(tableBuf + i * 8 + 4); + if (size == 0) + return S_FALSE; + if (prev != offset) + return S_FALSE; + if (offset > dataSize2 || + size > dataSize2 - offset) + return S_FALSE; + prev = offset + size; + } + + if (prev != dataSize2) + return S_FALSE; + + CBufInStream *bufInStreamSpec = new CBufInStream; + CMyComPtr<ISequentialInStream> bufInStream = bufInStreamSpec; + + UInt64 outPos = 0; + for (i = 0; i < numBlocks; i++) + { + UInt64 rem = item.UnpackSize - outPos; + if (rem == 0) + return S_FALSE; + UInt32 blockSize = kCompressionBlockSize; + if (rem < kCompressionBlockSize) + blockSize = (UInt32)rem; + + UInt32 size = GetUi32(tableBuf + i * 8 + 4); + + RINOK(ReadStream_FALSE(inStream, buf, size)); + + if ((buf[0] & 0xF) == 0xF) + { + // that code was not tested. Are there HFS archives with uncompressed block + if (size - 1 != blockSize) + return S_FALSE; + + if (outStream) + { + RINOK(WriteStream(outStream, buf, blockSize)); + } + } + else + { + UInt64 blockSize64 = blockSize; + bufInStreamSpec->Init(buf, size); + RINOK(_zlibDecoderSpec->Code(bufInStream, outStream, NULL, &blockSize64, NULL)); + if (_zlibDecoderSpec->GetOutputProcessedSize() != blockSize || + _zlibDecoderSpec->GetInputProcessedSize() != size) + return S_FALSE; + } + + outPos += blockSize; + UInt64 progressPos = progressStart + outPos; + RINOK(extractCallback->SetCompleted(&progressPos)); + } + + if (outPos != item.UnpackSize) + return S_FALSE; + + /* We check Resource Map + Are there HFS files with another values in Resource Map ??? */ + + RINOK(ReadStream_FALSE(inStream, buf, mapSize)); + UInt32 types = Get16(buf + 24); + UInt32 names = Get16(buf + 26); + UInt32 numTypes = Get16(buf + 28); + if (numTypes != 0 || types != 28 || names != kResMapSize) + return S_FALSE; + UInt32 resType = Get32(buf + 30); + UInt32 numResources = Get16(buf + 34); + UInt32 resListOffset = Get16(buf + 36); + if (resType != 0x636D7066) // cmpf + return S_FALSE; + if (numResources != 0 || resListOffset != 10) + return S_FALSE; + + UInt32 entryId = Get16(buf + 38); + UInt32 nameOffset = Get16(buf + 40); + // Byte attrib = buf[42]; + UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF; + if (entryId != 1 || nameOffset != 0xFFFF || resourceOffset != 0) + return S_FALSE; + + return S_OK; +} + +STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback) +{ + COM_TRY_BEGIN + bool allFilesMode = (numItems == (UInt32)(Int32)-1); + if (allFilesMode) + numItems = Refs.Size(); + if (numItems == 0) + return S_OK; + UInt32 i; + UInt64 totalSize = 0; + for (i = 0; i < numItems; i++) + { + const CRef &ref = Refs[allFilesMode ? i : indices[i]]; + totalSize += Get_UnpackSize_of_Ref(ref); + } + RINOK(extractCallback->SetTotal(totalSize)); + + UInt64 currentTotalSize = 0, currentItemSize = 0; + + const size_t kBufSize = kCompressionBlockSize; + CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header + + NCompress::NZlib::CDecoder *_zlibDecoderSpec = NULL; + CMyComPtr<ICompressCoder> _zlibDecoder; + + for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) + { + RINOK(extractCallback->SetCompleted(¤tTotalSize)); + UInt32 index = allFilesMode ? i : indices[i]; + const CRef &ref = Refs[index]; + const CItem &item = Items[ref.ItemIndex]; + currentItemSize = Get_UnpackSize_of_Ref(ref); + + CMyComPtr<ISequentialOutStream> realOutStream; + Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + + if (ref.AttrIndex < 0 && item.IsDir()) + { + RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + continue; + } + if (!testMode && !realOutStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)); + UInt64 pos = 0; + int res = NExtract::NOperationResult::kDataError; + if (ref.AttrIndex >= 0) + { + res = NExtract::NOperationResult::kOK; + if (realOutStream) + { + const CAttr &attr = Attrs[ref.AttrIndex]; + RINOK(WriteStream(realOutStream, AttrBuf + attr.Pos, attr.Size)); + } + } + else if (item.UseAttr) + { + if (item.UseInlineData) + { + res = NExtract::NOperationResult::kOK; + if (realOutStream) + { + RINOK(WriteStream(realOutStream, AttrBuf + item.DataPos, (size_t)item.UnpackSize)); + } + } + else + { + if (!_zlibDecoder) + { + _zlibDecoderSpec = new NCompress::NZlib::CDecoder(); + _zlibDecoder = _zlibDecoderSpec; + } + + if (item.Method == kMethod_Attr) + { + CBufInStream *bufInStreamSpec = new CBufInStream; + CMyComPtr<ISequentialInStream> bufInStream = bufInStreamSpec; + bufInStreamSpec->Init(AttrBuf + item.DataPos, item.PackSize); + + HRESULT hres = _zlibDecoder->Code(bufInStream, realOutStream, NULL, &item.UnpackSize, NULL); + if (hres != S_FALSE) + { + if (hres != S_OK) + return hres; + if (_zlibDecoderSpec->GetOutputProcessedSize() == item.UnpackSize && + _zlibDecoderSpec->GetInputProcessedSize() == item.PackSize) + res = NExtract::NOperationResult::kOK; + } + } + else + { + HRESULT hres = ExtractZlibFile(realOutStream, item, _zlibDecoderSpec, buf, + currentTotalSize, extractCallback); + if (hres != S_FALSE) + { + if (hres != S_OK) + return hres; + res = NExtract::NOperationResult::kOK; + } + } + } + } + else + { + const CFork &fork = item.GetFork(ref.IsResource); + if (fork.IsOk(Header.BlockSizeLog)) + { + res = NExtract::NOperationResult::kOK; + unsigned extentIndex; + for (extentIndex = 0; extentIndex < fork.Extents.Size(); extentIndex++) + { + if (res != NExtract::NOperationResult::kOK) + break; + if (fork.Size == pos) + break; + const CExtent &e = fork.Extents[extentIndex]; + RINOK(_stream->Seek((UInt64)e.Pos << Header.BlockSizeLog, STREAM_SEEK_SET, NULL)); + UInt64 extentRem = (UInt64)e.NumBlocks << Header.BlockSizeLog; + while (extentRem != 0) + { + UInt64 rem = fork.Size - pos; + if (rem == 0) + { + // Here we check that there are no extra (empty) blocks in last extent. + if (extentRem >= (UInt64)((UInt32)1 << Header.BlockSizeLog)) + res = NExtract::NOperationResult::kDataError; + break; + } + size_t cur = kBufSize; + if (cur > rem) + cur = (size_t)rem; + if (cur > extentRem) + cur = (size_t)extentRem; + RINOK(ReadStream(_stream, buf, &cur)); + if (cur == 0) + { + res = NExtract::NOperationResult::kDataError; + break; + } + if (realOutStream) + { + RINOK(WriteStream(realOutStream, buf, cur)); + } + pos += cur; + extentRem -= cur; + UInt64 processed = currentTotalSize + pos; + RINOK(extractCallback->SetCompleted(&processed)); + } + } + if (extentIndex != fork.Extents.Size() || fork.Size != pos) + res = NExtract::NOperationResult::kDataError; + } + } + realOutStream.Release(); + RINOK(extractCallback->SetOperationResult(res)); + } + return S_OK; + COM_TRY_END +} + +STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +{ + *numItems = Refs.Size(); + return S_OK; +} + +HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream) +{ + *stream = 0; + + if (!fork.IsOk(Header.BlockSizeLog)) + return S_FALSE; + + CExtentsStream *extentStreamSpec = new CExtentsStream(); + CMyComPtr<ISequentialInStream> extentStream = extentStreamSpec; + + UInt64 rem = fork.Size; + UInt64 virt = 0; + + FOR_VECTOR (i, fork.Extents) + { + const CExtent &e = fork.Extents[i]; + if (e.NumBlocks == 0) + continue; + UInt64 cur = ((UInt64)e.NumBlocks << Header.BlockSizeLog); + if (cur > rem) + { + cur = rem; + if (i != fork.Extents.Size() - 1) + return S_FALSE; + } + CSeekExtent se; + se.Phy = (UInt64)e.Pos << Header.BlockSizeLog; + se.Virt = virt; + virt += cur; + rem -= cur; + extentStreamSpec->Extents.Add(se); + } + + if (rem != 0) + return S_FALSE; + + CSeekExtent se; + se.Phy = 0; + se.Virt = virt; + extentStreamSpec->Extents.Add(se); + extentStreamSpec->Stream = _stream; + extentStreamSpec->Init(); + *stream = extentStream.Detach(); + return S_OK; +} + +STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +{ + *stream = 0; + + const CRef &ref = Refs[index]; + if (ref.AttrIndex >= 0) + return S_FALSE; + const CItem &item = Items[ref.ItemIndex]; + if (item.IsDir() || item.UseAttr) + return S_FALSE; + + return GetForkStream(item.GetFork(ref.IsResource), stream); +} + +IMP_CreateArcIn + +static CArcInfo g_ArcInfo = + { "HFS", "hfs hfsx", 0, 0xE3, + 2 * (4 + 1), + { + 4, 'H', '+', 0, 4, + 4, 'H', 'X', 0, 5, + }, + kHeaderPadSize, + NArcInfoFlags::kMultiSignature, + CreateArc }; + +REGISTER_ARC(Hfs) + +}} |