// 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 char * const kResFileName = "rsrc"; // "com.apple.ResourceFork"; struct CExtent { UInt32 Pos; UInt32 NumBlocks; }; struct CIdExtents { UInt32 ID; UInt32 StartBlock; CRecordVector Extents; }; struct CFork { UInt64 Size; UInt32 NumBlocks; // UInt32 ClumpSize; CRecordVector 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 &items, UInt32 id); bool UpgradeAndTest(const CObjectVector &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 &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 &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 &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 *overflowExtentsArray); HRESULT LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress); HRESULT LoadCatalog(const CFork &fork, const CObjectVector *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress); bool Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip); public: CRecordVector Refs; CObjectVector Items; CObjectVector 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 Parse2(const CByteBuffer &buf); }; HRESULT CHeaderRec::Parse2(const CByteBuffer &buf) { if (buf.Size() < kNodeDescriptor_Size + 0x2A + 16 * 4) return S_FALSE; const Byte * p = (const Byte *)buf + kNodeDescriptor_Size; // TreeDepth = Get16(p); // RootNode = Get32(p + 2); // LeafRecords = Get32(p + 6); FirstLeafNode = Get32(p + 0xA); // LastLeafNode = Get32(p + 0xE); const 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); */ if ((buf.Size() >> NodeSizeLog) < TotalNodes) return S_FALSE; 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 *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.Parse2(buf)); 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++) { const UInt32 nodeSize = (UInt32)1 << hr.NodeSizeLog; const UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); const 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 &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.GetBuf(len); unsigned i; for (i = 0; i < len; i++) { wchar_t c = Get16(data + i * 2); if (c == 0) break; p[i] = c; } p[i] = 0; dest.ReleaseBuf_SetLen(i); } 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.Parse2(AttrBuf)); // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); 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++) { const UInt32 nodeSize = (1 << hr.NodeSizeLog); const UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); const 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.IsEqualTo("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 *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress) { unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles); Items.ClearAndReserve(reserveSize); Refs.ClearAndReserve(reserveSize); CRecordVector IdToIndexMap; IdToIndexMap.ClearAndReserve(reserveSize); CByteBuffer buf; RINOK(ReadFile(fork, buf, inStream)); const Byte *p = (const Byte *)buf; // CNodeDescriptor nodeDesc; // nodeDesc.Parse(p); CHeaderRec hr; RINOK(hr.Parse2(buf)); // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); 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; const 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++) { const UInt32 nodeSize = (1 << hr.NodeSizeLog); const UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); const UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); UInt32 recSize = offsNext - offs; if (offs >= nodeSize || offsNext > 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 = "[HFS+ Private Data]"; } } // Some dmg files have ' ' folder item. if (item.Name.IsEmpty() || item.Name[0] == L' ') item.Name = "[]"; } } 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 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 _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 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 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); if (size > buf.Size() || size > kCompressionBlockSize + 1) return S_FALSE; 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 _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 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 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)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 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); } static const Byte k_Signature[] = { 4, 'H', '+', 0, 4, 4, 'H', 'X', 0, 5 }; REGISTER_ARC_I( "HFS", "hfs hfsx", 0, 0xE3, k_Signature, kHeaderPadSize, NArcInfoFlags::kMultiSignature, NULL) }}