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 namespace NArchive {
29 namespace NHfs {
30
31 static const char * const kResFileName = "rsrc"; // "com.apple.ResourceFork";
32
33 struct CExtent
34 {
35 UInt32 Pos;
36 UInt32 NumBlocks;
37 };
38
39 struct CIdExtents
40 {
41 UInt32 ID;
42 UInt32 StartBlock;
43 CRecordVector<CExtent> Extents;
44 };
45
46 struct CFork
47 {
48 UInt64 Size;
49 UInt32 NumBlocks;
50 // UInt32 ClumpSize;
51 CRecordVector<CExtent> Extents;
52
CForkNArchive::NHfs::CFork53 CFork(): Size(0), NumBlocks(0) {}
54
55 void Parse(const Byte *p);
56
IsEmptyNArchive::NHfs::CFork57 bool IsEmpty() const { return Size == 0 && NumBlocks == 0 && Extents.Size() == 0; }
58
59 UInt32 Calc_NumBlocks_from_Extents() const;
60 bool Check_NumBlocks() const;
61
Check_Size_with_NumBlocksNArchive::NHfs::CFork62 bool Check_Size_with_NumBlocks(unsigned blockSizeLog) const
63 {
64 return Size <= ((UInt64)NumBlocks << blockSizeLog);
65 }
66
IsOkNArchive::NHfs::CFork67 bool IsOk(unsigned blockSizeLog) const
68 {
69 // we don't check cases with extra (empty) blocks in last extent
70 return Check_NumBlocks() && Check_Size_with_NumBlocks(blockSizeLog);
71 }
72
73 bool Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id);
UpgradeAndTestNArchive::NHfs::CFork74 bool UpgradeAndTest(const CObjectVector<CIdExtents> &items, UInt32 id, unsigned blockSizeLog)
75 {
76 if (!Upgrade(items, id))
77 return false;
78 return IsOk(blockSizeLog);
79 }
80 };
81
82 static const unsigned kNumFixedExtents = 8;
83 static const unsigned kForkRecSize = 16 + kNumFixedExtents * 8;
84
85
Parse(const Byte * p)86 void CFork::Parse(const Byte *p)
87 {
88 Extents.Clear();
89 Size = Get64(p);
90 // ClumpSize = Get32(p + 8);
91 NumBlocks = Get32(p + 12);
92 p += 16;
93 for (unsigned i = 0; i < kNumFixedExtents; i++, p += 8)
94 {
95 CExtent e;
96 e.Pos = Get32(p);
97 e.NumBlocks = Get32(p + 4);
98 if (e.NumBlocks != 0)
99 Extents.Add(e);
100 }
101 }
102
Calc_NumBlocks_from_Extents() const103 UInt32 CFork::Calc_NumBlocks_from_Extents() const
104 {
105 UInt32 num = 0;
106 FOR_VECTOR (i, Extents)
107 {
108 num += Extents[i].NumBlocks;
109 }
110 return num;
111 }
112
Check_NumBlocks() const113 bool CFork::Check_NumBlocks() const
114 {
115 UInt32 num = 0;
116 FOR_VECTOR (i, Extents)
117 {
118 UInt32 next = num + Extents[i].NumBlocks;
119 if (next < num)
120 return false;
121 num = next;
122 }
123 return num == NumBlocks;
124 }
125
126 struct CIdIndexPair
127 {
128 UInt32 ID;
129 unsigned Index;
130
131 int Compare(const CIdIndexPair &a) const;
132 };
133
134 #define RINOZ(x) { const int _t_ = (x); if (_t_ != 0) return _t_; }
135
Compare(const CIdIndexPair & a) const136 int CIdIndexPair::Compare(const CIdIndexPair &a) const
137 {
138 RINOZ(MyCompare(ID, a.ID))
139 return MyCompare(Index, a.Index);
140 }
141
FindItemIndex(const CRecordVector<CIdIndexPair> & items,UInt32 id)142 static int FindItemIndex(const CRecordVector<CIdIndexPair> &items, UInt32 id)
143 {
144 unsigned left = 0, right = items.Size();
145 while (left != right)
146 {
147 const unsigned mid = (left + right) / 2;
148 const UInt32 midVal = items[mid].ID;
149 if (id == midVal)
150 return (int)items[mid].Index;
151 if (id < midVal)
152 right = mid;
153 else
154 left = mid + 1;
155 }
156 return -1;
157 }
158
Find_in_IdExtents(const CObjectVector<CIdExtents> & items,UInt32 id)159 static int Find_in_IdExtents(const CObjectVector<CIdExtents> &items, UInt32 id)
160 {
161 unsigned left = 0, right = items.Size();
162 while (left != right)
163 {
164 const unsigned mid = (left + right) / 2;
165 const UInt32 midVal = items[mid].ID;
166 if (id == midVal)
167 return (int)mid;
168 if (id < midVal)
169 right = mid;
170 else
171 left = mid + 1;
172 }
173 return -1;
174 }
175
Upgrade(const CObjectVector<CIdExtents> & items,UInt32 id)176 bool CFork::Upgrade(const CObjectVector<CIdExtents> &items, UInt32 id)
177 {
178 int index = Find_in_IdExtents(items, id);
179 if (index < 0)
180 return true;
181 const CIdExtents &item = items[index];
182 if (Calc_NumBlocks_from_Extents() != item.StartBlock)
183 return false;
184 Extents += item.Extents;
185 return true;
186 }
187
188
189 struct CVolHeader
190 {
191 Byte Header[2];
192 UInt16 Version;
193 // UInt32 Attr;
194 // UInt32 LastMountedVersion;
195 // UInt32 JournalInfoBlock;
196
197 UInt32 CTime;
198 UInt32 MTime;
199 // UInt32 BackupTime;
200 // UInt32 CheckedTime;
201
202 UInt32 NumFiles;
203 UInt32 NumFolders;
204 unsigned BlockSizeLog;
205 UInt32 NumBlocks;
206 UInt32 NumFreeBlocks;
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 Version > 4; }
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 UString ResFileName;
467
468 UInt64 SpecOffset;
469 UInt64 PhySize;
470 UInt64 PhySize2;
471 UInt64 ArcFileSize;
472 UInt32 MethodsMask;
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 >> 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
IsArc_HFS(const Byte * p,size_t size)1335 API_FUNC_static_IsArc IsArc_HFS(const Byte *p, size_t size)
1336 {
1337 if (size < kHfsHeaderSize)
1338 return k_IsArc_Res_NEED_MORE;
1339 p += kHeaderPadSize;
1340 if (p[0] == 'B' && p[1] == 'D')
1341 {
1342 if (p[0x7C] != 'H' || p[0x7C + 1] != '+')
1343 return k_IsArc_Res_NO;
1344 }
1345 else
1346 {
1347 if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X'))
1348 return k_IsArc_Res_NO;
1349 UInt32 version = Get16(p + 2);
1350 if (version < 4 || version > 5)
1351 return k_IsArc_Res_NO;
1352 }
1353 return k_IsArc_Res_YES;
1354 }
1355 }
1356
Open2(IInStream * inStream,IArchiveOpenCallback * progress)1357 HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress)
1358 {
1359 Clear();
1360 Byte buf[kHfsHeaderSize];
1361 RINOK(ReadStream_FALSE(inStream, buf, kHfsHeaderSize))
1362 {
1363 for (unsigned i = 0; i < kHeaderPadSize; i++)
1364 if (buf[i] != 0)
1365 return S_FALSE;
1366 }
1367 const Byte *p = buf + kHeaderPadSize;
1368 CVolHeader &h = Header;
1369
1370 h.Header[0] = p[0];
1371 h.Header[1] = p[1];
1372
1373 if (p[0] == 'B' && p[1] == 'D')
1374 {
1375 /*
1376 It's header for old HFS format.
1377 We don't support old HFS format, but we support
1378 special HFS volume that contains embedded HFS+ volume
1379 */
1380
1381 if (p[0x7C] != 'H' || p[0x7C + 1] != '+')
1382 return S_FALSE;
1383
1384 /*
1385 h.CTime = Get32(p + 0x2);
1386 h.MTime = Get32(p + 0x6);
1387
1388 h.NumFiles = Get32(p + 0x54);
1389 h.NumFolders = Get32(p + 0x58);
1390
1391 if (h.NumFolders > ((UInt32)1 << 29) ||
1392 h.NumFiles > ((UInt32)1 << 30))
1393 return S_FALSE;
1394 if (progress)
1395 {
1396 UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1397 RINOK(progress->SetTotal(&numFiles, NULL))
1398 }
1399 h.NumFreeBlocks = Get16(p + 0x22);
1400 */
1401
1402 UInt32 blockSize = Get32(p + 0x14);
1403
1404 {
1405 unsigned i;
1406 for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1407 if (i == 31)
1408 return S_FALSE;
1409 h.BlockSizeLog = i;
1410 }
1411
1412 h.NumBlocks = Get16(p + 0x12);
1413 /*
1414 we suppose that it has the follwing layout
1415 {
1416 start block with header
1417 [h.NumBlocks]
1418 end block with header
1419 }
1420 */
1421 PhySize2 = ((UInt64)h.NumBlocks + 2) << h.BlockSizeLog;
1422
1423 UInt32 startBlock = Get16(p + 0x7C + 2);
1424 UInt32 blockCount = Get16(p + 0x7C + 4);
1425 SpecOffset = (UInt64)(1 + startBlock) << h.BlockSizeLog;
1426 UInt64 phy = SpecOffset + ((UInt64)blockCount << h.BlockSizeLog);
1427 if (PhySize2 < phy)
1428 PhySize2 = phy;
1429 RINOK(InStream_SeekSet(inStream, SpecOffset))
1430 RINOK(ReadStream_FALSE(inStream, buf, kHfsHeaderSize))
1431 }
1432
1433 if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X'))
1434 return S_FALSE;
1435 h.Version = Get16(p + 2);
1436 if (h.Version < 4 || h.Version > 5)
1437 return S_FALSE;
1438
1439 // h.Attr = Get32(p + 4);
1440 // h.LastMountedVersion = Get32(p + 8);
1441 // h.JournalInfoBlock = Get32(p + 0xC);
1442
1443 h.CTime = Get32(p + 0x10);
1444 h.MTime = Get32(p + 0x14);
1445 // h.BackupTime = Get32(p + 0x18);
1446 // h.CheckedTime = Get32(p + 0x1C);
1447
1448 h.NumFiles = Get32(p + 0x20);
1449 h.NumFolders = Get32(p + 0x24);
1450
1451 if (h.NumFolders > ((UInt32)1 << 29) ||
1452 h.NumFiles > ((UInt32)1 << 30))
1453 return S_FALSE;
1454
1455 RINOK(InStream_GetSize_SeekToEnd(inStream, ArcFileSize))
1456
1457 if (progress)
1458 {
1459 const UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1;
1460 RINOK(progress->SetTotal(&numFiles, NULL))
1461 }
1462
1463 UInt32 blockSize = Get32(p + 0x28);
1464
1465 {
1466 unsigned i;
1467 for (i = 9; ((UInt32)1 << i) != blockSize; i++)
1468 if (i == 31)
1469 return S_FALSE;
1470 h.BlockSizeLog = i;
1471 }
1472
1473 h.NumBlocks = Get32(p + 0x2C);
1474 h.NumFreeBlocks = Get32(p + 0x30);
1475
1476 /*
1477 h.NextCalatlogNodeID = Get32(p + 0x40);
1478 h.WriteCount = Get32(p + 0x44);
1479 for (i = 0; i < 6; i++)
1480 h.FinderInfo[i] = Get32(p + 0x50 + i * 4);
1481 h.VolID = Get64(p + 0x68);
1482 */
1483
1484 ResFileName = kResFileName;
1485
1486 CFork extentsFork, catalogFork, attrFork;
1487 // allocationFork.Parse(p + 0x70 + 0x50 * 0);
1488 extentsFork.Parse(p + 0x70 + 0x50 * 1);
1489 catalogFork.Parse(p + 0x70 + 0x50 * 2);
1490 attrFork.Parse (p + 0x70 + 0x50 * 3);
1491 // startupFork.Parse(p + 0x70 + 0x50 * 4);
1492
1493 CObjectVector<CIdExtents> overflowExtents[2];
1494 if (!extentsFork.IsOk(Header.BlockSizeLog))
1495 HeadersError = true;
1496 else
1497 {
1498 HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents);
1499 if (res == S_FALSE)
1500 HeadersError = true;
1501 else if (res != S_OK)
1502 return res;
1503 }
1504
1505 if (!catalogFork.UpgradeAndTest(overflowExtents[0], kHfsID_CatalogFile, Header.BlockSizeLog))
1506 return S_FALSE;
1507
1508 if (!attrFork.UpgradeAndTest(overflowExtents[0], kHfsID_AttributesFile, Header.BlockSizeLog))
1509 HeadersError = true;
1510 else
1511 {
1512 if (attrFork.Size != 0)
1513 RINOK(LoadAttrs(attrFork, inStream, progress))
1514 }
1515
1516 RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress))
1517
1518 PhySize = Header.GetPhySize();
1519 return S_OK;
1520 }
1521
1522
1523
1524 Z7_class_CHandler_final:
1525 public IInArchive,
1526 public IArchiveGetRawProps,
1527 public IInArchiveGetStream,
1528 public CMyUnknownImp,
1529 public CDatabase
1530 {
1531 Z7_IFACES_IMP_UNK_3(
1532 IInArchive,
1533 IArchiveGetRawProps,
1534 IInArchiveGetStream)
1535
1536 CMyComPtr<IInStream> _stream;
1537 HRESULT GetForkStream(const CFork &fork, ISequentialInStream **stream);
1538 };
1539
1540 static const Byte kProps[] =
1541 {
1542 kpidPath,
1543 kpidIsDir,
1544 kpidSize,
1545 kpidPackSize,
1546 kpidCTime,
1547 kpidMTime,
1548 kpidATime,
1549 kpidChangeTime,
1550 kpidPosixAttrib,
1551 /*
1552 kpidUserId,
1553 kpidGroupId,
1554 */
1555 #ifdef HFS_SHOW_ALT_STREAMS
1556 kpidIsAltStream,
1557 #endif
1558 kpidMethod
1559 };
1560
1561 static const Byte kArcProps[] =
1562 {
1563 kpidMethod,
1564 kpidCharacts,
1565 kpidClusterSize,
1566 kpidFreeSpace,
1567 kpidCTime,
1568 kpidMTime
1569 };
1570
1571 IMP_IInArchive_Props
1572 IMP_IInArchive_ArcProps
1573
1574 static void HfsTimeToProp(UInt32 hfsTime, NWindows::NCOM::CPropVariant &prop)
1575 {
1576 if (hfsTime == 0)
1577 return;
1578 FILETIME ft;
1579 HfsTimeToFileTime(hfsTime, ft);
1580 prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base);
1581 }
1582
1583 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
1584 {
1585 COM_TRY_BEGIN
1586 NWindows::NCOM::CPropVariant prop;
1587 switch (propID)
1588 {
1589 case kpidExtension: prop = Header.IsHfsX() ? "hfsx" : "hfs"; break;
1590 case kpidMethod: prop = Header.IsHfsX() ? "HFSX" : "HFS+"; break;
1591 case kpidCharacts: MethodsMaskToProp(MethodsMask, prop); break;
1592 case kpidPhySize:
1593 {
1594 UInt64 v = SpecOffset + PhySize;
1595 if (v < PhySize2)
1596 v = PhySize2;
1597 prop = v;
1598 break;
1599 }
1600 case kpidClusterSize: prop = (UInt32)1 << Header.BlockSizeLog; break;
1601 case kpidFreeSpace: prop = (UInt64)Header.GetFreeSize(); break;
1602 case kpidMTime: HfsTimeToProp(Header.MTime, prop); break;
1603 case kpidCTime:
1604 {
1605 if (Header.CTime != 0)
1606 {
1607 FILETIME localFt, ft;
1608 HfsTimeToFileTime(Header.CTime, localFt);
1609 if (LocalFileTimeToFileTime(&localFt, &ft))
1610 prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base);
1611 }
1612 break;
1613 }
1614 case kpidIsTree: prop = true; break;
1615 case kpidErrorFlags:
1616 {
1617 UInt32 flags = 0;
1618 if (HeadersError) flags |= kpv_ErrorFlags_HeadersError;
1619 if (UnsupportedFeature) flags |= kpv_ErrorFlags_UnsupportedFeature;
1620 if (flags != 0)
1621 prop = flags;
1622 break;
1623 }
1624 case kpidIsAltStream: prop = ThereAreAltStreams; break;
1625 }
1626 prop.Detach(value);
1627 return S_OK;
1628 COM_TRY_END
1629 }
1630
1631 Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps))
1632 {
1633 *numProps = 0;
1634 return S_OK;
1635 }
1636
1637 Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID))
1638 {
1639 *name = NULL;
1640 *propID = 0;
1641 return S_OK;
1642 }
1643
1644 Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType))
1645 {
1646 const CRef &ref = Refs[index];
1647 *parentType = ref.IsAltStream() ?
1648 NParentType::kAltStream :
1649 NParentType::kDir;
1650 *parent = (UInt32)(Int32)ref.Parent;
1651 return S_OK;
1652 }
1653
1654 Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType))
1655 {
1656 *data = NULL;
1657 *dataSize = 0;
1658 *propType = 0;
1659 #ifdef MY_CPU_LE
1660 if (propID == kpidName)
1661 {
1662 const CRef &ref = Refs[index];
1663 const UString *s;
1664 if (ref.IsResource())
1665 s = &ResFileName;
1666 else if (ref.AttrIndex >= 0)
1667 s = &Attrs[ref.AttrIndex].Name;
1668 else
1669 s = &Items[ref.ItemIndex].Name;
1670 *data = (const wchar_t *)(*s);
1671 *dataSize = (s->Len() + 1) * (UInt32)sizeof(wchar_t);
1672 *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE;
1673 return S_OK;
1674 }
1675 #else
1676 UNUSED_VAR(index)
1677 UNUSED_VAR(propID)
1678 #endif
1679 return S_OK;
1680 }
1681
1682 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
1683 {
1684 COM_TRY_BEGIN
1685 NWindows::NCOM::CPropVariant prop;
1686 const CRef &ref = Refs[index];
1687 const CItem &item = Items[ref.ItemIndex];
1688 switch (propID)
1689 {
1690 case kpidPath: GetItemPath(index, prop); break;
1691 case kpidName:
1692 {
1693 const UString *s;
1694 if (ref.IsResource())
1695 s = &ResFileName;
1696 else if (ref.AttrIndex >= 0)
1697 s = &Attrs[ref.AttrIndex].Name;
1698 else
1699 s = &item.Name;
1700 prop = *s;
1701 break;
1702 }
1703 case kpidPackSize:
1704 {
1705 UInt64 size;
1706 if (ref.AttrIndex >= 0)
1707 size = Attrs[ref.AttrIndex].GetSize();
1708 else if (ref.IsResource())
1709 size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1710 else if (item.IsDir())
1711 break;
1712 else if (item.CompressHeader.IsCorrect)
1713 {
1714 if (item.CompressHeader.IsMethod_Resource())
1715 size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog;
1716 else if (item.decmpfs_AttrIndex >= 0)
1717 {
1718 // size = item.PackSize;
1719 const CAttr &attr = Attrs[item.decmpfs_AttrIndex];
1720 size = attr.Data.Size() - item.CompressHeader.DataPos;
1721 }
1722 else
1723 size = 0;
1724 }
1725 else
1726 size = (UInt64)item.DataFork.NumBlocks << Header.BlockSizeLog;
1727 prop = size;
1728 break;
1729 }
1730 case kpidSize:
1731 {
1732 UInt64 size;
1733 if (ref.AttrIndex >= 0)
1734 size = Attrs[ref.AttrIndex].GetSize();
1735 else if (ref.IsResource())
1736 size = item.ResourceFork.Size;
1737 else if (item.IsDir())
1738 break;
1739 else if (item.CompressHeader.IsCorrect)
1740 size = item.CompressHeader.UnpackSize;
1741 else
1742 size = item.DataFork.Size;
1743 prop = size;
1744 break;
1745 }
1746 case kpidIsDir: prop = (ref.IsItem() && item.IsDir()); break;
1747 case kpidIsAltStream: prop = ref.IsAltStream(); break;
1748 case kpidCTime: HfsTimeToProp(item.CTime, prop); break;
1749 case kpidMTime: HfsTimeToProp(item.MTime, prop); break;
1750 case kpidATime: HfsTimeToProp(item.ATime, prop); break;
1751 case kpidChangeTime: HfsTimeToProp(item.AttrMTime, prop); break;
1752 case kpidPosixAttrib: if (ref.IsItem()) prop = (UInt32)item.FileMode; break;
1753 /*
1754 case kpidUserId: prop = (UInt32)item.OwnerID; break;
1755 case kpidGroupId: prop = (UInt32)item.GroupID; break;
1756 */
1757
1758 case kpidMethod:
1759 if (ref.IsItem())
1760 item.CompressHeader.MethodToProp(prop);
1761 break;
1762 }
1763 prop.Detach(value);
1764 return S_OK;
1765 COM_TRY_END
1766 }
1767
1768 Z7_COM7F_IMF(CHandler::Open(IInStream *inStream,
1769 const UInt64 * /* maxCheckStartPosition */,
1770 IArchiveOpenCallback *callback))
1771 {
1772 COM_TRY_BEGIN
1773 Close();
1774 RINOK(Open2(inStream, callback))
1775 _stream = inStream;
1776 return S_OK;
1777 COM_TRY_END
1778 }
1779
1780 Z7_COM7F_IMF(CHandler::Close())
1781 {
1782 _stream.Release();
1783 Clear();
1784 return S_OK;
1785 }
1786
1787 static const UInt32 kCompressionBlockSize = 1 << 16;
1788
1789 CDecoder::CDecoder(bool IsAdlerOptional)
1790 {
1791 /* Some new hfs files contain zlib resource fork without Adler checksum.
1792 We do not know how we must detect case where there is Adler
1793 checksum or there is no Adler checksum.
1794 */
1795 _zlibDecoder->IsAdlerOptional = IsAdlerOptional;
1796 _lzfseDecoder->LzvnMode = true;
1797 }
1798
1799 HRESULT CDecoder::ExtractResourceFork_ZLIB(
1800 ISequentialInStream *inStream, ISequentialOutStream *outStream,
1801 UInt64 forkSize, UInt64 unpackSize,
1802 UInt64 progressStart, IArchiveExtractCallback *extractCallback)
1803 {
1804 const unsigned kHeaderSize = 0x100 + 8;
1805
1806 const size_t kBufSize = kCompressionBlockSize;
1807 _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
1808
1809 RINOK(ReadStream_FALSE(inStream, _buf, kHeaderSize))
1810 Byte *buf = _buf;
1811 const UInt32 dataPos = Get32(buf);
1812 const UInt32 mapPos = Get32(buf + 4);
1813 const UInt32 dataSize = Get32(buf + 8);
1814 const UInt32 mapSize = Get32(buf + 12);
1815
1816 const UInt32 kResMapSize = 50;
1817
1818 if (mapSize != kResMapSize
1819 || dataPos > mapPos
1820 || dataSize != mapPos - dataPos
1821 || mapSize > forkSize
1822 || mapPos != forkSize - mapSize)
1823 return S_FALSE;
1824
1825 const UInt32 dataSize2 = Get32(buf + 0x100);
1826 if (4 + dataSize2 != dataSize
1827 || dataSize2 < 8
1828 || dataSize2 > dataSize)
1829 return S_FALSE;
1830
1831 const UInt32 numBlocks = GetUi32(buf + 0x100 + 4);
1832 if (((dataSize2 - 4) >> 3) < numBlocks)
1833 return S_FALSE;
1834 {
1835 const UInt64 up = unpackSize + kCompressionBlockSize - 1;
1836 if (up < unpackSize || up / kCompressionBlockSize != numBlocks)
1837 return S_FALSE;
1838 }
1839
1840 const UInt32 tableSize = (numBlocks << 3);
1841
1842 _tableBuf.AllocAtLeast(tableSize);
1843
1844 RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize))
1845 const Byte *tableBuf = _tableBuf;
1846
1847 UInt32 prev = 4 + tableSize;
1848
1849 UInt32 i;
1850 for (i = 0; i < numBlocks; i++)
1851 {
1852 const UInt32 offs = GetUi32(tableBuf + i * 8);
1853 const UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1854 if (size == 0
1855 || prev != offs
1856 || offs > dataSize2
1857 || size > dataSize2 - offs)
1858 return S_FALSE;
1859 prev = offs + size;
1860 }
1861
1862 if (prev != dataSize2)
1863 return S_FALSE;
1864
1865 CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
1866
1867 // bool padError = false;
1868 UInt64 outPos = 0;
1869
1870 for (i = 0; i < numBlocks; i++)
1871 {
1872 const UInt64 rem = unpackSize - outPos;
1873 if (rem == 0)
1874 return S_FALSE;
1875 UInt32 blockSize = kCompressionBlockSize;
1876 if (rem < kCompressionBlockSize)
1877 blockSize = (UInt32)rem;
1878
1879 const UInt32 size = GetUi32(tableBuf + i * 8 + 4);
1880
1881 if (size > kCompressionBlockSize + 1)
1882 return S_FALSE;
1883
1884 RINOK(ReadStream_FALSE(inStream, buf, size))
1885
1886 if ((buf[0] & 0xF) == 0xF)
1887 {
1888 // (buf[0] = 0xff) is marker of uncompressed block in APFS
1889 // that code was not tested in HFS
1890 if (size - 1 != blockSize)
1891 return S_FALSE;
1892
1893 if (outStream)
1894 {
1895 RINOK(WriteStream(outStream, buf + 1, blockSize))
1896 }
1897 }
1898 else
1899 {
1900 const UInt64 blockSize64 = blockSize;
1901 bufInStream->Init(buf, size);
1902 RINOK(_zlibDecoder.Interface()->Code(bufInStream, outStream, NULL, &blockSize64, NULL))
1903 if (_zlibDecoder->GetOutputProcessedSize() != blockSize)
1904 return S_FALSE;
1905 const UInt64 inSize = _zlibDecoder->GetInputProcessedSize();
1906 if (inSize != size)
1907 {
1908 if (inSize > size)
1909 return S_FALSE;
1910 // apfs file can contain junk (non-zeros) after data block.
1911 /*
1912 if (!padError)
1913 {
1914 const Byte *p = buf + (UInt32)inSize;
1915 const Byte *e = p + (size - (UInt32)inSize);
1916 do
1917 {
1918 if (*p != 0)
1919 {
1920 padError = true;
1921 break;
1922 }
1923 }
1924 while (++p != e);
1925 }
1926 */
1927 }
1928 }
1929
1930 outPos += blockSize;
1931 if ((i & 0xFF) == 0)
1932 {
1933 const UInt64 progressPos = progressStart + outPos;
1934 RINOK(extractCallback->SetCompleted(&progressPos))
1935 }
1936 }
1937
1938 if (outPos != unpackSize)
1939 return S_FALSE;
1940
1941 // if (padError) return S_FALSE;
1942
1943 /* We check Resource Map
1944 Are there HFS files with another values in Resource Map ??? */
1945
1946 RINOK(ReadStream_FALSE(inStream, buf, mapSize))
1947 const UInt32 types = Get16(buf + 24);
1948 const UInt32 names = Get16(buf + 26);
1949 const UInt32 numTypes = Get16(buf + 28);
1950 if (numTypes != 0 || types != 28 || names != kResMapSize)
1951 return S_FALSE;
1952 const UInt32 resType = Get32(buf + 30);
1953 const UInt32 numResources = Get16(buf + 34);
1954 const UInt32 resListOffset = Get16(buf + 36);
1955 if (resType != 0x636D7066) // cmpf
1956 return S_FALSE;
1957 if (numResources != 0 || resListOffset != 10)
1958 return S_FALSE;
1959
1960 const UInt32 entryId = Get16(buf + 38);
1961 const UInt32 nameOffset = Get16(buf + 40);
1962 // Byte attrib = buf[42];
1963 const UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF;
1964 if (entryId != 1 || nameOffset != 0xFFFF || resourceOffset != 0)
1965 return S_FALSE;
1966
1967 return S_OK;
1968 }
1969
1970
1971
1972 HRESULT CDecoder::ExtractResourceFork_LZFSE(
1973 ISequentialInStream *inStream, ISequentialOutStream *outStream,
1974 UInt64 forkSize, UInt64 unpackSize,
1975 UInt64 progressStart, IArchiveExtractCallback *extractCallback)
1976 {
1977 const UInt32 kNumBlocksMax = (UInt32)1 << 29;
1978 if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize)
1979 return S_FALSE;
1980 const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize);
1981 const UInt32 numBlocks2 = numBlocks + 1;
1982 const UInt32 tableSize = (numBlocks2 << 2);
1983 if (tableSize > forkSize)
1984 return S_FALSE;
1985 _tableBuf.AllocAtLeast(tableSize);
1986 RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize))
1987 const Byte *tableBuf = _tableBuf;
1988
1989 {
1990 UInt32 prev = GetUi32(tableBuf);
1991 if (prev != tableSize)
1992 return S_FALSE;
1993 for (UInt32 i = 1; i < numBlocks2; i++)
1994 {
1995 const UInt32 offs = GetUi32(tableBuf + i * 4);
1996 if (offs <= prev)
1997 return S_FALSE;
1998 prev = offs;
1999 }
2000 if (prev != forkSize)
2001 return S_FALSE;
2002 }
2003
2004 const size_t kBufSize = kCompressionBlockSize;
2005 _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2006
2007 CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
2008
2009 UInt64 outPos = 0;
2010
2011 for (UInt32 i = 0; i < numBlocks; i++)
2012 {
2013 const UInt64 rem = unpackSize - outPos;
2014 if (rem == 0)
2015 return S_FALSE;
2016 UInt32 blockSize = kCompressionBlockSize;
2017 if (rem < kCompressionBlockSize)
2018 blockSize = (UInt32)rem;
2019
2020 const UInt32 size =
2021 GetUi32(tableBuf + i * 4 + 4) -
2022 GetUi32(tableBuf + i * 4);
2023
2024 if (size > kCompressionBlockSize + 1)
2025 return S_FALSE;
2026
2027 RINOK(ReadStream_FALSE(inStream, _buf, size))
2028 const Byte *buf = _buf;
2029
2030 if (buf[0] == k_LZVN_Uncompressed_Marker)
2031 {
2032 if (size - 1 != blockSize)
2033 return S_FALSE;
2034 if (outStream)
2035 {
2036 RINOK(WriteStream(outStream, buf + 1, blockSize))
2037 }
2038 }
2039 else
2040 {
2041 const UInt64 blockSize64 = blockSize;
2042 const UInt64 packSize64 = size;
2043 bufInStream->Init(buf, size);
2044 RINOK(_lzfseDecoder.Interface()->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL))
2045 // in/out sizes were checked in Code()
2046 }
2047
2048 outPos += blockSize;
2049 if ((i & 0xFF) == 0)
2050 {
2051 const UInt64 progressPos = progressStart + outPos;
2052 RINOK(extractCallback->SetCompleted(&progressPos))
2053 }
2054 }
2055
2056 return S_OK;
2057 }
2058
2059
2060 /*
2061 static UInt32 GetUi24(const Byte *p)
2062 {
2063 return p[0] + ((UInt32)p[1] << 8) + ((UInt32)p[2] << 24);
2064 }
2065
2066 HRESULT CDecoder::ExtractResourceFork_ZBM(
2067 ISequentialInStream *inStream, ISequentialOutStream *outStream,
2068 UInt64 forkSize, UInt64 unpackSize,
2069 UInt64 progressStart, IArchiveExtractCallback *extractCallback)
2070 {
2071 const UInt32 kNumBlocksMax = (UInt32)1 << 29;
2072 if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize)
2073 return S_FALSE;
2074 const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize);
2075 const UInt32 numBlocks2 = numBlocks + 1;
2076 const UInt32 tableSize = (numBlocks2 << 2);
2077 if (tableSize > forkSize)
2078 return S_FALSE;
2079 _tableBuf.AllocAtLeast(tableSize);
2080 RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize));
2081 const Byte *tableBuf = _tableBuf;
2082
2083 {
2084 UInt32 prev = GetUi32(tableBuf);
2085 if (prev != tableSize)
2086 return S_FALSE;
2087 for (UInt32 i = 1; i < numBlocks2; i++)
2088 {
2089 const UInt32 offs = GetUi32(tableBuf + i * 4);
2090 if (offs <= prev)
2091 return S_FALSE;
2092 prev = offs;
2093 }
2094 if (prev != forkSize)
2095 return S_FALSE;
2096 }
2097
2098 const size_t kBufSize = kCompressionBlockSize;
2099 _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2100
2101 CBufInStream *bufInStream = new CBufInStream;
2102 CMyComPtr<ISequentialInStream> bufInStream = bufInStream;
2103
2104 UInt64 outPos = 0;
2105
2106 for (UInt32 i = 0; i < numBlocks; i++)
2107 {
2108 const UInt64 rem = unpackSize - outPos;
2109 if (rem == 0)
2110 return S_FALSE;
2111 UInt32 blockSize = kCompressionBlockSize;
2112 if (rem < kCompressionBlockSize)
2113 blockSize = (UInt32)rem;
2114
2115 const UInt32 size =
2116 GetUi32(tableBuf + i * 4 + 4) -
2117 GetUi32(tableBuf + i * 4);
2118
2119 // if (size > kCompressionBlockSize + 1)
2120 if (size > blockSize + 1)
2121 return S_FALSE; // we don't expect it, because encode will use uncompressed chunk
2122
2123 RINOK(ReadStream_FALSE(inStream, _buf, size));
2124 const Byte *buf = _buf;
2125
2126 // (size != 0)
2127 // if (size == 0) return S_FALSE;
2128
2129 if (buf[0] == 0xFF) // uncompressed marker
2130 {
2131 if (size != blockSize + 1)
2132 return S_FALSE;
2133 if (outStream)
2134 {
2135 RINOK(WriteStream(outStream, buf + 1, blockSize));
2136 }
2137 }
2138 else
2139 {
2140 if (size < 4)
2141 return S_FALSE;
2142 if (buf[0] != 'Z' ||
2143 buf[1] != 'B' ||
2144 buf[2] != 'M' ||
2145 buf[3] != 9)
2146 return S_FALSE;
2147 // for debug:
2148 unsigned packPos = 4;
2149 unsigned unpackPos = 0;
2150 unsigned packRem = size - packPos;
2151 for (;;)
2152 {
2153 if (packRem < 6)
2154 return S_FALSE;
2155 const UInt32 packSize = GetUi24(buf + packPos);
2156 const UInt32 chunkUnpackSize = GetUi24(buf + packPos + 3);
2157 if (packSize < 6)
2158 return S_FALSE;
2159 if (packSize > packRem)
2160 return S_FALSE;
2161 if (chunkUnpackSize > blockSize - unpackPos)
2162 return S_FALSE;
2163 packPos += packSize;
2164 packRem -= packSize;
2165 unpackPos += chunkUnpackSize;
2166 if (packSize == 6)
2167 {
2168 if (chunkUnpackSize != 0)
2169 return S_FALSE;
2170 break;
2171 }
2172 if (packSize >= chunkUnpackSize + 6)
2173 {
2174 if (packSize > chunkUnpackSize + 6)
2175 return S_FALSE;
2176 // uncompressed chunk;
2177 }
2178 else
2179 {
2180 // compressed chunk
2181 const Byte *t = buf + packPos - packSize + 6;
2182 UInt32 r = packSize - 6;
2183 if (r < 9)
2184 return S_FALSE;
2185 const UInt32 v0 = GetUi24(t);
2186 const UInt32 v1 = GetUi24(t + 3);
2187 const UInt32 v2 = GetUi24(t + 6);
2188 if (v0 > v1 || v1 > v2 || v2 > packSize)
2189 return S_FALSE;
2190 // here we need the code that will decompress ZBM chunk
2191 }
2192 }
2193
2194 if (unpackPos != blockSize)
2195 return S_FALSE;
2196
2197 UInt32 size1 = size;
2198 if (size1 > kCompressionBlockSize)
2199 {
2200 size1 = kCompressionBlockSize;
2201 // return S_FALSE;
2202 }
2203 if (outStream)
2204 {
2205 RINOK(WriteStream(outStream, buf, size1))
2206
2207 const UInt32 kTempSize = 1 << 16;
2208 Byte temp[kTempSize];
2209 memset(temp, 0, kTempSize);
2210
2211 for (UInt32 k = size1; k < kCompressionBlockSize; k++)
2212 {
2213 UInt32 cur = kCompressionBlockSize - k;
2214 if (cur > kTempSize)
2215 cur = kTempSize;
2216 RINOK(WriteStream(outStream, temp, cur))
2217 k += cur;
2218 }
2219 }
2220
2221 // const UInt64 blockSize64 = blockSize;
2222 // const UInt64 packSize64 = size;
2223 // bufInStream->Init(buf, size);
2224 // RINOK(_zbmDecoderSpec->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL));
2225 // in/out sizes were checked in Code()
2226 }
2227
2228 outPos += blockSize;
2229 if ((i & 0xFF) == 0)
2230 {
2231 const UInt64 progressPos = progressStart + outPos;
2232 RINOK(extractCallback->SetCompleted(&progressPos));
2233 }
2234 }
2235
2236 return S_OK;
2237 }
2238 */
2239
2240 HRESULT CDecoder::Extract(
2241 ISequentialInStream *inStreamFork, ISequentialOutStream *realOutStream,
2242 UInt64 forkSize,
2243 const CCompressHeader &compressHeader,
2244 const CByteBuffer *data,
2245 UInt64 progressStart, IArchiveExtractCallback *extractCallback,
2246 int &opRes)
2247 {
2248 opRes = NExtract::NOperationResult::kDataError;
2249
2250 if (compressHeader.IsMethod_Uncompressed_Inline())
2251 {
2252 const size_t packSize = data->Size() - compressHeader.DataPos;
2253 if (realOutStream)
2254 {
2255 RINOK(WriteStream(realOutStream, *data + compressHeader.DataPos, packSize))
2256 }
2257 opRes = NExtract::NOperationResult::kOK;
2258 return S_OK;
2259 }
2260
2261 if (compressHeader.Method == kMethod_ZLIB_ATTR ||
2262 compressHeader.Method == kMethod_LZVN_ATTR)
2263 {
2264 CMyComPtr2_Create<ISequentialInStream, CBufInStream> bufInStream;
2265 const size_t packSize = data->Size() - compressHeader.DataPos;
2266 bufInStream->Init(*data + compressHeader.DataPos, packSize);
2267
2268 if (compressHeader.Method == kMethod_ZLIB_ATTR)
2269 {
2270 const HRESULT hres = _zlibDecoder.Interface()->Code(bufInStream, realOutStream,
2271 NULL, &compressHeader.UnpackSize, NULL);
2272 if (hres == S_OK)
2273 if (_zlibDecoder->GetOutputProcessedSize() == compressHeader.UnpackSize
2274 && _zlibDecoder->GetInputProcessedSize() == packSize)
2275 opRes = NExtract::NOperationResult::kOK;
2276 return hres;
2277 }
2278 {
2279 const UInt64 packSize64 = packSize;
2280 const HRESULT hres = _lzfseDecoder.Interface()->Code(bufInStream, realOutStream,
2281 &packSize64, &compressHeader.UnpackSize, NULL);
2282 if (hres == S_OK)
2283 {
2284 // in/out sizes were checked in Code()
2285 opRes = NExtract::NOperationResult::kOK;
2286 }
2287 return hres;
2288 }
2289 }
2290
2291 HRESULT hres;
2292 if (compressHeader.Method == NHfs::kMethod_ZLIB_RSRC)
2293 {
2294 hres = ExtractResourceFork_ZLIB(
2295 inStreamFork, realOutStream,
2296 forkSize, compressHeader.UnpackSize,
2297 progressStart, extractCallback);
2298 // for debug:
2299 // hres = NCompress::CopyStream(inStreamFork, realOutStream, NULL);
2300 }
2301 else if (compressHeader.Method == NHfs::kMethod_LZVN_RSRC)
2302 {
2303 hres = ExtractResourceFork_LZFSE(
2304 inStreamFork, realOutStream,
2305 forkSize, compressHeader.UnpackSize,
2306 progressStart, extractCallback);
2307 }
2308 /*
2309 else if (compressHeader.Method == NHfs::kMethod_ZBM_RSRC)
2310 {
2311 hres = ExtractResourceFork_ZBM(
2312 inStreamFork, realOutStream,
2313 forkSize, compressHeader.UnpackSize,
2314 progressStart, extractCallback);
2315 }
2316 */
2317 else
2318 {
2319 opRes = NExtract::NOperationResult::kUnsupportedMethod;
2320 hres = S_FALSE;
2321 }
2322
2323 if (hres == S_OK)
2324 opRes = NExtract::NOperationResult::kOK;
2325 return hres;
2326 }
2327
2328
2329 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
2330 Int32 testMode, IArchiveExtractCallback *extractCallback))
2331 {
2332 COM_TRY_BEGIN
2333 const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
2334 if (allFilesMode)
2335 numItems = Refs.Size();
2336 if (numItems == 0)
2337 return S_OK;
2338 UInt32 i;
2339 UInt64 totalSize = 0;
2340 for (i = 0; i < numItems; i++)
2341 {
2342 const CRef &ref = Refs[allFilesMode ? i : indices[i]];
2343 totalSize += Get_UnpackSize_of_Ref(ref);
2344 }
2345 RINOK(extractCallback->SetTotal(totalSize))
2346
2347 UInt64 currentTotalSize = 0, currentItemSize = 0;
2348
2349 const size_t kBufSize = kCompressionBlockSize;
2350 CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header
2351
2352 // there are hfs without adler in zlib.
2353 CDecoder decoder(true); // IsAdlerOptional
2354
2355 for (i = 0;; i++, currentTotalSize += currentItemSize)
2356 {
2357 RINOK(extractCallback->SetCompleted(¤tTotalSize))
2358 if (i >= numItems)
2359 break;
2360 const UInt32 index = allFilesMode ? i : indices[i];
2361 const CRef &ref = Refs[index];
2362 const CItem &item = Items[ref.ItemIndex];
2363 currentItemSize = Get_UnpackSize_of_Ref(ref);
2364
2365 int opRes;
2366 {
2367 CMyComPtr<ISequentialOutStream> realOutStream;
2368 const Int32 askMode = testMode ?
2369 NExtract::NAskMode::kTest :
2370 NExtract::NAskMode::kExtract;
2371 RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
2372
2373 if (ref.IsItem() && item.IsDir())
2374 {
2375 RINOK(extractCallback->PrepareOperation(askMode))
2376 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK))
2377 continue;
2378 }
2379 if (!testMode && !realOutStream)
2380 continue;
2381
2382 RINOK(extractCallback->PrepareOperation(askMode))
2383
2384 UInt64 pos = 0;
2385 opRes = NExtract::NOperationResult::kDataError;
2386 const CFork *fork = NULL;
2387
2388 if (ref.AttrIndex >= 0)
2389 {
2390 const CAttr &attr = Attrs[ref.AttrIndex];
2391 if (attr.Fork_defined && attr.Data.Size() == 0)
2392 fork = &attr.Fork;
2393 else
2394 {
2395 opRes = NExtract::NOperationResult::kOK;
2396 if (realOutStream)
2397 {
2398 RINOK(WriteStream(realOutStream,
2399 // AttrBuf + attr.Pos, attr.Size
2400 attr.Data, attr.Data.Size()
2401 ))
2402 }
2403 }
2404 }
2405 else if (ref.IsResource())
2406 fork = &item.ResourceFork;
2407 else if (item.CompressHeader.IsSupported)
2408 {
2409 CMyComPtr<ISequentialInStream> inStreamFork;
2410 UInt64 forkSize = 0;
2411 const CByteBuffer *decmpfs_Data = NULL;
2412
2413 if (item.CompressHeader.IsMethod_Resource())
2414 {
2415 const CFork &resourceFork = item.ResourceFork;
2416 forkSize = resourceFork.Size;
2417 GetForkStream(resourceFork, &inStreamFork);
2418 }
2419 else
2420 {
2421 const CAttr &attr = Attrs[item.decmpfs_AttrIndex];
2422 decmpfs_Data = &attr.Data;
2423 }
2424
2425 if (inStreamFork || decmpfs_Data)
2426 {
2427 const HRESULT hres = decoder.Extract(
2428 inStreamFork, realOutStream,
2429 forkSize,
2430 item.CompressHeader,
2431 decmpfs_Data,
2432 currentTotalSize, extractCallback,
2433 opRes);
2434 if (hres != S_FALSE && hres != S_OK)
2435 return hres;
2436 }
2437 }
2438 else if (item.CompressHeader.IsCorrect)
2439 opRes = NExtract::NOperationResult::kUnsupportedMethod;
2440 else
2441 fork = &item.DataFork;
2442
2443 if (fork)
2444 {
2445 if (fork->IsOk(Header.BlockSizeLog))
2446 {
2447 opRes = NExtract::NOperationResult::kOK;
2448 unsigned extentIndex;
2449 for (extentIndex = 0; extentIndex < fork->Extents.Size(); extentIndex++)
2450 {
2451 if (opRes != NExtract::NOperationResult::kOK)
2452 break;
2453 if (fork->Size == pos)
2454 break;
2455 const CExtent &e = fork->Extents[extentIndex];
2456 RINOK(InStream_SeekSet(_stream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog)))
2457 UInt64 extentRem = (UInt64)e.NumBlocks << Header.BlockSizeLog;
2458 while (extentRem != 0)
2459 {
2460 const UInt64 rem = fork->Size - pos;
2461 if (rem == 0)
2462 {
2463 // Here we check that there are no extra (empty) blocks in last extent.
2464 if (extentRem >= ((UInt64)1 << Header.BlockSizeLog))
2465 opRes = NExtract::NOperationResult::kDataError;
2466 break;
2467 }
2468 size_t cur = kBufSize;
2469 if (cur > rem)
2470 cur = (size_t)rem;
2471 if (cur > extentRem)
2472 cur = (size_t)extentRem;
2473 RINOK(ReadStream(_stream, buf, &cur))
2474 if (cur == 0)
2475 {
2476 opRes = NExtract::NOperationResult::kDataError;
2477 break;
2478 }
2479 if (realOutStream)
2480 {
2481 RINOK(WriteStream(realOutStream, buf, cur))
2482 }
2483 pos += cur;
2484 extentRem -= cur;
2485 const UInt64 processed = currentTotalSize + pos;
2486 RINOK(extractCallback->SetCompleted(&processed))
2487 }
2488 }
2489 if (extentIndex != fork->Extents.Size() || fork->Size != pos)
2490 opRes = NExtract::NOperationResult::kDataError;
2491 }
2492 }
2493 }
2494 RINOK(extractCallback->SetOperationResult(opRes))
2495 }
2496 return S_OK;
2497 COM_TRY_END
2498 }
2499
2500 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
2501 {
2502 *numItems = Refs.Size();
2503 return S_OK;
2504 }
2505
2506 HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream)
2507 {
2508 *stream = NULL;
2509
2510 if (!fork.IsOk(Header.BlockSizeLog))
2511 return S_FALSE;
2512
2513 CMyComPtr2<ISequentialInStream, CExtentsStream> extentStream;
2514 extentStream.Create_if_Empty();
2515
2516 UInt64 rem = fork.Size;
2517 UInt64 virt = 0;
2518
2519 FOR_VECTOR (i, fork.Extents)
2520 {
2521 const CExtent &e = fork.Extents[i];
2522 if (e.NumBlocks == 0)
2523 continue;
2524 UInt64 cur = ((UInt64)e.NumBlocks << Header.BlockSizeLog);
2525 if (cur > rem)
2526 {
2527 cur = rem;
2528 if (i != fork.Extents.Size() - 1)
2529 return S_FALSE;
2530 }
2531 CSeekExtent se;
2532 se.Phy = (UInt64)e.Pos << Header.BlockSizeLog;
2533 se.Virt = virt;
2534 virt += cur;
2535 rem -= cur;
2536 extentStream->Extents.Add(se);
2537 }
2538
2539 if (rem != 0)
2540 return S_FALSE;
2541
2542 CSeekExtent se;
2543 se.Phy = 0;
2544 se.Virt = virt;
2545 extentStream->Extents.Add(se);
2546 extentStream->Stream = _stream;
2547 extentStream->Init();
2548 *stream = extentStream.Detach();
2549 return S_OK;
2550 }
2551
2552 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
2553 {
2554 *stream = NULL;
2555
2556 const CRef &ref = Refs[index];
2557 const CFork *fork = NULL;
2558 if (ref.AttrIndex >= 0)
2559 {
2560 const CAttr &attr = Attrs[ref.AttrIndex];
2561 if (!attr.Fork_defined || attr.Data.Size() != 0)
2562 return S_FALSE;
2563 fork = &attr.Fork;
2564 }
2565 else
2566 {
2567 const CItem &item = Items[ref.ItemIndex];
2568 if (ref.IsResource())
2569 fork = &item.ResourceFork;
2570 else if (item.IsDir())
2571 return S_FALSE;
2572 else if (item.CompressHeader.IsCorrect)
2573 return S_FALSE;
2574 else
2575 fork = &item.DataFork;
2576 }
2577 return GetForkStream(*fork, stream);
2578 }
2579
2580 static const Byte k_Signature[] = {
2581 2, 'B', 'D',
2582 4, 'H', '+', 0, 4,
2583 4, 'H', 'X', 0, 5 };
2584
2585 REGISTER_ARC_I(
2586 "HFS", "hfs hfsx", NULL, 0xE3,
2587 k_Signature,
2588 kHeaderPadSize,
2589 NArcInfoFlags::kMultiSignature,
2590 IsArc_HFS)
2591
2592 }}
2593