xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/HfsHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // HfsHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/MyString.h"
9 
10 #include "../../Windows/PropVariantUtils.h"
11 
12 #include "../Common/LimitedStreams.h"
13 #include "../Common/RegisterArc.h"
14 #include "../Common/StreamObjects.h"
15 #include "../Common/StreamUtils.h"
16 
17 #include "HfsHandler.h"
18 
19 /* if HFS_SHOW_ALT_STREAMS is defined, the handler will show attribute files
20    and resource forks. In most cases it looks useless. So we disable it. */
21 
22 #define HFS_SHOW_ALT_STREAMS
23 
24 #define Get16(p) GetBe16(p)
25 #define Get32(p) GetBe32(p)
26 #define Get64(p) GetBe64(p)
27 
28 #define Get16a(p) GetBe16a(p)
29 #define Get32a(p) GetBe32a(p)
30 
31 namespace NArchive {
32 namespace NHfs {
33 
34 static const char * const kResFileName = "rsrc"; // "com.apple.ResourceFork";
35 
36 struct CExtent
37 {
38   UInt32 Pos;
39   UInt32 NumBlocks;
40 };
41 
42 struct CIdExtents
43 {
44   UInt32 ID;
45   UInt32 StartBlock;
46   CRecordVector<CExtent> Extents;
47 };
48 
49 struct CFork
50 {
51   UInt64 Size;
52   UInt32 NumBlocks;
53   // UInt32 ClumpSize;
54   CRecordVector<CExtent> Extents;
55 
CForkNArchive::NHfs::CFork56   CFork(): Size(0), NumBlocks(0) {}
57 
58   void Parse(const Byte *p);
59 
IsEmptyNArchive::NHfs::CFork60   bool IsEmpty() const { return Size == 0 && NumBlocks == 0 && Extents.Size() == 0; }
61 
62   UInt32 Calc_NumBlocks_from_Extents() const;
63   bool Check_NumBlocks() const;
64 
Check_Size_with_NumBlocksNArchive::NHfs::CFork65   bool Check_Size_with_NumBlocks(unsigned blockSizeLog) const
66   {
67     return Size <= ((UInt64)NumBlocks << blockSizeLog);
68   }
69 
IsOkNArchive::NHfs::CFork70   bool IsOk(unsigned blockSizeLog) const
71   {
72     // we don't check cases with extra (empty) blocks in last extent
73     return Check_NumBlocks() && Check_Size_with_NumBlocks(blockSizeLog);
74   }
75 
76   bool Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id);
UpgradeAndTestNArchive::NHfs::CFork77   bool UpgradeAndTest(const CObjectVector<CIdExtents> &items, UInt32 id, unsigned blockSizeLog)
78   {
79     if (!Upgrade(items, id))
80       return false;
81     return IsOk(blockSizeLog);
82   }
83 };
84 
85 static const unsigned kNumFixedExtents = 8;
86 static const unsigned kForkRecSize = 16 + kNumFixedExtents * 8;
87 
88 
Parse(const Byte * p)89 void CFork::Parse(const Byte *p)
90 {
91   Extents.Clear();
92   Size = Get64(p);
93   // ClumpSize = Get32(p + 8);
94   NumBlocks = Get32(p + 12);
95   p += 16;
96   for (unsigned i = 0; i < kNumFixedExtents; i++, p += 8)
97   {
98     CExtent e;
99     e.Pos = Get32(p);
100     e.NumBlocks = Get32(p + 4);
101     if (e.NumBlocks != 0)
102       Extents.Add(e);
103   }
104 }
105 
Calc_NumBlocks_from_Extents() const106 UInt32 CFork::Calc_NumBlocks_from_Extents() const
107 {
108   UInt32 num = 0;
109   FOR_VECTOR (i, Extents)
110     num += Extents[i].NumBlocks;
111   return num;
112 }
113 
Check_NumBlocks() const114 bool CFork::Check_NumBlocks() const
115 {
116   UInt32 num = NumBlocks;
117   FOR_VECTOR (i, Extents)
118   {
119     const UInt32 cur = Extents[i].NumBlocks;
120     if (num < cur)
121       return false;
122     num -= cur;
123   }
124   return num == 0;
125 }
126 
127 struct CIdIndexPair
128 {
129   UInt32 ID;
130   unsigned Index;
131 
132   int Compare(const CIdIndexPair &a) const;
133 };
134 
135 #define RINOZ(x) { const int _t_ = (x); if (_t_ != 0) return _t_; }
136 
Compare(const CIdIndexPair & a) const137 int CIdIndexPair::Compare(const CIdIndexPair &a) const
138 {
139   RINOZ(MyCompare(ID, a.ID))
140   return MyCompare(Index, a.Index);
141 }
142 
FindItemIndex(const CRecordVector<CIdIndexPair> & items,UInt32 id)143 static int FindItemIndex(const CRecordVector<CIdIndexPair> &items, UInt32 id)
144 {
145   unsigned left = 0, right = items.Size();
146   while (left != right)
147   {
148     const unsigned mid = (left + right) / 2;
149     const UInt32 midVal = items[mid].ID;
150     if (id == midVal)
151       return (int)items[mid].Index;
152     if (id < midVal)
153       right = mid;
154     else
155       left = mid + 1;
156   }
157   return -1;
158 }
159 
Find_in_IdExtents(const CObjectVector<CIdExtents> & items,UInt32 id)160 static int Find_in_IdExtents(const CObjectVector<CIdExtents> &items, UInt32 id)
161 {
162   unsigned left = 0, right = items.Size();
163   while (left != right)
164   {
165     const unsigned mid = (left + right) / 2;
166     const UInt32 midVal = items[mid].ID;
167     if (id == midVal)
168       return (int)mid;
169     if (id < midVal)
170       right = mid;
171     else
172       left = mid + 1;
173   }
174   return -1;
175 }
176 
Upgrade(const CObjectVector<CIdExtents> & items,UInt32 id)177 bool CFork::Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id)
178 {
179   const int index = Find_in_IdExtents(items, id);
180   if (index < 0)
181     return true;
182   const CIdExtents &item = items[index];
183   if (Calc_NumBlocks_from_Extents() != item.StartBlock)
184     return false;
185   Extents += item.Extents;
186   return true;
187 }
188 
189 
190 struct CVolHeader
191 {
192   unsigned BlockSizeLog;
193   UInt32 NumFiles;
194   UInt32 NumFolders;
195   UInt32 NumBlocks;
196   UInt32 NumFreeBlocks;
197 
198   bool Is_Hsfx_ver5;
199   // UInt32 Attr;
200   // UInt32 LastMountedVersion;
201   // UInt32 JournalInfoBlock;
202 
203   UInt32 CTime;
204   UInt32 MTime;
205   // UInt32 BackupTime;
206   // UInt32 CheckedTime;
207 
208   // UInt32 WriteCount;
209   // UInt32 FinderInfo[8];
210   // UInt64 VolID;
211 
GetPhySizeNArchive::NHfs::CVolHeader212   UInt64 GetPhySize() const { return (UInt64)NumBlocks << BlockSizeLog; }
GetFreeSizeNArchive::NHfs::CVolHeader213   UInt64 GetFreeSize() const { return (UInt64)NumFreeBlocks << BlockSizeLog; }
IsHfsXNArchive::NHfs::CVolHeader214   bool IsHfsX() const { return Is_Hsfx_ver5; }
215 };
216 
HfsTimeToFileTime(UInt32 hfsTime,FILETIME & ft)217 inline void HfsTimeToFileTime(UInt32 hfsTime, FILETIME &ft)
218 {
219   UInt64 v = ((UInt64)3600 * 24 * (365 * 303 + 24 * 3) + hfsTime) * 10000000;
220   ft.dwLowDateTime = (DWORD)v;
221   ft.dwHighDateTime = (DWORD)(v >> 32);
222 }
223 
224 enum ERecordType
225 {
226   RECORD_TYPE_FOLDER = 1,
227   RECORD_TYPE_FILE,
228   RECORD_TYPE_FOLDER_THREAD,
229   RECORD_TYPE_FILE_THREAD
230 };
231 
232 
233 
234 // static const UInt32 kMethod_1_NO_COMPRESSION = 1; // in xattr
235 static const UInt32 kMethod_ZLIB_ATTR = 3;
236 static const UInt32 kMethod_ZLIB_RSRC = 4;
237 // static const UInt32 kMethod_DEDUP = 5; // de-dup within the generation store
238 // macos 10.10
239 static const UInt32 kMethod_LZVN_ATTR = 7;
240 static const UInt32 kMethod_LZVN_RSRC = 8;
241 static const UInt32 kMethod_COPY_ATTR = 9;
242 static const UInt32 kMethod_COPY_RSRC = 10;
243 // macos 10.11
244 // static const UInt32 kMethod_LZFSE_ATTR = 11;
245 static const UInt32 kMethod_LZFSE_RSRC = 12;
246 
247 // static const UInt32 kMethod_ZBM_RSRC = 14;
248 
249 static const char * const g_Methods[] =
250 {
251     NULL
252   , NULL
253   , NULL
254   , "ZLIB-attr"
255   , "ZLIB-rsrc"
256   , NULL
257   , NULL
258   , "LZVN-attr"
259   , "LZVN-rsrc"
260   , "COPY-attr"
261   , "COPY-rsrc"
262   , "LZFSE-attr"
263   , "LZFSE-rsrc"
264   , NULL
265   , "ZBM-rsrc"
266 };
267 
268 
269 static const Byte k_COPY_Uncompressed_Marker = 0xcc;
270 static const Byte k_LZVN_Uncompressed_Marker = 6;
271 
Parse(const Byte * p,size_t dataSize)272 void CCompressHeader::Parse(const Byte *p, size_t dataSize)
273 {
274   Clear();
275 
276   if (dataSize < k_decmpfs_HeaderSize)
277     return;
278   if (GetUi32(p) != 0x636D7066) // magic == "fpmc"
279     return;
280   Method = GetUi32(p + 4);
281   UnpackSize = GetUi64(p + 8);
282   dataSize -= k_decmpfs_HeaderSize;
283   IsCorrect = true;
284 
285   if (   Method == kMethod_ZLIB_RSRC
286       || Method == kMethod_COPY_RSRC
287       || Method == kMethod_LZVN_RSRC
288       || Method == kMethod_LZFSE_RSRC
289       // || Method == kMethod_ZBM_RSRC // for debug
290       )
291   {
292     IsResource = true;
293     if (dataSize == 0)
294       IsSupported = (
295           Method != kMethod_LZFSE_RSRC &&
296           Method != kMethod_COPY_RSRC);
297     return;
298   }
299 
300   if (   Method == kMethod_ZLIB_ATTR
301       || Method == kMethod_COPY_ATTR
302       || Method == kMethod_LZVN_ATTR
303       // || Method == kMethod_LZFSE_ATTR
304     )
305   {
306     if (dataSize == 0)
307       return;
308     const Byte b = p[k_decmpfs_HeaderSize];
309     if (   (Method == kMethod_ZLIB_ATTR && (b & 0xf) == 0xf)
310         || (Method == kMethod_COPY_ATTR && b == k_COPY_Uncompressed_Marker)
311         || (Method == kMethod_LZVN_ATTR && b == k_LZVN_Uncompressed_Marker))
312     {
313       dataSize--;
314       // if (UnpackSize > dataSize)
315       if (UnpackSize != dataSize)
316         return;
317       DataPos = k_decmpfs_HeaderSize + 1;
318       IsSupported = true;
319     }
320     else
321     {
322       if (Method != kMethod_COPY_ATTR)
323         IsSupported = true;
324       DataPos = k_decmpfs_HeaderSize;
325     }
326   }
327 }
328 
329 
MethodToProp(NWindows::NCOM::CPropVariant & prop) const330 void CCompressHeader::MethodToProp(NWindows::NCOM::CPropVariant &prop) const
331 {
332   if (!IsCorrect)
333     return;
334   const UInt32 method = Method;
335   const char *p = NULL;
336   if (method < Z7_ARRAY_SIZE(g_Methods))
337     p = g_Methods[method];
338   AString s;
339   if (p)
340     s = p;
341   else
342     s.Add_UInt32(method);
343   // if (!IsSupported) s += "-unsuported";
344   prop = s;
345 }
346 
MethodsMaskToProp(UInt32 methodsMask,NWindows::NCOM::CPropVariant & prop)347 void MethodsMaskToProp(UInt32 methodsMask, NWindows::NCOM::CPropVariant &prop)
348 {
349   FLAGS_TO_PROP(g_Methods, methodsMask, prop);
350 }
351 
352 
353 struct CItem
354 {
355   UString Name;
356 
357   UInt32 ParentID;
358 
359   UInt16 Type;
360   UInt16 FileMode;
361   // UInt16 Flags;
362   // UInt32 Valence;
363   UInt32 ID;
364   UInt32 CTime;
365   UInt32 MTime;
366   UInt32 AttrMTime;
367   UInt32 ATime;
368   // UInt32 BackupDate;
369 
370   /*
371   UInt32 OwnerID;
372   UInt32 GroupID;
373   Byte AdminFlags;
374   Byte OwnerFlags;
375   union
376   {
377     UInt32  iNodeNum;
378     UInt32  LinkCount;
379     UInt32  RawDevice;
380   } special;
381 
382   UInt32 FileType;
383   UInt32 FileCreator;
384   UInt16 FinderFlags;
385   UInt16 Point[2];
386   */
387 
388   CFork DataFork;
389   CFork ResourceFork;
390 
391   // for compressed attribute (decmpfs)
392   int decmpfs_AttrIndex;
393   CCompressHeader CompressHeader;
394 
CItemNArchive::NHfs::CItem395   CItem():
396       decmpfs_AttrIndex(-1)
397       {}
IsDirNArchive::NHfs::CItem398   bool IsDir() const { return Type == RECORD_TYPE_FOLDER; }
399   // const CFork *GetFork(bool isResource) const { return (isResource ? &ResourceFork: &DataFork); }
400 };
401 
402 
403 struct CAttr
404 {
405   UInt32 ID;
406   bool Fork_defined;
407 
408   // UInt32 Size;    // for (Fork_defined == false) case
409   // size_t DataPos; // for (Fork_defined == false) case
410   CByteBuffer Data;
411 
412   CFork Fork;
413 
414   UString Name;
415 
GetSizeNArchive::NHfs::CAttr416   UInt64 GetSize() const
417   {
418     if (Fork_defined)
419       return Fork.Size;
420     return Data.Size();
421   }
422 
CAttrNArchive::NHfs::CAttr423   CAttr():
424       Fork_defined(false)
425       // Size(0),
426       // DataPos(0),
427       {}
428 };
429 
430 
431 static const int kAttrIndex_Item     = -1;
432 static const int kAttrIndex_Resource = -2;
433 
434 struct CRef
435 {
436   unsigned ItemIndex;
437   int AttrIndex;
438   int Parent;
439 
CRefNArchive::NHfs::CRef440   CRef(): AttrIndex(kAttrIndex_Item), Parent(-1) {}
IsResourceNArchive::NHfs::CRef441   bool IsResource() const { return AttrIndex == kAttrIndex_Resource; }
IsAltStreamNArchive::NHfs::CRef442   bool IsAltStream() const { return AttrIndex != kAttrIndex_Item; }
IsItemNArchive::NHfs::CRef443   bool IsItem() const { return AttrIndex == kAttrIndex_Item; }
444 };
445 
446 
447 class CDatabase
448 {
449   HRESULT ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream);
450   HRESULT LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray);
451   HRESULT LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress);
452   HRESULT LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress);
453   bool Parse_decmpgfs(unsigned attrIndex, CItem &item, bool &skip);
454 public:
455   CRecordVector<CRef> Refs;
456   CObjectVector<CItem> Items;
457   CObjectVector<CAttr> Attrs;
458 
459   // CByteBuffer AttrBuf;
460 
461   CVolHeader Header;
462   bool HeadersError;
463   bool UnsupportedFeature;
464   bool ThereAreAltStreams;
465   // bool CaseSensetive;
466   UInt32 MethodsMask;
467   UString ResFileName;
468 
469   UInt64 SpecOffset;
470   // UInt64 PhySize;
471   UInt64 PhySize2;
472   UInt64 ArcFileSize;
473 
Clear()474   void Clear()
475   {
476     SpecOffset = 0;
477     // PhySize = 0;
478     PhySize2 = 0;
479     ArcFileSize = 0;
480     MethodsMask = 0;
481     HeadersError = false;
482     UnsupportedFeature = false;
483     ThereAreAltStreams = false;
484     // CaseSensetive = false;
485 
486     Refs.Clear();
487     Items.Clear();
488     Attrs.Clear();
489     // AttrBuf.Free();
490   }
491 
Get_UnpackSize_of_Ref(const CRef & ref) const492   UInt64 Get_UnpackSize_of_Ref(const CRef &ref) const
493   {
494     if (ref.AttrIndex >= 0)
495       return Attrs[ref.AttrIndex].GetSize();
496     const CItem &item = Items[ref.ItemIndex];
497     if (ref.IsResource())
498       return item.ResourceFork.Size;
499     if (item.IsDir())
500       return 0;
501     else if (item.CompressHeader.IsCorrect)
502       return item.CompressHeader.UnpackSize;
503     return item.DataFork.Size;
504   }
505 
506   void GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const;
507   HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *progress);
508 };
509 
510 enum
511 {
512   kHfsID_Root                  = 1,
513   kHfsID_RootFolder            = 2,
514   kHfsID_ExtentsFile           = 3,
515   kHfsID_CatalogFile           = 4,
516   kHfsID_BadBlockFile          = 5,
517   kHfsID_AllocationFile        = 6,
518   kHfsID_StartupFile           = 7,
519   kHfsID_AttributesFile        = 8,
520   kHfsID_RepairCatalogFile     = 14,
521   kHfsID_BogusExtentFile       = 15,
522   kHfsID_FirstUserCatalogNode  = 16
523 };
524 
GetItemPath(unsigned index,NWindows::NCOM::CPropVariant & path) const525 void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const
526 {
527   unsigned len = 0;
528   const unsigned kNumLevelsMax = (1 << 10);
529   unsigned cur = index;
530   unsigned i;
531 
532   for (i = 0; i < kNumLevelsMax; i++)
533   {
534     const CRef &ref = Refs[cur];
535     const UString *s;
536 
537     if (ref.IsResource())
538       s = &ResFileName;
539     else if (ref.AttrIndex >= 0)
540       s = &Attrs[ref.AttrIndex].Name;
541     else
542       s = &Items[ref.ItemIndex].Name;
543 
544     len += s->Len();
545     len++;
546     cur = (unsigned)ref.Parent;
547     if (ref.Parent < 0)
548       break;
549   }
550 
551   len--;
552   wchar_t *p = path.AllocBstr(len);
553   p[len] = 0;
554   cur = index;
555 
556   for (;;)
557   {
558     const CRef &ref = Refs[cur];
559     const UString *s;
560     wchar_t delimChar = L':';
561 
562     if (ref.IsResource())
563       s = &ResFileName;
564     else if (ref.AttrIndex >= 0)
565       s = &Attrs[ref.AttrIndex].Name;
566     else
567     {
568       delimChar = WCHAR_PATH_SEPARATOR;
569       s = &Items[ref.ItemIndex].Name;
570     }
571 
572     unsigned curLen = s->Len();
573     len -= curLen;
574 
575     const wchar_t *src = (const wchar_t *)*s;
576     wchar_t *dest = p + len;
577     for (unsigned j = 0; j < curLen; j++)
578     {
579       wchar_t c = src[j];
580       // 18.06
581       if (c == CHAR_PATH_SEPARATOR || c == '/')
582         c = '_';
583       dest[j] = c;
584     }
585 
586     if (len == 0)
587       break;
588     p[--len] = delimChar;
589     cur = (unsigned)ref.Parent;
590   }
591 }
592 
593 // Actually we read all blocks. It can be larger than fork.Size
594 
ReadFile(const CFork & fork,CByteBuffer & buf,IInStream * inStream)595 HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream)
596 {
597   if (fork.NumBlocks >= Header.NumBlocks)
598     return S_FALSE;
599   if (((ArcFileSize - SpecOffset) >> Header.BlockSizeLog) + 1 < fork.NumBlocks)
600     return S_FALSE;
601 
602   const size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog;
603   if ((totalSize >> Header.BlockSizeLog) != fork.NumBlocks)
604     return S_FALSE;
605   buf.Alloc(totalSize);
606   UInt32 curBlock = 0;
607   FOR_VECTOR (i, fork.Extents)
608   {
609     if (curBlock >= fork.NumBlocks)
610       return S_FALSE;
611     const CExtent &e = fork.Extents[i];
612     if (e.Pos > Header.NumBlocks ||
613         e.NumBlocks > fork.NumBlocks - curBlock ||
614         e.NumBlocks > Header.NumBlocks - e.Pos)
615       return S_FALSE;
616     RINOK(InStream_SeekSet(inStream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog)))
617     RINOK(ReadStream_FALSE(inStream,
618         (Byte *)buf + ((size_t)curBlock << Header.BlockSizeLog),
619         (size_t)e.NumBlocks << Header.BlockSizeLog))
620     curBlock += e.NumBlocks;
621   }
622   return S_OK;
623 }
624 
625 static const unsigned kNodeDescriptor_Size = 14;
626 
627 struct CNodeDescriptor
628 {
629   UInt32 fLink;
630   // UInt32 bLink;
631   Byte Kind;
632   // Byte Height;
633   unsigned NumRecords;
634 
635   bool Parse(const Byte *p, unsigned nodeSizeLog);
636 };
637 
638 
Parse(const Byte * p,unsigned nodeSizeLog)639 bool CNodeDescriptor::Parse(const Byte *p, unsigned nodeSizeLog)
640 {
641   fLink = Get32(p);
642   // bLink = Get32(p + 4);
643   Kind = p[8];
644   // Height = p[9];
645   NumRecords = Get16(p + 10);
646 
647   const size_t nodeSize = (size_t)1 << nodeSizeLog;
648   if (kNodeDescriptor_Size + ((UInt32)NumRecords + 1) * 2 > nodeSize)
649     return false;
650   const size_t limit = nodeSize - ((UInt32)NumRecords + 1) * 2;
651 
652   p += nodeSize - 2;
653 
654   for (unsigned i = 0; i < NumRecords; i++)
655   {
656     const UInt32 offs = Get16(p);
657     p -= 2;
658     const UInt32 offsNext = Get16(p);
659     if (offs < kNodeDescriptor_Size
660         || offs >= offsNext
661         || offsNext > limit)
662       return false;
663   }
664   return true;
665 }
666 
667 struct CHeaderRec
668 {
669   // UInt16 TreeDepth;
670   // UInt32 RootNode;
671   // UInt32 LeafRecords;
672   UInt32 FirstLeafNode;
673   // UInt32 LastLeafNode;
674   unsigned NodeSizeLog;
675   // UInt16 MaxKeyLength;
676   UInt32 TotalNodes;
677   // UInt32 FreeNodes;
678   // UInt16 Reserved1;
679   // UInt32 ClumpSize;
680   // Byte BtreeType;
681   // Byte KeyCompareType;
682   // UInt32 Attributes;
683   // UInt32 Reserved3[16];
684 
685   HRESULT Parse2(const CByteBuffer &buf);
686 };
687 
Parse2(const CByteBuffer & buf)688 HRESULT CHeaderRec::Parse2(const CByteBuffer &buf)
689 {
690   if (buf.Size() < kNodeDescriptor_Size + 0x2A + 16 * 4)
691     return S_FALSE;
692   const Byte * p = (const Byte *)buf + kNodeDescriptor_Size;
693   // TreeDepth = Get16(p);
694   // RootNode = Get32(p + 2);
695   // LeafRecords = Get32(p + 6);
696   FirstLeafNode = Get32(p + 0xA);
697   // LastLeafNode = Get32(p + 0xE);
698   const UInt32 nodeSize = Get16(p + 0x12);
699 
700   unsigned i;
701   for (i = 9; ((UInt32)1 << i) != nodeSize; i++)
702     if (i == 16)
703       return S_FALSE;
704   NodeSizeLog = i;
705 
706   // MaxKeyLength = Get16(p + 0x14);
707   TotalNodes = Get32(p + 0x16);
708   // FreeNodes = Get32(p + 0x1A);
709   // Reserved1 = Get16(p + 0x1E);
710   // ClumpSize = Get32(p + 0x20);
711   // BtreeType = p[0x24];
712   // KeyCompareType = p[0x25];
713   // Attributes = Get32(p + 0x26);
714   /*
715   for (int i = 0; i < 16; i++)
716     Reserved3[i] = Get32(p + 0x2A + i * 4);
717   */
718 
719   if ((buf.Size() >> NodeSizeLog) < TotalNodes)
720     return S_FALSE;
721 
722   return S_OK;
723 }
724 
725 
726 static const Byte kNodeType_Leaf   = 0xFF;
727 // static const Byte kNodeType_Index  = 0;
728 // static const Byte kNodeType_Header = 1;
729 // static const Byte kNodeType_Mode   = 2;
730 
731 static const Byte kExtentForkType_Data = 0;
732 static const Byte kExtentForkType_Resource = 0xFF;
733 
734 /* It loads data extents from Extents Overflow File
735    Most dmg installers are not fragmented. So there are no extents in Overflow File. */
736 
LoadExtentFile(const CFork & fork,IInStream * inStream,CObjectVector<CIdExtents> * overflowExtentsArray)737 HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector<CIdExtents> *overflowExtentsArray)
738 {
739   if (fork.NumBlocks == 0)
740     return S_OK;
741   CByteBuffer buf;
742   RINOK(ReadFile(fork, buf, inStream))
743   const Byte *p = (const Byte *)buf;
744 
745   // CNodeDescriptor nodeDesc;
746   // nodeDesc.Parse(p);
747   CHeaderRec hr;
748   RINOK(hr.Parse2(buf))
749 
750   UInt32 node = hr.FirstLeafNode;
751   if (node == 0)
752     return S_OK;
753   if (hr.TotalNodes == 0)
754     return S_FALSE;
755 
756   CByteArr usedBuf(hr.TotalNodes);
757   memset(usedBuf, 0, hr.TotalNodes);
758 
759   while (node != 0)
760   {
761     if (node >= hr.TotalNodes || usedBuf[node] != 0)
762       return S_FALSE;
763     usedBuf[node] = 1;
764 
765     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
766     CNodeDescriptor desc;
767     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
768       return S_FALSE;
769     if (desc.Kind != kNodeType_Leaf)
770       return S_FALSE;
771 
772     UInt32 endBlock = 0;
773 
774     for (unsigned i = 0; i < desc.NumRecords; i++)
775     {
776       const UInt32 nodeSize = ((UInt32)1 << hr.NodeSizeLog);
777       const Byte *r = p + nodeOffset + nodeSize - i * 2;
778       const UInt32 offs = Get16(r - 2);
779       UInt32 recSize = Get16(r - 4) - offs;
780       const unsigned kKeyLen = 10;
781 
782       if (recSize != 2 + kKeyLen + kNumFixedExtents * 8)
783         return S_FALSE;
784 
785       r = p + nodeOffset + offs;
786       if (Get16(r) != kKeyLen)
787         return S_FALSE;
788 
789       const Byte forkType = r[2];
790       unsigned forkTypeIndex;
791       if (forkType == kExtentForkType_Data)
792         forkTypeIndex = 0;
793       else if (forkType == kExtentForkType_Resource)
794         forkTypeIndex = 1;
795       else
796         continue;
797       CObjectVector<CIdExtents> &overflowExtents = overflowExtentsArray[forkTypeIndex];
798 
799       const UInt32 id = Get32(r + 4);
800       const UInt32 startBlock = Get32(r + 8);
801       r += 2 + kKeyLen;
802 
803       bool needNew = true;
804 
805       if (overflowExtents.Size() != 0)
806       {
807         CIdExtents &e = overflowExtents.Back();
808         if (e.ID == id)
809         {
810           if (endBlock != startBlock)
811             return S_FALSE;
812           needNew = false;
813         }
814       }
815 
816       if (needNew)
817       {
818         CIdExtents &e = overflowExtents.AddNew();
819         e.ID = id;
820         e.StartBlock = startBlock;
821         endBlock = startBlock;
822       }
823 
824       CIdExtents &e = overflowExtents.Back();
825 
826       for (unsigned k = 0; k < kNumFixedExtents; k++, r += 8)
827       {
828         CExtent ee;
829         ee.Pos = Get32(r);
830         ee.NumBlocks = Get32(r + 4);
831         if (ee.NumBlocks != 0)
832         {
833           e.Extents.Add(ee);
834           endBlock += ee.NumBlocks;
835         }
836       }
837     }
838 
839     node = desc.fLink;
840   }
841   return S_OK;
842 }
843 
LoadName(const Byte * data,unsigned len,UString & dest)844 static void LoadName(const Byte *data, unsigned len, UString &dest)
845 {
846   wchar_t *p = dest.GetBuf(len);
847   unsigned i;
848   for (i = 0; i < len; i++)
849   {
850     const wchar_t c = Get16(data + i * 2);
851     if (c == 0)
852       break;
853     p[i] = c;
854   }
855   p[i] = 0;
856   dest.ReleaseBuf_SetLen(i);
857 }
858 
IsNameEqualTo(const Byte * data,const char * name)859 static bool IsNameEqualTo(const Byte *data, const char *name)
860 {
861   for (unsigned i = 0;; i++)
862   {
863     const char c = name[i];
864     if (c == 0)
865       return true;
866     if (Get16(data + i * 2) != (Byte)c)
867       return false;
868   }
869 }
870 
871 static const UInt32 kAttrRecordType_Inline = 0x10;
872 static const UInt32 kAttrRecordType_Fork = 0x20;
873 // static const UInt32 kAttrRecordType_Extents = 0x30;
874 
LoadAttrs(const CFork & fork,IInStream * inStream,IArchiveOpenCallback * progress)875 HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress)
876 {
877   if (fork.NumBlocks == 0)
878     return S_OK;
879 
880   CByteBuffer AttrBuf;
881   RINOK(ReadFile(fork, AttrBuf, inStream))
882   const Byte *p = (const Byte *)AttrBuf;
883 
884   // CNodeDescriptor nodeDesc;
885   // nodeDesc.Parse(p);
886   CHeaderRec hr;
887   RINOK(hr.Parse2(AttrBuf))
888 
889   // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC);
890 
891   UInt32 node = hr.FirstLeafNode;
892   if (node == 0)
893     return S_OK;
894   if (hr.TotalNodes == 0)
895     return S_FALSE;
896 
897   CByteArr usedBuf(hr.TotalNodes);
898   memset(usedBuf, 0, hr.TotalNodes);
899 
900   CFork resFork;
901 
902   while (node != 0)
903   {
904     if (node >= hr.TotalNodes || usedBuf[node] != 0)
905       return S_FALSE;
906     usedBuf[node] = 1;
907 
908     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
909     CNodeDescriptor desc;
910     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
911       return S_FALSE;
912     if (desc.Kind != kNodeType_Leaf)
913       return S_FALSE;
914 
915     for (unsigned i = 0; i < desc.NumRecords; i++)
916     {
917       const UInt32 nodeSize = ((UInt32)1 << hr.NodeSizeLog);
918       const Byte *r = p + nodeOffset + nodeSize - i * 2;
919       const UInt32 offs = Get16(r - 2);
920       UInt32 recSize = Get16(r - 4) - offs;
921       const unsigned kHeadSize = 14;
922       if (recSize < kHeadSize)
923         return S_FALSE;
924 
925       r = p + nodeOffset + offs;
926       const UInt32 keyLen = Get16(r);
927 
928       // UInt16 pad = Get16(r + 2);
929       const UInt32 fileID = Get32(r + 4);
930       const unsigned startBlock = Get32(r + 8);
931       if (startBlock != 0)
932       {
933         // that case is still unsupported
934         UnsupportedFeature = true;
935         continue;
936       }
937       const unsigned nameLen = Get16(r + 12);
938 
939       if (keyLen + 2 > recSize ||
940           keyLen != kHeadSize - 2 + nameLen * 2)
941         return S_FALSE;
942       r += kHeadSize;
943       recSize -= kHeadSize;
944 
945       const Byte *name = r;
946       r += nameLen * 2;
947       recSize -= nameLen * 2;
948 
949       if (recSize < 4)
950         return S_FALSE;
951 
952       const UInt32 recordType = Get32(r);
953 
954       if (progress && (Attrs.Size() & 0xFFF) == 0)
955       {
956         const UInt64 numFiles = 0;
957         RINOK(progress->SetCompleted(&numFiles, NULL))
958       }
959 
960       if (Attrs.Size() >= ((UInt32)1 << 31))
961         return S_FALSE;
962 
963       CAttr &attr = Attrs.AddNew();
964       attr.ID = fileID;
965       LoadName(name, nameLen, attr.Name);
966 
967       if (recordType == kAttrRecordType_Fork)
968       {
969         // 22.00 : some hfs files contain it;
970         /* spec: If the attribute has more than 8 extents, there will be additional
971             records (of type kAttrRecordType_Extents) for this attribute. */
972         if (recSize != 8 + kForkRecSize)
973           return S_FALSE;
974         if (Get32(r + 4) != 0) // reserved
975           return S_FALSE;
976         attr.Fork.Parse(r + 8);
977         attr.Fork_defined = true;
978         continue;
979       }
980       else if (recordType != kAttrRecordType_Inline)
981       {
982         UnsupportedFeature = true;
983         continue;
984       }
985 
986       const unsigned kRecordHeaderSize = 16;
987       if (recSize < kRecordHeaderSize)
988         return S_FALSE;
989       if (Get32(r + 4) != 0 || Get32(r + 8) != 0) // reserved
990         return S_FALSE;
991       const UInt32 dataSize = Get32(r + 12);
992 
993       r += kRecordHeaderSize;
994       recSize -= kRecordHeaderSize;
995 
996       if (recSize < dataSize)
997         return S_FALSE;
998 
999       attr.Data.CopyFrom(r, dataSize);
1000       // attr.DataPos = nodeOffset + offs + 2 + keyLen + kRecordHeaderSize;
1001       // attr.Size = dataSize;
1002     }
1003 
1004     node = desc.fLink;
1005   }
1006   return S_OK;
1007 }
1008 
1009 
Parse_decmpgfs(unsigned attrIndex,CItem & item,bool & skip)1010 bool CDatabase::Parse_decmpgfs(unsigned attrIndex, CItem &item, bool &skip)
1011 {
1012   const CAttr &attr = Attrs[attrIndex];
1013   skip = false;
1014   if (item.CompressHeader.IsCorrect || !item.DataFork.IsEmpty())
1015     return false;
1016 
1017   item.CompressHeader.Parse(attr.Data, attr.Data.Size());
1018 
1019   if (item.CompressHeader.IsCorrect)
1020   {
1021     item.decmpfs_AttrIndex = (int)attrIndex;
1022     skip = true;
1023     if (item.CompressHeader.Method < sizeof(MethodsMask) * 8)
1024       MethodsMask |= ((UInt32)1 << item.CompressHeader.Method);
1025   }
1026 
1027   return true;
1028 }
1029 
1030 
LoadCatalog(const CFork & fork,const CObjectVector<CIdExtents> * overflowExtentsArray,IInStream * inStream,IArchiveOpenCallback * progress)1031 HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector<CIdExtents> *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress)
1032 {
1033   CByteBuffer buf;
1034   RINOK(ReadFile(fork, buf, inStream))
1035   const Byte *p = (const Byte *)buf;
1036 
1037   // CNodeDescriptor nodeDesc;
1038   // nodeDesc.Parse(p);
1039   CHeaderRec hr;
1040   RINOK(hr.Parse2(buf))
1041 
1042   CRecordVector<CIdIndexPair> IdToIndexMap;
1043 
1044   const unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles);
1045 
1046   const unsigned kBasicRecSize = 0x58;
1047   const unsigned kMinRecSize = kBasicRecSize + 10;
1048 
1049   if ((UInt64)reserveSize * kMinRecSize < buf.Size())
1050   {
1051     Items.ClearAndReserve(reserveSize);
1052     Refs.ClearAndReserve(reserveSize);
1053     IdToIndexMap.ClearAndReserve(reserveSize);
1054   }
1055 
1056   // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC);
1057 
1058   CByteArr usedBuf(hr.TotalNodes);
1059   if (hr.TotalNodes != 0)
1060     memset(usedBuf, 0, hr.TotalNodes);
1061 
1062   CFork resFork;
1063 
1064   UInt32 node = hr.FirstLeafNode;
1065   UInt32 numFiles = 0;
1066   UInt32 numFolders = 0;
1067 
1068   while (node != 0)
1069   {
1070     if (node >= hr.TotalNodes || usedBuf[node] != 0)
1071       return S_FALSE;
1072     usedBuf[node] = 1;
1073 
1074     const size_t nodeOffset = (size_t)node << hr.NodeSizeLog;
1075     CNodeDescriptor desc;
1076     if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog))
1077       return S_FALSE;
1078     if (desc.Kind != kNodeType_Leaf)
1079       return S_FALSE;
1080 
1081     for (unsigned i = 0; i < desc.NumRecords; i++)
1082     {
1083       const UInt32 nodeSize = (1 << hr.NodeSizeLog);
1084       const Byte *r = p + nodeOffset + nodeSize - i * 2;
1085       const UInt32 offs = Get16(r - 2);
1086       UInt32 recSize = Get16(r - 4) - offs;
1087       if (recSize < 6)
1088         return S_FALSE;
1089 
1090       r = p + nodeOffset + offs;
1091       UInt32 keyLen = Get16(r);
1092       UInt32 parentID = Get32(r + 2);
1093       if (keyLen < 6 || (keyLen & 1) != 0 || keyLen + 2 > recSize)
1094         return S_FALSE;
1095       r += 6;
1096       recSize -= 6;
1097       keyLen -= 6;
1098 
1099       unsigned nameLen = Get16(r);
1100       if (nameLen * 2 != (unsigned)keyLen)
1101         return S_FALSE;
1102       r += 2;
1103       recSize -= 2;
1104 
1105       r += nameLen * 2;
1106       recSize -= nameLen * 2;
1107 
1108       if (recSize < 2)
1109         return S_FALSE;
1110       UInt16 type = Get16(r);
1111 
1112       if (type != RECORD_TYPE_FOLDER &&
1113           type != RECORD_TYPE_FILE)
1114         continue;
1115 
1116       if (recSize < kBasicRecSize)
1117         return S_FALSE;
1118 
1119       CItem &item = Items.AddNew();
1120       item.ParentID = parentID;
1121       item.Type = type;
1122       // item.Flags = Get16(r + 2);
1123       // item.Valence = Get32(r + 4);
1124       item.ID = Get32(r + 8);
1125       {
1126         const Byte *name = r - (nameLen * 2);
1127         LoadName(name, nameLen, item.Name);
1128         if (item.Name.Len() <= 1)
1129         {
1130           if (item.Name.IsEmpty() && nameLen == 21)
1131           {
1132             if (GetUi32(name) == 0 &&
1133                 GetUi32(name + 4) == 0 &&
1134                 IsNameEqualTo(name + 8, "HFS+ Private Data"))
1135             {
1136               // it's folder for "Hard Links" files
1137               item.Name = "[HFS+ Private Data]";
1138             }
1139           }
1140 
1141           // Some dmg files have ' ' folder item.
1142           if (item.Name.IsEmpty() || item.Name[0] == L' ')
1143             item.Name = "[]";
1144         }
1145       }
1146 
1147       item.CTime = Get32(r + 0xC);
1148       item.MTime = Get32(r + 0x10);
1149       item.AttrMTime = Get32(r + 0x14);
1150       item.ATime = Get32(r + 0x18);
1151       // item.BackupDate = Get32(r + 0x1C);
1152 
1153       /*
1154       item.OwnerID = Get32(r + 0x20);
1155       item.GroupID = Get32(r + 0x24);
1156       item.AdminFlags = r[0x28];
1157       item.OwnerFlags = r[0x29];
1158       */
1159       item.FileMode = Get16(r + 0x2A);
1160       /*
1161       item.special.iNodeNum = Get16(r + 0x2C); // or .linkCount
1162       item.FileType = Get32(r + 0x30);
1163       item.FileCreator = Get32(r + 0x34);
1164       item.FinderFlags = Get16(r + 0x38);
1165       item.Point[0] = Get16(r + 0x3A); // v
1166       item.Point[1] = Get16(r + 0x3C); // h
1167       */
1168 
1169       // const refIndex = Refs.Size();
1170       CIdIndexPair pair;
1171       pair.ID = item.ID;
1172       pair.Index = Items.Size() - 1;
1173       IdToIndexMap.Add(pair);
1174 
1175       recSize -= kBasicRecSize;
1176       r += kBasicRecSize;
1177       if (item.IsDir())
1178       {
1179         numFolders++;
1180         if (recSize != 0)
1181           return S_FALSE;
1182       }
1183       else
1184       {
1185         numFiles++;
1186         if (recSize != kForkRecSize * 2)
1187           return S_FALSE;
1188 
1189         item.DataFork.Parse(r);
1190 
1191         if (!item.DataFork.UpgradeAndTest(overflowExtentsArray[0], item.ID, Header.BlockSizeLog))
1192           HeadersError = true;
1193 
1194         item.ResourceFork.Parse(r + kForkRecSize);
1195         if (!item.ResourceFork.IsEmpty())
1196         {
1197           if (!item.ResourceFork.UpgradeAndTest(overflowExtentsArray[1], item.ID, Header.BlockSizeLog))
1198             HeadersError = true;
1199           // ThereAreAltStreams = true;
1200         }
1201       }
1202       if (progress && (Items.Size() & 0xFFF) == 0)
1203       {
1204         const UInt64 numItems = Items.Size();
1205         RINOK(progress->SetCompleted(&numItems, NULL))
1206       }
1207     }
1208     node = desc.fLink;
1209   }
1210 
1211   if (Header.NumFiles != numFiles ||
1212       Header.NumFolders + 1 != numFolders)
1213     HeadersError = true;
1214 
1215   IdToIndexMap.Sort2();
1216   {
1217     for (unsigned i = 1; i < IdToIndexMap.Size(); i++)
1218       if (IdToIndexMap[i - 1].ID == IdToIndexMap[i].ID)
1219         return S_FALSE;
1220   }
1221 
1222 
1223   CBoolArr skipAttr(Attrs.Size());
1224   {
1225     for (unsigned i = 0; i < Attrs.Size(); i++)
1226       skipAttr[i] = false;
1227   }
1228 
1229   {
1230     FOR_VECTOR (i, Attrs)
1231     {
1232       const CAttr &attr = Attrs[i];
1233 
1234       const int itemIndex = FindItemIndex(IdToIndexMap, attr.ID);
1235       if (itemIndex < 0)
1236       {
1237         HeadersError = true;
1238         continue;
1239       }
1240 
1241       if (attr.Name.IsEqualTo("com.apple.decmpfs"))
1242       {
1243         if (!Parse_decmpgfs(i, Items[itemIndex], skipAttr[i]))
1244           HeadersError = true;
1245       }
1246     }
1247   }
1248 
1249   IdToIndexMap.ClearAndReserve(Items.Size());
1250 
1251   {
1252     FOR_VECTOR (i, Items)
1253     {
1254       const CItem &item = Items[i];
1255 
1256       CIdIndexPair pair;
1257       pair.ID = item.ID;
1258       pair.Index = Refs.Size();
1259       IdToIndexMap.Add(pair);
1260 
1261       CRef ref;
1262       ref.ItemIndex = i;
1263       Refs.Add(ref);
1264 
1265       #ifdef HFS_SHOW_ALT_STREAMS
1266 
1267       if (item.ResourceFork.IsEmpty())
1268         continue;
1269       if (item.CompressHeader.IsSupported && item.CompressHeader.IsMethod_Resource())
1270         continue;
1271 
1272       ThereAreAltStreams = true;
1273       ref.AttrIndex = kAttrIndex_Resource;
1274       ref.Parent = (int)(Refs.Size() - 1);
1275       Refs.Add(ref);
1276 
1277       #endif
1278     }
1279   }
1280 
1281   IdToIndexMap.Sort2();
1282 
1283   {
1284     FOR_VECTOR (i, Refs)
1285     {
1286       CRef &ref = Refs[i];
1287       if (ref.IsResource())
1288         continue;
1289       const CItem &item = Items[ref.ItemIndex];
1290       ref.Parent = FindItemIndex(IdToIndexMap, item.ParentID);
1291       if (ref.Parent >= 0)
1292       {
1293         if (!Items[Refs[ref.Parent].ItemIndex].IsDir())
1294         {
1295           ref.Parent = -1;
1296           HeadersError = true;
1297         }
1298       }
1299     }
1300   }
1301 
1302   #ifdef HFS_SHOW_ALT_STREAMS
1303   {
1304     FOR_VECTOR (i, Attrs)
1305     {
1306       if (skipAttr[i])
1307         continue;
1308       const CAttr &attr = Attrs[i];
1309 
1310       const int refIndex = FindItemIndex(IdToIndexMap, attr.ID);
1311       if (refIndex < 0)
1312       {
1313         HeadersError = true;
1314         continue;
1315       }
1316 
1317       ThereAreAltStreams = true;
1318 
1319       CRef ref;
1320       ref.AttrIndex = (int)i;
1321       ref.Parent = refIndex;
1322       ref.ItemIndex = Refs[refIndex].ItemIndex;
1323       Refs.Add(ref);
1324     }
1325   }
1326   #endif
1327 
1328   return S_OK;
1329 }
1330 
1331 static const unsigned kHeaderPadSize = 1 << 10;
1332 static const unsigned kMainHeaderSize = 512;
1333 static const unsigned kHfsHeaderSize = kHeaderPadSize + kMainHeaderSize;
1334 
1335 static const unsigned k_Signature_LE16_HFS_BD = 'B' + ((unsigned)'D' << 8);
1336 static const unsigned k_Signature_LE16_HPLUS  = 'H' + ((unsigned)'+' << 8);
1337 static const UInt32   k_Signature_LE32_HFSP_VER4 = 'H' + ((UInt32)'+' << 8) + ((UInt32)4 << 24);
1338 static const UInt32   k_Signature_LE32_HFSX_VER5 = 'H' + ((UInt32)'X' << 8) + ((UInt32)5 << 24);
1339 
IsArc_HFS(const Byte * p,size_t size)1340 API_FUNC_static_IsArc IsArc_HFS(const Byte *p, size_t size)
1341 {
1342   if (size < kHfsHeaderSize)
1343     return k_IsArc_Res_NEED_MORE;
1344   p += kHeaderPadSize;
1345   const UInt32 sig = GetUi32(p);
1346   if (sig != k_Signature_LE32_HFSP_VER4)
1347   if (sig != k_Signature_LE32_HFSX_VER5)
1348   if ((UInt16)sig != k_Signature_LE16_HFS_BD
1349       || GetUi16(p + 0x7c) != k_Signature_LE16_HPLUS)
1350     return k_IsArc_Res_NO;
1351   return k_IsArc_Res_YES;
1352 }
1353 }
1354 
Open2(IInStream * inStream,IArchiveOpenCallback * progress)1355 HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress)
1356 {
1357   Clear();
1358   UInt32 buf32[kHfsHeaderSize / 4];
1359   RINOK(ReadStream_FALSE(inStream, buf32, kHfsHeaderSize))
1360   const Byte *p = (const Byte *)buf32 + kHeaderPadSize;
1361   CVolHeader &h = Header;
1362 
1363   if (GetUi16a(p) == k_Signature_LE16_HFS_BD)
1364   {
1365     /*
1366     It's header for old HFS format.
1367     We don't support old HFS format, but we support
1368     special HFS volume that contains embedded HFS+ volume.
1369     HFS MDB : Master directory block
1370     HFS VIB : Volume information block
1371     some old images contain boot data with "LK" signature at start of buf32.
1372     */
1373 #if 1
1374     // here we check first bytes of archive,
1375     // because start data can contain signature of some another
1376     // archive type that could have priority over HFS.
1377     const void *buf_ptr = (const void *)buf32;
1378     const unsigned sig = GetUi16a(buf_ptr);
1379     if (sig != 'L' + ((unsigned)'K' << 8))
1380     {
1381       // some old HFS (non HFS+) files have no "LK" signature,
1382       // but have non-zero data after 2 first bytes in start 1 KiB.
1383       if (sig != 0)
1384         return S_FALSE;
1385 /*
1386       for (unsigned i = 0; i < kHeaderPadSize / 4; i++)
1387         if (buf32[i] != 0)
1388           return S_FALSE;
1389 */
1390     }
1391 #endif
1392     if (GetUi16a(p + 0x7c) != k_Signature_LE16_HPLUS) // signature of embedded HFS+ volume
1393       return S_FALSE;
1394     /*
1395     h.CTime = Get32(p + 0x2);
1396     h.MTime = Get32(p + 0x6);
1397 
1398     h.NumFiles = Get32(p + 0x54);
1399     h.NumFolders = Get32(p + 0x58);
1400 
1401     if (h.NumFolders > ((UInt32)1 << 29) ||
1402         h.NumFiles > ((UInt32)1 << 30))
1403       return S_FALSE;
1404     if (progress)
1405     {
1406       UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1407       RINOK(progress->SetTotal(&numFiles, NULL))
1408     }
1409     h.NumFreeBlocks = Get16(p + 0x22);
1410     */
1411 
1412     // v24.09: blockSize in old HFS image can be non-power of 2.
1413     const UInt32 blockSize = Get32a(p + 0x14); // drAlBlkSiz
1414     if (blockSize == 0 || (blockSize & 0x1ff))
1415       return S_FALSE;
1416     const unsigned numBlocks = Get16a(p + 0x12); // drNmAlBlks
1417     // UInt16 drFreeBks = Get16a(p + 0x22); // number of unused allocation blocks
1418     /*
1419     we suppose that it has the following layout:
1420     {
1421       start data with header
1422       blocks[h.NumBlocks]
1423       end data with header (probably size_of_footer <= blockSize).
1424     }
1425     */
1426     // PhySize2 = ((UInt64)numBlocks + 2) * blockSize;
1427     const unsigned sector_of_FirstBlock = Get16a(p + 0x1c); // drAlBlSt : first allocation block in volume
1428     const UInt32 startBlock = Get16a(p + 0x7c + 2);
1429     const UInt32 blockCount = Get16a(p + 0x7c + 4);
1430     SpecOffset = (UInt32)sector_of_FirstBlock << 9; // it's 32-bit here
1431     PhySize2 = SpecOffset + (UInt64)numBlocks * blockSize;
1432     SpecOffset += (UInt64)startBlock * blockSize;
1433     // before v24.09: // SpecOffset = (UInt64)(1 + startBlock) * blockSize;
1434     const UInt64 phy = SpecOffset + (UInt64)blockCount * blockSize;
1435     if (PhySize2 < phy)
1436         PhySize2 = phy;
1437     UInt32 tail = 1 << 10; // at least 1 KiB tail (for footer MDB) is expected.
1438     if (tail < blockSize)
1439         tail = blockSize;
1440     RINOK(InStream_GetSize_SeekToEnd(inStream, ArcFileSize))
1441     if (ArcFileSize > PhySize2 &&
1442         ArcFileSize - PhySize2 <= tail)
1443     {
1444       // data after blocks[h.NumBlocks] must contain another copy of MDB.
1445       // In example where blockSize is not power of 2, we have
1446       //   (ArcFileSize - PhySize2) < blockSize.
1447       // We suppose that data after blocks[h.NumBlocks] is part of HFS archive.
1448       // Maybe we should scan for footer MDB data (in last 1 KiB)?
1449       PhySize2 = ArcFileSize;
1450     }
1451     RINOK(InStream_SeekSet(inStream, SpecOffset))
1452     RINOK(ReadStream_FALSE(inStream, buf32, kHfsHeaderSize))
1453   }
1454 
1455   // HFS+ / HFSX volume header (starting from offset==1024):
1456   {
1457     // v24.09: we use strict condition test for pair signature(Version):
1458     // H+(4), HX(5):
1459     const UInt32 sig = GetUi32a(p);
1460     // h.Version = Get16(p + 2);
1461     h.Is_Hsfx_ver5 = false;
1462     if (sig != k_Signature_LE32_HFSP_VER4)
1463     {
1464       if (sig != k_Signature_LE32_HFSX_VER5)
1465         return S_FALSE;
1466       h.Is_Hsfx_ver5 = true;
1467     }
1468   }
1469   {
1470     const UInt32 blockSize = Get32a(p + 0x28);
1471     unsigned i;
1472     for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1473       if (i == 31)
1474         return S_FALSE;
1475     h.BlockSizeLog = i;
1476   }
1477 #if 1
1478   // HFS Plus DOCs: The first 1024 bytes are reserved for use as boot blocks
1479   // v24.09: we don't check starting 1 KiB before old (HFS MDB) block ("BD" signture) .
1480   //     but we still check starting 1 KiB before HFS+ / HFSX volume header.
1481   // are there HFS+ / HFSX images with non-zero data in this reserved area?
1482   {
1483     for (unsigned i = 0; i < kHeaderPadSize / 4; i++)
1484       if (buf32[i] != 0)
1485         return S_FALSE;
1486   }
1487 #endif
1488   // h.Attr = Get32a(p + 4);
1489   // h.LastMountedVersion = Get32a(p + 8);
1490   // h.JournalInfoBlock = Get32a(p + 0xC);
1491   h.CTime = Get32a(p + 0x10);
1492   h.MTime = Get32a(p + 0x14);
1493   // h.BackupTime = Get32a(p + 0x18);
1494   // h.CheckedTime = Get32a(p + 0x1C);
1495   h.NumFiles = Get32a(p + 0x20);
1496   h.NumFolders = Get32a(p + 0x24);
1497   if (h.NumFolders > ((UInt32)1 << 29) ||
1498       h.NumFiles > ((UInt32)1 << 30))
1499     return S_FALSE;
1500 
1501   RINOK(InStream_GetSize_SeekToEnd(inStream, ArcFileSize))
1502   if (progress)
1503   {
1504     const UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1505     RINOK(progress->SetTotal(&numFiles, NULL))
1506   }
1507 
1508   h.NumBlocks = Get32a(p + 0x2C);
1509   h.NumFreeBlocks = Get32a(p + 0x30);
1510   /*
1511   h.NextCalatlogNodeID = Get32(p + 0x40);
1512   h.WriteCount = Get32(p + 0x44);
1513   for (i = 0; i < 6; i++)
1514     h.FinderInfo[i] = Get32(p + 0x50 + i * 4);
1515   h.VolID = Get64(p + 0x68);
1516   */
1517 
1518   ResFileName = kResFileName;
1519 
1520   CFork extentsFork, catalogFork, attrFork;
1521   // allocationFork.Parse(p + 0x70 + 0x50 * 0);
1522   extentsFork.Parse(p + 0x70 + 0x50 * 1);
1523   catalogFork.Parse(p + 0x70 + 0x50 * 2);
1524   attrFork.Parse   (p + 0x70 + 0x50 * 3);
1525   // startupFork.Parse(p + 0x70 + 0x50 * 4);
1526 
1527   CObjectVector<CIdExtents> overflowExtents[2];
1528   if (!extentsFork.IsOk(Header.BlockSizeLog))
1529     HeadersError = true;
1530   else
1531   {
1532     const HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents);
1533     if (res == S_FALSE)
1534       HeadersError = true;
1535     else if (res != S_OK)
1536       return res;
1537   }
1538 
1539   if (!catalogFork.UpgradeAndTest(overflowExtents[0], kHfsID_CatalogFile, Header.BlockSizeLog))
1540     return S_FALSE;
1541 
1542   if (!attrFork.UpgradeAndTest(overflowExtents[0], kHfsID_AttributesFile, Header.BlockSizeLog))
1543     HeadersError = true;
1544   else
1545   {
1546     if (attrFork.Size != 0)
1547       RINOK(LoadAttrs(attrFork, inStream, progress))
1548   }
1549 
1550   RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress))
1551 
1552   // PhySize = Header.GetPhySize();
1553   return S_OK;
1554 }
1555 
1556 
1557 
1558 Z7_class_CHandler_final:
1559   public IInArchive,
1560   public IArchiveGetRawProps,
1561   public IInArchiveGetStream,
1562   public CMyUnknownImp,
1563   public CDatabase
1564 {
1565   Z7_IFACES_IMP_UNK_3(
1566       IInArchive,
1567       IArchiveGetRawProps,
1568       IInArchiveGetStream)
1569 
1570   CMyComPtr<IInStream> _stream;
1571   HRESULT GetForkStream(const CFork &fork, ISequentialInStream **stream);
1572 };
1573 
1574 static const Byte kProps[] =
1575 {
1576   kpidPath,
1577   kpidIsDir,
1578   kpidSize,
1579   kpidPackSize,
1580   kpidCTime,
1581   kpidMTime,
1582   kpidATime,
1583   kpidChangeTime,
1584   kpidPosixAttrib,
1585   /*
1586   kpidUserId,
1587   kpidGroupId,
1588   */
1589 #ifdef HFS_SHOW_ALT_STREAMS
1590   kpidIsAltStream,
1591 #endif
1592   kpidMethod
1593 };
1594 
1595 static const Byte kArcProps[] =
1596 {
1597   kpidMethod,
1598   kpidCharacts,
1599   kpidClusterSize,
1600   kpidFreeSpace,
1601   kpidCTime,
1602   kpidMTime
1603 };
1604 
1605 IMP_IInArchive_Props
1606 IMP_IInArchive_ArcProps
1607 
1608 static void HfsTimeToProp(UInt32 hfsTime, NWindows::NCOM::CPropVariant &prop)
1609 {
1610   if (hfsTime == 0)
1611     return;
1612   FILETIME ft;
1613   HfsTimeToFileTime(hfsTime, ft);
1614   prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base);
1615 }
1616 
1617 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
1618 {
1619   COM_TRY_BEGIN
1620   NWindows::NCOM::CPropVariant prop;
1621   switch (propID)
1622   {
1623     case kpidExtension: prop = Header.IsHfsX() ? "hfsx" : "hfs"; break;
1624     case kpidMethod: prop = Header.IsHfsX() ? "HFSX" : "HFS+"; break;
1625     case kpidCharacts: MethodsMaskToProp(MethodsMask, prop); break;
1626     case kpidPhySize:
1627     {
1628       UInt64 v = SpecOffset + Header.GetPhySize(); // PhySize;
1629       if (v < PhySize2)
1630         v = PhySize2;
1631       prop = v;
1632       break;
1633     }
1634     case kpidClusterSize: prop = (UInt32)1 << Header.BlockSizeLog; break;
1635     case kpidFreeSpace: prop = (UInt64)Header.GetFreeSize(); break;
1636     case kpidMTime: HfsTimeToProp(Header.MTime, prop); break;
1637     case kpidCTime:
1638     {
1639       if (Header.CTime != 0)
1640       {
1641         FILETIME localFt, ft;
1642         HfsTimeToFileTime(Header.CTime, localFt);
1643         if (LocalFileTimeToFileTime(&localFt, &ft))
1644           prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base);
1645       }
1646       break;
1647     }
1648     case kpidIsTree: prop = true; break;
1649     case kpidErrorFlags:
1650     {
1651       UInt32 flags = 0;
1652       if (HeadersError) flags |= kpv_ErrorFlags_HeadersError;
1653       if (UnsupportedFeature) flags |= kpv_ErrorFlags_UnsupportedFeature;
1654       if (flags != 0)
1655         prop = flags;
1656       break;
1657     }
1658     case kpidIsAltStream: prop = ThereAreAltStreams; break;
1659   }
1660   prop.Detach(value);
1661   return S_OK;
1662   COM_TRY_END
1663 }
1664 
1665 Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
1666 {
1667   *numProps = 0;
1668   return S_OK;
1669 }
1670 
1671 Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID))
1672 {
1673   *name = NULL;
1674   *propID = 0;
1675   return S_OK;
1676 }
1677 
1678 Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType))
1679 {
1680   const CRef &ref = Refs[index];
1681   *parentType = ref.IsAltStream() ?
1682       NParentType::kAltStream :
1683       NParentType::kDir;
1684   *parent = (UInt32)(Int32)ref.Parent;
1685   return S_OK;
1686 }
1687 
1688 Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
1689 {
1690   *data = NULL;
1691   *dataSize = 0;
1692   *propType = 0;
1693   #ifdef MY_CPU_LE
1694   if (propID == kpidName)
1695   {
1696     const CRef &ref = Refs[index];
1697     const UString *s;
1698     if (ref.IsResource())
1699       s = &ResFileName;
1700     else if (ref.AttrIndex >= 0)
1701       s = &Attrs[ref.AttrIndex].Name;
1702     else
1703       s = &Items[ref.ItemIndex].Name;
1704     *data = (const wchar_t *)(*s);
1705     *dataSize = (s->Len() + 1) * (UInt32)sizeof(wchar_t);
1706     *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE;
1707     return S_OK;
1708   }
1709   #else
1710   UNUSED_VAR(index)
1711   UNUSED_VAR(propID)
1712   #endif
1713   return S_OK;
1714 }
1715 
1716 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
1717 {
1718   COM_TRY_BEGIN
1719   NWindows::NCOM::CPropVariant prop;
1720   const CRef &ref = Refs[index];
1721   const CItem &item = Items[ref.ItemIndex];
1722   switch (propID)
1723   {
1724     case kpidPath: GetItemPath(index, prop); break;
1725     case kpidName:
1726     {
1727       const UString *s;
1728       if (ref.IsResource())
1729         s = &ResFileName;
1730       else if (ref.AttrIndex >= 0)
1731         s = &Attrs[ref.AttrIndex].Name;
1732       else
1733         s = &item.Name;
1734       prop = *s;
1735       break;
1736     }
1737     case kpidPackSize:
1738       {
1739         UInt64 size;
1740         if (ref.AttrIndex >= 0)
1741           size = Attrs[ref.AttrIndex].GetSize();
1742         else if (ref.IsResource())
1743           size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1744         else if (item.IsDir())
1745           break;
1746         else if (item.CompressHeader.IsCorrect)
1747         {
1748           if (item.CompressHeader.IsMethod_Resource())
1749             size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1750           else if (item.decmpfs_AttrIndex >= 0)
1751           {
1752             // size = item.PackSize;
1753             const CAttr &attr = Attrs[item.decmpfs_AttrIndex];
1754             size = attr.Data.Size() - item.CompressHeader.DataPos;
1755           }
1756           else
1757             size = 0;
1758         }
1759         else
1760           size = (UInt64)item.DataFork.NumBlocks << Header.BlockSizeLog;
1761         prop = size;
1762         break;
1763       }
1764     case kpidSize:
1765       {
1766         UInt64 size;
1767         if (ref.AttrIndex >= 0)
1768           size = Attrs[ref.AttrIndex].GetSize();
1769         else if (ref.IsResource())
1770           size = item.ResourceFork.Size;
1771         else if (item.IsDir())
1772           break;
1773         else if (item.CompressHeader.IsCorrect)
1774           size = item.CompressHeader.UnpackSize;
1775         else
1776           size = item.DataFork.Size;
1777         prop = size;
1778         break;
1779       }
1780     case kpidIsDir: prop = (ref.IsItem() && item.IsDir()); break;
1781     case kpidIsAltStream: prop = ref.IsAltStream(); break;
1782     case kpidCTime: HfsTimeToProp(item.CTime, prop); break;
1783     case kpidMTime: HfsTimeToProp(item.MTime, prop); break;
1784     case kpidATime: HfsTimeToProp(item.ATime, prop); break;
1785     case kpidChangeTime: HfsTimeToProp(item.AttrMTime, prop); break;
1786     case kpidPosixAttrib: if (ref.IsItem()) prop = (UInt32)item.FileMode; break;
1787     /*
1788     case kpidUserId: prop = (UInt32)item.OwnerID; break;
1789     case kpidGroupId: prop = (UInt32)item.GroupID; break;
1790     */
1791 
1792     case kpidMethod:
1793       if (ref.IsItem())
1794         item.CompressHeader.MethodToProp(prop);
1795       break;
1796   }
1797   prop.Detach(value);
1798   return S_OK;
1799   COM_TRY_END
1800 }
1801 
1802 Z7_COM7F_IMF(CHandler::Open(IInStream *inStream,
1803     const UInt64 * /* maxCheckStartPosition */,
1804     IArchiveOpenCallback *callback))
1805 {
1806   COM_TRY_BEGIN
1807   Close();
1808   RINOK(Open2(inStream, callback))
1809   _stream = inStream;
1810   return S_OK;
1811   COM_TRY_END
1812 }
1813 
1814 Z7_COM7F_IMF(CHandler::Close())
1815 {
1816   _stream.Release();
1817   Clear();
1818   return S_OK;
1819 }
1820 
1821 static const UInt32 kCompressionBlockSize = 1 << 16;
1822 
1823 CDecoder::CDecoder(bool IsAdlerOptional)
1824 {
1825   /* Some new hfs files contain zlib resource fork without Adler checksum.
1826      We do not know how we must detect case where there is Adler
1827      checksum or there is no Adler checksum.
1828   */
1829   _zlibDecoder->IsAdlerOptional = IsAdlerOptional;
1830   _lzfseDecoder->LzvnMode = true;
1831 }
1832 
1833 HRESULT CDecoder::ExtractResourceFork_ZLIB(
1834     ISequentialInStream *inStream, ISequentialOutStream *outStream,
1835     UInt64 forkSize, UInt64 unpackSize,
1836     UInt64 progressStart, IArchiveExtractCallback *extractCallback)
1837 {
1838   const unsigned kHeaderSize = 0x100 + 8;
1839 
1840   const size_t kBufSize = kCompressionBlockSize;
1841   _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
1842 
1843   RINOK(ReadStream_FALSE(inStream, _buf, kHeaderSize))
1844   Byte *buf = _buf;
1845   const UInt32 dataPos = Get32(buf);
1846   const UInt32 mapPos = Get32(buf + 4);
1847   const UInt32 dataSize = Get32(buf + 8);
1848   const UInt32 mapSize = Get32(buf + 12);
1849 
1850   const UInt32 kResMapSize = 50;
1851 
1852   if (mapSize != kResMapSize
1853       || dataPos > mapPos
1854       || dataSize != mapPos - dataPos
1855       || mapSize > forkSize
1856       || mapPos != forkSize - mapSize)
1857     return S_FALSE;
1858 
1859   const UInt32 dataSize2 = Get32(buf + 0x100);
1860   if (4 + dataSize2 != dataSize
1861       || dataSize2 < 8
1862       || dataSize2 > dataSize)
1863     return S_FALSE;
1864 
1865   const UInt32 numBlocks = GetUi32(buf + 0x100 + 4);
1866   if (((dataSize2 - 4) >> 3) < numBlocks)
1867     return S_FALSE;
1868   {
1869     const UInt64 up = unpackSize + kCompressionBlockSize - 1;
1870     if (up < unpackSize || up / kCompressionBlockSize != numBlocks)
1871       return S_FALSE;
1872   }
1873 
1874   const UInt32 tableSize = (numBlocks << 3);
1875 
1876   _tableBuf.AllocAtLeast(tableSize);
1877 
1878   RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize))
1879   const Byte *tableBuf = _tableBuf;
1880 
1881   UInt32 prev = 4 + tableSize;
1882 
1883   UInt32 i;
1884   for (i = 0; i < numBlocks; i++)
1885   {
1886     const UInt32 offs = GetUi32(tableBuf + i * 8);
1887     const UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1888     if (size == 0
1889         || prev != offs
1890         || offs > dataSize2
1891         || size > dataSize2 - offs)
1892       return S_FALSE;
1893     prev = offs + size;
1894   }
1895 
1896   if (prev != dataSize2)
1897     return S_FALSE;
1898 
1899   CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
1900 
1901   // bool padError = false;
1902   UInt64 outPos = 0;
1903 
1904   for (i = 0; i < numBlocks; i++)
1905   {
1906     const UInt64 rem = unpackSize - outPos;
1907     if (rem == 0)
1908       return S_FALSE;
1909     UInt32 blockSize = kCompressionBlockSize;
1910     if (rem < kCompressionBlockSize)
1911       blockSize = (UInt32)rem;
1912 
1913     const UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1914 
1915     if (size > kCompressionBlockSize + 1)
1916       return S_FALSE;
1917 
1918     RINOK(ReadStream_FALSE(inStream, buf, size))
1919 
1920     if ((buf[0] & 0xF) == 0xF)
1921     {
1922       // (buf[0] = 0xff) is marker of uncompressed block in APFS
1923       // that code was not tested in HFS
1924       if (size - 1 != blockSize)
1925         return S_FALSE;
1926 
1927       if (outStream)
1928       {
1929         RINOK(WriteStream(outStream, buf + 1, blockSize))
1930       }
1931     }
1932     else
1933     {
1934       const UInt64 blockSize64 = blockSize;
1935       bufInStream->Init(buf, size);
1936       RINOK(_zlibDecoder.Interface()->Code(bufInStream, outStream, NULL, &blockSize64, NULL))
1937       if (_zlibDecoder->GetOutputProcessedSize() != blockSize)
1938         return S_FALSE;
1939       const UInt64 inSize = _zlibDecoder->GetInputProcessedSize();
1940       if (inSize != size)
1941       {
1942         if (inSize > size)
1943           return S_FALSE;
1944         // apfs file can contain junk (non-zeros) after data block.
1945         /*
1946         if (!padError)
1947         {
1948           const Byte *p = buf + (UInt32)inSize;
1949           const Byte *e = p + (size - (UInt32)inSize);
1950           do
1951           {
1952             if (*p != 0)
1953             {
1954               padError = true;
1955               break;
1956             }
1957           }
1958           while (++p != e);
1959         }
1960         */
1961       }
1962     }
1963 
1964     outPos += blockSize;
1965     if ((i & 0xFF) == 0)
1966     {
1967       const UInt64 progressPos = progressStart + outPos;
1968       RINOK(extractCallback->SetCompleted(&progressPos))
1969     }
1970   }
1971 
1972   if (outPos != unpackSize)
1973     return S_FALSE;
1974 
1975   // if (padError) return S_FALSE;
1976 
1977   /* We check Resource Map
1978      Are there HFS files with another values in Resource Map ??? */
1979 
1980   RINOK(ReadStream_FALSE(inStream, buf, mapSize))
1981   const UInt32 types = Get16(buf + 24);
1982   const UInt32 names = Get16(buf + 26);
1983   const UInt32 numTypes = Get16(buf + 28);
1984   if (numTypes != 0 || types != 28 || names != kResMapSize)
1985     return S_FALSE;
1986   const UInt32 resType = Get32(buf + 30);
1987   const UInt32 numResources = Get16(buf + 34);
1988   const UInt32 resListOffset = Get16(buf + 36);
1989   if (resType != 0x636D7066) // cmpf
1990     return S_FALSE;
1991   if (numResources != 0 || resListOffset != 10)
1992     return S_FALSE;
1993 
1994   const UInt32 entryId = Get16(buf + 38);
1995   const UInt32 nameOffset = Get16(buf + 40);
1996   // Byte attrib = buf[42];
1997   const UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF;
1998   if (entryId != 1 || nameOffset != 0xFFFF || resourceOffset != 0)
1999     return S_FALSE;
2000 
2001   return S_OK;
2002 }
2003 
2004 
2005 
2006 HRESULT CDecoder::ExtractResourceFork_LZFSE(
2007     ISequentialInStream *inStream, ISequentialOutStream *outStream,
2008     UInt64 forkSize, UInt64 unpackSize,
2009     UInt64 progressStart, IArchiveExtractCallback *extractCallback)
2010 {
2011   const UInt32 kNumBlocksMax = (UInt32)1 << 29;
2012   if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize)
2013     return S_FALSE;
2014   const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize);
2015   const UInt32 numBlocks2 = numBlocks + 1;
2016   const UInt32 tableSize = (numBlocks2 << 2);
2017   if (tableSize > forkSize)
2018     return S_FALSE;
2019   _tableBuf.AllocAtLeast(tableSize);
2020   RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize))
2021   const Byte *tableBuf = _tableBuf;
2022 
2023   {
2024     UInt32 prev = GetUi32(tableBuf);
2025     if (prev != tableSize)
2026       return S_FALSE;
2027     for (UInt32 i = 1; i < numBlocks2; i++)
2028     {
2029       const UInt32 offs = GetUi32(tableBuf + i * 4);
2030       if (offs <= prev)
2031         return S_FALSE;
2032       prev = offs;
2033     }
2034     if (prev != forkSize)
2035       return S_FALSE;
2036   }
2037 
2038   const size_t kBufSize = kCompressionBlockSize;
2039   _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2040 
2041   CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
2042 
2043   UInt64 outPos = 0;
2044 
2045   for (UInt32 i = 0; i < numBlocks; i++)
2046   {
2047     const UInt64 rem = unpackSize - outPos;
2048     if (rem == 0)
2049       return S_FALSE;
2050     UInt32 blockSize = kCompressionBlockSize;
2051     if (rem < kCompressionBlockSize)
2052       blockSize = (UInt32)rem;
2053 
2054     const UInt32 size =
2055         GetUi32(tableBuf + i * 4 + 4) -
2056         GetUi32(tableBuf + i * 4);
2057 
2058     if (size > kCompressionBlockSize + 1)
2059       return S_FALSE;
2060 
2061     RINOK(ReadStream_FALSE(inStream, _buf, size))
2062     const Byte *buf = _buf;
2063 
2064     if (buf[0] == k_LZVN_Uncompressed_Marker)
2065     {
2066       if (size - 1 != blockSize)
2067         return S_FALSE;
2068       if (outStream)
2069       {
2070         RINOK(WriteStream(outStream, buf + 1, blockSize))
2071       }
2072     }
2073     else
2074     {
2075       const UInt64 blockSize64 = blockSize;
2076       const UInt64 packSize64 = size;
2077       bufInStream->Init(buf, size);
2078       RINOK(_lzfseDecoder.Interface()->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL))
2079       // in/out sizes were checked in Code()
2080     }
2081 
2082     outPos += blockSize;
2083     if ((i & 0xFF) == 0)
2084     {
2085       const UInt64 progressPos = progressStart + outPos;
2086       RINOK(extractCallback->SetCompleted(&progressPos))
2087     }
2088   }
2089 
2090   return S_OK;
2091 }
2092 
2093 
2094 /*
2095 static UInt32 GetUi24(const Byte *p)
2096 {
2097   return p[0] + ((UInt32)p[1] << 8) + ((UInt32)p[2] << 24);
2098 }
2099 
2100 HRESULT CDecoder::ExtractResourceFork_ZBM(
2101     ISequentialInStream *inStream, ISequentialOutStream *outStream,
2102     UInt64 forkSize, UInt64 unpackSize,
2103     UInt64 progressStart, IArchiveExtractCallback *extractCallback)
2104 {
2105   const UInt32 kNumBlocksMax = (UInt32)1 << 29;
2106   if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize)
2107     return S_FALSE;
2108   const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize);
2109   const UInt32 numBlocks2 = numBlocks + 1;
2110   const UInt32 tableSize = (numBlocks2 << 2);
2111   if (tableSize > forkSize)
2112     return S_FALSE;
2113   _tableBuf.AllocAtLeast(tableSize);
2114   RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize));
2115   const Byte *tableBuf = _tableBuf;
2116 
2117   {
2118     UInt32 prev = GetUi32(tableBuf);
2119     if (prev != tableSize)
2120       return S_FALSE;
2121     for (UInt32 i = 1; i < numBlocks2; i++)
2122     {
2123       const UInt32 offs = GetUi32(tableBuf + i * 4);
2124       if (offs <= prev)
2125         return S_FALSE;
2126       prev = offs;
2127     }
2128     if (prev != forkSize)
2129       return S_FALSE;
2130   }
2131 
2132   const size_t kBufSize = kCompressionBlockSize;
2133   _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2134 
2135   CBufInStream *bufInStream = new CBufInStream;
2136   CMyComPtr<ISequentialInStream> bufInStream = bufInStream;
2137 
2138   UInt64 outPos = 0;
2139 
2140   for (UInt32 i = 0; i < numBlocks; i++)
2141   {
2142     const UInt64 rem = unpackSize - outPos;
2143     if (rem == 0)
2144       return S_FALSE;
2145     UInt32 blockSize = kCompressionBlockSize;
2146     if (rem < kCompressionBlockSize)
2147       blockSize = (UInt32)rem;
2148 
2149     const UInt32 size =
2150         GetUi32(tableBuf + i * 4 + 4) -
2151         GetUi32(tableBuf + i * 4);
2152 
2153     // if (size > kCompressionBlockSize + 1)
2154     if (size > blockSize + 1)
2155       return S_FALSE; // we don't expect it, because encode will use uncompressed chunk
2156 
2157     RINOK(ReadStream_FALSE(inStream, _buf, size));
2158     const Byte *buf = _buf;
2159 
2160     // (size != 0)
2161     // if (size == 0) return S_FALSE;
2162 
2163     if (buf[0] == 0xFF) // uncompressed marker
2164     {
2165       if (size != blockSize + 1)
2166         return S_FALSE;
2167       if (outStream)
2168       {
2169         RINOK(WriteStream(outStream, buf + 1, blockSize));
2170       }
2171     }
2172     else
2173     {
2174       if (size < 4)
2175         return S_FALSE;
2176       if (buf[0] != 'Z' ||
2177           buf[1] != 'B' ||
2178           buf[2] != 'M' ||
2179           buf[3] != 9)
2180         return S_FALSE;
2181       // for debug:
2182       unsigned packPos = 4;
2183       unsigned unpackPos = 0;
2184       unsigned packRem = size - packPos;
2185       for (;;)
2186       {
2187         if (packRem < 6)
2188           return S_FALSE;
2189         const UInt32 packSize = GetUi24(buf + packPos);
2190         const UInt32 chunkUnpackSize = GetUi24(buf + packPos + 3);
2191         if (packSize < 6)
2192           return S_FALSE;
2193         if (packSize > packRem)
2194           return S_FALSE;
2195         if (chunkUnpackSize > blockSize - unpackPos)
2196           return S_FALSE;
2197         packPos += packSize;
2198         packRem -= packSize;
2199         unpackPos += chunkUnpackSize;
2200         if (packSize == 6)
2201         {
2202           if (chunkUnpackSize != 0)
2203             return S_FALSE;
2204           break;
2205         }
2206         if (packSize >= chunkUnpackSize + 6)
2207         {
2208           if (packSize > chunkUnpackSize + 6)
2209             return S_FALSE;
2210           // uncompressed chunk;
2211         }
2212         else
2213         {
2214           // compressed chunk
2215           const Byte *t = buf + packPos - packSize + 6;
2216           UInt32 r = packSize - 6;
2217           if (r < 9)
2218             return S_FALSE;
2219           const UInt32 v0 = GetUi24(t);
2220           const UInt32 v1 = GetUi24(t + 3);
2221           const UInt32 v2 = GetUi24(t + 6);
2222           if (v0 > v1 || v1 > v2 || v2 > packSize)
2223             return S_FALSE;
2224           // here we need the code that will decompress ZBM chunk
2225         }
2226       }
2227 
2228       if (unpackPos != blockSize)
2229         return S_FALSE;
2230 
2231       UInt32 size1 = size;
2232       if (size1 > kCompressionBlockSize)
2233       {
2234         size1 = kCompressionBlockSize;
2235         // return S_FALSE;
2236       }
2237       if (outStream)
2238       {
2239         RINOK(WriteStream(outStream, buf, size1))
2240 
2241         const UInt32 kTempSize = 1 << 16;
2242         Byte temp[kTempSize];
2243         memset(temp, 0, kTempSize);
2244 
2245         for (UInt32 k = size1; k < kCompressionBlockSize; k++)
2246         {
2247           UInt32 cur = kCompressionBlockSize - k;
2248           if (cur > kTempSize)
2249             cur = kTempSize;
2250           RINOK(WriteStream(outStream, temp, cur))
2251           k += cur;
2252         }
2253       }
2254 
2255       // const UInt64 blockSize64 = blockSize;
2256       // const UInt64 packSize64 = size;
2257       // bufInStream->Init(buf, size);
2258       // RINOK(_zbmDecoderSpec->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL));
2259       // in/out sizes were checked in Code()
2260     }
2261 
2262     outPos += blockSize;
2263     if ((i & 0xFF) == 0)
2264     {
2265       const UInt64 progressPos = progressStart + outPos;
2266       RINOK(extractCallback->SetCompleted(&progressPos));
2267     }
2268   }
2269 
2270   return S_OK;
2271 }
2272 */
2273 
2274 HRESULT CDecoder::Extract(
2275     ISequentialInStream *inStreamFork, ISequentialOutStream *realOutStream,
2276     UInt64 forkSize,
2277     const CCompressHeader &compressHeader,
2278     const CByteBuffer *data,
2279     UInt64 progressStart, IArchiveExtractCallback *extractCallback,
2280     int &opRes)
2281 {
2282   opRes = NExtract::NOperationResult::kDataError;
2283 
2284   if (compressHeader.IsMethod_Uncompressed_Inline())
2285   {
2286     const size_t packSize = data->Size() - compressHeader.DataPos;
2287     if (realOutStream)
2288     {
2289       RINOK(WriteStream(realOutStream, *data + compressHeader.DataPos, packSize))
2290     }
2291     opRes = NExtract::NOperationResult::kOK;
2292     return S_OK;
2293   }
2294 
2295   if (compressHeader.Method == kMethod_ZLIB_ATTR ||
2296       compressHeader.Method == kMethod_LZVN_ATTR)
2297   {
2298     CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
2299     const size_t packSize = data->Size() - compressHeader.DataPos;
2300     bufInStream->Init(*data + compressHeader.DataPos, packSize);
2301 
2302     if (compressHeader.Method == kMethod_ZLIB_ATTR)
2303     {
2304       const HRESULT hres = _zlibDecoder.Interface()->Code(bufInStream, realOutStream,
2305           NULL, &compressHeader.UnpackSize, NULL);
2306       if (hres == S_OK)
2307         if (_zlibDecoder->GetOutputProcessedSize() == compressHeader.UnpackSize
2308             && _zlibDecoder->GetInputProcessedSize() == packSize)
2309           opRes = NExtract::NOperationResult::kOK;
2310       return hres;
2311     }
2312     {
2313       const UInt64 packSize64 = packSize;
2314       const HRESULT hres = _lzfseDecoder.Interface()->Code(bufInStream, realOutStream,
2315           &packSize64, &compressHeader.UnpackSize, NULL);
2316       if (hres == S_OK)
2317       {
2318         // in/out sizes were checked in Code()
2319         opRes = NExtract::NOperationResult::kOK;
2320       }
2321       return hres;
2322     }
2323   }
2324 
2325   HRESULT hres;
2326   if (compressHeader.Method == NHfs::kMethod_ZLIB_RSRC)
2327   {
2328     hres = ExtractResourceFork_ZLIB(
2329         inStreamFork, realOutStream,
2330         forkSize, compressHeader.UnpackSize,
2331         progressStart, extractCallback);
2332     // for debug:
2333     // hres = NCompress::CopyStream(inStreamFork, realOutStream, NULL);
2334   }
2335   else if (compressHeader.Method == NHfs::kMethod_LZVN_RSRC)
2336   {
2337     hres = ExtractResourceFork_LZFSE(
2338         inStreamFork, realOutStream,
2339         forkSize, compressHeader.UnpackSize,
2340         progressStart, extractCallback);
2341   }
2342   /*
2343   else if (compressHeader.Method == NHfs::kMethod_ZBM_RSRC)
2344   {
2345     hres = ExtractResourceFork_ZBM(
2346         inStreamFork, realOutStream,
2347         forkSize, compressHeader.UnpackSize,
2348         progressStart, extractCallback);
2349   }
2350   */
2351   else
2352   {
2353     opRes = NExtract::NOperationResult::kUnsupportedMethod;
2354     hres = S_FALSE;
2355   }
2356 
2357   if (hres == S_OK)
2358     opRes = NExtract::NOperationResult::kOK;
2359   return hres;
2360 }
2361 
2362 
2363 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
2364     Int32 testMode, IArchiveExtractCallback *extractCallback))
2365 {
2366   COM_TRY_BEGIN
2367   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
2368   if (allFilesMode)
2369     numItems = Refs.Size();
2370   if (numItems == 0)
2371     return S_OK;
2372   UInt32 i;
2373   UInt64 totalSize = 0;
2374   for (i = 0; i < numItems; i++)
2375   {
2376     const CRef &ref = Refs[allFilesMode ? i : indices[i]];
2377     totalSize += Get_UnpackSize_of_Ref(ref);
2378   }
2379   RINOK(extractCallback->SetTotal(totalSize))
2380 
2381   UInt64 currentTotalSize = 0, currentItemSize = 0;
2382 
2383   const size_t kBufSize = kCompressionBlockSize;
2384   CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2385 
2386   // there are hfs without adler in zlib.
2387   CDecoder decoder(true); // IsAdlerOptional
2388 
2389   for (i = 0;; i++, currentTotalSize += currentItemSize)
2390   {
2391     RINOK(extractCallback->SetCompleted(&currentTotalSize))
2392     if (i >= numItems)
2393       break;
2394     const UInt32 index = allFilesMode ? i : indices[i];
2395     const CRef &ref = Refs[index];
2396     const CItem &item = Items[ref.ItemIndex];
2397     currentItemSize = Get_UnpackSize_of_Ref(ref);
2398 
2399     int opRes;
2400    {
2401     CMyComPtr<ISequentialOutStream> realOutStream;
2402     const Int32 askMode = testMode ?
2403         NExtract::NAskMode::kTest :
2404         NExtract::NAskMode::kExtract;
2405     RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
2406 
2407     if (ref.IsItem() && item.IsDir())
2408     {
2409       RINOK(extractCallback->PrepareOperation(askMode))
2410       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
2411       continue;
2412     }
2413     if (!testMode && !realOutStream)
2414       continue;
2415 
2416     RINOK(extractCallback->PrepareOperation(askMode))
2417 
2418     UInt64 pos = 0;
2419     opRes = NExtract::NOperationResult::kDataError;
2420     const CFork *fork = NULL;
2421 
2422     if (ref.AttrIndex >= 0)
2423     {
2424       const CAttr &attr = Attrs[ref.AttrIndex];
2425       if (attr.Fork_defined && attr.Data.Size() == 0)
2426         fork = &attr.Fork;
2427       else
2428       {
2429         opRes = NExtract::NOperationResult::kOK;
2430         if (realOutStream)
2431         {
2432           RINOK(WriteStream(realOutStream,
2433               // AttrBuf + attr.Pos, attr.Size
2434               attr.Data, attr.Data.Size()
2435               ))
2436         }
2437       }
2438     }
2439     else if (ref.IsResource())
2440       fork = &item.ResourceFork;
2441     else if (item.CompressHeader.IsSupported)
2442     {
2443       CMyComPtr<ISequentialInStream> inStreamFork;
2444       UInt64 forkSize = 0;
2445       const CByteBuffer *decmpfs_Data = NULL;
2446 
2447       if (item.CompressHeader.IsMethod_Resource())
2448       {
2449         const CFork &resourceFork = item.ResourceFork;
2450         forkSize = resourceFork.Size;
2451         GetForkStream(resourceFork, &inStreamFork);
2452       }
2453       else
2454       {
2455         const CAttr &attr = Attrs[item.decmpfs_AttrIndex];
2456         decmpfs_Data = &attr.Data;
2457       }
2458 
2459       if (inStreamFork || decmpfs_Data)
2460       {
2461         const HRESULT hres = decoder.Extract(
2462             inStreamFork, realOutStream,
2463             forkSize,
2464             item.CompressHeader,
2465             decmpfs_Data,
2466             currentTotalSize, extractCallback,
2467             opRes);
2468         if (hres != S_FALSE && hres != S_OK)
2469           return hres;
2470       }
2471     }
2472     else if (item.CompressHeader.IsCorrect)
2473       opRes = NExtract::NOperationResult::kUnsupportedMethod;
2474     else
2475       fork = &item.DataFork;
2476 
2477     if (fork)
2478     {
2479       if (fork->IsOk(Header.BlockSizeLog))
2480       {
2481         opRes = NExtract::NOperationResult::kOK;
2482         unsigned extentIndex;
2483         for (extentIndex = 0; extentIndex < fork->Extents.Size(); extentIndex++)
2484         {
2485           if (opRes != NExtract::NOperationResult::kOK)
2486             break;
2487           if (fork->Size == pos)
2488             break;
2489           const CExtent &e = fork->Extents[extentIndex];
2490           RINOK(InStream_SeekSet(_stream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog)))
2491           UInt64 extentRem = (UInt64)e.NumBlocks << Header.BlockSizeLog;
2492           while (extentRem != 0)
2493           {
2494             const UInt64 rem = fork->Size - pos;
2495             if (rem == 0)
2496             {
2497               // Here we check that there are no extra (empty) blocks in last extent.
2498               if (extentRem >= ((UInt64)1 << Header.BlockSizeLog))
2499                 opRes = NExtract::NOperationResult::kDataError;
2500               break;
2501             }
2502             size_t cur = kBufSize;
2503             if (cur > rem)
2504               cur = (size_t)rem;
2505             if (cur > extentRem)
2506               cur = (size_t)extentRem;
2507             RINOK(ReadStream(_stream, buf, &cur))
2508             if (cur == 0)
2509             {
2510               opRes = NExtract::NOperationResult::kDataError;
2511               break;
2512             }
2513             if (realOutStream)
2514             {
2515               RINOK(WriteStream(realOutStream, buf, cur))
2516             }
2517             pos += cur;
2518             extentRem -= cur;
2519             const UInt64 processed = currentTotalSize + pos;
2520             RINOK(extractCallback->SetCompleted(&processed))
2521           }
2522         }
2523         if (extentIndex != fork->Extents.Size() || fork->Size != pos)
2524           opRes = NExtract::NOperationResult::kDataError;
2525       }
2526     }
2527    }
2528     RINOK(extractCallback->SetOperationResult(opRes))
2529   }
2530   return S_OK;
2531   COM_TRY_END
2532 }
2533 
2534 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
2535 {
2536   *numItems = Refs.Size();
2537   return S_OK;
2538 }
2539 
2540 HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream)
2541 {
2542   *stream = NULL;
2543 
2544   if (!fork.IsOk(Header.BlockSizeLog))
2545     return S_FALSE;
2546 
2547   CMyComPtr2<ISequentialInStream, CExtentsStream> extentStream;
2548   extentStream.Create_if_Empty();
2549 
2550   UInt64 rem = fork.Size;
2551   UInt64 virt = 0;
2552 
2553   FOR_VECTOR (i, fork.Extents)
2554   {
2555     const CExtent &e = fork.Extents[i];
2556     if (e.NumBlocks == 0)
2557       continue;
2558     UInt64 cur = ((UInt64)e.NumBlocks << Header.BlockSizeLog);
2559     if (cur > rem)
2560     {
2561       cur = rem;
2562       if (i != fork.Extents.Size() - 1)
2563         return S_FALSE;
2564     }
2565     CSeekExtent se;
2566     se.Phy = SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog);
2567     se.Virt = virt;
2568     virt += cur;
2569     rem -= cur;
2570     extentStream->Extents.Add(se);
2571   }
2572 
2573   if (rem != 0)
2574     return S_FALSE;
2575 
2576   CSeekExtent se;
2577   se.Phy = 0; // = SpecOffset ?
2578   se.Virt = virt;
2579   extentStream->Extents.Add(se);
2580   extentStream->Stream = _stream;
2581   extentStream->Init();
2582   *stream = extentStream.Detach();
2583   return S_OK;
2584 }
2585 
2586 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
2587 {
2588   *stream = NULL;
2589 
2590   const CRef &ref = Refs[index];
2591   const CFork *fork = NULL;
2592   if (ref.AttrIndex >= 0)
2593   {
2594     const CAttr &attr = Attrs[ref.AttrIndex];
2595     if (!attr.Fork_defined || attr.Data.Size() != 0)
2596       return S_FALSE;
2597     fork = &attr.Fork;
2598   }
2599   else
2600   {
2601     const CItem &item = Items[ref.ItemIndex];
2602     if (ref.IsResource())
2603       fork = &item.ResourceFork;
2604     else if (item.IsDir())
2605       return S_FALSE;
2606     else if (item.CompressHeader.IsCorrect)
2607       return S_FALSE;
2608     else
2609       fork = &item.DataFork;
2610   }
2611   return GetForkStream(*fork, stream);
2612 }
2613 
2614 static const Byte k_Signature[] = {
2615     2, 'B', 'D',
2616     4, 'H', '+', 0, 4,
2617     4, 'H', 'X', 0, 5 };
2618 
2619 REGISTER_ARC_I(
2620   "HFS", "hfs hfsx", NULL, 0xE3,
2621   k_Signature,
2622   kHeaderPadSize,
2623   NArcInfoFlags::kMultiSignature,
2624   IsArc_HFS)
2625 
2626 }}
2627