xref: /aosp_15_r20/external/lzma/CPP/7zip/Archive/DmgHandler.cpp (revision f6dc9357d832569d4d1f5d24eacdb3935a1ae8e6)
1 // DmgHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/Alloc.h"
6 #include "../../../C/CpuArch.h"
7 
8 #include "../../Common/AutoPtr.h"
9 #include "../../Common/ComTry.h"
10 #include "../../Common/IntToString.h"
11 #include "../../Common/MyBuffer2.h"
12 #include "../../Common/MyXml.h"
13 #include "../../Common/UTFConvert.h"
14 
15 #include "../../Windows/PropVariant.h"
16 
17 #include "../Common/LimitedStreams.h"
18 #include "../Common/ProgressUtils.h"
19 #include "../Common/RegisterArc.h"
20 #include "../Common/StreamObjects.h"
21 #include "../Common/StreamUtils.h"
22 
23 #include "../Compress/BZip2Decoder.h"
24 #include "../Compress/CopyCoder.h"
25 #include "../Compress/LzfseDecoder.h"
26 #include "../Compress/XzDecoder.h"
27 #include "../Compress/ZlibDecoder.h"
28 
29 #include "Common/OutStreamWithCRC.h"
30 
31 // #define DMG_SHOW_RAW
32 
33 // #define SHOW_DEBUG_INFO
34 
35 /* for debug only: we can use block cache also for METHOD_COPY blocks.
36    It can reduce the number of Stream->Read() requests.
37    But it can increase memory usage.
38    Note: METHOD_COPY blocks are not fused usually.
39    But if METHOD_COPY blocks is fused in some dmg example,
40    block size can exceed k_Chunk_Size_MAX.
41    So we don't use cache for METHOD_COPY block, if (block_size > k_Chunk_Size_MAX)
42 */
43 // for debug only:
44 // #define Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
45 
46 #ifdef SHOW_DEBUG_INFO
47 #include <stdio.h>
48 #define PRF(x) x
49 #else
50 #define PRF(x)
51 #endif
52 
53 
54 #define Get16(p) GetBe16(p)
55 #define Get32(p) GetBe32(p)
56 #define Get64(p) GetBe64(p)
57 
58 #define Get32a(p) GetBe32a(p)
59 #define Get64a(p) GetBe64a(p)
60 
61 Byte *Base64ToBin(Byte *dest, const char *src);
62 
63 namespace NArchive {
64 namespace NDmg {
65 
66 // allocation limits for compressed blocks for GetStream() interface:
67 static const unsigned k_NumChunks_MAX = 128;
68 static const size_t k_Chunk_Size_MAX = (size_t)1 << 28;
69 // 256 MB cache for 32-bit:
70 //   4 GB cache for 64-bit:
71 static const size_t k_Chunks_TotalSize_MAX = (size_t)1 << (sizeof(size_t) + 24);
72 
73 // 2 GB limit for 32-bit:
74 // 4 GB limit for 64-bit:
75 // that limit can be increased for 64-bit mode, if there are such dmg files
76 static const size_t k_XmlSize_MAX =
77     ((size_t)1 << (sizeof(size_t) / 4 + 30)) - 256;
78 
79 static const UInt32  METHOD_ZERO_0  = 0; // sparse
80 static const UInt32  METHOD_COPY    = 1;
81 static const UInt32  METHOD_ZERO_2  = 2; // sparse : without file CRC calculation
82 static const UInt32  METHOD_ADC     = 0x80000004;
83 static const UInt32  METHOD_ZLIB    = 0x80000005;
84 static const UInt32  METHOD_BZIP2   = 0x80000006;
85 static const UInt32  METHOD_LZFSE   = 0x80000007;
86 static const UInt32  METHOD_XZ      = 0x80000008;
87 static const UInt32  METHOD_COMMENT = 0x7FFFFFFE; // is used to comment "+beg" and "+end" in extra field.
88 static const UInt32  METHOD_END     = 0xFFFFFFFF;
89 
90 
91 struct CBlock
92 {
93   UInt32 Type;
94   UInt64 UnpPos;
95   // UInt64 UnpSize;
96   UInt64 PackPos;
97   UInt64 PackSize;
98 
NeedCrcNArchive::NDmg::CBlock99   bool NeedCrc() const { return Type != METHOD_ZERO_2; }
IsZeroMethodNArchive::NDmg::CBlock100   bool IsZeroMethod() const
101   {
102     return (Type & ~(UInt32)METHOD_ZERO_2) == 0;
103     // return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2;
104   }
105 
IsClusteredMethodNArchive::NDmg::CBlock106   bool IsClusteredMethod() const
107   {
108     // most of dmg files have non-fused COPY_METHOD blocks.
109     // so we don't exclude COPY_METHOD blocks when we try to detect size of cluster.
110     return !IsZeroMethod(); // include COPY_METHOD blocks.
111     // Type > METHOD_ZERO_2; // for debug: exclude COPY_METHOD blocks.
112   }
113 
NeedAllocateBufferNArchive::NDmg::CBlock114   bool NeedAllocateBuffer() const
115   {
116     return
117 #ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
118         !IsZeroMethod();
119 #else
120         Type > METHOD_ZERO_2;
121         // !IsZeroMethod() && Type != METHOD_COPY;
122 #endif
123   }
124   // we don't store non-data blocks now
125   // bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; }
126 };
127 
128 static const UInt32 kCheckSumType_CRC = 2;
129 static const unsigned kChecksumSize_Max = 0x80;
130 
131 struct CChecksum
132 {
133   UInt32 Type;
134   UInt32 NumBits;
135   Byte Data[kChecksumSize_Max];
136 
IsCrc32NArchive::NDmg::CChecksum137   bool IsCrc32() const { return Type == kCheckSumType_CRC && NumBits == 32; }
GetCrc32NArchive::NDmg::CChecksum138   UInt32 GetCrc32() const { return Get32(Data); }
139   void Parse(const Byte *p);
140 
141   void PrintType(AString &s) const;
142   void Print(AString &s) const;
143   void Print_with_Name(AString &s) const;
144   void AddToComment(AString &s, const char *name) const;
145 };
146 
Parse(const Byte * p)147 void CChecksum::Parse(const Byte *p)
148 {
149   Type = Get32(p);
150   NumBits = Get32(p + 4);
151   memcpy(Data, p + 8, kChecksumSize_Max);
152 }
153 
154 
PrintType(AString & s) const155 void CChecksum::PrintType(AString &s) const
156 {
157   if (NumBits == 0)
158     return;
159   if (IsCrc32())
160     s += "CRC";
161   else
162   {
163     s += "Checksum";
164     s.Add_UInt32(Type);
165     s.Add_Minus();
166     s.Add_UInt32(NumBits);
167   }
168 }
169 
Print(AString & s) const170 void CChecksum::Print(AString &s) const
171 {
172   if (NumBits == 0)
173     return;
174   char temp[kChecksumSize_Max * 2 + 2];
175   /*
176   if (IsCrc32())
177     ConvertUInt32ToHex8Digits(GetCrc32(), temp);
178   else
179   */
180   {
181     UInt32 numBits = kChecksumSize_Max * 8;
182     if (numBits > NumBits)
183         numBits = NumBits;
184     const unsigned numBytes = (numBits + 7) >> 3;
185     if (numBytes <= 8)
186       ConvertDataToHex_Upper(temp, Data, numBytes);
187     else
188       ConvertDataToHex_Lower(temp, Data, numBytes);
189   }
190   s += temp;
191 }
192 
Print_with_Name(AString & s) const193 void CChecksum::Print_with_Name(AString &s) const
194 {
195   if (NumBits == 0)
196     return;
197   PrintType(s);
198   s += ": ";
199   Print(s);
200 }
201 
AddToComment_Prop(AString & s,const char * name,const char * val)202 static void AddToComment_Prop(AString &s, const char *name, const char *val)
203 {
204   s += name;
205   s += ": ";
206   s += val;
207   s.Add_LF();
208 }
209 
AddToComment(AString & s,const char * name) const210 void CChecksum::AddToComment(AString &s, const char *name) const
211 {
212   AString s2;
213   Print_with_Name(s2);
214   if (!s2.IsEmpty())
215     AddToComment_Prop(s, name, s2);
216 }
217 
218 
219 struct CFile
220 {
221   UInt64 Size;
222   CRecordVector<CBlock> Blocks;
223   UInt64 PackSize;
224   UInt64 StartPackPos;
225   UInt64 BlockSize_MAX;
226   UInt64 StartUnpackSector;  // unpack sector position of this file from all files
227   UInt64 NumUnpackSectors;
228   Int32 Descriptor;
229   bool IsCorrect;
230   bool FullFileChecksum;
231   AString Name;
232   // AString Id;
233   CChecksum Checksum;
234 
GetUnpackSize_of_BlockNArchive::NDmg::CFile235   UInt64 GetUnpackSize_of_Block(unsigned blockIndex) const
236   {
237     return (blockIndex == Blocks.Size() - 1 ?
238         Size : Blocks[blockIndex + 1].UnpPos) - Blocks[blockIndex].UnpPos;
239   }
240 
CFileNArchive::NDmg::CFile241   CFile():
242       Size(0),
243       PackSize(0),
244       StartPackPos(0),
245       BlockSize_MAX(0),
246       StartUnpackSector(0),
247       NumUnpackSectors(0),
248       Descriptor(0),
249       IsCorrect(false),
250       FullFileChecksum(false)
251   {
252     Checksum.Type = 0;
253     Checksum.NumBits = 0;
254   }
255   HRESULT Parse(const Byte *p, UInt32 size);
256 };
257 
258 #ifdef DMG_SHOW_RAW
259 struct CExtraFile
260 {
261   CByteBuffer Data;
262   AString Name;
263 };
264 #endif
265 
266 
AddToComment_UInt64(AString & s,UInt64 v,const char * name)267 static void AddToComment_UInt64(AString &s, UInt64 v, const char *name)
268 {
269   s += name;
270   s += ": ";
271   s.Add_UInt64(v);
272   s.Add_LF();
273 }
274 
275 struct CForkPair
276 {
277   UInt64 Offset;
278   UInt64 Len;
279 
280   // (p) is aligned for 8-bytes
ParseNArchive::NDmg::CForkPair281   void Parse(const Byte *p)
282   {
283     Offset = Get64a(p);
284     Len = Get64a(p + 8);
285   }
286 
GetEndPosNArchive::NDmg::CForkPair287   bool GetEndPos(UInt64 &endPos)
288   {
289     endPos = Offset + Len;
290     return endPos >= Offset;
291   }
292 
UpdateTopNArchive::NDmg::CForkPair293   bool UpdateTop(UInt64 limit, UInt64 &top)
294   {
295     if (Offset > limit || Len > limit - Offset)
296       return false;
297     const UInt64 top2 = Offset + Len;
298     if (top <= top2)
299       top = top2;
300     return true;
301   }
302 
303   void Print(AString &s, const char *name) const;
304 };
305 
Print(AString & s,const char * name) const306 void CForkPair::Print(AString &s, const char *name) const
307 {
308   if (Offset != 0 || Len != 0)
309   {
310     s += name;  s.Add_Minus();  AddToComment_UInt64(s, Offset, "offset");
311     s += name;  s.Add_Minus();  AddToComment_UInt64(s, Len,    "length");
312   }
313 }
314 
315 
316 Z7_CLASS_IMP_CHandler_IInArchive_1(
317   IInArchiveGetStream
318 )
319   bool _masterCrcError;
320   bool _headersError;
321   bool _dataForkError;
322   bool _rsrcMode_wasUsed;
323 
324   CMyComPtr<IInStream> _inStream;
325   CObjectVector<CFile> _files;
326 
327   // UInt32 _dataStartOffset;
328   UInt64 _startPos;
329   UInt64 _phySize;
330 
331   AString _name;
332 
333   CForkPair _dataForkPair;
334   CForkPair rsrcPair, xmlPair, blobPair;
335 
336   // UInt64 _runningDataForkOffset;
337   // UInt32 _segmentNumber;
338   // UInt32 _segmentCount;
339   UInt64 _numSectors; // total unpacked number of sectors
340   Byte _segmentGUID[16];
341 
342   CChecksum _dataForkChecksum;
343   CChecksum _masterChecksum;
344 
345 #ifdef DMG_SHOW_RAW
346   CObjectVector<CExtraFile> _extras;
347 #endif
348 
349   HRESULT ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf);
350   bool ParseBlob(const CByteBuffer &data);
351   HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback);
352   HRESULT Extract(IInStream *stream);
353 };
354 
355 
356 struct CMethods
357 {
358   CRecordVector<UInt32> Types;
359 
360   void Update(const CFile &file);
361   void AddToString(AString &s) const;
362 };
363 
Update(const CFile & file)364 void CMethods::Update(const CFile &file)
365 {
366   FOR_VECTOR (i, file.Blocks)
367   {
368     if (Types.Size() >= (1 << 8))
369       break;
370     Types.AddToUniqueSorted(file.Blocks[i].Type);
371   }
372 }
373 
AddToString(AString & res) const374 void CMethods::AddToString(AString &res) const
375 {
376   FOR_VECTOR (i, Types)
377   {
378     const UInt32 type = Types[i];
379     /*
380     if (type == METHOD_COMMENT || type == METHOD_END)
381       continue;
382     */
383     char buf[16];
384     const char *s;
385     switch (type)
386     {
387       // case METHOD_COMMENT: s = "Comment"; break;
388       case METHOD_ZERO_0: s = "Zero0"; break;
389       case METHOD_ZERO_2: s = "Zero2"; break;
390       case METHOD_COPY:   s = "Copy";  break;
391       case METHOD_ADC:    s = "ADC";   break;
392       case METHOD_ZLIB:   s = "ZLIB";  break;
393       case METHOD_BZIP2:  s = "BZip2"; break;
394       case METHOD_LZFSE:  s = "LZFSE"; break;
395       case METHOD_XZ:     s = "XZ";    break;
396       default: ConvertUInt32ToHex(type, buf); s = buf;
397     }
398     res.Add_OptSpaced(s);
399   }
400 }
401 
402 
403 
404 struct CAppleName
405 {
406   bool IsFs;
407   const char *Ext;
408   const char *AppleName;
409 };
410 
411 static const CAppleName k_Names[] =
412 {
413   { true,  "hfs",  "Apple_HFS" },
414   { true,  "hfsx", "Apple_HFSX" },
415   { true,  "ufs",  "Apple_UFS" },
416   { true,  "apfs", "Apple_APFS" },
417   { true,  "iso",  "Apple_ISO" },
418 
419   // efi_sys partition is FAT32, but it's not main file. So we use (IsFs = false)
420   { false,  "efi_sys", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" },
421 
422   { false, "free", "Apple_Free" },
423   { false, "ddm",  "DDM" },
424   { false, NULL,   "Apple_partition_map" },
425   { false, NULL,   " GPT " },
426     /* " GPT " is substring of full name entry in dmg that contains
427          some small metadata GPT entry. It's not real FS entry:
428       "Primary GPT Header",
429       "Primary GPT Table"
430       "Backup GPT Header",
431       "Backup GPT Table",
432     */
433   { false, NULL,   "MBR" },
434   { false, NULL,   "Driver" },
435   { false, NULL,   "Patches" }
436 };
437 
438 static const unsigned kNumAppleNames = Z7_ARRAY_SIZE(k_Names);
439 
440 const char *Find_Apple_FS_Ext(const AString &name);
Find_Apple_FS_Ext(const AString & name)441 const char *Find_Apple_FS_Ext(const AString &name)
442 {
443   for (unsigned i = 0; i < kNumAppleNames; i++)
444   {
445     const CAppleName &a = k_Names[i];
446     if (a.Ext)
447       if (name == a.AppleName)
448         return a.Ext;
449   }
450   return NULL;
451 }
452 
453 
454 bool Is_Apple_FS_Or_Unknown(const AString &name);
Is_Apple_FS_Or_Unknown(const AString & name)455 bool Is_Apple_FS_Or_Unknown(const AString &name)
456 {
457   for (unsigned i = 0; i < kNumAppleNames; i++)
458   {
459     const CAppleName &a = k_Names[i];
460     // if (name.Find(a.AppleName) >= 0)
461     if (strstr(name, a.AppleName))
462       return a.IsFs;
463   }
464   return true;
465 }
466 
467 
468 
469 // define it for debug only:
470 // #define Z7_DMG_SINGLE_FILE_MODE
471 
472 static const Byte kProps[] =
473 {
474   kpidPath,
475   kpidSize,
476   kpidPackSize,
477   kpidComment,
478   kpidMethod,
479   kpidNumBlocks,
480   kpidClusterSize,
481   kpidChecksum,
482   kpidCRC,
483   kpidId
484   // kpidOffset
485 #ifdef Z7_DMG_SINGLE_FILE_MODE
486   , kpidPosition
487 #endif
488 };
489 
490 IMP_IInArchive_Props
491 
492 static const Byte kArcProps[] =
493 {
494   kpidMethod,
495   kpidNumBlocks,
496   kpidClusterSize,
497   kpidComment
498 };
499 
500 
Z7_COM7F_IMF(CHandler::GetArchiveProperty (PROPID propID,PROPVARIANT * value))501 Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value))
502 {
503   COM_TRY_BEGIN
504   NWindows::NCOM::CPropVariant prop;
505   switch (propID)
506   {
507     case kpidMethod:
508     {
509       CMethods m;
510       CRecordVector<UInt32> ChecksumTypes;
511       {
512         FOR_VECTOR (i, _files)
513         {
514           const CFile &file = _files[i];
515           m.Update(file);
516           if (ChecksumTypes.Size() < (1 << 8))
517             ChecksumTypes.AddToUniqueSorted(file.Checksum.Type);
518         }
519       }
520       AString s;
521       m.AddToString(s);
522 
523       FOR_VECTOR (i, ChecksumTypes)
524       {
525         const UInt32 type = ChecksumTypes[i];
526         switch (type)
527         {
528           case kCheckSumType_CRC:
529             s.Add_OptSpaced("CRC");
530             break;
531           default:
532             s.Add_OptSpaced("Checksum");
533             s.Add_UInt32(type);
534         }
535       }
536 
537       if (!s.IsEmpty())
538         prop = s;
539       break;
540     }
541     case kpidNumBlocks:
542     {
543       UInt64 numBlocks = 0;
544       FOR_VECTOR (i, _files)
545         numBlocks += _files[i].Blocks.Size();
546       prop = numBlocks;
547       break;
548     }
549     case kpidClusterSize:
550     {
551       UInt64 blockSize_MAX = 0;
552       FOR_VECTOR (i, _files)
553       {
554         const UInt64 a = _files[i].BlockSize_MAX;
555         if (blockSize_MAX < a)
556             blockSize_MAX = a;
557       }
558       prop = blockSize_MAX;
559       break;
560     }
561     case kpidMainSubfile:
562     {
563       int mainIndex = -1;
564       FOR_VECTOR (i, _files)
565       {
566         if (Is_Apple_FS_Or_Unknown(_files[i].Name))
567         {
568           if (mainIndex != -1)
569           {
570             mainIndex = -1;
571             break;
572           }
573           mainIndex = (int)i;
574         }
575       }
576       if (mainIndex != -1)
577         prop = (UInt32)(Int32)mainIndex;
578       break;
579     }
580     case kpidWarning:
581       if (_masterCrcError)
582         prop = "Master CRC error";
583       break;
584 
585     case kpidWarningFlags:
586     {
587       UInt32 v = 0;
588       if (_headersError)  v |= kpv_ErrorFlags_HeadersError;
589       if (_dataForkError) v |= kpv_ErrorFlags_CrcError;
590       if (v != 0)
591         prop = v;
592       break;
593     }
594 
595     case kpidOffset: prop = _startPos; break;
596     case kpidPhySize: prop = _phySize; break;
597 
598     case kpidComment:
599     {
600       AString s;
601       if (!_name.IsEmpty())
602         AddToComment_Prop(s, "Name", _name);
603       AddToComment_UInt64(s, _numSectors << 9, "unpack-size");
604       {
605         char temp[sizeof(_segmentGUID) * 2 + 2];
606         ConvertDataToHex_Lower(temp, _segmentGUID, sizeof(_segmentGUID));
607         AddToComment_Prop(s, "ID", temp);
608       }
609       _masterChecksum.AddToComment(s, "master-checksum");
610       _dataForkChecksum.AddToComment(s, "pack-checksum");
611       {
612         /*
613         if (_dataStartOffset != 0)
614           AddToComment_UInt64(s, _dataStartOffset,    "payload-start-offset");
615         */
616         // if (_dataForkPair.Offset != 0)
617         _dataForkPair.Print(s, "pack");
618         rsrcPair.Print(s, "rsrc");
619         xmlPair.Print(s, "xml");
620         blobPair.Print(s, "blob");
621       }
622       if (_rsrcMode_wasUsed)
623         s += "RSRC_MODE\n";
624       if (!s.IsEmpty())
625         prop = s;
626     }
627     break;
628 
629     case kpidName:
630       if (!_name.IsEmpty())
631         prop = _name + ".dmg";
632       break;
633   }
634   prop.Detach(value);
635   return S_OK;
636   COM_TRY_END
637 }
638 
639 IMP_IInArchive_ArcProps
640 
641 
642 
643 static const UInt64 kSectorNumber_LIMIT = (UInt64)1 << (63 - 9);
644 
Parse(const Byte * p,UInt32 size)645 HRESULT CFile::Parse(const Byte *p, UInt32 size)
646 {
647   // CFile was initialized to default values: 0 in size variables and (IsCorrect == false)
648   const unsigned kHeadSize = 0xCC;
649   if (size < kHeadSize)
650     return S_FALSE;
651   if (Get32(p) != 0x6D697368) // "mish" signature
652     return S_FALSE;
653   if (Get32(p + 4) != 1) // version
654     return S_FALSE;
655 
656   StartUnpackSector = Get64(p + 8);
657   NumUnpackSectors = Get64(p + 0x10);
658   StartPackPos = Get64(p + 0x18);
659 
660 #ifdef SHOW_DEBUG_INFO
661   /* the number of sectors that must be allocated.
662      ==  0x208 for 256KB clusters
663      ==  0x808 for   1MB clusters
664      == 0x1001 for   1MB clusters in some example
665   */
666   const UInt32 decompressedBufRequested = Get32(p + 0x20);
667 #endif
668 
669   // Descriptor is file index. usually started from -1
670   // in one dmg it was started from 0
671   Descriptor = (Int32)Get32(p + 0x24);
672   // char Reserved1[24];
673 
674   Checksum.Parse(p + 0x40);
675   PRF(printf("\n" " Checksum Type = %2u"
676         "\n StartUnpackSector = %8x"
677         "\n NumUnpackSectors  = %8x"
678         "\n StartPos = %8x"
679         "\n decompressedBufRequested=%8x"
680         "\n blocksDescriptor=%8x"
681         , (unsigned)Checksum.Type
682         , (unsigned)StartUnpackSector
683         , (unsigned)NumUnpackSectors
684         , (unsigned)StartPackPos
685         , (unsigned)decompressedBufRequested
686         , (unsigned)Descriptor
687     );)
688 
689   const UInt32 numBlocks = Get32(p + 0xC8);
690   const unsigned kRecordSize = 40;
691   if ((UInt64)numBlocks * kRecordSize + kHeadSize != size)
692     return S_FALSE;
693 
694   Blocks.ClearAndReserve(numBlocks);
695   FullFileChecksum = true;
696   /* IsCorrect = false; by default
697      So we return S_OK, if we can ignore some error in headers.
698   */
699 
700   p += kHeadSize;
701   UInt32 i;
702 
703   for (i = 0; i < numBlocks; i++, p += kRecordSize)
704   {
705     CBlock b;
706     b.Type = Get32(p);
707     {
708       const UInt64 a = Get64(p + 0x08);
709       if (a >= kSectorNumber_LIMIT)
710         return S_OK;
711       b.UnpPos = a << 9;
712     }
713     UInt64 unpSize;
714     {
715       const UInt64 a = Get64(p + 0x10);
716       if (a >= kSectorNumber_LIMIT)
717         return S_OK;
718       unpSize = a << 9;
719     }
720     const UInt64 newSize = b.UnpPos + unpSize;
721     if (newSize >= ((UInt64)1 << 63))
722       return S_OK;
723 
724     b.PackPos  = Get64(p + 0x18);
725     b.PackSize = Get64(p + 0x20);
726     // b.PackPos can be 0 for some types. So we don't check it
727     if (b.UnpPos != Size)
728       return S_OK;
729 
730     PRF(printf("\nType=%8x  comment=%8x  uPos=%8x  uSize=%7x  pPos=%8x  pSize=%7x",
731         (unsigned)b.Type, Get32(p + 4), (unsigned)b.UnpPos, (unsigned)unpSize, (unsigned)b.PackPos, (unsigned)b.PackSize));
732 
733     if (b.Type == METHOD_COMMENT)
734     {
735       // some files contain 2 comment block records:
736       // record[0]     : Type=METHOD_COMMENT, comment_field = "+beg"
737       // record[num-2] : Type=METHOD_COMMENT, comment_field = "+end"
738       // we skip these useless records.
739       continue;
740     }
741     if (b.Type == METHOD_END)
742       break;
743 
744     // we add only blocks that have non empty unpacked data:
745     if (unpSize != 0)
746     {
747       const UInt64 k_max_pos = (UInt64)1 << 63;
748       if (b.PackPos >= k_max_pos ||
749           b.PackSize >= k_max_pos - b.PackPos)
750         return S_OK;
751 
752       /* we don't count non-ZERO blocks here, because
753          ZERO blocks in dmg files are not limited by some cluster size.
754          note: COPY blocks also sometimes are fused to larger blocks.
755       */
756       if (b.IsClusteredMethod())
757       if (BlockSize_MAX < unpSize)
758           BlockSize_MAX = unpSize;
759 
760       PackSize += b.PackSize;
761       if (!b.NeedCrc())
762         FullFileChecksum = false;
763       Blocks.AddInReserved(b);
764       Size = newSize;
765     }
766   }
767 
768   PRF(printf("\n");)
769 
770   if (i != numBlocks - 1)
771   {
772     // return S_FALSE;
773     return S_OK;
774   }
775 
776   if ((Size >> 9) == NumUnpackSectors)
777     IsCorrect = true;
778   return S_OK;
779 }
780 
781 
FindKeyPair(const CXmlItem & item,const char * key,const char * nextTag)782 static const CXmlItem *FindKeyPair(const CXmlItem &item, const char *key, const char *nextTag)
783 {
784   for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++)
785   {
786     const CXmlItem &si = item.SubItems[i];
787     if (si.IsTagged("key") && si.GetSubString() == key)
788     {
789       const CXmlItem *si_1 = &item.SubItems[i + 1];
790       if (si_1->IsTagged(nextTag))
791         return si_1;
792     }
793   }
794   return NULL;
795 }
796 
GetStringFromKeyPair(const CXmlItem & item,const char * key,const char * nextTag)797 static const AString *GetStringFromKeyPair(const CXmlItem &item, const char *key, const char *nextTag)
798 {
799   const CXmlItem *si_1 = FindKeyPair(item, key, nextTag);
800   if (si_1)
801     return si_1->GetSubStringPtr();
802   return NULL;
803 }
804 
805 static const Byte k_Signature[] = { 'k','o','l','y', 0, 0, 0, 4, 0, 0, 2, 0 };
806 
IsKoly(const Byte * p)807 static inline bool IsKoly(const Byte *p)
808 {
809   return memcmp(p, k_Signature, Z7_ARRAY_SIZE(k_Signature)) == 0;
810   /*
811   if (Get32(p) != 0x6B6F6C79) // "koly" signature
812     return false;
813   if (Get32(p + 4) != 4) // version
814     return false;
815   if (Get32(p + 8) != HEADER_SIZE)
816     return false;
817   return true;
818   */
819 }
820 
821 
ReadData(IInStream * stream,const CForkPair & pair,CByteBuffer & buf)822 HRESULT CHandler::ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf)
823 {
824   size_t size = (size_t)pair.Len;
825   if (size != pair.Len)
826     return E_OUTOFMEMORY;
827   buf.Alloc(size);
828   RINOK(InStream_SeekSet(stream, _startPos + pair.Offset))
829   return ReadStream_FALSE(stream, buf, size);
830 }
831 
832 
833 
ParseBlob(const CByteBuffer & data)834 bool CHandler::ParseBlob(const CByteBuffer &data)
835 {
836   const unsigned kHeaderSize = 3 * 4;
837   if (data.Size() < kHeaderSize)
838     return false;
839   const Byte * const p = (const Byte *)data;
840   if (Get32a(p) != 0xfade0cc0) // CSMAGIC_EMBEDDED_SIGNATURE
841     return true;
842   const UInt32 size = Get32a(p + 4);
843   if (size != data.Size())
844     return false;
845   const UInt32 num = Get32a(p + 8);
846   if (num > (size - kHeaderSize) / 8)
847     return false;
848 
849   const UInt32 limit = num * 8 + kHeaderSize;
850   for (size_t i = kHeaderSize; i < limit; i += 8)
851   {
852     // type == 0 == CSSLOT_CODEDIRECTORY for CSMAGIC_CODEDIRECTORY item
853     // UInt32 type = Get32(p + i);
854     const UInt32 offset = Get32a(p + i + 4);
855     if (offset < limit || offset > size - 8)
856       return false;
857     // offset is not aligned for 4 here !!!
858     const Byte * const p2 = p + offset;
859     const UInt32 magic = Get32(p2);
860     const UInt32 len = Get32(p2 + 4);
861     if (size - offset < len || len < 8)
862       return false;
863 
864 #ifdef DMG_SHOW_RAW
865     CExtraFile &extra = _extras.AddNew();
866     extra.Name = "_blob_";
867     extra.Data.CopyFrom(p2, len);
868 #endif
869 
870     if (magic == 0xfade0c02) // CSMAGIC_CODEDIRECTORY
871     {
872 #ifdef DMG_SHOW_RAW
873       extra.Name += "codedir";
874 #endif
875 
876       if (len < 11 * 4)
877         return false;
878       const UInt32 idOffset = Get32(p2 + 5 * 4);
879       if (idOffset >= len)
880         return false;
881       UInt32 len2 = len - idOffset;
882       const UInt32 kNameLenMax = 1 << 8;
883       if (len2 > kNameLenMax)
884           len2 = kNameLenMax;
885       _name.SetFrom_CalcLen((const char *)(p2 + idOffset), len2);
886       /*
887       // #define kSecCodeSignatureHashSHA1     1
888       // #define kSecCodeSignatureHashSHA256   2
889       const UInt32 hashOffset    = Get32(p2 + 4 * 4);
890       const UInt32 nSpecialSlots = Get32(p2 + 6 * 4);
891       const UInt32 nCodeSlots    = Get32(p2 + 7 * 4);
892       const unsigned hashSize = p2[36];
893       const unsigned hashType = p2[37];
894       // const unsigned unused = p2[38];
895       const unsigned pageSize = p2[39];
896       */
897     }
898 #ifdef DMG_SHOW_RAW
899     else if (magic == 0xfade0c01) extra.Name += "requirements";
900     else if (magic == 0xfade0b01) extra.Name += "signed";
901     else
902     {
903       char temp[16];
904       ConvertUInt32ToHex8Digits(magic, temp);
905       extra.Name += temp;
906     }
907 #endif
908   }
909 
910   return true;
911 }
912 
913 
914 
915 
Open2(IInStream * stream,IArchiveOpenCallback * openArchiveCallback)916 HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback)
917 {
918   /*
919   - usual dmg contains Koly Header at the end:
920   - rare case old dmg contains Koly Header at the begin.
921   */
922 
923   // _dataStartOffset = 0;
924   UInt64 fileSize;
925   RINOK(InStream_GetPos_GetSize(stream, _startPos, fileSize))
926 
927   const unsigned HEADER_SIZE = 0x200;
928   UInt64 buf[HEADER_SIZE / sizeof(UInt64)];
929   RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE))
930 
931   UInt64 headerPos;
932   bool front_Koly_Mode = false;
933 
934   /*
935   _dataForkChecksum.Offset ==   0 for koly-at-the-end
936   _dataForkChecksum.Offset == 512 for koly-at-the-start
937   so we can use (_dataForkChecksum.Offset) to detect "koly-at-the-start" mode
938   */
939 
940   if (IsKoly((const Byte *)(const void *)buf))
941   {
942     // it can be normal koly-at-the-end or koly-at-the-start
943     headerPos = _startPos;
944     /*
945     if (_startPos <= (1 << 8))
946     {
947       // we want to support front_Koly_Mode, even if there is
948       // some data before dmg file, like 128 bytes MacBin header
949       _dataStartOffset = HEADER_SIZE;
950       front_Koly_Mode = true;
951     }
952     */
953   }
954   else
955   {
956     /* we try to open in backward mode only for first attempt
957        when (_startPos == 0) */
958     if (_startPos != 0)
959       return S_FALSE;
960     headerPos = fileSize;
961     if (headerPos < HEADER_SIZE)
962       return S_FALSE;
963     headerPos -= HEADER_SIZE;
964     RINOK(InStream_SeekSet(stream, headerPos))
965     RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE))
966     if (!IsKoly((const Byte *)(const void *)buf))
967       return S_FALSE;
968   }
969 
970   // UInt32 flags = Get32a((const Byte *)(const void *)buf + 12);
971   // _runningDataForkOffset = Get64a((const Byte *)(const void *)buf + 0x10);
972   _dataForkPair.Parse((const Byte *)(const void *)buf + 0x18);
973   rsrcPair.Parse((const Byte *)(const void *)buf + 0x28);
974   // _segmentNumber = Get32a(buf + 0x38); // 0 or 1
975   // _segmentCount = Get32a(buf + 0x3C);  // 0 (if not set) or 1
976   memcpy(_segmentGUID, (const Byte *)(const void *)buf + 0x40, 16);
977   _dataForkChecksum.Parse((const Byte *)(const void *)buf + 0x50);
978   xmlPair.Parse((const Byte *)(const void *)buf + 0xD8);
979   // Byte resereved[]
980   blobPair.Parse((const Byte *)(const void *)buf + 0x128);
981   _masterChecksum.Parse((const Byte *)(const void *)buf + 0x160);
982   // UInt32 imageVariant = Get32a((const Byte *)(const void *)buf + 0x1E8); imageVariant = imageVariant;
983   _numSectors = Get64((const Byte *)(const void *)buf + 0x1EC);  // it's not aligned for 8-bytes
984   // Byte resereved[12];
985 
986   if (_dataForkPair.Offset == HEADER_SIZE
987       && headerPos + HEADER_SIZE < fileSize)
988     front_Koly_Mode = true;
989 
990   const UInt64 limit = front_Koly_Mode ? fileSize : headerPos;
991   UInt64 top = 0;
992   if (!_dataForkPair.UpdateTop(limit, top)) return S_FALSE;
993   if (!xmlPair.UpdateTop(limit, top)) return S_FALSE;
994   if (!rsrcPair.UpdateTop(limit, top)) return S_FALSE;
995 
996   /* Some old dmg files contain garbage data in blobPair field.
997      So we need to ignore such garbage case;
998      And we still need to detect offset of start of archive for "parser" mode. */
999   const bool useBlob = blobPair.UpdateTop(limit, top);
1000 
1001   if (front_Koly_Mode)
1002     _phySize = top;
1003   else
1004   {
1005     _phySize = headerPos + HEADER_SIZE;
1006     _startPos = 0;
1007     if (top != headerPos)
1008     {
1009       /*
1010       if expected absolute offset is not equal to real header offset,
1011       2 cases are possible:
1012         - additional (unknown) headers
1013         - archive with offset.
1014        So we try to read XML with absolute offset to select from these two ways.
1015       */
1016       CForkPair xmlPair2 = xmlPair;
1017       const char *sz = "<?xml version";
1018       const unsigned len = (unsigned)strlen(sz);
1019       if (xmlPair2.Len > len)
1020         xmlPair2.Len = len;
1021       CByteBuffer buf2;
1022       if (xmlPair2.Len < len
1023         || ReadData(stream, xmlPair2, buf2) != S_OK
1024         || memcmp(buf2, sz, len) != 0)
1025       {
1026         // if absolute offset is not OK, probably it's archive with offset
1027         _startPos = headerPos - top;
1028         _phySize = top + HEADER_SIZE;
1029       }
1030     }
1031   }
1032 
1033   if (useBlob
1034       && blobPair.Len != 0
1035       && blobPair.Len <= (1u << 24)) // we don't want parsing of big blobs
1036   {
1037 #ifdef DMG_SHOW_RAW
1038     CExtraFile &extra = _extras.AddNew();
1039     extra.Name = "_blob.bin";
1040     CByteBuffer &blobBuf = extra.Data;
1041 #else
1042     CByteBuffer blobBuf;
1043 #endif
1044     RINOK(ReadData(stream, blobPair, blobBuf))
1045     if (!ParseBlob(blobBuf))
1046       _headersError = true;
1047   }
1048 
1049 
1050   UInt64 openTotal = 0;
1051   UInt64 openCur = 0;
1052   if (_dataForkChecksum.IsCrc32())
1053     openTotal = _dataForkPair.Len;
1054 
1055   const UInt32 RSRC_HEAD_SIZE = 0x100;
1056 
1057   /* we have 2 ways to read files and blocks metadata:
1058      via Xml or via Rsrc.
1059      But some images have no Rsrc fork.
1060      Is it possible that there is no Xml?
1061      Rsrc method will not work for big files. */
1062   // v23.02: we use xml mode by default
1063   // if (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24)) // for debug
1064   if (xmlPair.Len == 0)
1065   {
1066     // We don't know the size of the field "offset" in Rsrc.
1067     // We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcPair.Len <= (1 << 24).
1068     const bool canUseRsrc = (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24));
1069     if (!canUseRsrc)
1070       return S_FALSE;
1071 
1072     _rsrcMode_wasUsed = true;
1073 #ifdef DMG_SHOW_RAW
1074     CExtraFile &extra = _extras.AddNew();
1075     extra.Name = "rsrc.bin";
1076     CByteBuffer &rsrcBuf = extra.Data;
1077 #else
1078     CByteBuffer rsrcBuf;
1079 #endif
1080 
1081     RINOK(ReadData(stream, rsrcPair, rsrcBuf))
1082 
1083     const Byte *p = rsrcBuf;
1084     const UInt32 headSize     = Get32a(p + 0);
1085     const UInt32 footerOffset = Get32a(p + 4);
1086     const UInt32 mainDataSize = Get32a(p + 8);
1087     const UInt32 footerSize   = Get32a(p + 12);
1088     if (headSize != RSRC_HEAD_SIZE
1089         || footerOffset >= rsrcPair.Len
1090         || mainDataSize >= rsrcPair.Len
1091         || footerOffset < mainDataSize
1092         || footerOffset != headSize + mainDataSize)
1093       return S_FALSE;
1094 
1095     {
1096       const UInt32 footerEnd = footerOffset + footerSize;
1097       if (footerEnd != rsrcPair.Len)
1098       {
1099         // there is rare case dmg example, where there are 4 additional bytes
1100         const UInt64 rem = rsrcPair.Len - footerOffset;
1101         if (rem < footerSize
1102           || rem - footerSize != 4
1103           || Get32(p + footerEnd) != 0)
1104           return S_FALSE;
1105       }
1106     }
1107 
1108     if (footerSize < 0x1e)
1109       return S_FALSE;
1110     if (memcmp(p, p + footerOffset, 16) != 0)
1111       return S_FALSE;
1112 
1113     p += footerOffset;
1114 
1115     if ((UInt32)Get16(p + 0x18) != 0x1c)
1116       return S_FALSE;
1117     const UInt32 namesOffset = Get16(p + 0x1a);
1118     if (namesOffset > footerSize)
1119       return S_FALSE;
1120 
1121     const UInt32 numItems = (UInt32)Get16(p + 0x1c) + 1;
1122     if (numItems * 8 + 0x1e > namesOffset)
1123       return S_FALSE;
1124 
1125     for (UInt32 i = 0; i < numItems; i++)
1126     {
1127       const Byte *p2 = p + 0x1e + (size_t)i * 8;
1128       const UInt32 typeId = Get32(p2);
1129       const UInt32 k_typeId_blkx = 0x626c6b78; // blkx
1130 #ifndef DMG_SHOW_RAW
1131       if (typeId != k_typeId_blkx)
1132         continue;
1133 #endif
1134       const UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1;
1135       const UInt32 offs = Get16(p2 + 6);
1136       if (0x1c + offs + 12 * numFiles > namesOffset)
1137         return S_FALSE;
1138 
1139       for (UInt32 k = 0; k < numFiles; k++)
1140       {
1141         const Byte *p3 = p + 0x1c + offs + k * 12;
1142         // UInt32 id = Get16(p3);
1143         const UInt32 namePos = Get16(p3 + 2);
1144         // Byte attributes = p3[4]; // = 0x50 for blkx #define (ATTRIBUTE_HDIUTIL)
1145         // we don't know how many bits we can use. So we use 24 bits only
1146         UInt32 blockOffset = Get32(p3 + 4);
1147         blockOffset &= ((UInt32)1 << 24) - 1;
1148         // UInt32 unknown2 = Get32(p3 + 8); // ???
1149         if (blockOffset + 4 >= mainDataSize)
1150           return S_FALSE;
1151         const Byte *pBlock = rsrcBuf + headSize + blockOffset;
1152         const UInt32 blockSize = Get32(pBlock);
1153         if (mainDataSize - (blockOffset + 4) < blockSize)
1154           return S_FALSE;
1155 
1156         AString name;
1157 
1158         if (namePos != 0xffff)
1159         {
1160           const UInt32 namesBlockSize = footerSize - namesOffset;
1161           if (namePos >= namesBlockSize)
1162             return S_FALSE;
1163           const Byte *namePtr = p + namesOffset + namePos;
1164           const UInt32 nameLen = *namePtr;
1165           if (namesBlockSize - namePos <= nameLen)
1166             return S_FALSE;
1167           for (UInt32 r = 1; r <= nameLen; r++)
1168           {
1169             const Byte c = namePtr[r];
1170             if (c < 0x20 || c >= 0x80)
1171               break;
1172             name += (char)c;
1173           }
1174         }
1175 
1176         if (typeId == k_typeId_blkx)
1177         {
1178           CFile &file = _files.AddNew();
1179           file.Name = name;
1180           RINOK(file.Parse(pBlock + 4, blockSize))
1181           if (!file.IsCorrect)
1182             _headersError = true;
1183         }
1184 
1185 #ifdef DMG_SHOW_RAW
1186         {
1187           AString name2;
1188           name2.Add_UInt32(i);
1189           name2 += '_';
1190           {
1191             char temp[4 + 1] = { 0 };
1192             memcpy(temp, p2, 4);
1193             name2 += temp;
1194           }
1195           name2.Trim();
1196           name2 += '_';
1197           name2.Add_UInt32(k);
1198           if (!name.IsEmpty())
1199           {
1200             name2 += '_';
1201             name2 += name;
1202           }
1203           CExtraFile &extra2 = _extras.AddNew();
1204           extra2.Name = name2;
1205           extra2.Data.CopyFrom(pBlock + 4, blockSize);
1206         }
1207 #endif
1208       }
1209     }
1210   }
1211   else
1212   {
1213     if (xmlPair.Len > k_XmlSize_MAX)
1214       return S_FALSE;
1215     // if (!canUseXml) return S_FALSE;
1216     const size_t size = (size_t)xmlPair.Len;
1217     // if (size + 1 <= xmlPair.Len) return S_FALSE; // optional check
1218     RINOK(InStream_SeekSet(stream, _startPos + xmlPair.Offset))
1219     CXml xml;
1220     {
1221       openTotal += size;
1222       if (openArchiveCallback)
1223       {
1224         RINOK(openArchiveCallback->SetTotal(NULL, &openTotal))
1225       }
1226       size_t pos = 0;
1227       CAlignedBuffer1 xmlStr(size + 1);
1228       for (;;)
1229       {
1230         const size_t k_OpenStep = 1 << 24;
1231         const size_t cur = MyMin(k_OpenStep, size - pos);
1232         RINOK(ReadStream_FALSE(stream, xmlStr + pos, cur))
1233         pos += cur;
1234         openCur += cur;
1235         if (pos == size)
1236           break;
1237         if (openArchiveCallback)
1238         {
1239           RINOK(openArchiveCallback->SetCompleted(NULL, &openCur))
1240         }
1241       }
1242       xmlStr[size] = 0;
1243       // if (strlen((const char *)(const void *)(const Byte *)xmlStr) != size) return S_FALSE;
1244       if (!xml.Parse((char *)(void *)(Byte *)xmlStr))
1245         return S_FALSE;
1246 
1247 #ifdef DMG_SHOW_RAW
1248       CExtraFile &extra = _extras.AddNew();
1249       extra.Name = "a.xml";
1250       extra.Data.CopyFrom(xmlStr, size);
1251 #endif
1252     }
1253 
1254     if (xml.Root.Name != "plist")
1255       return S_FALSE;
1256 
1257     const CXmlItem *dictItem = xml.Root.FindSubTag_GetPtr("dict");
1258     if (!dictItem)
1259       return S_FALSE;
1260 
1261     const CXmlItem *rfDictItem = FindKeyPair(*dictItem, "resource-fork", "dict");
1262     if (!rfDictItem)
1263       return S_FALSE;
1264 
1265     const CXmlItem *arrItem = FindKeyPair(*rfDictItem, "blkx", "array");
1266     if (!arrItem)
1267       return S_FALSE;
1268 
1269     FOR_VECTOR (i, arrItem->SubItems)
1270     {
1271       const CXmlItem &item = arrItem->SubItems[i];
1272       if (!item.IsTagged("dict"))
1273         continue;
1274 
1275       CByteBuffer rawBuf;
1276       unsigned destLen = 0;
1277       {
1278         const AString *dataString = GetStringFromKeyPair(item, "Data", "data");
1279         if (!dataString)
1280           return S_FALSE;
1281         destLen = dataString->Len() / 4 * 3 + 4;
1282         rawBuf.Alloc(destLen);
1283         {
1284           const Byte *endPtr = Base64ToBin(rawBuf, *dataString);
1285           if (!endPtr)
1286             return S_FALSE;
1287           destLen = (unsigned)(endPtr - (const Byte *)rawBuf);
1288         }
1289 
1290 #ifdef DMG_SHOW_RAW
1291         CExtraFile &extra = _extras.AddNew();
1292         extra.Name.Add_UInt32(_files.Size());
1293         extra.Data.CopyFrom(rawBuf, destLen);
1294 #endif
1295       }
1296       CFile &file = _files.AddNew();
1297       {
1298         /* xml code removes front space for such string:
1299              <string> (Apple_Free : 3)</string>
1300            maybe we shoud fix xml code and return full string with space.
1301         */
1302         const AString *name = GetStringFromKeyPair(item, "Name", "string");
1303         if (!name || name->IsEmpty())
1304           name = GetStringFromKeyPair(item, "CFName", "string");
1305         if (name)
1306           file.Name = *name;
1307       }
1308       /*
1309       {
1310         const AString *s = GetStringFromKeyPair(item, "ID", "string");
1311         if (s)
1312           file.Id = *s;
1313       }
1314       */
1315       RINOK(file.Parse(rawBuf, destLen))
1316       if (!file.IsCorrect)
1317         _headersError = true;
1318     }
1319   }
1320 
1321   if (_masterChecksum.IsCrc32())
1322   {
1323     UInt32 crc = CRC_INIT_VAL;
1324     unsigned i;
1325     for (i = 0; i < _files.Size(); i++)
1326     {
1327       const CChecksum &cs = _files[i].Checksum;
1328       if ((cs.NumBits & 0x7) != 0)
1329         break;
1330       const UInt32 len = cs.NumBits >> 3;
1331       if (len > kChecksumSize_Max)
1332         break;
1333       crc = CrcUpdate(crc, cs.Data, (size_t)len);
1334     }
1335     if (i == _files.Size())
1336       _masterCrcError = (CRC_GET_DIGEST(crc) != _masterChecksum.GetCrc32());
1337   }
1338 
1339   {
1340     UInt64 sec = 0;
1341     FOR_VECTOR (i, _files)
1342     {
1343       const CFile &file = _files[i];
1344       /*
1345       if (file.Descriptor != (Int32)i - 1)
1346         _headersError = true;
1347       */
1348       if (file.StartUnpackSector != sec)
1349         _headersError = true;
1350       if (file.NumUnpackSectors >= kSectorNumber_LIMIT)
1351         _headersError = true;
1352       sec += file.NumUnpackSectors;
1353       if (sec >= kSectorNumber_LIMIT)
1354         _headersError = true;
1355     }
1356     if (sec != _numSectors)
1357       _headersError = true;
1358   }
1359 
1360   // data checksum calculation can be slow for big dmg file
1361   if (_dataForkChecksum.IsCrc32())
1362   {
1363     UInt64 endPos;
1364     if (!_dataForkPair.GetEndPos(endPos)
1365         || _dataForkPair.Offset >= ((UInt64)1 << 63))
1366       _headersError = true;
1367     else
1368     {
1369       const UInt64 seekPos = _startPos + _dataForkPair.Offset;
1370       if (seekPos > fileSize
1371         || endPos > fileSize - _startPos)
1372       {
1373         _headersError = true;
1374         // kpv_ErrorFlags_UnexpectedEnd
1375       }
1376       else
1377       {
1378         const size_t kBufSize = 1 << 15;
1379         CAlignedBuffer1 buf2(kBufSize);
1380         RINOK(InStream_SeekSet(stream, seekPos))
1381         if (openArchiveCallback)
1382         {
1383           RINOK(openArchiveCallback->SetTotal(NULL, &openTotal))
1384         }
1385         UInt32 crc = CRC_INIT_VAL;
1386         UInt64 pos = 0;
1387         for (;;)
1388         {
1389           const UInt64 rem = _dataForkPair.Len - pos;
1390           size_t cur = kBufSize;
1391           if (cur > rem)
1392             cur = (UInt32)rem;
1393           if (cur == 0)
1394             break;
1395           RINOK(ReadStream_FALSE(stream, buf2, cur))
1396           crc = CrcUpdate(crc, buf2, cur);
1397           pos += cur;
1398           openCur += cur;
1399           if ((pos & ((1 << 24) - 1)) == 0 && openArchiveCallback)
1400           {
1401             RINOK(openArchiveCallback->SetCompleted(NULL, &openCur))
1402           }
1403         }
1404         if (_dataForkChecksum.GetCrc32() != CRC_GET_DIGEST(crc))
1405           _dataForkError = true;
1406       }
1407     }
1408   }
1409 
1410   return S_OK;
1411 }
1412 
1413 
1414 
Z7_COM7F_IMF(CHandler::Open (IInStream * stream,const UInt64 *,IArchiveOpenCallback * openArchiveCallback))1415 Z7_COM7F_IMF(CHandler::Open(IInStream *stream,
1416     const UInt64 * /* maxCheckStartPosition */,
1417     IArchiveOpenCallback *openArchiveCallback))
1418 {
1419   COM_TRY_BEGIN
1420   Close();
1421   RINOK(Open2(stream, openArchiveCallback))
1422   _inStream = stream;
1423   return S_OK;
1424   COM_TRY_END
1425 }
1426 
Z7_COM7F_IMF(CHandler::Close ())1427 Z7_COM7F_IMF(CHandler::Close())
1428 {
1429   _masterCrcError = false;
1430   _headersError = false;
1431   _dataForkError = false;
1432   _rsrcMode_wasUsed = false;
1433   _phySize = 0;
1434   _startPos = 0;
1435   _name.Empty();
1436   _inStream.Release();
1437   _files.Clear();
1438 #ifdef DMG_SHOW_RAW
1439   _extras.Clear();
1440 #endif
1441   return S_OK;
1442 }
1443 
Z7_COM7F_IMF(CHandler::GetNumberOfItems (UInt32 * numItems))1444 Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems))
1445 {
1446   *numItems = _files.Size()
1447 #ifdef DMG_SHOW_RAW
1448     + _extras.Size()
1449 #endif
1450     ;
1451   return S_OK;
1452 }
1453 
1454 #ifdef DMG_SHOW_RAW
1455 #define RAW_PREFIX "raw" STRING_PATH_SEPARATOR
1456 #endif
1457 
Z7_COM7F_IMF(CHandler::GetProperty (UInt32 index,PROPID propID,PROPVARIANT * value))1458 Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value))
1459 {
1460   COM_TRY_BEGIN
1461   NWindows::NCOM::CPropVariant prop;
1462 
1463 #ifdef DMG_SHOW_RAW
1464   if (index >= _files.Size())
1465   {
1466     const CExtraFile &extra = _extras[index - _files.Size()];
1467     switch (propID)
1468     {
1469       case kpidPath:
1470         prop = (AString)RAW_PREFIX + extra.Name;
1471         break;
1472       case kpidSize:
1473       case kpidPackSize:
1474         prop = (UInt64)extra.Data.Size();
1475         break;
1476     }
1477   }
1478   else
1479 #endif
1480   {
1481     const CFile &item = _files[index];
1482     switch (propID)
1483     {
1484       case kpidSize:  prop = item.Size; break;
1485       case kpidPackSize:  prop = item.PackSize; break;
1486       case kpidCRC:
1487       {
1488         if (item.Checksum.IsCrc32() && item.FullFileChecksum)
1489           prop = item.Checksum.GetCrc32();
1490         break;
1491       }
1492       case kpidChecksum:
1493       {
1494         AString s;
1495         item.Checksum.Print(s);
1496         if (!s.IsEmpty())
1497           prop = s;
1498         break;
1499       }
1500 
1501       /*
1502       case kpidOffset:
1503       {
1504         prop = item.StartPackPos;
1505         break;
1506       }
1507       */
1508 
1509       case kpidNumBlocks:
1510           prop = (UInt32)item.Blocks.Size();
1511           break;
1512       case kpidClusterSize:
1513           prop = item.BlockSize_MAX;
1514           break;
1515 
1516       case kpidMethod:
1517       {
1518         AString s;
1519         if (!item.IsCorrect)
1520           s.Add_OptSpaced("CORRUPTED");
1521         CMethods m;
1522         m.Update(item);
1523         m.AddToString(s);
1524         {
1525           AString s2;
1526           item.Checksum.PrintType(s2);
1527           if (!s2.IsEmpty())
1528             s.Add_OptSpaced(s2);
1529         }
1530         if (!s.IsEmpty())
1531           prop = s;
1532         break;
1533       }
1534 
1535       case kpidPath:
1536       {
1537 #ifdef Z7_DMG_SINGLE_FILE_MODE
1538         prop = "a.img";
1539 #else
1540         UString name;
1541         name.Add_UInt32(index);
1542         unsigned num = 10;
1543         unsigned numDigits;
1544         for (numDigits = 1; num < _files.Size(); numDigits++)
1545           num *= 10;
1546         while (name.Len() < numDigits)
1547           name.InsertAtFront(L'0');
1548 
1549         AString subName;
1550         int pos1 = item.Name.Find('(');
1551         if (pos1 >= 0)
1552         {
1553           pos1++;
1554           const int pos2 = item.Name.Find(')', pos1);
1555           if (pos2 >= 0)
1556           {
1557             subName.SetFrom(item.Name.Ptr(pos1), pos2 - pos1);
1558             pos1 = subName.Find(':');
1559             if (pos1 >= 0)
1560               subName.DeleteFrom(pos1);
1561           }
1562         }
1563         else
1564           subName = item.Name; // new apfs dmg can be without braces
1565         subName.Trim();
1566         if (!subName.IsEmpty())
1567         {
1568           const char *ext = Find_Apple_FS_Ext(subName);
1569           if (ext)
1570             subName = ext;
1571           UString name2;
1572           ConvertUTF8ToUnicode(subName, name2);
1573           name.Add_Dot();
1574           name += name2;
1575         }
1576         else
1577         {
1578           UString name2;
1579           ConvertUTF8ToUnicode(item.Name, name2);
1580           if (!name2.IsEmpty())
1581             name += "_";
1582           name += name2;
1583         }
1584         prop = name;
1585 #endif
1586 
1587         break;
1588       }
1589 
1590       case kpidComment:
1591       {
1592         UString name;
1593         ConvertUTF8ToUnicode(item.Name, name);
1594         prop = name;
1595         break;
1596       }
1597       case kpidId:
1598       {
1599         prop.Set_Int32((Int32)item.Descriptor);
1600         /*
1601         if (!item.Id.IsEmpty())
1602         {
1603           UString s;
1604           ConvertUTF8ToUnicode(item.Id, s);
1605           prop = s;
1606         }
1607         */
1608         break;
1609       }
1610 #ifdef Z7_DMG_SINGLE_FILE_MODE
1611       case kpidPosition:
1612         prop = item.StartUnpackSector << 9;
1613         break;
1614 #endif
1615     }
1616   }
1617   prop.Detach(value);
1618   return S_OK;
1619   COM_TRY_END
1620 }
1621 
1622 
1623 class CAdcDecoder
1624 {
1625   CLzOutWindow m_OutWindowStream;
1626   CInBuffer m_InStream;
1627 
1628   class CCoderReleaser Z7_final
1629   {
1630     CAdcDecoder *m_Coder;
1631   public:
1632     bool NeedFlush;
CCoderReleaser(CAdcDecoder * coder)1633     CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {}
~CCoderReleaser()1634     ~CCoderReleaser()
1635     {
1636       if (NeedFlush)
1637         m_Coder->m_OutWindowStream.Flush();
1638     }
1639   };
1640   friend class CCoderReleaser;
1641 
1642 public:
1643   HRESULT Code(ISequentialInStream * const inStream,
1644     ISequentialOutStream *outStream,
1645     const UInt64 * const inSize,
1646     const UInt64 * const outSize,
1647     ICompressProgressInfo * const progress);
1648 };
1649 
1650 
Code(ISequentialInStream * const inStream,ISequentialOutStream * outStream,const UInt64 * const inSize,const UInt64 * const outSizePtr,ICompressProgressInfo * const progress)1651 HRESULT CAdcDecoder::Code(ISequentialInStream * const inStream,
1652     ISequentialOutStream *outStream,
1653     const UInt64 * const inSize,
1654     const UInt64 * const outSizePtr,
1655     ICompressProgressInfo * const progress)
1656 {
1657   try {
1658 
1659   if (!m_OutWindowStream.Create(1 << 18)) // at least (1 << 16) is required here
1660     return E_OUTOFMEMORY;
1661   if (!m_InStream.Create(1 << 18))
1662     return E_OUTOFMEMORY;
1663 
1664   m_OutWindowStream.SetStream(outStream);
1665   m_OutWindowStream.Init(false);
1666   m_InStream.SetStream(inStream);
1667   m_InStream.Init();
1668 
1669   CCoderReleaser coderReleaser(this);
1670 
1671   const UInt32 kStep = 1 << 22;
1672   UInt64 nextLimit = kStep;
1673   const UInt64 outSize = *outSizePtr;
1674   UInt64 pos = 0;
1675   /* match sequences and literal sequences do not cross 64KB range
1676      in some dmg archive examples. But is it so for any Adc stream? */
1677 
1678   while (pos < outSize)
1679   {
1680     if (pos >= nextLimit && progress)
1681     {
1682       nextLimit += kStep;
1683       const UInt64 packSize = m_InStream.GetProcessedSize();
1684       RINOK(progress->SetRatioInfo(&packSize, &pos))
1685     }
1686     Byte b;
1687     if (!m_InStream.ReadByte(b))
1688       return S_FALSE;
1689     const UInt64 rem = outSize - pos;
1690     if (b & 0x80)
1691     {
1692       unsigned num = (unsigned)b - 0x80 + 1;
1693       if (num > rem)
1694         return S_FALSE;
1695       pos += num;
1696       do
1697       {
1698         if (!m_InStream.ReadByte(b))
1699           return S_FALSE;
1700         m_OutWindowStream.PutByte(b);
1701       }
1702       while (--num);
1703       continue;
1704     }
1705     Byte b1;
1706     if (!m_InStream.ReadByte(b1))
1707       return S_FALSE;
1708 
1709     UInt32 len, dist;
1710 
1711     if (b & 0x40)
1712     {
1713       len = (UInt32)b - 0x40 + 4;
1714       Byte b2;
1715       if (!m_InStream.ReadByte(b2))
1716         return S_FALSE;
1717       dist = ((UInt32)b1 << 8) + b2;
1718     }
1719     else
1720     {
1721       len = ((UInt32)b >> 2) + 3;
1722       dist = (((UInt32)b & 3) << 8) + b1;
1723     }
1724 
1725     if (/* dist >= pos || */ len > rem)
1726       return S_FALSE;
1727     if (!m_OutWindowStream.CopyBlock(dist, len))
1728       return S_FALSE;
1729     pos += len;
1730   }
1731   if (*inSize != m_InStream.GetProcessedSize())
1732     return S_FALSE;
1733   coderReleaser.NeedFlush = false;
1734   return m_OutWindowStream.Flush();
1735 
1736   }
1737   catch(const CInBufferException &e) { return e.ErrorCode; }
1738   catch(const CLzOutWindowException &e) { return e.ErrorCode; }
1739   catch(...) { return S_FALSE; }
1740 }
1741 
1742 
1743 
1744 struct CDecoders
1745 {
1746   CMyComPtr2<ICompressCoder, NCompress::NZlib::CDecoder> zlib;
1747   CMyComPtr2<ICompressCoder, NCompress::NBZip2::CDecoder> bzip2;
1748   CMyComPtr2<ICompressCoder, NCompress::NLzfse::CDecoder> lzfse;
1749   CMyUniquePtr<NCompress::NXz::CDecoder> xz;
1750   CMyUniquePtr<CAdcDecoder> adc;
1751 
1752   HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
1753     const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress);
1754 };
1755 
Code(ISequentialInStream * inStream,ISequentialOutStream * outStream,const CBlock & block,const UInt64 * unpSize,ICompressProgressInfo * progress)1756 HRESULT CDecoders::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
1757     const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress)
1758 {
1759   HRESULT hres;
1760   UInt64 processed;
1761   switch (block.Type)
1762   {
1763     case METHOD_ADC:
1764       adc.Create_if_Empty();
1765       return adc->Code(inStream, outStream, &block.PackSize, unpSize, progress);
1766     case METHOD_LZFSE:
1767       lzfse.Create_if_Empty();
1768       return lzfse.Interface()->Code(inStream, outStream, &block.PackSize, unpSize, progress);
1769     case METHOD_ZLIB:
1770       zlib.Create_if_Empty();
1771       hres = zlib.Interface()->Code(inStream, outStream, NULL, unpSize, progress);
1772       processed = zlib->GetInputProcessedSize();
1773       break;
1774     case METHOD_BZIP2:
1775       bzip2.Create_if_Empty();
1776       hres = bzip2.Interface()->Code(inStream, outStream, NULL, unpSize, progress);
1777       processed = bzip2->GetInputProcessedSize();
1778       break;
1779     case METHOD_XZ:
1780       xz.Create_if_Empty();
1781       hres = xz->Decode(inStream, outStream, unpSize, true, progress);
1782       processed = xz->Stat.InSize;
1783       break;
1784     default:
1785       return E_NOTIMPL;
1786   }
1787   if (hres == S_OK && processed != block.PackSize)
1788     hres = S_FALSE;
1789   return hres;
1790 }
1791 
1792 
Z7_COM7F_IMF(CHandler::Extract (const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback))1793 Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1794     Int32 testMode, IArchiveExtractCallback *extractCallback))
1795 {
1796   COM_TRY_BEGIN
1797   const bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1798   if (allFilesMode)
1799     numItems = _files.Size()
1800 #ifdef DMG_SHOW_RAW
1801     + _extras.Size()
1802 #endif
1803   ;
1804   if (numItems == 0)
1805     return S_OK;
1806   UInt64 totalSize = 0;
1807   UInt32 i;
1808 
1809   for (i = 0; i < numItems; i++)
1810   {
1811     const UInt32 index = allFilesMode ? i : indices[i];
1812 #ifdef DMG_SHOW_RAW
1813     if (index >= _files.Size())
1814       totalSize += _extras[index - _files.Size()].Data.Size();
1815     else
1816 #endif
1817       totalSize += _files[index].Size;
1818   }
1819   RINOK(extractCallback->SetTotal(totalSize))
1820 
1821   const size_t kZeroBufSize = 1 << 14;
1822   CAlignedBuffer1 zeroBuf(kZeroBufSize);
1823   memset(zeroBuf, 0, kZeroBufSize);
1824 
1825   CDecoders decoders;
1826   CMyComPtr2_Create<ICompressCoder, NCompress::CCopyCoder> copyCoder;
1827   CMyComPtr2_Create<ICompressProgressInfo, CLocalProgress> lps;
1828   lps->Init(extractCallback, false);
1829   CMyComPtr2_Create<ISequentialInStream, CLimitedSequentialInStream> inStream;
1830   inStream->SetStream(_inStream);
1831 
1832   UInt64 total_PackSize = 0;
1833   UInt64 total_UnpackSize = 0;
1834   UInt64 cur_PackSize = 0;
1835   UInt64 cur_UnpackSize = 0;
1836 
1837   for (i = 0;; i++,
1838         total_PackSize   += cur_PackSize,
1839         total_UnpackSize += cur_UnpackSize)
1840   {
1841     lps->InSize = total_PackSize;
1842     lps->OutSize = total_UnpackSize;
1843     cur_PackSize = 0;
1844     cur_UnpackSize = 0;
1845     RINOK(lps->SetCur())
1846     if (i >= numItems)
1847       return S_OK;
1848 
1849     Int32 opRes = NExtract::NOperationResult::kOK;
1850    {
1851     CMyComPtr<ISequentialOutStream> realOutStream;
1852     const Int32 askMode = testMode ?
1853         NExtract::NAskMode::kTest :
1854         NExtract::NAskMode::kExtract;
1855     const UInt32 index = allFilesMode ? i : indices[i];
1856     RINOK(extractCallback->GetStream(index, &realOutStream, askMode))
1857 
1858     if (!testMode && !realOutStream)
1859       continue;
1860     RINOK(extractCallback->PrepareOperation(askMode))
1861 
1862 #ifdef DMG_SHOW_RAW
1863     if (index >= _files.Size())
1864     {
1865       const CByteBuffer &buf = _extras[index - _files.Size()].Data;
1866       if (realOutStream)
1867         RINOK(WriteStream(realOutStream, buf, buf.Size()))
1868       cur_PackSize = cur_UnpackSize = buf.Size();
1869     }
1870     else
1871 #endif
1872     {
1873       const CFile &item = _files[index];
1874       cur_PackSize = item.PackSize;
1875       cur_UnpackSize = item.Size;
1876 
1877       if (!item.IsCorrect)
1878         opRes = NExtract::NOperationResult::kHeadersError;
1879       else
1880       {
1881         CMyComPtr2_Create<ISequentialOutStream, COutStreamWithCRC> outCrcStream;
1882         outCrcStream->SetStream(realOutStream);
1883         // realOutStream.Release();
1884         const bool needCrc = item.Checksum.IsCrc32();
1885         outCrcStream->Init(needCrc);
1886 
1887         CMyComPtr2_Create<ISequentialOutStream, CLimitedSequentialOutStream> outStream;
1888         outStream->SetStream(outCrcStream);
1889 
1890         UInt64 unpPos = 0;
1891         UInt64 packPos = 0;
1892 
1893         FOR_VECTOR (blockIndex, item.Blocks)
1894         {
1895           lps->InSize = total_PackSize + packPos;
1896           lps->OutSize = total_UnpackSize + unpPos;
1897           RINOK(lps->SetCur())
1898 
1899           const CBlock &block = item.Blocks[blockIndex];
1900           // if (!block.ThereAreDataInBlock()) continue;
1901 
1902           packPos += block.PackSize;
1903           if (block.UnpPos != unpPos)
1904           {
1905             opRes = NExtract::NOperationResult::kHeadersError;
1906             break;
1907           }
1908 
1909           RINOK(InStream_SeekSet(_inStream, _startPos + _dataForkPair.Offset + item.StartPackPos + block.PackPos))
1910           inStream->Init(block.PackSize);
1911 
1912           const UInt64 unpSize = item.GetUnpackSize_of_Block(blockIndex);
1913 
1914           outStream->Init(unpSize);
1915           HRESULT res = S_OK;
1916 
1917           outCrcStream->EnableCalc(needCrc && block.NeedCrc());
1918 
1919           if (block.IsZeroMethod())
1920           {
1921             if (block.PackSize != 0)
1922               opRes = NExtract::NOperationResult::kUnsupportedMethod;
1923           }
1924           else if (block.Type == METHOD_COPY)
1925           {
1926             if (unpSize != block.PackSize)
1927               opRes = NExtract::NOperationResult::kUnsupportedMethod;
1928             else
1929               res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps);
1930           }
1931           else
1932             res = decoders.Code(inStream, outStream, block, &unpSize, lps);
1933 
1934           if (res != S_OK)
1935           {
1936             if (res != S_FALSE)
1937             {
1938               if (res != E_NOTIMPL)
1939                 return res;
1940               opRes = NExtract::NOperationResult::kUnsupportedMethod;
1941             }
1942             if (opRes == NExtract::NOperationResult::kOK)
1943               opRes = NExtract::NOperationResult::kDataError;
1944           }
1945 
1946           unpPos += unpSize;
1947 
1948           if (!outStream->IsFinishedOK())
1949           {
1950             if (!block.IsZeroMethod() && opRes == NExtract::NOperationResult::kOK)
1951               opRes = NExtract::NOperationResult::kDataError;
1952 
1953             for (unsigned k = 0;;)
1954             {
1955               const UInt64 rem = outStream->GetRem();
1956               if (rem == 0)
1957                 break;
1958               size_t size = kZeroBufSize;
1959               if (size > rem)
1960                 size = (size_t)rem;
1961               RINOK(WriteStream(outStream, zeroBuf, size))
1962               k++;
1963               if ((k & 0xfff) == 0)
1964               {
1965                 lps->OutSize = total_UnpackSize + unpPos - outStream->GetRem();
1966                 RINOK(lps->SetCur())
1967               }
1968             }
1969           }
1970         }
1971         if (needCrc && opRes == NExtract::NOperationResult::kOK)
1972         {
1973           if (outCrcStream->GetCRC() != item.Checksum.GetCrc32())
1974             opRes = NExtract::NOperationResult::kCRCError;
1975         }
1976       }
1977     }
1978    }
1979     RINOK(extractCallback->SetOperationResult(opRes))
1980   }
1981 
1982   COM_TRY_END
1983 }
1984 
1985 
1986 
1987 
1988 struct CChunk
1989 {
1990   int BlockIndex;
1991   UInt64 AccessMark;
1992   Byte *Buf;
1993   size_t BufSize;
1994 
FreeNArchive::CChunk1995   void Free()
1996   {
1997     z7_AlignedFree(Buf);
1998     Buf = NULL;
1999     BufSize = 0;
2000   }
AllocNArchive::CChunk2001   void Alloc(size_t size)
2002   {
2003     Buf = (Byte *)z7_AlignedAlloc(size);
2004   }
2005 };
2006 
2007 
2008 Z7_CLASS_IMP_IInStream(
2009   CInStream
2010 )
2011   bool _errorMode;
2012   UInt64 _virtPos;
2013   int _latestChunk;
2014   int _latestBlock;
2015   UInt64 _accessMark;
2016   UInt64 _chunks_TotalSize;
2017   CRecordVector<CChunk> _chunks;
2018 
2019 public:
2020   CMyComPtr<IInStream> Stream;
2021   const CFile *File;
2022   UInt64 Size;
2023 private:
2024   UInt64 _startPos;
2025 
2026   ~CInStream();
2027   CMyComPtr2<ISequentialOutStream, CBufPtrSeqOutStream> outStream;
2028   CMyComPtr2<ISequentialInStream, CLimitedSequentialInStream> inStream;
2029   CDecoders decoders;
2030 public:
2031 
2032   // HRESULT
2033   void Init(UInt64 startPos)
2034   {
2035     _errorMode = false;
2036     _startPos = startPos;
2037     _virtPos = 0;
2038     _latestChunk = -1;
2039     _latestBlock = -1;
2040     _accessMark = 0;
2041     _chunks_TotalSize = 0;
2042 
2043     inStream.Create_if_Empty();
2044     inStream->SetStream(Stream);
2045 
2046     outStream.Create_if_Empty();
2047     // return S_OK;
2048   }
2049 };
2050 
2051 
2052 CInStream::~CInStream()
2053 {
2054   unsigned i = _chunks.Size();
2055   while (i)
2056     _chunks[--i].Free();
2057 }
2058 
2059 static unsigned FindBlock(const CRecordVector<CBlock> &blocks, UInt64 pos)
2060 {
2061   unsigned left = 0, right = blocks.Size();
2062   for (;;)
2063   {
2064     const unsigned mid = (left + right) / 2;
2065     if (mid == left)
2066       return left;
2067     if (pos < blocks[mid].UnpPos)
2068       right = mid;
2069     else
2070       left = mid;
2071   }
2072 }
2073 
2074 Z7_COM7F_IMF(CInStream::Read(void *data, UInt32 size, UInt32 *processedSize))
2075 {
2076   // COM_TRY_BEGIN
2077   try {
2078 
2079   if (_errorMode)
2080     return E_OUTOFMEMORY;
2081 
2082   if (processedSize)
2083     *processedSize = 0;
2084   if (size == 0)
2085     return S_OK;
2086   if (_virtPos >= Size)
2087     return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL;
2088   {
2089     const UInt64 rem = Size - _virtPos;
2090     if (size > rem)
2091       size = (UInt32)rem;
2092   }
2093 
2094   if (_latestBlock >= 0)
2095   {
2096     const CBlock &block = File->Blocks[(unsigned)_latestBlock];
2097     if (_virtPos < block.UnpPos ||
2098         _virtPos - block.UnpPos >= File->GetUnpackSize_of_Block((unsigned)_latestBlock))
2099       _latestBlock = -1;
2100   }
2101 
2102   if (_latestBlock < 0)
2103   {
2104     _latestChunk = -1;
2105     const unsigned blockIndex = FindBlock(File->Blocks, _virtPos);
2106     const CBlock &block = File->Blocks[blockIndex];
2107     const UInt64 unpSize = File->GetUnpackSize_of_Block(blockIndex);
2108 
2109     if (block.NeedAllocateBuffer()
2110         && unpSize <= k_Chunk_Size_MAX)
2111     {
2112       unsigned i = 0;
2113       {
2114         unsigned numChunks = _chunks.Size();
2115         if (numChunks)
2116         {
2117           const CChunk *chunk = _chunks.ConstData();
2118           do
2119           {
2120             if (chunk->BlockIndex == (int)blockIndex)
2121               break;
2122             chunk++;
2123           }
2124           while (--numChunks);
2125           i = _chunks.Size() - numChunks;
2126         }
2127       }
2128       if (i != _chunks.Size())
2129         _latestChunk = (int)i;
2130       else
2131       {
2132         unsigned chunkIndex;
2133         for (;;)
2134         {
2135           if (_chunks.IsEmpty() ||
2136               (_chunks.Size() < k_NumChunks_MAX
2137               && _chunks_TotalSize + unpSize <= k_Chunks_TotalSize_MAX))
2138           {
2139             CChunk chunk;
2140             chunk.Buf = NULL;
2141             chunk.BufSize = 0;
2142             chunk.BlockIndex = -1;
2143             chunk.AccessMark = 0;
2144             chunkIndex = _chunks.Add(chunk);
2145             break;
2146           }
2147           chunkIndex = 0;
2148           if (_chunks.Size() == 1)
2149             break;
2150           {
2151             const CChunk *chunks = _chunks.ConstData();
2152             UInt64 accessMark_min = chunks[chunkIndex].AccessMark;
2153             const unsigned numChunks = _chunks.Size();
2154             for (i = 1; i < numChunks; i++)
2155             {
2156               if (chunks[i].AccessMark < accessMark_min)
2157               {
2158                 chunkIndex = i;
2159                 accessMark_min = chunks[i].AccessMark;
2160               }
2161             }
2162           }
2163           {
2164             CChunk &chunk = _chunks[chunkIndex];
2165             const UInt64 newTotalSize = _chunks_TotalSize - chunk.BufSize;
2166             if (newTotalSize + unpSize <= k_Chunks_TotalSize_MAX)
2167               break;
2168             _chunks_TotalSize = newTotalSize;
2169             chunk.Free();
2170           }
2171           // we have called chunk.Free() before, because
2172           // _chunks.Delete() doesn't call chunk.Free().
2173           _chunks.Delete(chunkIndex);
2174           PRF(printf("\n++num_chunks=%u, _chunks_TotalSize = %u\n", (unsigned)_chunks.Size(), (unsigned)_chunks_TotalSize);)
2175         }
2176 
2177         CChunk &chunk = _chunks[chunkIndex];
2178         chunk.BlockIndex = -1;
2179         chunk.AccessMark = 0;
2180 
2181         if (chunk.BufSize < unpSize)
2182         {
2183           _chunks_TotalSize -= chunk.BufSize;
2184           chunk.Free();
2185           // if (unpSize > k_Chunk_Size_MAX) return E_FAIL;
2186           chunk.Alloc((size_t)unpSize);
2187           if (!chunk.Buf)
2188             return E_OUTOFMEMORY;
2189           chunk.BufSize = (size_t)unpSize;
2190           _chunks_TotalSize += chunk.BufSize;
2191         }
2192 
2193         RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos))
2194 
2195         inStream->Init(block.PackSize);
2196 #ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
2197         if (block.Type == METHOD_COPY)
2198         {
2199           if (block.PackSize != unpSize)
2200             return E_FAIL;
2201           RINOK(ReadStream_FAIL(inStream, chunk.Buf, (size_t)unpSize))
2202         }
2203         else
2204 #endif
2205         {
2206           outStream->Init(chunk.Buf, (size_t)unpSize);
2207           RINOK(decoders.Code(inStream, outStream, block, &unpSize, NULL))
2208           if (outStream->GetPos() != unpSize)
2209             return E_FAIL;
2210         }
2211         chunk.BlockIndex = (int)blockIndex;
2212         _latestChunk = (int)chunkIndex;
2213       }
2214 
2215       _chunks[_latestChunk].AccessMark = _accessMark++;
2216     }
2217 
2218     _latestBlock = (int)blockIndex;
2219   }
2220 
2221   const CBlock &block = File->Blocks[(unsigned)_latestBlock];
2222   const UInt64 offset = _virtPos - block.UnpPos;
2223   {
2224     const UInt64 rem = File->GetUnpackSize_of_Block((unsigned)_latestBlock) - offset;
2225     if (size > rem)
2226       size = (UInt32)rem;
2227     if (size == 0) // it's unexpected case. but we check it.
2228       return S_OK;
2229   }
2230   HRESULT res = S_OK;
2231 
2232   if (block.IsZeroMethod())
2233     memset(data, 0, size);
2234   else if (_latestChunk >= 0)
2235     memcpy(data, _chunks[_latestChunk].Buf + (size_t)offset, size);
2236   else
2237   {
2238     if (block.Type != METHOD_COPY)
2239       return E_FAIL;
2240     RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos + offset))
2241     res = Stream->Read(data, size, &size);
2242   }
2243 
2244   _virtPos += size;
2245   if (processedSize)
2246     *processedSize = size;
2247   return res;
2248   // COM_TRY_END
2249   }
2250   catch(...)
2251   {
2252     _errorMode = true;
2253     return E_OUTOFMEMORY;
2254   }
2255 }
2256 
2257 
2258 Z7_COM7F_IMF(CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition))
2259 {
2260   switch (seekOrigin)
2261   {
2262     case STREAM_SEEK_SET: break;
2263     case STREAM_SEEK_CUR: offset += _virtPos; break;
2264     case STREAM_SEEK_END: offset += Size; break;
2265     default: return STG_E_INVALIDFUNCTION;
2266   }
2267   if (offset < 0)
2268     return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
2269   _virtPos = (UInt64)offset;
2270   if (newPosition)
2271     *newPosition = (UInt64)offset;
2272   return S_OK;
2273 }
2274 
2275 Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream))
2276 {
2277   COM_TRY_BEGIN
2278 
2279 #ifdef DMG_SHOW_RAW
2280   if (index >= _files.Size())
2281     return S_FALSE;
2282 #endif
2283 
2284   CMyComPtr2<ISequentialInStream, CInStream> spec;
2285   spec.Create_if_Empty();
2286   spec->File = &_files[index];
2287   const CFile &file = *spec->File;
2288 
2289   if (!file.IsCorrect)
2290     return S_FALSE;
2291 
2292   FOR_VECTOR (i, file.Blocks)
2293   {
2294     const CBlock &block = file.Blocks[i];
2295     if (!block.NeedAllocateBuffer())
2296       continue;
2297 
2298     switch (block.Type)
2299     {
2300 #ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS
2301       case METHOD_COPY:
2302         break;
2303 #endif
2304       case METHOD_ADC:
2305       case METHOD_ZLIB:
2306       case METHOD_BZIP2:
2307       case METHOD_LZFSE:
2308       case METHOD_XZ:
2309       // case METHOD_END:
2310         if (file.GetUnpackSize_of_Block(i) > k_Chunk_Size_MAX)
2311           return S_FALSE;
2312         break;
2313       default:
2314         return S_FALSE;
2315     }
2316   }
2317 
2318   spec->Stream = _inStream;
2319   spec->Size = spec->File->Size;
2320   // RINOK(
2321   spec->Init(_startPos + _dataForkPair.Offset);
2322   *stream = spec.Detach();
2323   return S_OK;
2324 
2325   COM_TRY_END
2326 }
2327 
2328 REGISTER_ARC_I(
2329   "Dmg", "dmg", NULL, 0xE4,
2330   k_Signature,
2331   0,
2332   NArcInfoFlags::kBackwardOpen |
2333   NArcInfoFlags::kUseGlobalOffset,
2334   NULL)
2335 
2336 }}
2337