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(¤tTotalSize))
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