// DmgHandler.cpp #include "StdAfx.h" #include "../../../C/Alloc.h" #include "../../../C/CpuArch.h" #include "../../Common/AutoPtr.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../../Common/MyBuffer2.h" #include "../../Common/MyXml.h" #include "../../Common/UTFConvert.h" #include "../../Windows/PropVariant.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/BZip2Decoder.h" #include "../Compress/CopyCoder.h" #include "../Compress/LzfseDecoder.h" #include "../Compress/XzDecoder.h" #include "../Compress/ZlibDecoder.h" #include "Common/OutStreamWithCRC.h" // #define DMG_SHOW_RAW // #define SHOW_DEBUG_INFO /* for debug only: we can use block cache also for METHOD_COPY blocks. It can reduce the number of Stream->Read() requests. But it can increase memory usage. Note: METHOD_COPY blocks are not fused usually. But if METHOD_COPY blocks is fused in some dmg example, block size can exceed k_Chunk_Size_MAX. So we don't use cache for METHOD_COPY block, if (block_size > k_Chunk_Size_MAX) */ // for debug only: // #define Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS #ifdef SHOW_DEBUG_INFO #include #define PRF(x) x #else #define PRF(x) #endif #define Get16(p) GetBe16(p) #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) #define Get32a(p) GetBe32a(p) #define Get64a(p) GetBe64a(p) Byte *Base64ToBin(Byte *dest, const char *src); namespace NArchive { namespace NDmg { // allocation limits for compressed blocks for GetStream() interface: static const unsigned k_NumChunks_MAX = 128; static const size_t k_Chunk_Size_MAX = (size_t)1 << 28; // 256 MB cache for 32-bit: // 4 GB cache for 64-bit: static const size_t k_Chunks_TotalSize_MAX = (size_t)1 << (sizeof(size_t) + 24); // 2 GB limit for 32-bit: // 4 GB limit for 64-bit: // that limit can be increased for 64-bit mode, if there are such dmg files static const size_t k_XmlSize_MAX = ((size_t)1 << (sizeof(size_t) / 4 + 30)) - 256; static const UInt32 METHOD_ZERO_0 = 0; // sparse static const UInt32 METHOD_COPY = 1; static const UInt32 METHOD_ZERO_2 = 2; // sparse : without file CRC calculation static const UInt32 METHOD_ADC = 0x80000004; static const UInt32 METHOD_ZLIB = 0x80000005; static const UInt32 METHOD_BZIP2 = 0x80000006; static const UInt32 METHOD_LZFSE = 0x80000007; static const UInt32 METHOD_XZ = 0x80000008; static const UInt32 METHOD_COMMENT = 0x7FFFFFFE; // is used to comment "+beg" and "+end" in extra field. static const UInt32 METHOD_END = 0xFFFFFFFF; struct CBlock { UInt32 Type; UInt64 UnpPos; // UInt64 UnpSize; UInt64 PackPos; UInt64 PackSize; bool NeedCrc() const { return Type != METHOD_ZERO_2; } bool IsZeroMethod() const { return (Type & ~(UInt32)METHOD_ZERO_2) == 0; // return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; } bool IsClusteredMethod() const { // most of dmg files have non-fused COPY_METHOD blocks. // so we don't exclude COPY_METHOD blocks when we try to detect size of cluster. return !IsZeroMethod(); // include COPY_METHOD blocks. // Type > METHOD_ZERO_2; // for debug: exclude COPY_METHOD blocks. } bool NeedAllocateBuffer() const { return #ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS !IsZeroMethod(); #else Type > METHOD_ZERO_2; // !IsZeroMethod() && Type != METHOD_COPY; #endif } // we don't store non-data blocks now // bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; } }; static const UInt32 kCheckSumType_CRC = 2; static const unsigned kChecksumSize_Max = 0x80; struct CChecksum { UInt32 Type; UInt32 NumBits; Byte Data[kChecksumSize_Max]; bool IsCrc32() const { return Type == kCheckSumType_CRC && NumBits == 32; } UInt32 GetCrc32() const { return Get32(Data); } void Parse(const Byte *p); void PrintType(AString &s) const; void Print(AString &s) const; void Print_with_Name(AString &s) const; void AddToComment(AString &s, const char *name) const; }; void CChecksum::Parse(const Byte *p) { Type = Get32(p); NumBits = Get32(p + 4); memcpy(Data, p + 8, kChecksumSize_Max); } void CChecksum::PrintType(AString &s) const { if (NumBits == 0) return; if (IsCrc32()) s += "CRC"; else { s += "Checksum"; s.Add_UInt32(Type); s.Add_Minus(); s.Add_UInt32(NumBits); } } void CChecksum::Print(AString &s) const { if (NumBits == 0) return; char temp[kChecksumSize_Max * 2 + 2]; /* if (IsCrc32()) ConvertUInt32ToHex8Digits(GetCrc32(), temp); else */ { UInt32 numBits = kChecksumSize_Max * 8; if (numBits > NumBits) numBits = NumBits; const unsigned numBytes = (numBits + 7) >> 3; if (numBytes <= 8) ConvertDataToHex_Upper(temp, Data, numBytes); else ConvertDataToHex_Lower(temp, Data, numBytes); } s += temp; } void CChecksum::Print_with_Name(AString &s) const { if (NumBits == 0) return; PrintType(s); s += ": "; Print(s); } static void AddToComment_Prop(AString &s, const char *name, const char *val) { s += name; s += ": "; s += val; s.Add_LF(); } void CChecksum::AddToComment(AString &s, const char *name) const { AString s2; Print_with_Name(s2); if (!s2.IsEmpty()) AddToComment_Prop(s, name, s2); } struct CFile { UInt64 Size; CRecordVector Blocks; UInt64 PackSize; UInt64 StartPackPos; UInt64 BlockSize_MAX; UInt64 StartUnpackSector; // unpack sector position of this file from all files UInt64 NumUnpackSectors; Int32 Descriptor; bool IsCorrect; bool FullFileChecksum; AString Name; // AString Id; CChecksum Checksum; UInt64 GetUnpackSize_of_Block(unsigned blockIndex) const { return (blockIndex == Blocks.Size() - 1 ? Size : Blocks[blockIndex + 1].UnpPos) - Blocks[blockIndex].UnpPos; } CFile(): Size(0), PackSize(0), StartPackPos(0), BlockSize_MAX(0), StartUnpackSector(0), NumUnpackSectors(0), Descriptor(0), IsCorrect(false), FullFileChecksum(false) { Checksum.Type = 0; Checksum.NumBits = 0; } HRESULT Parse(const Byte *p, UInt32 size); }; #ifdef DMG_SHOW_RAW struct CExtraFile { CByteBuffer Data; AString Name; }; #endif static void AddToComment_UInt64(AString &s, UInt64 v, const char *name) { s += name; s += ": "; s.Add_UInt64(v); s.Add_LF(); } struct CForkPair { UInt64 Offset; UInt64 Len; // (p) is aligned for 8-bytes void Parse(const Byte *p) { Offset = Get64a(p); Len = Get64a(p + 8); } bool GetEndPos(UInt64 &endPos) { endPos = Offset + Len; return endPos >= Offset; } bool UpdateTop(UInt64 limit, UInt64 &top) { if (Offset > limit || Len > limit - Offset) return false; const UInt64 top2 = Offset + Len; if (top <= top2) top = top2; return true; } void Print(AString &s, const char *name) const; }; void CForkPair::Print(AString &s, const char *name) const { if (Offset != 0 || Len != 0) { s += name; s.Add_Minus(); AddToComment_UInt64(s, Offset, "offset"); s += name; s.Add_Minus(); AddToComment_UInt64(s, Len, "length"); } } Z7_CLASS_IMP_CHandler_IInArchive_1( IInArchiveGetStream ) bool _masterCrcError; bool _headersError; bool _dataForkError; bool _rsrcMode_wasUsed; CMyComPtr _inStream; CObjectVector _files; // UInt32 _dataStartOffset; UInt64 _startPos; UInt64 _phySize; AString _name; CForkPair _dataForkPair; CForkPair rsrcPair, xmlPair, blobPair; // UInt64 _runningDataForkOffset; // UInt32 _segmentNumber; // UInt32 _segmentCount; UInt64 _numSectors; // total unpacked number of sectors Byte _segmentGUID[16]; CChecksum _dataForkChecksum; CChecksum _masterChecksum; #ifdef DMG_SHOW_RAW CObjectVector _extras; #endif HRESULT ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf); bool ParseBlob(const CByteBuffer &data); HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback); HRESULT Extract(IInStream *stream); }; struct CMethods { CRecordVector Types; void Update(const CFile &file); void AddToString(AString &s) const; }; void CMethods::Update(const CFile &file) { FOR_VECTOR (i, file.Blocks) { if (Types.Size() >= (1 << 8)) break; Types.AddToUniqueSorted(file.Blocks[i].Type); } } void CMethods::AddToString(AString &res) const { FOR_VECTOR (i, Types) { const UInt32 type = Types[i]; /* if (type == METHOD_COMMENT || type == METHOD_END) continue; */ char buf[16]; const char *s; switch (type) { // case METHOD_COMMENT: s = "Comment"; break; case METHOD_ZERO_0: s = "Zero0"; break; case METHOD_ZERO_2: s = "Zero2"; break; case METHOD_COPY: s = "Copy"; break; case METHOD_ADC: s = "ADC"; break; case METHOD_ZLIB: s = "ZLIB"; break; case METHOD_BZIP2: s = "BZip2"; break; case METHOD_LZFSE: s = "LZFSE"; break; case METHOD_XZ: s = "XZ"; break; default: ConvertUInt32ToHex(type, buf); s = buf; } res.Add_OptSpaced(s); } } struct CAppleName { bool IsFs; const char *Ext; const char *AppleName; }; static const CAppleName k_Names[] = { { true, "hfs", "Apple_HFS" }, { true, "hfsx", "Apple_HFSX" }, { true, "ufs", "Apple_UFS" }, { true, "apfs", "Apple_APFS" }, { true, "iso", "Apple_ISO" }, // efi_sys partition is FAT32, but it's not main file. So we use (IsFs = false) { false, "efi_sys", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" }, { false, "free", "Apple_Free" }, { false, "ddm", "DDM" }, { false, NULL, "Apple_partition_map" }, { false, NULL, " GPT " }, /* " GPT " is substring of full name entry in dmg that contains some small metadata GPT entry. It's not real FS entry: "Primary GPT Header", "Primary GPT Table" "Backup GPT Header", "Backup GPT Table", */ { false, NULL, "MBR" }, { false, NULL, "Driver" }, { false, NULL, "Patches" } }; static const unsigned kNumAppleNames = Z7_ARRAY_SIZE(k_Names); const char *Find_Apple_FS_Ext(const AString &name); const char *Find_Apple_FS_Ext(const AString &name) { for (unsigned i = 0; i < kNumAppleNames; i++) { const CAppleName &a = k_Names[i]; if (a.Ext) if (name == a.AppleName) return a.Ext; } return NULL; } bool Is_Apple_FS_Or_Unknown(const AString &name); bool Is_Apple_FS_Or_Unknown(const AString &name) { for (unsigned i = 0; i < kNumAppleNames; i++) { const CAppleName &a = k_Names[i]; // if (name.Find(a.AppleName) >= 0) if (strstr(name, a.AppleName)) return a.IsFs; } return true; } // define it for debug only: // #define Z7_DMG_SINGLE_FILE_MODE static const Byte kProps[] = { kpidPath, kpidSize, kpidPackSize, kpidComment, kpidMethod, kpidNumBlocks, kpidClusterSize, kpidChecksum, kpidCRC, kpidId // kpidOffset #ifdef Z7_DMG_SINGLE_FILE_MODE , kpidPosition #endif }; IMP_IInArchive_Props static const Byte kArcProps[] = { kpidMethod, kpidNumBlocks, kpidClusterSize, kpidComment }; Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch (propID) { case kpidMethod: { CMethods m; CRecordVector ChecksumTypes; { FOR_VECTOR (i, _files) { const CFile &file = _files[i]; m.Update(file); if (ChecksumTypes.Size() < (1 << 8)) ChecksumTypes.AddToUniqueSorted(file.Checksum.Type); } } AString s; m.AddToString(s); FOR_VECTOR (i, ChecksumTypes) { const UInt32 type = ChecksumTypes[i]; switch (type) { case kCheckSumType_CRC: s.Add_OptSpaced("CRC"); break; default: s.Add_OptSpaced("Checksum"); s.Add_UInt32(type); } } if (!s.IsEmpty()) prop = s; break; } case kpidNumBlocks: { UInt64 numBlocks = 0; FOR_VECTOR (i, _files) numBlocks += _files[i].Blocks.Size(); prop = numBlocks; break; } case kpidClusterSize: { UInt64 blockSize_MAX = 0; FOR_VECTOR (i, _files) { const UInt64 a = _files[i].BlockSize_MAX; if (blockSize_MAX < a) blockSize_MAX = a; } prop = blockSize_MAX; break; } case kpidMainSubfile: { int mainIndex = -1; FOR_VECTOR (i, _files) { if (Is_Apple_FS_Or_Unknown(_files[i].Name)) { if (mainIndex != -1) { mainIndex = -1; break; } mainIndex = (int)i; } } if (mainIndex != -1) prop = (UInt32)(Int32)mainIndex; break; } case kpidWarning: if (_masterCrcError) prop = "Master CRC error"; break; case kpidWarningFlags: { UInt32 v = 0; if (_headersError) v |= kpv_ErrorFlags_HeadersError; if (_dataForkError) v |= kpv_ErrorFlags_CrcError; if (v != 0) prop = v; break; } case kpidOffset: prop = _startPos; break; case kpidPhySize: prop = _phySize; break; case kpidComment: { AString s; if (!_name.IsEmpty()) AddToComment_Prop(s, "Name", _name); AddToComment_UInt64(s, _numSectors << 9, "unpack-size"); { char temp[sizeof(_segmentGUID) * 2 + 2]; ConvertDataToHex_Lower(temp, _segmentGUID, sizeof(_segmentGUID)); AddToComment_Prop(s, "ID", temp); } _masterChecksum.AddToComment(s, "master-checksum"); _dataForkChecksum.AddToComment(s, "pack-checksum"); { /* if (_dataStartOffset != 0) AddToComment_UInt64(s, _dataStartOffset, "payload-start-offset"); */ // if (_dataForkPair.Offset != 0) _dataForkPair.Print(s, "pack"); rsrcPair.Print(s, "rsrc"); xmlPair.Print(s, "xml"); blobPair.Print(s, "blob"); } if (_rsrcMode_wasUsed) s += "RSRC_MODE\n"; if (!s.IsEmpty()) prop = s; } break; case kpidName: if (!_name.IsEmpty()) prop = _name + ".dmg"; break; } prop.Detach(value); return S_OK; COM_TRY_END } IMP_IInArchive_ArcProps static const UInt64 kSectorNumber_LIMIT = (UInt64)1 << (63 - 9); HRESULT CFile::Parse(const Byte *p, UInt32 size) { // CFile was initialized to default values: 0 in size variables and (IsCorrect == false) const unsigned kHeadSize = 0xCC; if (size < kHeadSize) return S_FALSE; if (Get32(p) != 0x6D697368) // "mish" signature return S_FALSE; if (Get32(p + 4) != 1) // version return S_FALSE; StartUnpackSector = Get64(p + 8); NumUnpackSectors = Get64(p + 0x10); StartPackPos = Get64(p + 0x18); #ifdef SHOW_DEBUG_INFO /* the number of sectors that must be allocated. == 0x208 for 256KB clusters == 0x808 for 1MB clusters == 0x1001 for 1MB clusters in some example */ const UInt32 decompressedBufRequested = Get32(p + 0x20); #endif // Descriptor is file index. usually started from -1 // in one dmg it was started from 0 Descriptor = (Int32)Get32(p + 0x24); // char Reserved1[24]; Checksum.Parse(p + 0x40); PRF(printf("\n" " Checksum Type = %2u" "\n StartUnpackSector = %8x" "\n NumUnpackSectors = %8x" "\n StartPos = %8x" "\n decompressedBufRequested=%8x" "\n blocksDescriptor=%8x" , (unsigned)Checksum.Type , (unsigned)StartUnpackSector , (unsigned)NumUnpackSectors , (unsigned)StartPackPos , (unsigned)decompressedBufRequested , (unsigned)Descriptor );) const UInt32 numBlocks = Get32(p + 0xC8); const unsigned kRecordSize = 40; if ((UInt64)numBlocks * kRecordSize + kHeadSize != size) return S_FALSE; Blocks.ClearAndReserve(numBlocks); FullFileChecksum = true; /* IsCorrect = false; by default So we return S_OK, if we can ignore some error in headers. */ p += kHeadSize; UInt32 i; for (i = 0; i < numBlocks; i++, p += kRecordSize) { CBlock b; b.Type = Get32(p); { const UInt64 a = Get64(p + 0x08); if (a >= kSectorNumber_LIMIT) return S_OK; b.UnpPos = a << 9; } UInt64 unpSize; { const UInt64 a = Get64(p + 0x10); if (a >= kSectorNumber_LIMIT) return S_OK; unpSize = a << 9; } const UInt64 newSize = b.UnpPos + unpSize; if (newSize >= ((UInt64)1 << 63)) return S_OK; b.PackPos = Get64(p + 0x18); b.PackSize = Get64(p + 0x20); // b.PackPos can be 0 for some types. So we don't check it if (b.UnpPos != Size) return S_OK; PRF(printf("\nType=%8x comment=%8x uPos=%8x uSize=%7x pPos=%8x pSize=%7x", (unsigned)b.Type, Get32(p + 4), (unsigned)b.UnpPos, (unsigned)unpSize, (unsigned)b.PackPos, (unsigned)b.PackSize)); if (b.Type == METHOD_COMMENT) { // some files contain 2 comment block records: // record[0] : Type=METHOD_COMMENT, comment_field = "+beg" // record[num-2] : Type=METHOD_COMMENT, comment_field = "+end" // we skip these useless records. continue; } if (b.Type == METHOD_END) break; // we add only blocks that have non empty unpacked data: if (unpSize != 0) { const UInt64 k_max_pos = (UInt64)1 << 63; if (b.PackPos >= k_max_pos || b.PackSize >= k_max_pos - b.PackPos) return S_OK; /* we don't count non-ZERO blocks here, because ZERO blocks in dmg files are not limited by some cluster size. note: COPY blocks also sometimes are fused to larger blocks. */ if (b.IsClusteredMethod()) if (BlockSize_MAX < unpSize) BlockSize_MAX = unpSize; PackSize += b.PackSize; if (!b.NeedCrc()) FullFileChecksum = false; Blocks.AddInReserved(b); Size = newSize; } } PRF(printf("\n");) if (i != numBlocks - 1) { // return S_FALSE; return S_OK; } if ((Size >> 9) == NumUnpackSectors) IsCorrect = true; return S_OK; } static const CXmlItem *FindKeyPair(const CXmlItem &item, const char *key, const char *nextTag) { for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++) { const CXmlItem &si = item.SubItems[i]; if (si.IsTagged("key") && si.GetSubString() == key) { const CXmlItem *si_1 = &item.SubItems[i + 1]; if (si_1->IsTagged(nextTag)) return si_1; } } return NULL; } static const AString *GetStringFromKeyPair(const CXmlItem &item, const char *key, const char *nextTag) { const CXmlItem *si_1 = FindKeyPair(item, key, nextTag); if (si_1) return si_1->GetSubStringPtr(); return NULL; } static const Byte k_Signature[] = { 'k','o','l','y', 0, 0, 0, 4, 0, 0, 2, 0 }; static inline bool IsKoly(const Byte *p) { return memcmp(p, k_Signature, Z7_ARRAY_SIZE(k_Signature)) == 0; /* if (Get32(p) != 0x6B6F6C79) // "koly" signature return false; if (Get32(p + 4) != 4) // version return false; if (Get32(p + 8) != HEADER_SIZE) return false; return true; */ } HRESULT CHandler::ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf) { size_t size = (size_t)pair.Len; if (size != pair.Len) return E_OUTOFMEMORY; buf.Alloc(size); RINOK(InStream_SeekSet(stream, _startPos + pair.Offset)) return ReadStream_FALSE(stream, buf, size); } bool CHandler::ParseBlob(const CByteBuffer &data) { const unsigned kHeaderSize = 3 * 4; if (data.Size() < kHeaderSize) return false; const Byte * const p = (const Byte *)data; if (Get32a(p) != 0xfade0cc0) // CSMAGIC_EMBEDDED_SIGNATURE return true; const UInt32 size = Get32a(p + 4); if (size != data.Size()) return false; const UInt32 num = Get32a(p + 8); if (num > (size - kHeaderSize) / 8) return false; const UInt32 limit = num * 8 + kHeaderSize; for (size_t i = kHeaderSize; i < limit; i += 8) { // type == 0 == CSSLOT_CODEDIRECTORY for CSMAGIC_CODEDIRECTORY item // UInt32 type = Get32(p + i); const UInt32 offset = Get32a(p + i + 4); if (offset < limit || offset > size - 8) return false; // offset is not aligned for 4 here !!! const Byte * const p2 = p + offset; const UInt32 magic = Get32(p2); const UInt32 len = Get32(p2 + 4); if (size - offset < len || len < 8) return false; #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "_blob_"; extra.Data.CopyFrom(p2, len); #endif if (magic == 0xfade0c02) // CSMAGIC_CODEDIRECTORY { #ifdef DMG_SHOW_RAW extra.Name += "codedir"; #endif if (len < 11 * 4) return false; const UInt32 idOffset = Get32(p2 + 5 * 4); if (idOffset >= len) return false; UInt32 len2 = len - idOffset; const UInt32 kNameLenMax = 1 << 8; if (len2 > kNameLenMax) len2 = kNameLenMax; _name.SetFrom_CalcLen((const char *)(p2 + idOffset), len2); /* // #define kSecCodeSignatureHashSHA1 1 // #define kSecCodeSignatureHashSHA256 2 const UInt32 hashOffset = Get32(p2 + 4 * 4); const UInt32 nSpecialSlots = Get32(p2 + 6 * 4); const UInt32 nCodeSlots = Get32(p2 + 7 * 4); const unsigned hashSize = p2[36]; const unsigned hashType = p2[37]; // const unsigned unused = p2[38]; const unsigned pageSize = p2[39]; */ } #ifdef DMG_SHOW_RAW else if (magic == 0xfade0c01) extra.Name += "requirements"; else if (magic == 0xfade0b01) extra.Name += "signed"; else { char temp[16]; ConvertUInt32ToHex8Digits(magic, temp); extra.Name += temp; } #endif } return true; } HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) { /* - usual dmg contains Koly Header at the end: - rare case old dmg contains Koly Header at the begin. */ // _dataStartOffset = 0; UInt64 fileSize; RINOK(InStream_GetPos_GetSize(stream, _startPos, fileSize)) const unsigned HEADER_SIZE = 0x200; UInt64 buf[HEADER_SIZE / sizeof(UInt64)]; RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)) UInt64 headerPos; bool front_Koly_Mode = false; /* _dataForkChecksum.Offset == 0 for koly-at-the-end _dataForkChecksum.Offset == 512 for koly-at-the-start so we can use (_dataForkChecksum.Offset) to detect "koly-at-the-start" mode */ if (IsKoly((const Byte *)(const void *)buf)) { // it can be normal koly-at-the-end or koly-at-the-start headerPos = _startPos; /* if (_startPos <= (1 << 8)) { // we want to support front_Koly_Mode, even if there is // some data before dmg file, like 128 bytes MacBin header _dataStartOffset = HEADER_SIZE; front_Koly_Mode = true; } */ } else { /* we try to open in backward mode only for first attempt when (_startPos == 0) */ if (_startPos != 0) return S_FALSE; headerPos = fileSize; if (headerPos < HEADER_SIZE) return S_FALSE; headerPos -= HEADER_SIZE; RINOK(InStream_SeekSet(stream, headerPos)) RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)) if (!IsKoly((const Byte *)(const void *)buf)) return S_FALSE; } // UInt32 flags = Get32a((const Byte *)(const void *)buf + 12); // _runningDataForkOffset = Get64a((const Byte *)(const void *)buf + 0x10); _dataForkPair.Parse((const Byte *)(const void *)buf + 0x18); rsrcPair.Parse((const Byte *)(const void *)buf + 0x28); // _segmentNumber = Get32a(buf + 0x38); // 0 or 1 // _segmentCount = Get32a(buf + 0x3C); // 0 (if not set) or 1 memcpy(_segmentGUID, (const Byte *)(const void *)buf + 0x40, 16); _dataForkChecksum.Parse((const Byte *)(const void *)buf + 0x50); xmlPair.Parse((const Byte *)(const void *)buf + 0xD8); // Byte resereved[] blobPair.Parse((const Byte *)(const void *)buf + 0x128); _masterChecksum.Parse((const Byte *)(const void *)buf + 0x160); // UInt32 imageVariant = Get32a((const Byte *)(const void *)buf + 0x1E8); imageVariant = imageVariant; _numSectors = Get64((const Byte *)(const void *)buf + 0x1EC); // it's not aligned for 8-bytes // Byte resereved[12]; if (_dataForkPair.Offset == HEADER_SIZE && headerPos + HEADER_SIZE < fileSize) front_Koly_Mode = true; const UInt64 limit = front_Koly_Mode ? fileSize : headerPos; UInt64 top = 0; if (!_dataForkPair.UpdateTop(limit, top)) return S_FALSE; if (!xmlPair.UpdateTop(limit, top)) return S_FALSE; if (!rsrcPair.UpdateTop(limit, top)) return S_FALSE; /* Some old dmg files contain garbage data in blobPair field. So we need to ignore such garbage case; And we still need to detect offset of start of archive for "parser" mode. */ const bool useBlob = blobPair.UpdateTop(limit, top); if (front_Koly_Mode) _phySize = top; else { _phySize = headerPos + HEADER_SIZE; _startPos = 0; if (top != headerPos) { /* if expected absolute offset is not equal to real header offset, 2 cases are possible: - additional (unknown) headers - archive with offset. So we try to read XML with absolute offset to select from these two ways. */ CForkPair xmlPair2 = xmlPair; const char *sz = " len) xmlPair2.Len = len; CByteBuffer buf2; if (xmlPair2.Len < len || ReadData(stream, xmlPair2, buf2) != S_OK || memcmp(buf2, sz, len) != 0) { // if absolute offset is not OK, probably it's archive with offset _startPos = headerPos - top; _phySize = top + HEADER_SIZE; } } } if (useBlob && blobPair.Len != 0 && blobPair.Len <= (1u << 24)) // we don't want parsing of big blobs { #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "_blob.bin"; CByteBuffer &blobBuf = extra.Data; #else CByteBuffer blobBuf; #endif RINOK(ReadData(stream, blobPair, blobBuf)) if (!ParseBlob(blobBuf)) _headersError = true; } UInt64 openTotal = 0; UInt64 openCur = 0; if (_dataForkChecksum.IsCrc32()) openTotal = _dataForkPair.Len; const UInt32 RSRC_HEAD_SIZE = 0x100; /* we have 2 ways to read files and blocks metadata: via Xml or via Rsrc. But some images have no Rsrc fork. Is it possible that there is no Xml? Rsrc method will not work for big files. */ // v23.02: we use xml mode by default // if (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24)) // for debug if (xmlPair.Len == 0) { // We don't know the size of the field "offset" in Rsrc. // We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcPair.Len <= (1 << 24). const bool canUseRsrc = (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24)); if (!canUseRsrc) return S_FALSE; _rsrcMode_wasUsed = true; #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "rsrc.bin"; CByteBuffer &rsrcBuf = extra.Data; #else CByteBuffer rsrcBuf; #endif RINOK(ReadData(stream, rsrcPair, rsrcBuf)) const Byte *p = rsrcBuf; const UInt32 headSize = Get32a(p + 0); const UInt32 footerOffset = Get32a(p + 4); const UInt32 mainDataSize = Get32a(p + 8); const UInt32 footerSize = Get32a(p + 12); if (headSize != RSRC_HEAD_SIZE || footerOffset >= rsrcPair.Len || mainDataSize >= rsrcPair.Len || footerOffset < mainDataSize || footerOffset != headSize + mainDataSize) return S_FALSE; { const UInt32 footerEnd = footerOffset + footerSize; if (footerEnd != rsrcPair.Len) { // there is rare case dmg example, where there are 4 additional bytes const UInt64 rem = rsrcPair.Len - footerOffset; if (rem < footerSize || rem - footerSize != 4 || Get32(p + footerEnd) != 0) return S_FALSE; } } if (footerSize < 0x1e) return S_FALSE; if (memcmp(p, p + footerOffset, 16) != 0) return S_FALSE; p += footerOffset; if ((UInt32)Get16(p + 0x18) != 0x1c) return S_FALSE; const UInt32 namesOffset = Get16(p + 0x1a); if (namesOffset > footerSize) return S_FALSE; const UInt32 numItems = (UInt32)Get16(p + 0x1c) + 1; if (numItems * 8 + 0x1e > namesOffset) return S_FALSE; for (UInt32 i = 0; i < numItems; i++) { const Byte *p2 = p + 0x1e + (size_t)i * 8; const UInt32 typeId = Get32(p2); const UInt32 k_typeId_blkx = 0x626c6b78; // blkx #ifndef DMG_SHOW_RAW if (typeId != k_typeId_blkx) continue; #endif const UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1; const UInt32 offs = Get16(p2 + 6); if (0x1c + offs + 12 * numFiles > namesOffset) return S_FALSE; for (UInt32 k = 0; k < numFiles; k++) { const Byte *p3 = p + 0x1c + offs + k * 12; // UInt32 id = Get16(p3); const UInt32 namePos = Get16(p3 + 2); // Byte attributes = p3[4]; // = 0x50 for blkx #define (ATTRIBUTE_HDIUTIL) // we don't know how many bits we can use. So we use 24 bits only UInt32 blockOffset = Get32(p3 + 4); blockOffset &= ((UInt32)1 << 24) - 1; // UInt32 unknown2 = Get32(p3 + 8); // ??? if (blockOffset + 4 >= mainDataSize) return S_FALSE; const Byte *pBlock = rsrcBuf + headSize + blockOffset; const UInt32 blockSize = Get32(pBlock); if (mainDataSize - (blockOffset + 4) < blockSize) return S_FALSE; AString name; if (namePos != 0xffff) { const UInt32 namesBlockSize = footerSize - namesOffset; if (namePos >= namesBlockSize) return S_FALSE; const Byte *namePtr = p + namesOffset + namePos; const UInt32 nameLen = *namePtr; if (namesBlockSize - namePos <= nameLen) return S_FALSE; for (UInt32 r = 1; r <= nameLen; r++) { const Byte c = namePtr[r]; if (c < 0x20 || c >= 0x80) break; name += (char)c; } } if (typeId == k_typeId_blkx) { CFile &file = _files.AddNew(); file.Name = name; RINOK(file.Parse(pBlock + 4, blockSize)) if (!file.IsCorrect) _headersError = true; } #ifdef DMG_SHOW_RAW { AString name2; name2.Add_UInt32(i); name2 += '_'; { char temp[4 + 1] = { 0 }; memcpy(temp, p2, 4); name2 += temp; } name2.Trim(); name2 += '_'; name2.Add_UInt32(k); if (!name.IsEmpty()) { name2 += '_'; name2 += name; } CExtraFile &extra2 = _extras.AddNew(); extra2.Name = name2; extra2.Data.CopyFrom(pBlock + 4, blockSize); } #endif } } } else { if (xmlPair.Len > k_XmlSize_MAX) return S_FALSE; // if (!canUseXml) return S_FALSE; const size_t size = (size_t)xmlPair.Len; // if (size + 1 <= xmlPair.Len) return S_FALSE; // optional check RINOK(InStream_SeekSet(stream, _startPos + xmlPair.Offset)) CXml xml; { openTotal += size; if (openArchiveCallback) { RINOK(openArchiveCallback->SetTotal(NULL, &openTotal)) } size_t pos = 0; CAlignedBuffer1 xmlStr(size + 1); for (;;) { const size_t k_OpenStep = 1 << 24; const size_t cur = MyMin(k_OpenStep, size - pos); RINOK(ReadStream_FALSE(stream, xmlStr + pos, cur)) pos += cur; openCur += cur; if (pos == size) break; if (openArchiveCallback) { RINOK(openArchiveCallback->SetCompleted(NULL, &openCur)) } } xmlStr[size] = 0; // if (strlen((const char *)(const void *)(const Byte *)xmlStr) != size) return S_FALSE; if (!xml.Parse((char *)(void *)(Byte *)xmlStr)) return S_FALSE; #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "a.xml"; extra.Data.CopyFrom(xmlStr, size); #endif } if (xml.Root.Name != "plist") return S_FALSE; const CXmlItem *dictItem = xml.Root.FindSubTag_GetPtr("dict"); if (!dictItem) return S_FALSE; const CXmlItem *rfDictItem = FindKeyPair(*dictItem, "resource-fork", "dict"); if (!rfDictItem) return S_FALSE; const CXmlItem *arrItem = FindKeyPair(*rfDictItem, "blkx", "array"); if (!arrItem) return S_FALSE; FOR_VECTOR (i, arrItem->SubItems) { const CXmlItem &item = arrItem->SubItems[i]; if (!item.IsTagged("dict")) continue; CByteBuffer rawBuf; unsigned destLen = 0; { const AString *dataString = GetStringFromKeyPair(item, "Data", "data"); if (!dataString) return S_FALSE; destLen = dataString->Len() / 4 * 3 + 4; rawBuf.Alloc(destLen); { const Byte *endPtr = Base64ToBin(rawBuf, *dataString); if (!endPtr) return S_FALSE; destLen = (unsigned)(endPtr - (const Byte *)rawBuf); } #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name.Add_UInt32(_files.Size()); extra.Data.CopyFrom(rawBuf, destLen); #endif } CFile &file = _files.AddNew(); { /* xml code removes front space for such string: (Apple_Free : 3) maybe we shoud fix xml code and return full string with space. */ const AString *name = GetStringFromKeyPair(item, "Name", "string"); if (!name || name->IsEmpty()) name = GetStringFromKeyPair(item, "CFName", "string"); if (name) file.Name = *name; } /* { const AString *s = GetStringFromKeyPair(item, "ID", "string"); if (s) file.Id = *s; } */ RINOK(file.Parse(rawBuf, destLen)) if (!file.IsCorrect) _headersError = true; } } if (_masterChecksum.IsCrc32()) { UInt32 crc = CRC_INIT_VAL; unsigned i; for (i = 0; i < _files.Size(); i++) { const CChecksum &cs = _files[i].Checksum; if ((cs.NumBits & 0x7) != 0) break; const UInt32 len = cs.NumBits >> 3; if (len > kChecksumSize_Max) break; crc = CrcUpdate(crc, cs.Data, (size_t)len); } if (i == _files.Size()) _masterCrcError = (CRC_GET_DIGEST(crc) != _masterChecksum.GetCrc32()); } { UInt64 sec = 0; FOR_VECTOR (i, _files) { const CFile &file = _files[i]; /* if (file.Descriptor != (Int32)i - 1) _headersError = true; */ if (file.StartUnpackSector != sec) _headersError = true; if (file.NumUnpackSectors >= kSectorNumber_LIMIT) _headersError = true; sec += file.NumUnpackSectors; if (sec >= kSectorNumber_LIMIT) _headersError = true; } if (sec != _numSectors) _headersError = true; } // data checksum calculation can be slow for big dmg file if (_dataForkChecksum.IsCrc32()) { UInt64 endPos; if (!_dataForkPair.GetEndPos(endPos) || _dataForkPair.Offset >= ((UInt64)1 << 63)) _headersError = true; else { const UInt64 seekPos = _startPos + _dataForkPair.Offset; if (seekPos > fileSize || endPos > fileSize - _startPos) { _headersError = true; // kpv_ErrorFlags_UnexpectedEnd } else { const size_t kBufSize = 1 << 15; CAlignedBuffer1 buf2(kBufSize); RINOK(InStream_SeekSet(stream, seekPos)) if (openArchiveCallback) { RINOK(openArchiveCallback->SetTotal(NULL, &openTotal)) } UInt32 crc = CRC_INIT_VAL; UInt64 pos = 0; for (;;) { const UInt64 rem = _dataForkPair.Len - pos; size_t cur = kBufSize; if (cur > rem) cur = (UInt32)rem; if (cur == 0) break; RINOK(ReadStream_FALSE(stream, buf2, cur)) crc = CrcUpdate(crc, buf2, cur); pos += cur; openCur += cur; if ((pos & ((1 << 24) - 1)) == 0 && openArchiveCallback) { RINOK(openArchiveCallback->SetCompleted(NULL, &openCur)) } } if (_dataForkChecksum.GetCrc32() != CRC_GET_DIGEST(crc)) _dataForkError = true; } } } return S_OK; } Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *openArchiveCallback)) { COM_TRY_BEGIN Close(); RINOK(Open2(stream, openArchiveCallback)) _inStream = stream; return S_OK; COM_TRY_END } Z7_COM7F_IMF(CHandler::Close()) { _masterCrcError = false; _headersError = false; _dataForkError = false; _rsrcMode_wasUsed = false; _phySize = 0; _startPos = 0; _name.Empty(); _inStream.Release(); _files.Clear(); #ifdef DMG_SHOW_RAW _extras.Clear(); #endif return S_OK; } Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _files.Size() #ifdef DMG_SHOW_RAW + _extras.Size() #endif ; return S_OK; } #ifdef DMG_SHOW_RAW #define RAW_PREFIX "raw" STRING_PATH_SEPARATOR #endif Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; #ifdef DMG_SHOW_RAW if (index >= _files.Size()) { const CExtraFile &extra = _extras[index - _files.Size()]; switch (propID) { case kpidPath: prop = (AString)RAW_PREFIX + extra.Name; break; case kpidSize: case kpidPackSize: prop = (UInt64)extra.Data.Size(); break; } } else #endif { const CFile &item = _files[index]; switch (propID) { case kpidSize: prop = item.Size; break; case kpidPackSize: prop = item.PackSize; break; case kpidCRC: { if (item.Checksum.IsCrc32() && item.FullFileChecksum) prop = item.Checksum.GetCrc32(); break; } case kpidChecksum: { AString s; item.Checksum.Print(s); if (!s.IsEmpty()) prop = s; break; } /* case kpidOffset: { prop = item.StartPackPos; break; } */ case kpidNumBlocks: prop = (UInt32)item.Blocks.Size(); break; case kpidClusterSize: prop = item.BlockSize_MAX; break; case kpidMethod: { AString s; if (!item.IsCorrect) s.Add_OptSpaced("CORRUPTED"); CMethods m; m.Update(item); m.AddToString(s); { AString s2; item.Checksum.PrintType(s2); if (!s2.IsEmpty()) s.Add_OptSpaced(s2); } if (!s.IsEmpty()) prop = s; break; } case kpidPath: { #ifdef Z7_DMG_SINGLE_FILE_MODE prop = "a.img"; #else UString name; name.Add_UInt32(index); unsigned num = 10; unsigned numDigits; for (numDigits = 1; num < _files.Size(); numDigits++) num *= 10; while (name.Len() < numDigits) name.InsertAtFront(L'0'); AString subName; int pos1 = item.Name.Find('('); if (pos1 >= 0) { pos1++; const int pos2 = item.Name.Find(')', pos1); if (pos2 >= 0) { subName.SetFrom(item.Name.Ptr(pos1), pos2 - pos1); pos1 = subName.Find(':'); if (pos1 >= 0) subName.DeleteFrom(pos1); } } else subName = item.Name; // new apfs dmg can be without braces subName.Trim(); if (!subName.IsEmpty()) { const char *ext = Find_Apple_FS_Ext(subName); if (ext) subName = ext; UString name2; ConvertUTF8ToUnicode(subName, name2); name.Add_Dot(); name += name2; } else { UString name2; ConvertUTF8ToUnicode(item.Name, name2); if (!name2.IsEmpty()) name += "_"; name += name2; } prop = name; #endif break; } case kpidComment: { UString name; ConvertUTF8ToUnicode(item.Name, name); prop = name; break; } case kpidId: { prop.Set_Int32((Int32)item.Descriptor); /* if (!item.Id.IsEmpty()) { UString s; ConvertUTF8ToUnicode(item.Id, s); prop = s; } */ break; } #ifdef Z7_DMG_SINGLE_FILE_MODE case kpidPosition: prop = item.StartUnpackSector << 9; break; #endif } } prop.Detach(value); return S_OK; COM_TRY_END } class CAdcDecoder { CLzOutWindow m_OutWindowStream; CInBuffer m_InStream; class CCoderReleaser Z7_final { CAdcDecoder *m_Coder; public: bool NeedFlush; CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {} ~CCoderReleaser() { if (NeedFlush) m_Coder->m_OutWindowStream.Flush(); } }; friend class CCoderReleaser; public: HRESULT Code(ISequentialInStream * const inStream, ISequentialOutStream *outStream, const UInt64 * const inSize, const UInt64 * const outSize, ICompressProgressInfo * const progress); }; HRESULT CAdcDecoder::Code(ISequentialInStream * const inStream, ISequentialOutStream *outStream, const UInt64 * const inSize, const UInt64 * const outSizePtr, ICompressProgressInfo * const progress) { try { if (!m_OutWindowStream.Create(1 << 18)) // at least (1 << 16) is required here return E_OUTOFMEMORY; if (!m_InStream.Create(1 << 18)) return E_OUTOFMEMORY; m_OutWindowStream.SetStream(outStream); m_OutWindowStream.Init(false); m_InStream.SetStream(inStream); m_InStream.Init(); CCoderReleaser coderReleaser(this); const UInt32 kStep = 1 << 22; UInt64 nextLimit = kStep; const UInt64 outSize = *outSizePtr; UInt64 pos = 0; /* match sequences and literal sequences do not cross 64KB range in some dmg archive examples. But is it so for any Adc stream? */ while (pos < outSize) { if (pos >= nextLimit && progress) { nextLimit += kStep; const UInt64 packSize = m_InStream.GetProcessedSize(); RINOK(progress->SetRatioInfo(&packSize, &pos)) } Byte b; if (!m_InStream.ReadByte(b)) return S_FALSE; const UInt64 rem = outSize - pos; if (b & 0x80) { unsigned num = (unsigned)b - 0x80 + 1; if (num > rem) return S_FALSE; pos += num; do { if (!m_InStream.ReadByte(b)) return S_FALSE; m_OutWindowStream.PutByte(b); } while (--num); continue; } Byte b1; if (!m_InStream.ReadByte(b1)) return S_FALSE; UInt32 len, dist; if (b & 0x40) { len = (UInt32)b - 0x40 + 4; Byte b2; if (!m_InStream.ReadByte(b2)) return S_FALSE; dist = ((UInt32)b1 << 8) + b2; } else { len = ((UInt32)b >> 2) + 3; dist = (((UInt32)b & 3) << 8) + b1; } if (/* dist >= pos || */ len > rem) return S_FALSE; if (!m_OutWindowStream.CopyBlock(dist, len)) return S_FALSE; pos += len; } if (*inSize != m_InStream.GetProcessedSize()) return S_FALSE; coderReleaser.NeedFlush = false; return m_OutWindowStream.Flush(); } catch(const CInBufferException &e) { return e.ErrorCode; } catch(const CLzOutWindowException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } struct CDecoders { CMyComPtr2 zlib; CMyComPtr2 bzip2; CMyComPtr2 lzfse; CMyUniquePtr xz; CMyUniquePtr adc; HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress); }; HRESULT CDecoders::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress) { HRESULT hres; UInt64 processed; switch (block.Type) { case METHOD_ADC: adc.Create_if_Empty(); return adc->Code(inStream, outStream, &block.PackSize, unpSize, progress); case METHOD_LZFSE: lzfse.Create_if_Empty(); return lzfse.Interface()->Code(inStream, outStream, &block.PackSize, unpSize, progress); case METHOD_ZLIB: zlib.Create_if_Empty(); hres = zlib.Interface()->Code(inStream, outStream, NULL, unpSize, progress); processed = zlib->GetInputProcessedSize(); break; case METHOD_BZIP2: bzip2.Create_if_Empty(); hres = bzip2.Interface()->Code(inStream, outStream, NULL, unpSize, progress); processed = bzip2->GetInputProcessedSize(); break; case METHOD_XZ: xz.Create_if_Empty(); hres = xz->Decode(inStream, outStream, unpSize, true, progress); processed = xz->Stat.InSize; break; default: return E_NOTIMPL; } if (hres == S_OK && processed != block.PackSize) hres = S_FALSE; return hres; } Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _files.Size() #ifdef DMG_SHOW_RAW + _extras.Size() #endif ; if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { const UInt32 index = allFilesMode ? i : indices[i]; #ifdef DMG_SHOW_RAW if (index >= _files.Size()) totalSize += _extras[index - _files.Size()].Data.Size(); else #endif totalSize += _files[index].Size; } RINOK(extractCallback->SetTotal(totalSize)) const size_t kZeroBufSize = 1 << 14; CAlignedBuffer1 zeroBuf(kZeroBufSize); memset(zeroBuf, 0, kZeroBufSize); CDecoders decoders; CMyComPtr2_Create copyCoder; CMyComPtr2_Create lps; lps->Init(extractCallback, false); CMyComPtr2_Create inStream; inStream->SetStream(_inStream); UInt64 total_PackSize = 0; UInt64 total_UnpackSize = 0; UInt64 cur_PackSize = 0; UInt64 cur_UnpackSize = 0; for (i = 0;; i++, total_PackSize += cur_PackSize, total_UnpackSize += cur_UnpackSize) { lps->InSize = total_PackSize; lps->OutSize = total_UnpackSize; cur_PackSize = 0; cur_UnpackSize = 0; RINOK(lps->SetCur()) if (i >= numItems) return S_OK; Int32 opRes = NExtract::NOperationResult::kOK; { CMyComPtr realOutStream; const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; const UInt32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)) #ifdef DMG_SHOW_RAW if (index >= _files.Size()) { const CByteBuffer &buf = _extras[index - _files.Size()].Data; if (realOutStream) RINOK(WriteStream(realOutStream, buf, buf.Size())) cur_PackSize = cur_UnpackSize = buf.Size(); } else #endif { const CFile &item = _files[index]; cur_PackSize = item.PackSize; cur_UnpackSize = item.Size; if (!item.IsCorrect) opRes = NExtract::NOperationResult::kHeadersError; else { CMyComPtr2_Create outCrcStream; outCrcStream->SetStream(realOutStream); // realOutStream.Release(); const bool needCrc = item.Checksum.IsCrc32(); outCrcStream->Init(needCrc); CMyComPtr2_Create outStream; outStream->SetStream(outCrcStream); UInt64 unpPos = 0; UInt64 packPos = 0; FOR_VECTOR (blockIndex, item.Blocks) { lps->InSize = total_PackSize + packPos; lps->OutSize = total_UnpackSize + unpPos; RINOK(lps->SetCur()) const CBlock &block = item.Blocks[blockIndex]; // if (!block.ThereAreDataInBlock()) continue; packPos += block.PackSize; if (block.UnpPos != unpPos) { opRes = NExtract::NOperationResult::kHeadersError; break; } RINOK(InStream_SeekSet(_inStream, _startPos + _dataForkPair.Offset + item.StartPackPos + block.PackPos)) inStream->Init(block.PackSize); const UInt64 unpSize = item.GetUnpackSize_of_Block(blockIndex); outStream->Init(unpSize); HRESULT res = S_OK; outCrcStream->EnableCalc(needCrc && block.NeedCrc()); if (block.IsZeroMethod()) { if (block.PackSize != 0) opRes = NExtract::NOperationResult::kUnsupportedMethod; } else if (block.Type == METHOD_COPY) { if (unpSize != block.PackSize) opRes = NExtract::NOperationResult::kUnsupportedMethod; else res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps); } else res = decoders.Code(inStream, outStream, block, &unpSize, lps); if (res != S_OK) { if (res != S_FALSE) { if (res != E_NOTIMPL) return res; opRes = NExtract::NOperationResult::kUnsupportedMethod; } if (opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; } unpPos += unpSize; if (!outStream->IsFinishedOK()) { if (!block.IsZeroMethod() && opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; for (unsigned k = 0;;) { const UInt64 rem = outStream->GetRem(); if (rem == 0) break; size_t size = kZeroBufSize; if (size > rem) size = (size_t)rem; RINOK(WriteStream(outStream, zeroBuf, size)) k++; if ((k & 0xfff) == 0) { lps->OutSize = total_UnpackSize + unpPos - outStream->GetRem(); RINOK(lps->SetCur()) } } } } if (needCrc && opRes == NExtract::NOperationResult::kOK) { if (outCrcStream->GetCRC() != item.Checksum.GetCrc32()) opRes = NExtract::NOperationResult::kCRCError; } } } } RINOK(extractCallback->SetOperationResult(opRes)) } COM_TRY_END } struct CChunk { int BlockIndex; UInt64 AccessMark; Byte *Buf; size_t BufSize; void Free() { z7_AlignedFree(Buf); Buf = NULL; BufSize = 0; } void Alloc(size_t size) { Buf = (Byte *)z7_AlignedAlloc(size); } }; Z7_CLASS_IMP_IInStream( CInStream ) bool _errorMode; UInt64 _virtPos; int _latestChunk; int _latestBlock; UInt64 _accessMark; UInt64 _chunks_TotalSize; CRecordVector _chunks; public: CMyComPtr Stream; const CFile *File; UInt64 Size; private: UInt64 _startPos; ~CInStream(); CMyComPtr2 outStream; CMyComPtr2 inStream; CDecoders decoders; public: // HRESULT void Init(UInt64 startPos) { _errorMode = false; _startPos = startPos; _virtPos = 0; _latestChunk = -1; _latestBlock = -1; _accessMark = 0; _chunks_TotalSize = 0; inStream.Create_if_Empty(); inStream->SetStream(Stream); outStream.Create_if_Empty(); // return S_OK; } }; CInStream::~CInStream() { unsigned i = _chunks.Size(); while (i) _chunks[--i].Free(); } static unsigned FindBlock(const CRecordVector &blocks, UInt64 pos) { unsigned left = 0, right = blocks.Size(); for (;;) { const unsigned mid = (left + right) / 2; if (mid == left) return left; if (pos < blocks[mid].UnpPos) right = mid; else left = mid; } } Z7_COM7F_IMF(CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { // COM_TRY_BEGIN try { if (_errorMode) return E_OUTOFMEMORY; if (processedSize) *processedSize = 0; if (size == 0) return S_OK; if (_virtPos >= Size) return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL; { const UInt64 rem = Size - _virtPos; if (size > rem) size = (UInt32)rem; } if (_latestBlock >= 0) { const CBlock &block = File->Blocks[(unsigned)_latestBlock]; if (_virtPos < block.UnpPos || _virtPos - block.UnpPos >= File->GetUnpackSize_of_Block((unsigned)_latestBlock)) _latestBlock = -1; } if (_latestBlock < 0) { _latestChunk = -1; const unsigned blockIndex = FindBlock(File->Blocks, _virtPos); const CBlock &block = File->Blocks[blockIndex]; const UInt64 unpSize = File->GetUnpackSize_of_Block(blockIndex); if (block.NeedAllocateBuffer() && unpSize <= k_Chunk_Size_MAX) { unsigned i = 0; { unsigned numChunks = _chunks.Size(); if (numChunks) { const CChunk *chunk = _chunks.ConstData(); do { if (chunk->BlockIndex == (int)blockIndex) break; chunk++; } while (--numChunks); i = _chunks.Size() - numChunks; } } if (i != _chunks.Size()) _latestChunk = (int)i; else { unsigned chunkIndex; for (;;) { if (_chunks.IsEmpty() || (_chunks.Size() < k_NumChunks_MAX && _chunks_TotalSize + unpSize <= k_Chunks_TotalSize_MAX)) { CChunk chunk; chunk.Buf = NULL; chunk.BufSize = 0; chunk.BlockIndex = -1; chunk.AccessMark = 0; chunkIndex = _chunks.Add(chunk); break; } chunkIndex = 0; if (_chunks.Size() == 1) break; { const CChunk *chunks = _chunks.ConstData(); UInt64 accessMark_min = chunks[chunkIndex].AccessMark; const unsigned numChunks = _chunks.Size(); for (i = 1; i < numChunks; i++) { if (chunks[i].AccessMark < accessMark_min) { chunkIndex = i; accessMark_min = chunks[i].AccessMark; } } } { CChunk &chunk = _chunks[chunkIndex]; const UInt64 newTotalSize = _chunks_TotalSize - chunk.BufSize; if (newTotalSize + unpSize <= k_Chunks_TotalSize_MAX) break; _chunks_TotalSize = newTotalSize; chunk.Free(); } // we have called chunk.Free() before, because // _chunks.Delete() doesn't call chunk.Free(). _chunks.Delete(chunkIndex); PRF(printf("\n++num_chunks=%u, _chunks_TotalSize = %u\n", (unsigned)_chunks.Size(), (unsigned)_chunks_TotalSize);) } CChunk &chunk = _chunks[chunkIndex]; chunk.BlockIndex = -1; chunk.AccessMark = 0; if (chunk.BufSize < unpSize) { _chunks_TotalSize -= chunk.BufSize; chunk.Free(); // if (unpSize > k_Chunk_Size_MAX) return E_FAIL; chunk.Alloc((size_t)unpSize); if (!chunk.Buf) return E_OUTOFMEMORY; chunk.BufSize = (size_t)unpSize; _chunks_TotalSize += chunk.BufSize; } RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos)) inStream->Init(block.PackSize); #ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS if (block.Type == METHOD_COPY) { if (block.PackSize != unpSize) return E_FAIL; RINOK(ReadStream_FAIL(inStream, chunk.Buf, (size_t)unpSize)) } else #endif { outStream->Init(chunk.Buf, (size_t)unpSize); RINOK(decoders.Code(inStream, outStream, block, &unpSize, NULL)) if (outStream->GetPos() != unpSize) return E_FAIL; } chunk.BlockIndex = (int)blockIndex; _latestChunk = (int)chunkIndex; } _chunks[_latestChunk].AccessMark = _accessMark++; } _latestBlock = (int)blockIndex; } const CBlock &block = File->Blocks[(unsigned)_latestBlock]; const UInt64 offset = _virtPos - block.UnpPos; { const UInt64 rem = File->GetUnpackSize_of_Block((unsigned)_latestBlock) - offset; if (size > rem) size = (UInt32)rem; if (size == 0) // it's unexpected case. but we check it. return S_OK; } HRESULT res = S_OK; if (block.IsZeroMethod()) memset(data, 0, size); else if (_latestChunk >= 0) memcpy(data, _chunks[_latestChunk].Buf + (size_t)offset, size); else { if (block.Type != METHOD_COPY) return E_FAIL; RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos + offset)) res = Stream->Read(data, size, &size); } _virtPos += size; if (processedSize) *processedSize = size; return res; // COM_TRY_END } catch(...) { _errorMode = true; return E_OUTOFMEMORY; } } Z7_COM7F_IMF(CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { case STREAM_SEEK_SET: break; case STREAM_SEEK_CUR: offset += _virtPos; break; case STREAM_SEEK_END: offset += Size; break; default: return STG_E_INVALIDFUNCTION; } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; _virtPos = (UInt64)offset; if (newPosition) *newPosition = (UInt64)offset; return S_OK; } Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN #ifdef DMG_SHOW_RAW if (index >= _files.Size()) return S_FALSE; #endif CMyComPtr2 spec; spec.Create_if_Empty(); spec->File = &_files[index]; const CFile &file = *spec->File; if (!file.IsCorrect) return S_FALSE; FOR_VECTOR (i, file.Blocks) { const CBlock &block = file.Blocks[i]; if (!block.NeedAllocateBuffer()) continue; switch (block.Type) { #ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS case METHOD_COPY: break; #endif case METHOD_ADC: case METHOD_ZLIB: case METHOD_BZIP2: case METHOD_LZFSE: case METHOD_XZ: // case METHOD_END: if (file.GetUnpackSize_of_Block(i) > k_Chunk_Size_MAX) return S_FALSE; break; default: return S_FALSE; } } spec->Stream = _inStream; spec->Size = spec->File->Size; // RINOK( spec->Init(_startPos + _dataForkPair.Offset); *stream = spec.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( "Dmg", "dmg", NULL, 0xE4, k_Signature, 0, NArcInfoFlags::kBackwardOpen | NArcInfoFlags::kUseGlobalOffset, NULL) }}