1 // DmgHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/IntToString.h"
9 #include "../../Common/MyXml.h"
10 #include "../../Common/UTFConvert.h"
11 
12 #include "../../Windows/PropVariant.h"
13 
14 #include "../Common/LimitedStreams.h"
15 #include "../Common/ProgressUtils.h"
16 #include "../Common/RegisterArc.h"
17 #include "../Common/StreamObjects.h"
18 #include "../Common/StreamUtils.h"
19 
20 #include "../Compress/BZip2Decoder.h"
21 #include "../Compress/CopyCoder.h"
22 #include "../Compress/LzfseDecoder.h"
23 #include "../Compress/ZlibDecoder.h"
24 
25 #include "Common/OutStreamWithCRC.h"
26 
27 // #define DMG_SHOW_RAW
28 
29 // #include <stdio.h>
30 #define PRF(x) // x
31 
32 #define Get16(p) GetBe16(p)
33 #define Get32(p) GetBe32(p)
34 #define Get64(p) GetBe64(p)
35 
36 Byte *Base64ToBin(Byte *dest, const char *src);
37 
38 namespace NArchive {
39 namespace NDmg {
40 
41 
42 static const UInt32  METHOD_ZERO_0  = 0;
43 static const UInt32  METHOD_COPY    = 1;
44 static const UInt32  METHOD_ZERO_2  = 2; // without file CRC calculation
45 static const UInt32  METHOD_ADC     = 0x80000004;
46 static const UInt32  METHOD_ZLIB    = 0x80000005;
47 static const UInt32  METHOD_BZIP2   = 0x80000006;
48 static const UInt32  METHOD_LZFSE   = 0x80000007;
49 static const UInt32  METHOD_COMMENT = 0x7FFFFFFE; // is used to comment "+beg" and "+end" in extra field.
50 static const UInt32  METHOD_END     = 0xFFFFFFFF;
51 
52 
53 struct CBlock
54 {
55   UInt32 Type;
56   UInt64 UnpPos;
57   UInt64 UnpSize;
58   UInt64 PackPos;
59   UInt64 PackSize;
60 
GetNextPackOffsetNArchive::NDmg::CBlock61   UInt64 GetNextPackOffset() const { return PackPos + PackSize; }
GetNextUnpPosNArchive::NDmg::CBlock62   UInt64 GetNextUnpPos() const { return UnpPos + UnpSize; }
63 
IsZeroMethodNArchive::NDmg::CBlock64   bool IsZeroMethod() const { return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; }
ThereAreDataInBlockNArchive::NDmg::CBlock65   bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; }
66 };
67 
68 static const UInt32 kCheckSumType_CRC = 2;
69 
70 static const size_t kChecksumSize_Max = 0x80;
71 
72 struct CChecksum
73 {
74   UInt32 Type;
75   UInt32 NumBits;
76   Byte Data[kChecksumSize_Max];
77 
IsCrc32NArchive::NDmg::CChecksum78   bool IsCrc32() const { return Type == kCheckSumType_CRC && NumBits == 32; }
GetCrc32NArchive::NDmg::CChecksum79   UInt32 GetCrc32() const { return Get32(Data); }
80   void Parse(const Byte *p);
81 };
82 
Parse(const Byte * p)83 void CChecksum::Parse(const Byte *p)
84 {
85   Type = Get32(p);
86   NumBits = Get32(p + 4);
87   memcpy(Data, p + 8, kChecksumSize_Max);
88 };
89 
90 struct CFile
91 {
92   UInt64 Size;
93   UInt64 PackSize;
94   UInt64 StartPos;
95   AString Name;
96   CRecordVector<CBlock> Blocks;
97   CChecksum Checksum;
98   bool FullFileChecksum;
99 
100   HRESULT Parse(const Byte *p, UInt32 size);
101 };
102 
103 #ifdef DMG_SHOW_RAW
104 struct CExtraFile
105 {
106   CByteBuffer Data;
107   AString Name;
108 };
109 #endif
110 
111 
112 struct CForkPair
113 {
114   UInt64 Offset;
115   UInt64 Len;
116 
ParseNArchive::NDmg::CForkPair117   void Parse(const Byte *p)
118   {
119     Offset = Get64(p);
120     Len = Get64(p + 8);
121   }
122 
UpdateTopNArchive::NDmg::CForkPair123   bool UpdateTop(UInt64 limit, UInt64 &top)
124   {
125     if (Offset > limit || Len > limit - Offset)
126       return false;
127     UInt64 top2 = Offset + Len;
128     if (top <= top2)
129       top = top2;
130     return true;
131   }
132 };
133 
134 
135 class CHandler:
136   public IInArchive,
137   public IInArchiveGetStream,
138   public CMyUnknownImp
139 {
140   CMyComPtr<IInStream> _inStream;
141   CObjectVector<CFile> _files;
142   bool _masterCrcError;
143   bool _headersError;
144 
145   UInt32 _dataStartOffset;
146   UInt64 _startPos;
147   UInt64 _phySize;
148 
149   AString _name;
150 
151   #ifdef DMG_SHOW_RAW
152   CObjectVector<CExtraFile> _extras;
153   #endif
154 
155   HRESULT ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf);
156   bool ParseBlob(const CByteBuffer &data);
157   HRESULT Open2(IInStream *stream);
158   HRESULT Extract(IInStream *stream);
159 public:
160   MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
161   INTERFACE_IInArchive(;)
162   STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
163 };
164 
165 // that limit can be increased, if there are such dmg files
166 static const size_t kXmlSizeMax = 0xFFFF0000; // 4 GB - 64 KB;
167 
168 struct CMethods
169 {
170   CRecordVector<UInt32> Types;
171   CRecordVector<UInt32> ChecksumTypes;
172 
173   void Update(const CFile &file);
174   void GetString(AString &s) const;
175 };
176 
Update(const CFile & file)177 void CMethods::Update(const CFile &file)
178 {
179   ChecksumTypes.AddToUniqueSorted(file.Checksum.Type);
180   FOR_VECTOR (i, file.Blocks)
181     Types.AddToUniqueSorted(file.Blocks[i].Type);
182 }
183 
GetString(AString & res) const184 void CMethods::GetString(AString &res) const
185 {
186   res.Empty();
187 
188   unsigned i;
189 
190   for (i = 0; i < Types.Size(); i++)
191   {
192     const UInt32 type = Types[i];
193     if (type == METHOD_COMMENT || type == METHOD_END)
194       continue;
195     char buf[16];
196     const char *s;
197     switch (type)
198     {
199       case METHOD_ZERO_0: s = "Zero0"; break;
200       case METHOD_ZERO_2: s = "Zero2"; break;
201       case METHOD_COPY:   s = "Copy";  break;
202       case METHOD_ADC:    s = "ADC";   break;
203       case METHOD_ZLIB:   s = "ZLIB";  break;
204       case METHOD_BZIP2:  s = "BZip2"; break;
205       case METHOD_LZFSE:  s = "LZFSE"; break;
206       default: ConvertUInt32ToString(type, buf); s = buf;
207     }
208     res.Add_OptSpaced(s);
209   }
210 
211   for (i = 0; i < ChecksumTypes.Size(); i++)
212   {
213     res.Add_Space_if_NotEmpty();
214     UInt32 type = ChecksumTypes[i];
215     switch (type)
216     {
217       case kCheckSumType_CRC: res += "CRC"; break;
218       default:
219         res += "Check";
220         res.Add_UInt32(type);
221     }
222   }
223 }
224 
225 struct CAppleName
226 {
227   bool IsFs;
228   const char *Ext;
229   const char *AppleName;
230 };
231 
232 static const CAppleName k_Names[] =
233 {
234   { true,  "hfs",  "Apple_HFS" },
235   { true,  "hfsx", "Apple_HFSX" },
236   { true,  "ufs",  "Apple_UFS" },
237   { true,  "apfs", "Apple_APFS" },
238 
239   // efi_sys partition is FAT32, but it's not main file. So we use (IsFs = false)
240   { false,  "efi_sys", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" },
241 
242   { false, "free", "Apple_Free" },
243   { false, "ddm",  "DDM" },
244   { false, NULL,   "Apple_partition_map" },
245   { false, NULL,   " GPT " },
246   { false, NULL,   "MBR" },
247   { false, NULL,   "Driver" },
248   { false, NULL,   "Patches" }
249 };
250 
251 static const unsigned kNumAppleNames = ARRAY_SIZE(k_Names);
252 
253 static const Byte kProps[] =
254 {
255   kpidPath,
256   kpidSize,
257   kpidPackSize,
258   kpidCRC,
259   kpidComment,
260   kpidMethod
261   // kpidOffset
262 };
263 
264 IMP_IInArchive_Props
265 
266 static const Byte kArcProps[] =
267 {
268   kpidMethod,
269   kpidNumBlocks,
270   kpidComment
271 };
272 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)273 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
274 {
275   COM_TRY_BEGIN
276   NWindows::NCOM::CPropVariant prop;
277   switch (propID)
278   {
279     case kpidMethod:
280     {
281       CMethods m;
282       FOR_VECTOR (i, _files)
283         m.Update(_files[i]);
284       AString s;
285       m.GetString(s);
286       if (!s.IsEmpty())
287         prop = s;
288       break;
289     }
290     case kpidNumBlocks:
291     {
292       UInt64 numBlocks = 0;
293       FOR_VECTOR (i, _files)
294         numBlocks += _files[i].Blocks.Size();
295       prop = numBlocks;
296       break;
297     }
298     case kpidMainSubfile:
299     {
300       int mainIndex = -1;
301       unsigned numFS = 0;
302       unsigned numUnknown = 0;
303       FOR_VECTOR (i, _files)
304       {
305         const AString &name = _files[i].Name;
306         unsigned n;
307         for (n = 0; n < kNumAppleNames; n++)
308         {
309           const CAppleName &appleName = k_Names[n];
310           // if (name.Find(appleName.AppleName) >= 0)
311           if (strstr(name, appleName.AppleName))
312           {
313             if (appleName.IsFs)
314             {
315               numFS++;
316               mainIndex = i;
317             }
318             break;
319           }
320         }
321         if (n == kNumAppleNames)
322         {
323           mainIndex = i;
324           numUnknown++;
325         }
326       }
327       if (numFS + numUnknown == 1)
328         prop = (UInt32)mainIndex;
329       break;
330     }
331     case kpidWarning:
332       if (_masterCrcError)
333         prop = "Master CRC error";
334       break;
335 
336     case kpidWarningFlags:
337     {
338       UInt32 v = 0;
339       if (_headersError) v |= kpv_ErrorFlags_HeadersError;
340       if (v != 0)
341         prop = v;
342       break;
343     }
344 
345     case kpidOffset: prop = _startPos; break;
346     case kpidPhySize: prop = _phySize; break;
347 
348     case kpidComment:
349       if (!_name.IsEmpty() && _name.Len() < 256)
350         prop = _name;
351       break;
352 
353     case kpidName:
354       if (!_name.IsEmpty() && _name.Len() < 256)
355       {
356         prop = _name + ".dmg";
357       }
358       break;
359   }
360   prop.Detach(value);
361   return S_OK;
362   COM_TRY_END
363 }
364 
365 IMP_IInArchive_ArcProps
366 
Parse(const Byte * p,UInt32 size)367 HRESULT CFile::Parse(const Byte *p, UInt32 size)
368 {
369   const UInt32 kHeadSize = 0xCC;
370   if (size < kHeadSize)
371     return S_FALSE;
372   if (Get32(p) != 0x6D697368) // "mish" signature
373     return S_FALSE;
374   if (Get32(p + 4) != 1) // version
375     return S_FALSE;
376   // UInt64 firstSectorNumber = Get64(p + 8);
377   UInt64 numSectors = Get64(p + 0x10);
378 
379   StartPos = Get64(p + 0x18);
380 
381   // UInt32 decompressedBufRequested = Get32(p + 0x20); // ???
382   // UInt32 blocksDescriptor = Get32(p + 0x24); // number starting from -1?
383   // char Reserved1[24];
384 
385   Checksum.Parse(p + 0x40);
386   PRF(printf("\n\nChecksum Type = %2d", Checksum.Type));
387 
388   UInt32 numBlocks = Get32(p + 0xC8);
389   if (numBlocks > ((UInt32)1 << 28))
390     return S_FALSE;
391 
392   const UInt32 kRecordSize = 40;
393   if (numBlocks * kRecordSize + kHeadSize != size)
394     return S_FALSE;
395 
396   PackSize = 0;
397   Size = 0;
398   Blocks.ClearAndReserve(numBlocks);
399   FullFileChecksum = true;
400 
401   p += kHeadSize;
402   UInt32 i;
403 
404   for (i = 0; i < numBlocks; i++, p += kRecordSize)
405   {
406     CBlock b;
407     b.Type = Get32(p);
408     b.UnpPos   = Get64(p + 0x08) << 9;
409     b.UnpSize  = Get64(p + 0x10) << 9;
410     b.PackPos  = Get64(p + 0x18);
411     b.PackSize = Get64(p + 0x20);
412 
413     // b.PackPos can be 0 for some types. So we don't check it
414     if (!Blocks.IsEmpty())
415       if (b.UnpPos != Blocks.Back().GetNextUnpPos())
416         return S_FALSE;
417 
418     PRF(printf("\nType=%8x  m[1]=%8x  uPos=%8x  uSize=%7x  pPos=%8x  pSize=%7x",
419         b.Type, Get32(p + 4), (UInt32)b.UnpPos, (UInt32)b.UnpSize, (UInt32)b.PackPos, (UInt32)b.PackSize));
420 
421     if (b.Type == METHOD_COMMENT)
422       continue;
423     if (b.Type == METHOD_END)
424       break;
425     PackSize += b.PackSize;
426 
427     if (b.UnpSize != 0)
428     {
429       if (b.Type == METHOD_ZERO_2)
430         FullFileChecksum = false;
431       Blocks.AddInReserved(b);
432     }
433   }
434 
435   if (i != numBlocks - 1)
436     return S_FALSE;
437   if (!Blocks.IsEmpty())
438     Size = Blocks.Back().GetNextUnpPos();
439   if (Size != (numSectors << 9))
440     return S_FALSE;
441 
442   return S_OK;
443 }
444 
FindKeyPair(const CXmlItem & item,const char * key,const char * nextTag)445 static int FindKeyPair(const CXmlItem &item, const char *key, const char *nextTag)
446 {
447   for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++)
448   {
449     const CXmlItem &si = item.SubItems[i];
450     if (si.IsTagged("key") && si.GetSubString() == key && item.SubItems[i + 1].IsTagged(nextTag))
451       return i + 1;
452   }
453   return -1;
454 }
455 
GetStringFromKeyPair(const CXmlItem & item,const char * key,const char * nextTag)456 static const AString *GetStringFromKeyPair(const CXmlItem &item, const char *key, const char *nextTag)
457 {
458   int index = FindKeyPair(item, key, nextTag);
459   if (index >= 0)
460     return item.SubItems[index].GetSubStringPtr();
461   return NULL;
462 }
463 
464 static const unsigned HEADER_SIZE = 0x200;
465 
466 static const Byte k_Signature[] = { 'k','o','l','y', 0, 0, 0, 4, 0, 0, 2, 0 };
467 
IsKoly(const Byte * p)468 static inline bool IsKoly(const Byte *p)
469 {
470   return memcmp(p, k_Signature, ARRAY_SIZE(k_Signature)) == 0;
471   /*
472   if (Get32(p) != 0x6B6F6C79) // "koly" signature
473     return false;
474   if (Get32(p + 4) != 4) // version
475     return false;
476   if (Get32(p + 8) != HEADER_SIZE)
477     return false;
478   return true;
479   */
480 }
481 
482 
ReadData(IInStream * stream,const CForkPair & pair,CByteBuffer & buf)483 HRESULT CHandler::ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf)
484 {
485   size_t size = (size_t)pair.Len;
486   if (size != pair.Len)
487     return E_OUTOFMEMORY;
488   buf.Alloc(size);
489   RINOK(stream->Seek(_startPos + pair.Offset, STREAM_SEEK_SET, NULL));
490   return ReadStream_FALSE(stream, buf, size);
491 }
492 
493 
ParseBlob(const CByteBuffer & data)494 bool CHandler::ParseBlob(const CByteBuffer &data)
495 {
496   if (data.Size() < 12)
497     return false;
498   const Byte *p = (const Byte *)data;
499   if (Get32(p) != 0xFADE0CC0)
500     return true;
501   const UInt32 size = Get32(p + 4);
502   if (size != data.Size())
503     return false;
504   const UInt32 num = Get32(p + 8);
505   if (num > (size - 12) / 8)
506     return false;
507 
508   for (UInt32 i = 0; i < num; i++)
509   {
510     // UInt32 type = Get32(p + i * 8 + 12);
511     UInt32 offset = Get32(p + i * 8 + 12 + 4);
512     if (size - offset < 8)
513       return false;
514     const Byte *p2 = (const Byte *)data + offset;
515     const UInt32 magic = Get32(p2);
516     const UInt32 len = Get32(p2 + 4);
517     if (size - offset < len || len < 8)
518       return false;
519 
520     #ifdef DMG_SHOW_RAW
521     CExtraFile &extra = _extras.AddNew();
522     extra.Name = "_blob_";
523     extra.Data.CopyFrom(p2, len);
524     #endif
525 
526     if (magic == 0xFADE0C02)
527     {
528       #ifdef DMG_SHOW_RAW
529       extra.Name += "codedir";
530       #endif
531 
532       if (len < 11 * 4)
533         return false;
534       UInt32 idOffset = Get32(p2 + 0x14);
535       if (idOffset >= len)
536         return false;
537       UInt32 len2 = len - idOffset;
538       if (len2 < (1 << 10))
539         _name.SetFrom_CalcLen((const char *)(p2 + idOffset), len2);
540     }
541     #ifdef DMG_SHOW_RAW
542     else if (magic == 0xFADE0C01)
543       extra.Name += "requirements";
544     else if (magic == 0xFADE0B01)
545       extra.Name += "signed";
546     else
547     {
548       char temp[16];
549       ConvertUInt32ToHex8Digits(magic, temp);
550       extra.Name += temp;
551     }
552     #endif
553   }
554 
555   return true;
556 }
557 
558 
Open2(IInStream * stream)559 HRESULT CHandler::Open2(IInStream *stream)
560 {
561   /*
562   - usual dmg contains Koly Header at the end:
563   - rare case dmg contains Koly Header at the start.
564   */
565 
566   _dataStartOffset = 0;
567   RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_startPos));
568 
569   UInt64 fileSize = 0;
570   RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize));
571   RINOK(stream->Seek(_startPos, STREAM_SEEK_SET, NULL));
572 
573   Byte buf[HEADER_SIZE];
574   RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE));
575 
576   UInt64 headerPos;
577   bool startKolyMode = false;
578 
579   if (IsKoly(buf))
580   {
581     // it can be normal koly-at-the-end or koly-at-the-start
582     headerPos = _startPos;
583     if (_startPos <= (1 << 8))
584     {
585       // we want to support startKolyMode, even if there is
586       // some data before dmg file, like 128 bytes MacBin header
587       _dataStartOffset = HEADER_SIZE;
588       startKolyMode = true;
589     }
590   }
591   else
592   {
593     // we check only koly-at-the-end
594     headerPos = fileSize;
595     if (headerPos < HEADER_SIZE)
596       return S_FALSE;
597     headerPos -= HEADER_SIZE;
598     RINOK(stream->Seek(headerPos, STREAM_SEEK_SET, NULL));
599     RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE));
600     if (!IsKoly(buf))
601       return S_FALSE;
602   }
603 
604   // UInt32 flags = Get32(buf + 12);
605   // UInt64 runningDataForkOffset = Get64(buf + 0x10);
606 
607   CForkPair dataForkPair, rsrcPair, xmlPair, blobPair;
608 
609   dataForkPair.Parse(buf + 0x18);
610   rsrcPair.Parse(buf + 0x28);
611   xmlPair.Parse(buf + 0xD8);
612   blobPair.Parse(buf + 0x128);
613 
614   // UInt32 segmentNumber = Get32(buf + 0x38);
615   // UInt32 segmentCount = Get32(buf + 0x3C);
616   // Byte segmentGUID[16];
617   // CChecksum dataForkChecksum;
618   // dataForkChecksum.Parse(buf + 0x50);
619 
620   UInt64 top = 0;
621   UInt64 limit = startKolyMode ? fileSize : headerPos;
622 
623   if (!dataForkPair.UpdateTop(limit, top)) return S_FALSE;
624   if (!xmlPair.UpdateTop(limit, top)) return S_FALSE;
625   if (!rsrcPair.UpdateTop(limit, top)) return S_FALSE;
626 
627   /* Some old dmg files contain garbage data in blobPair field.
628      So we need to ignore such garbage case;
629      And we still need to detect offset of start of archive for "parser" mode. */
630 
631   bool useBlob = blobPair.UpdateTop(limit, top);
632 
633 
634   if (startKolyMode)
635     _phySize = top;
636   else
637   {
638     _phySize = headerPos + HEADER_SIZE;
639     _startPos = 0;
640     if (top != headerPos)
641     {
642       /*
643       if expected absolute offset is not equal to real header offset,
644       2 cases are possible:
645         - additional (unknown) headers
646         - archive with offset.
647        So we try to read XML with absolute offset to select from these two ways.
648       */
649     CForkPair xmlPair2 = xmlPair;
650     const char *sz = "<?xml version";
651     const unsigned len = (unsigned)strlen(sz);
652     if (xmlPair2.Len > len)
653       xmlPair2.Len = len;
654     CByteBuffer buf2;
655     if (xmlPair2.Len < len
656         || ReadData(stream, xmlPair2, buf2) != S_OK
657         || memcmp(buf2, sz, len) != 0)
658     {
659       // if absolute offset is not OK, probably it's archive with offset
660       _startPos = headerPos - top;
661       _phySize = top + HEADER_SIZE;
662     }
663     }
664   }
665 
666   // Byte reserved[0x78]
667 
668   if (useBlob && blobPair.Len != 0)
669   {
670     #ifdef DMG_SHOW_RAW
671     CExtraFile &extra = _extras.AddNew();
672     extra.Name = "_blob.bin";
673     CByteBuffer &blobBuf = extra.Data;
674     #else
675     CByteBuffer blobBuf;
676     #endif
677     RINOK(ReadData(stream, blobPair, blobBuf));
678     if (!ParseBlob(blobBuf))
679       _headersError = true;
680   }
681 
682 
683   CChecksum masterChecksum;
684   masterChecksum.Parse(buf + 0x160);
685 
686   // UInt32 imageVariant = Get32(buf + 0x1E8);
687   // UInt64 numSectors = Get64(buf + 0x1EC);
688   // Byte reserved[0x12]
689 
690   const UInt32 RSRC_HEAD_SIZE = 0x100;
691 
692   // We don't know the size of the field "offset" in rsrc.
693   // We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcLen < (1 << 24).
694   bool useRsrc = (rsrcPair.Len > RSRC_HEAD_SIZE && rsrcPair.Len < ((UInt32)1 << 24));
695   // useRsrc = false;
696 
697   if (useRsrc)
698   {
699     #ifdef DMG_SHOW_RAW
700     CExtraFile &extra = _extras.AddNew();
701     extra.Name = "rsrc.bin";
702     CByteBuffer &rsrcBuf = extra.Data;
703     #else
704     CByteBuffer rsrcBuf;
705     #endif
706 
707     RINOK(ReadData(stream, rsrcPair, rsrcBuf));
708 
709     const Byte *p = rsrcBuf;
710     UInt32 headSize = Get32(p + 0);
711     UInt32 footerOffset = Get32(p + 4);
712     UInt32 mainDataSize = Get32(p + 8);
713     UInt32 footerSize = Get32(p + 12);
714     if (headSize != RSRC_HEAD_SIZE
715         || footerOffset >= rsrcPair.Len
716         || mainDataSize >= rsrcPair.Len
717         || footerOffset < mainDataSize
718         || footerOffset != headSize + mainDataSize)
719       return S_FALSE;
720 
721     const UInt32 footerEnd = footerOffset + footerSize;
722     if (footerEnd != rsrcPair.Len)
723     {
724       // there is rare case dmg example, where there are 4 additional bytes
725       UInt64 rem = rsrcPair.Len - footerOffset;
726       if (rem < footerSize
727           || rem - footerSize != 4
728           || Get32(p + footerEnd) != 0)
729         return S_FALSE;
730     }
731 
732     if (footerSize < 16)
733       return S_FALSE;
734     if (memcmp(p, p + footerOffset, 16) != 0)
735       return S_FALSE;
736 
737     p += footerOffset;
738 
739     if ((UInt32)Get16(p + 0x18) != 0x1C)
740       return S_FALSE;
741     const UInt32 namesOffset = Get16(p + 0x1A);
742     if (namesOffset > footerSize)
743       return S_FALSE;
744 
745     UInt32 numItems = (UInt32)Get16(p + 0x1C) + 1;
746     if (numItems * 8 + 0x1E > namesOffset)
747       return S_FALSE;
748 
749     for (UInt32 i = 0; i < numItems; i++)
750     {
751       const Byte *p2 = p + 0x1E + i * 8;
752 
753       const UInt32 typeId = Get32(p2);
754 
755       #ifndef DMG_SHOW_RAW
756       if (typeId != 0x626C6B78) // blkx
757         continue;
758       #endif
759 
760       const UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1;
761       const UInt32 offs = Get16(p2 + 6);
762       if (0x1C + offs + 12 * numFiles > namesOffset)
763         return S_FALSE;
764 
765       for (UInt32 k = 0; k < numFiles; k++)
766       {
767         const Byte *p3 = p + 0x1C + offs + k * 12;
768         // UInt32 id = Get16(p3);
769         const UInt32 namePos = Get16(p3 + 2);
770         // Byte attributes = p3[4]; // = 0x50 for blkx
771         // we don't know how many bits we can use. So we use 24 bits only
772         UInt32 blockOffset = Get32(p3 + 4);
773         blockOffset &= (((UInt32)1 << 24) - 1);
774         // UInt32 unknown2 = Get32(p3 + 8); // ???
775         if (blockOffset + 4 >= mainDataSize)
776           return S_FALSE;
777         const Byte *pBlock = rsrcBuf + headSize + blockOffset;
778         const UInt32 blockSize = Get32(pBlock);
779         if (mainDataSize - (blockOffset + 4) < blockSize)
780           return S_FALSE;
781 
782         AString name;
783 
784         if (namePos != 0xFFFF)
785         {
786           UInt32 namesBlockSize = footerSize - namesOffset;
787           if (namePos >= namesBlockSize)
788             return S_FALSE;
789           const Byte *namePtr = p + namesOffset + namePos;
790           UInt32 nameLen = *namePtr;
791           if (namesBlockSize - namePos <= nameLen)
792             return S_FALSE;
793           for (UInt32 r = 1; r <= nameLen; r++)
794           {
795             Byte c = namePtr[r];
796             if (c < 0x20 || c >= 0x80)
797               break;
798             name += (char)c;
799           }
800         }
801 
802         if (typeId == 0x626C6B78) // blkx
803         {
804           CFile &file = _files.AddNew();
805           file.Name = name;
806           RINOK(file.Parse(pBlock + 4, blockSize));
807         }
808 
809         #ifdef DMG_SHOW_RAW
810         {
811           AString name2;
812 
813           name2.Add_UInt32(i);
814           name2 += '_';
815 
816           {
817             char temp[4 + 1] = { 0 };
818             memcpy(temp, p2, 4);
819             name2 += temp;
820           }
821           name2.Trim();
822           name2 += '_';
823           name2.Add_UInt32(k);
824 
825           if (!name.IsEmpty())
826           {
827             name2 += '_';
828             name2 += name;
829           }
830 
831           CExtraFile &extra = _extras.AddNew();
832           extra.Name = name2;
833           extra.Data.CopyFrom(pBlock + 4, blockSize);
834         }
835         #endif
836       }
837     }
838   }
839   else
840   {
841     if (xmlPair.Len >= kXmlSizeMax || xmlPair.Len == 0)
842       return S_FALSE;
843     size_t size = (size_t)xmlPair.Len;
844     if (size != xmlPair.Len)
845       return S_FALSE;
846 
847     RINOK(stream->Seek(_startPos + xmlPair.Offset, STREAM_SEEK_SET, NULL));
848 
849     CXml xml;
850     {
851       CObjArray<char> xmlStr(size + 1);
852       RINOK(ReadStream_FALSE(stream, xmlStr, size));
853       xmlStr[size] = 0;
854       // if (strlen(xmlStr) != size) return S_FALSE;
855       if (!xml.Parse(xmlStr))
856         return S_FALSE;
857 
858       #ifdef DMG_SHOW_RAW
859       CExtraFile &extra = _extras.AddNew();
860       extra.Name = "a.xml";
861       extra.Data.CopyFrom((const Byte *)(const char *)xmlStr, size);
862       #endif
863     }
864 
865     if (xml.Root.Name != "plist")
866       return S_FALSE;
867 
868     int dictIndex = xml.Root.FindSubTag("dict");
869     if (dictIndex < 0)
870       return S_FALSE;
871 
872     const CXmlItem &dictItem = xml.Root.SubItems[dictIndex];
873     int rfDictIndex = FindKeyPair(dictItem, "resource-fork", "dict");
874     if (rfDictIndex < 0)
875       return S_FALSE;
876 
877     const CXmlItem &rfDictItem = dictItem.SubItems[rfDictIndex];
878     int arrIndex = FindKeyPair(rfDictItem, "blkx", "array");
879     if (arrIndex < 0)
880       return S_FALSE;
881 
882     const CXmlItem &arrItem = rfDictItem.SubItems[arrIndex];
883 
884     FOR_VECTOR (i, arrItem.SubItems)
885     {
886       const CXmlItem &item = arrItem.SubItems[i];
887       if (!item.IsTagged("dict"))
888         continue;
889 
890       CByteBuffer rawBuf;
891       unsigned destLen = 0;
892       {
893         const AString *dataString = GetStringFromKeyPair(item, "Data", "data");
894         if (!dataString)
895           return S_FALSE;
896         destLen = dataString->Len() / 4 * 3 + 4;
897         rawBuf.Alloc(destLen);
898         {
899           const Byte *endPtr = Base64ToBin(rawBuf, *dataString);
900           if (!endPtr)
901             return S_FALSE;
902           destLen = (unsigned)(endPtr - (const Byte *)rawBuf);
903         }
904 
905         #ifdef DMG_SHOW_RAW
906         CExtraFile &extra = _extras.AddNew();
907         extra.Name.Add_UInt32(_files.Size());
908         extra.Data.CopyFrom(rawBuf, destLen);
909         #endif
910       }
911       CFile &file = _files.AddNew();
912       {
913         const AString *name = GetStringFromKeyPair(item, "Name", "string");
914         if (!name || name->IsEmpty())
915           name = GetStringFromKeyPair(item, "CFName", "string");
916         if (name)
917           file.Name = *name;
918       }
919       RINOK(file.Parse(rawBuf, destLen));
920     }
921   }
922 
923   if (masterChecksum.IsCrc32())
924   {
925     UInt32 crc = CRC_INIT_VAL;
926     unsigned i;
927     for (i = 0; i < _files.Size(); i++)
928     {
929       const CChecksum &cs = _files[i].Checksum;
930       if ((cs.NumBits & 0x7) != 0)
931         break;
932       UInt32 len = cs.NumBits >> 3;
933       if (len > kChecksumSize_Max)
934         break;
935       crc = CrcUpdate(crc, cs.Data, (size_t)len);
936     }
937     if (i == _files.Size())
938       _masterCrcError = (CRC_GET_DIGEST(crc) != masterChecksum.GetCrc32());
939   }
940 
941   return S_OK;
942 }
943 
Open(IInStream * stream,const UInt64 *,IArchiveOpenCallback *)944 STDMETHODIMP CHandler::Open(IInStream *stream,
945     const UInt64 * /* maxCheckStartPosition */,
946     IArchiveOpenCallback * /* openArchiveCallback */)
947 {
948   COM_TRY_BEGIN
949   {
950     Close();
951     if (Open2(stream) != S_OK)
952       return S_FALSE;
953     _inStream = stream;
954   }
955   return S_OK;
956   COM_TRY_END
957 }
958 
Close()959 STDMETHODIMP CHandler::Close()
960 {
961   _phySize = 0;
962   _inStream.Release();
963   _files.Clear();
964   _masterCrcError = false;
965   _headersError = false;
966   _name.Empty();
967   #ifdef DMG_SHOW_RAW
968   _extras.Clear();
969   #endif
970   return S_OK;
971 }
972 
GetNumberOfItems(UInt32 * numItems)973 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
974 {
975   *numItems = _files.Size()
976     #ifdef DMG_SHOW_RAW
977     + _extras.Size()
978     #endif
979   ;
980   return S_OK;
981 }
982 
983 #ifdef DMG_SHOW_RAW
984 #define RAW_PREFIX "raw" STRING_PATH_SEPARATOR
985 #endif
986 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)987 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
988 {
989   COM_TRY_BEGIN
990   NWindows::NCOM::CPropVariant prop;
991 
992   #ifdef DMG_SHOW_RAW
993   if (index >= _files.Size())
994   {
995     const CExtraFile &extra = _extras[index - _files.Size()];
996     switch (propID)
997     {
998       case kpidPath:
999         prop = (AString)RAW_PREFIX + extra.Name;
1000         break;
1001       case kpidSize:
1002       case kpidPackSize:
1003         prop = (UInt64)extra.Data.Size();
1004         break;
1005     }
1006   }
1007   else
1008   #endif
1009   {
1010     const CFile &item = _files[index];
1011     switch (propID)
1012     {
1013       case kpidSize:  prop = item.Size; break;
1014       case kpidPackSize:  prop = item.PackSize; break;
1015       case kpidCRC:
1016       {
1017         if (item.Checksum.IsCrc32() && item.FullFileChecksum)
1018           prop = item.Checksum.GetCrc32();
1019         break;
1020       }
1021 
1022       /*
1023       case kpidOffset:
1024       {
1025         prop = item.StartPos;
1026         break;
1027       }
1028       */
1029 
1030       case kpidMethod:
1031       {
1032         CMethods m;
1033         m.Update(item);
1034         AString s;
1035         m.GetString(s);
1036         if (!s.IsEmpty())
1037           prop = s;
1038         break;
1039       }
1040 
1041       case kpidPath:
1042       {
1043         UString name;
1044         name.Add_UInt32(index);
1045         unsigned num = 10;
1046         unsigned numDigits;
1047         for (numDigits = 1; num < _files.Size(); numDigits++)
1048           num *= 10;
1049         while (name.Len() < numDigits)
1050           name.InsertAtFront(L'0');
1051 
1052         AString subName;
1053         int pos1 = item.Name.Find('(');
1054         if (pos1 >= 0)
1055         {
1056           pos1++;
1057           int pos2 = item.Name.Find(')', pos1);
1058           if (pos2 >= 0)
1059           {
1060             subName.SetFrom(item.Name.Ptr(pos1), pos2 - pos1);
1061             pos1 = subName.Find(':');
1062             if (pos1 >= 0)
1063               subName.DeleteFrom(pos1);
1064           }
1065         }
1066         subName.Trim();
1067         if (!subName.IsEmpty())
1068         {
1069           for (unsigned n = 0; n < kNumAppleNames; n++)
1070           {
1071             const CAppleName &appleName = k_Names[n];
1072             if (appleName.Ext)
1073             {
1074               if (subName == appleName.AppleName)
1075               {
1076                 subName = appleName.Ext;
1077                 break;
1078               }
1079             }
1080           }
1081           UString name2;
1082           ConvertUTF8ToUnicode(subName, name2);
1083           name += '.';
1084           name += name2;
1085         }
1086         else
1087         {
1088           UString name2;
1089           ConvertUTF8ToUnicode(item.Name, name2);
1090           if (!name2.IsEmpty())
1091             name += "_";
1092           name += name2;
1093         }
1094         prop = name;
1095         break;
1096       }
1097 
1098       case kpidComment:
1099       {
1100         UString name;
1101         ConvertUTF8ToUnicode(item.Name, name);
1102         prop = name;
1103         break;
1104       }
1105     }
1106   }
1107   prop.Detach(value);
1108   return S_OK;
1109   COM_TRY_END
1110 }
1111 
1112 class CAdcDecoder:
1113   public ICompressCoder,
1114   public CMyUnknownImp
1115 {
1116   CLzOutWindow m_OutWindowStream;
1117   CInBuffer m_InStream;
1118 
1119   /*
1120   void ReleaseStreams()
1121   {
1122     m_OutWindowStream.ReleaseStream();
1123     m_InStream.ReleaseStream();
1124   }
1125   */
1126 
1127   class CCoderReleaser
1128   {
1129     CAdcDecoder *m_Coder;
1130   public:
1131     bool NeedFlush;
CCoderReleaser(CAdcDecoder * coder)1132     CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {}
~CCoderReleaser()1133     ~CCoderReleaser()
1134     {
1135       if (NeedFlush)
1136         m_Coder->m_OutWindowStream.Flush();
1137       // m_Coder->ReleaseStreams();
1138     }
1139   };
1140   friend class CCoderReleaser;
1141 
1142 public:
1143   MY_UNKNOWN_IMP
1144 
1145   STDMETHOD(CodeReal)(ISequentialInStream *inStream,
1146       ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
1147       ICompressProgressInfo *progress);
1148 
1149   STDMETHOD(Code)(ISequentialInStream *inStream,
1150       ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
1151       ICompressProgressInfo *progress);
1152 };
1153 
CodeReal(ISequentialInStream * inStream,ISequentialOutStream * outStream,const UInt64 * inSize,const UInt64 * outSize,ICompressProgressInfo * progress)1154 STDMETHODIMP CAdcDecoder::CodeReal(ISequentialInStream *inStream,
1155     ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
1156     ICompressProgressInfo *progress)
1157 {
1158   if (!m_OutWindowStream.Create(1 << 18))
1159     return E_OUTOFMEMORY;
1160   if (!m_InStream.Create(1 << 18))
1161     return E_OUTOFMEMORY;
1162 
1163   m_OutWindowStream.SetStream(outStream);
1164   m_OutWindowStream.Init(false);
1165   m_InStream.SetStream(inStream);
1166   m_InStream.Init();
1167 
1168   CCoderReleaser coderReleaser(this);
1169 
1170   const UInt32 kStep = (1 << 20);
1171   UInt64 nextLimit = kStep;
1172 
1173   UInt64 pos = 0;
1174   while (pos < *outSize)
1175   {
1176     if (pos > nextLimit && progress)
1177     {
1178       UInt64 packSize = m_InStream.GetProcessedSize();
1179       RINOK(progress->SetRatioInfo(&packSize, &pos));
1180       nextLimit += kStep;
1181     }
1182     Byte b;
1183     if (!m_InStream.ReadByte(b))
1184       return S_FALSE;
1185     UInt64 rem = *outSize - pos;
1186     if (b & 0x80)
1187     {
1188       unsigned num = (b & 0x7F) + 1;
1189       if (num > rem)
1190         return S_FALSE;
1191       for (unsigned i = 0; i < num; i++)
1192       {
1193         if (!m_InStream.ReadByte(b))
1194           return S_FALSE;
1195         m_OutWindowStream.PutByte(b);
1196       }
1197       pos += num;
1198       continue;
1199     }
1200     Byte b1;
1201     if (!m_InStream.ReadByte(b1))
1202       return S_FALSE;
1203 
1204     UInt32 len, distance;
1205 
1206     if (b & 0x40)
1207     {
1208       len = ((UInt32)b & 0x3F) + 4;
1209       Byte b2;
1210       if (!m_InStream.ReadByte(b2))
1211         return S_FALSE;
1212       distance = ((UInt32)b1 << 8) + b2;
1213     }
1214     else
1215     {
1216       b &= 0x3F;
1217       len = ((UInt32)b >> 2) + 3;
1218       distance = (((UInt32)b & 3) << 8) + b1;
1219     }
1220 
1221     if (distance >= pos || len > rem)
1222       return S_FALSE;
1223     m_OutWindowStream.CopyBlock(distance, len);
1224     pos += len;
1225   }
1226   if (*inSize != m_InStream.GetProcessedSize())
1227     return S_FALSE;
1228   coderReleaser.NeedFlush = false;
1229   return m_OutWindowStream.Flush();
1230 }
1231 
Code(ISequentialInStream * inStream,ISequentialOutStream * outStream,const UInt64 * inSize,const UInt64 * outSize,ICompressProgressInfo * progress)1232 STDMETHODIMP CAdcDecoder::Code(ISequentialInStream *inStream,
1233     ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
1234     ICompressProgressInfo *progress)
1235 {
1236   try { return CodeReal(inStream, outStream, inSize, outSize, progress);}
1237   catch(const CInBufferException &e) { return e.ErrorCode; }
1238   catch(const CLzOutWindowException &e) { return e.ErrorCode; }
1239   catch(...) { return S_FALSE; }
1240 }
1241 
1242 
1243 
1244 
1245 
1246 
1247 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback)1248 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
1249     Int32 testMode, IArchiveExtractCallback *extractCallback)
1250 {
1251   COM_TRY_BEGIN
1252   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
1253   if (allFilesMode)
1254     numItems = _files.Size();
1255   if (numItems == 0)
1256     return S_OK;
1257   UInt64 totalSize = 0;
1258   UInt32 i;
1259 
1260   for (i = 0; i < numItems; i++)
1261   {
1262     UInt32 index = (allFilesMode ? i : indices[i]);
1263     #ifdef DMG_SHOW_RAW
1264     if (index >= _files.Size())
1265       totalSize += _extras[index - _files.Size()].Data.Size();
1266     else
1267     #endif
1268       totalSize += _files[index].Size;
1269   }
1270   extractCallback->SetTotal(totalSize);
1271 
1272   UInt64 currentPackTotal = 0;
1273   UInt64 currentUnpTotal = 0;
1274   UInt64 currentPackSize = 0;
1275   UInt64 currentUnpSize = 0;
1276 
1277   const UInt32 kZeroBufSize = (1 << 14);
1278   CByteBuffer zeroBuf(kZeroBufSize);
1279   memset(zeroBuf, 0, kZeroBufSize);
1280 
1281   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
1282   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
1283 
1284   NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
1285   CMyComPtr<ICompressCoder> bzip2Coder = bzip2CoderSpec;
1286 
1287   NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();
1288   CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;
1289 
1290   CAdcDecoder *adcCoderSpec = new CAdcDecoder();
1291   CMyComPtr<ICompressCoder> adcCoder = adcCoderSpec;
1292 
1293   NCompress::NLzfse::CDecoder *lzfseCoderSpec = new NCompress::NLzfse::CDecoder();
1294   CMyComPtr<ICompressCoder> lzfseCoder = lzfseCoderSpec;
1295 
1296   CLocalProgress *lps = new CLocalProgress;
1297   CMyComPtr<ICompressProgressInfo> progress = lps;
1298   lps->Init(extractCallback, false);
1299 
1300   CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
1301   CMyComPtr<ISequentialInStream> inStream(streamSpec);
1302   streamSpec->SetStream(_inStream);
1303 
1304   for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize)
1305   {
1306     lps->InSize = currentPackTotal;
1307     lps->OutSize = currentUnpTotal;
1308     currentPackSize = 0;
1309     currentUnpSize = 0;
1310     RINOK(lps->SetCur());
1311     CMyComPtr<ISequentialOutStream> realOutStream;
1312     Int32 askMode = testMode ?
1313         NExtract::NAskMode::kTest :
1314         NExtract::NAskMode::kExtract;
1315     UInt32 index = allFilesMode ? i : indices[i];
1316     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
1317 
1318     if (!testMode && !realOutStream)
1319       continue;
1320     RINOK(extractCallback->PrepareOperation(askMode));
1321 
1322 
1323     COutStreamWithCRC *outCrcStreamSpec = new COutStreamWithCRC;
1324     CMyComPtr<ISequentialOutStream> outCrcStream = outCrcStreamSpec;
1325     outCrcStreamSpec->SetStream(realOutStream);
1326     bool needCrc = false;
1327     outCrcStreamSpec->Init(needCrc);
1328 
1329     CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;
1330     CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
1331     outStreamSpec->SetStream(outCrcStream);
1332 
1333     realOutStream.Release();
1334 
1335     Int32 opRes = NExtract::NOperationResult::kOK;
1336     #ifdef DMG_SHOW_RAW
1337     if (index >= _files.Size())
1338     {
1339       const CByteBuffer &buf = _extras[index - _files.Size()].Data;
1340       outStreamSpec->Init(buf.Size());
1341       RINOK(WriteStream(outStream, buf, buf.Size()));
1342       currentPackSize = currentUnpSize = buf.Size();
1343     }
1344     else
1345     #endif
1346     {
1347       const CFile &item = _files[index];
1348       currentPackSize = item.PackSize;
1349       currentUnpSize = item.Size;
1350 
1351       needCrc = item.Checksum.IsCrc32();
1352 
1353       UInt64 unpPos = 0;
1354       UInt64 packPos = 0;
1355       {
1356         FOR_VECTOR (j, item.Blocks)
1357         {
1358           lps->InSize = currentPackTotal + packPos;
1359           lps->OutSize = currentUnpTotal + unpPos;
1360           RINOK(lps->SetCur());
1361 
1362           const CBlock &block = item.Blocks[j];
1363           if (!block.ThereAreDataInBlock())
1364             continue;
1365 
1366           packPos += block.PackSize;
1367           if (block.UnpPos != unpPos)
1368           {
1369             opRes = NExtract::NOperationResult::kDataError;
1370             break;
1371           }
1372 
1373           RINOK(_inStream->Seek(_startPos + _dataStartOffset + item.StartPos + block.PackPos, STREAM_SEEK_SET, NULL));
1374           streamSpec->Init(block.PackSize);
1375           bool realMethod = true;
1376           outStreamSpec->Init(block.UnpSize);
1377           HRESULT res = S_OK;
1378 
1379           outCrcStreamSpec->EnableCalc(needCrc);
1380 
1381           switch (block.Type)
1382           {
1383             case METHOD_ZERO_0:
1384             case METHOD_ZERO_2:
1385               realMethod = false;
1386               if (block.PackSize != 0)
1387                 opRes = NExtract::NOperationResult::kUnsupportedMethod;
1388               outCrcStreamSpec->EnableCalc(block.Type == METHOD_ZERO_0);
1389               break;
1390 
1391             case METHOD_COPY:
1392               if (block.UnpSize != block.PackSize)
1393               {
1394                 opRes = NExtract::NOperationResult::kUnsupportedMethod;
1395                 break;
1396               }
1397               res = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
1398               break;
1399 
1400             case METHOD_ADC:
1401             {
1402               res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, progress);
1403               break;
1404             }
1405 
1406             case METHOD_ZLIB:
1407             {
1408               res = zlibCoder->Code(inStream, outStream, NULL, NULL, progress);
1409               if (res == S_OK)
1410                 if (zlibCoderSpec->GetInputProcessedSize() != block.PackSize)
1411                   opRes = NExtract::NOperationResult::kDataError;
1412               break;
1413             }
1414 
1415             case METHOD_BZIP2:
1416             {
1417               res = bzip2Coder->Code(inStream, outStream, NULL, NULL, progress);
1418               if (res == S_OK)
1419                 if (bzip2CoderSpec->GetInputProcessedSize() != block.PackSize)
1420                   opRes = NExtract::NOperationResult::kDataError;
1421               break;
1422             }
1423 
1424             case METHOD_LZFSE:
1425             {
1426               res = lzfseCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, progress);
1427               break;
1428             }
1429 
1430             default:
1431               opRes = NExtract::NOperationResult::kUnsupportedMethod;
1432               break;
1433           }
1434 
1435           if (res != S_OK)
1436           {
1437             if (res != S_FALSE)
1438               return res;
1439             if (opRes == NExtract::NOperationResult::kOK)
1440               opRes = NExtract::NOperationResult::kDataError;
1441           }
1442 
1443           unpPos += block.UnpSize;
1444 
1445           if (!outStreamSpec->IsFinishedOK())
1446           {
1447             if (realMethod && opRes == NExtract::NOperationResult::kOK)
1448               opRes = NExtract::NOperationResult::kDataError;
1449 
1450             while (outStreamSpec->GetRem() != 0)
1451             {
1452               UInt64 rem = outStreamSpec->GetRem();
1453               UInt32 size = (UInt32)MyMin(rem, (UInt64)kZeroBufSize);
1454               RINOK(WriteStream(outStream, zeroBuf, size));
1455             }
1456           }
1457         }
1458       }
1459 
1460       if (needCrc && opRes == NExtract::NOperationResult::kOK)
1461       {
1462         if (outCrcStreamSpec->GetCRC() != item.Checksum.GetCrc32())
1463           opRes = NExtract::NOperationResult::kCRCError;
1464       }
1465     }
1466     outStream.Release();
1467     RINOK(extractCallback->SetOperationResult(opRes));
1468   }
1469 
1470   return S_OK;
1471   COM_TRY_END
1472 }
1473 
1474 struct CChunk
1475 {
1476   int BlockIndex;
1477   UInt64 AccessMark;
1478   CByteBuffer Buf;
1479 };
1480 
1481 class CInStream:
1482   public IInStream,
1483   public CMyUnknownImp
1484 {
1485   UInt64 _virtPos;
1486   int _latestChunk;
1487   int _latestBlock;
1488   UInt64 _accessMark;
1489   CObjectVector<CChunk> _chunks;
1490 
1491   NCompress::NBZip2::CDecoder *bzip2CoderSpec;
1492   CMyComPtr<ICompressCoder> bzip2Coder;
1493 
1494   NCompress::NZlib::CDecoder *zlibCoderSpec;
1495   CMyComPtr<ICompressCoder> zlibCoder;
1496 
1497   CAdcDecoder *adcCoderSpec;
1498   CMyComPtr<ICompressCoder> adcCoder;
1499 
1500   NCompress::NLzfse::CDecoder *lzfseCoderSpec;
1501   CMyComPtr<ICompressCoder> lzfseCoder;
1502 
1503   CBufPtrSeqOutStream *outStreamSpec;
1504   CMyComPtr<ISequentialOutStream> outStream;
1505 
1506   CLimitedSequentialInStream *limitedStreamSpec;
1507   CMyComPtr<ISequentialInStream> inStream;
1508 
1509 public:
1510   CMyComPtr<IInStream> Stream;
1511   UInt64 Size;
1512   const CFile *File;
1513   UInt64 _startPos;
1514 
InitAndSeek(UInt64 startPos)1515   HRESULT InitAndSeek(UInt64 startPos)
1516   {
1517     _startPos = startPos;
1518     _virtPos = 0;
1519     _latestChunk = -1;
1520     _latestBlock = -1;
1521     _accessMark = 0;
1522 
1523     limitedStreamSpec = new CLimitedSequentialInStream;
1524     inStream = limitedStreamSpec;
1525     limitedStreamSpec->SetStream(Stream);
1526 
1527     outStreamSpec = new CBufPtrSeqOutStream;
1528     outStream = outStreamSpec;
1529     return S_OK;
1530   }
1531 
1532   MY_UNKNOWN_IMP1(IInStream)
1533 
1534   STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
1535   STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
1536 };
1537 
1538 
FindBlock(const CRecordVector<CBlock> & blocks,UInt64 pos)1539 static unsigned FindBlock(const CRecordVector<CBlock> &blocks, UInt64 pos)
1540 {
1541   unsigned left = 0, right = blocks.Size();
1542   for (;;)
1543   {
1544     unsigned mid = (left + right) / 2;
1545     if (mid == left)
1546       return left;
1547     if (pos < blocks[mid].UnpPos)
1548       right = mid;
1549     else
1550       left = mid;
1551   }
1552 }
1553 
Read(void * data,UInt32 size,UInt32 * processedSize)1554 STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
1555 {
1556   COM_TRY_BEGIN
1557 
1558   if (processedSize)
1559     *processedSize = 0;
1560   if (size == 0)
1561     return S_OK;
1562   if (_virtPos >= Size)
1563     return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL;
1564   {
1565     UInt64 rem = Size - _virtPos;
1566     if (size > rem)
1567       size = (UInt32)rem;
1568   }
1569 
1570   if (_latestBlock >= 0)
1571   {
1572     const CBlock &block = File->Blocks[_latestBlock];
1573     if (_virtPos < block.UnpPos || (_virtPos - block.UnpPos) >= block.UnpSize)
1574       _latestBlock = -1;
1575   }
1576 
1577   if (_latestBlock < 0)
1578   {
1579     _latestChunk = -1;
1580     unsigned blockIndex = FindBlock(File->Blocks, _virtPos);
1581     const CBlock &block = File->Blocks[blockIndex];
1582 
1583     if (!block.IsZeroMethod() && block.Type != METHOD_COPY)
1584     {
1585       unsigned i;
1586       for (i = 0; i < _chunks.Size(); i++)
1587         if (_chunks[i].BlockIndex == (int)blockIndex)
1588           break;
1589 
1590       if (i != _chunks.Size())
1591         _latestChunk = i;
1592       else
1593       {
1594         const unsigned kNumChunksMax = 128;
1595         unsigned chunkIndex;
1596 
1597         if (_chunks.Size() != kNumChunksMax)
1598           chunkIndex = _chunks.Add(CChunk());
1599         else
1600         {
1601           chunkIndex = 0;
1602           for (i = 0; i < _chunks.Size(); i++)
1603             if (_chunks[i].AccessMark < _chunks[chunkIndex].AccessMark)
1604               chunkIndex = i;
1605         }
1606 
1607         CChunk &chunk = _chunks[chunkIndex];
1608         chunk.BlockIndex = -1;
1609         chunk.AccessMark = 0;
1610 
1611         if (chunk.Buf.Size() < block.UnpSize)
1612         {
1613           chunk.Buf.Free();
1614           if (block.UnpSize > ((UInt32)1 << 31))
1615             return E_FAIL;
1616           chunk.Buf.Alloc((size_t)block.UnpSize);
1617         }
1618 
1619         outStreamSpec->Init(chunk.Buf, (size_t)block.UnpSize);
1620 
1621         RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos, STREAM_SEEK_SET, NULL));
1622 
1623         limitedStreamSpec->Init(block.PackSize);
1624         HRESULT res = S_OK;
1625 
1626         switch (block.Type)
1627         {
1628           case METHOD_COPY:
1629             if (block.PackSize != block.UnpSize)
1630               return E_FAIL;
1631             res = ReadStream_FAIL(inStream, chunk.Buf, (size_t)block.UnpSize);
1632             break;
1633 
1634           case METHOD_ADC:
1635             if (!adcCoder)
1636             {
1637               adcCoderSpec = new CAdcDecoder();
1638               adcCoder = adcCoderSpec;
1639             }
1640             res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, NULL);
1641             break;
1642 
1643           case METHOD_ZLIB:
1644             if (!zlibCoder)
1645             {
1646               zlibCoderSpec = new NCompress::NZlib::CDecoder();
1647               zlibCoder = zlibCoderSpec;
1648             }
1649             res = zlibCoder->Code(inStream, outStream, NULL, NULL, NULL);
1650             if (res == S_OK && zlibCoderSpec->GetInputProcessedSize() != block.PackSize)
1651               res = S_FALSE;
1652             break;
1653 
1654           case METHOD_BZIP2:
1655             if (!bzip2Coder)
1656             {
1657               bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
1658               bzip2Coder = bzip2CoderSpec;
1659             }
1660             res = bzip2Coder->Code(inStream, outStream, NULL, NULL, NULL);
1661             if (res == S_OK && bzip2CoderSpec->GetInputProcessedSize() != block.PackSize)
1662               res = S_FALSE;
1663             break;
1664 
1665           case METHOD_LZFSE:
1666             if (!lzfseCoder)
1667             {
1668               lzfseCoderSpec = new NCompress::NLzfse::CDecoder();
1669               lzfseCoder = lzfseCoderSpec;
1670             }
1671             res = lzfseCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, NULL);
1672             break;
1673 
1674           default:
1675             return E_FAIL;
1676         }
1677 
1678         if (res != S_OK)
1679           return res;
1680         if (block.Type != METHOD_COPY && outStreamSpec->GetPos() != block.UnpSize)
1681           return E_FAIL;
1682         chunk.BlockIndex = blockIndex;
1683         _latestChunk = chunkIndex;
1684       }
1685 
1686       _chunks[_latestChunk].AccessMark = _accessMark++;
1687     }
1688 
1689     _latestBlock = blockIndex;
1690   }
1691 
1692   const CBlock &block = File->Blocks[_latestBlock];
1693   const UInt64 offset = _virtPos - block.UnpPos;
1694   const UInt64 rem = block.UnpSize - offset;
1695   if (size > rem)
1696     size = (UInt32)rem;
1697 
1698   HRESULT res = S_OK;
1699 
1700   if (block.Type == METHOD_COPY)
1701   {
1702     RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos + offset, STREAM_SEEK_SET, NULL));
1703     res = Stream->Read(data, size, &size);
1704   }
1705   else if (block.IsZeroMethod())
1706     memset(data, 0, size);
1707   else if (size != 0)
1708     memcpy(data, _chunks[_latestChunk].Buf + (size_t)offset, size);
1709 
1710   _virtPos += size;
1711   if (processedSize)
1712     *processedSize = size;
1713 
1714   return res;
1715   COM_TRY_END
1716 }
1717 
Seek(Int64 offset,UInt32 seekOrigin,UInt64 * newPosition)1718 STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
1719 {
1720   switch (seekOrigin)
1721   {
1722     case STREAM_SEEK_SET: break;
1723     case STREAM_SEEK_CUR: offset += _virtPos; break;
1724     case STREAM_SEEK_END: offset += Size; break;
1725     default: return STG_E_INVALIDFUNCTION;
1726   }
1727   if (offset < 0)
1728     return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
1729   _virtPos = offset;
1730   if (newPosition)
1731     *newPosition = offset;
1732   return S_OK;
1733 }
1734 
GetStream(UInt32 index,ISequentialInStream ** stream)1735 STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
1736 {
1737   COM_TRY_BEGIN
1738 
1739   #ifdef DMG_SHOW_RAW
1740   if (index >= (UInt32)_files.Size())
1741     return S_FALSE;
1742   #endif
1743 
1744   CInStream *spec = new CInStream;
1745   CMyComPtr<ISequentialInStream> specStream = spec;
1746   spec->File = &_files[index];
1747   const CFile &file = *spec->File;
1748 
1749   FOR_VECTOR (i, file.Blocks)
1750   {
1751     const CBlock &block = file.Blocks[i];
1752     switch (block.Type)
1753     {
1754       case METHOD_ZERO_0:
1755       case METHOD_ZERO_2:
1756       case METHOD_COPY:
1757       case METHOD_ADC:
1758       case METHOD_ZLIB:
1759       case METHOD_BZIP2:
1760       case METHOD_LZFSE:
1761       case METHOD_END:
1762         break;
1763       default:
1764         return S_FALSE;
1765     }
1766   }
1767 
1768   spec->Stream = _inStream;
1769   spec->Size = spec->File->Size;
1770   RINOK(spec->InitAndSeek(_startPos + _dataStartOffset));
1771   *stream = specStream.Detach();
1772   return S_OK;
1773 
1774   COM_TRY_END
1775 }
1776 
1777 REGISTER_ARC_I(
1778   "Dmg", "dmg", 0, 0xE4,
1779   k_Signature,
1780   0,
1781   NArcInfoFlags::kBackwardOpen |
1782   NArcInfoFlags::kUseGlobalOffset,
1783   NULL)
1784 
1785 }}
1786