// DmgHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.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/ZlibDecoder.h" #include "Common/OutStreamWithCRC.h" // #define DMG_SHOW_RAW // #include #define PRF(x) // x #define Get16(p) GetBe16(p) #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) static const Byte k_Base64Table[256] = { 66,77,77,77,77,77,77,77,77,65,65,77,77,65,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 65,77,77,77,77,77,77,77,77,77,77,62,77,77,77,63, 52,53,54,55,56,57,58,59,60,61,77,77,77,64,77,77, 77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, 15,16,17,18,19,20,21,22,23,24,25,77,77,77,77,77, 77,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, 41,42,43,44,45,46,47,48,49,50,51,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77 }; static Byte *Base64ToBin(Byte *dest, const char *src) { UInt32 val = 1; for (;;) { UInt32 c = k_Base64Table[(Byte)(*src++)]; if (c < 64) { val = (val << 6) | c; if ((val & ((UInt32)1 << 24)) == 0) continue; dest[0] = (Byte)(val >> 16); dest[1] = (Byte)(val >> 8); dest[2] = (Byte)(val); dest += 3; val = 1; continue; } if (c == 65) // space continue; if (c == 64) // '=' break; if (c == 66 && val == 1) // end of string return dest; return NULL; } if (val < (1 << 12)) return NULL; if (val & (1 << 18)) { *dest++ = (Byte)(val >> 10); *dest++ = (Byte)(val >> 2); } else if (k_Base64Table[(Byte)(*src++)] != 64) // '=' return NULL; else *dest++ = (Byte)(val >> 4); for (;;) { Byte c = k_Base64Table[(Byte)(*src++)]; if (c == 65) // space continue; if (c == 66) // end of string return dest; return NULL; } } namespace NArchive { namespace NDmg { enum { METHOD_ZERO_0 = 0, METHOD_COPY = 1, METHOD_ZERO_2 = 2, // without file CRC calculation METHOD_ADC = 0x80000004, METHOD_ZLIB = 0x80000005, METHOD_BZIP2 = 0x80000006, METHOD_COMMENT = 0x7FFFFFFE, // is used to comment "+beg" and "+end" in extra field. METHOD_END = 0xFFFFFFFF }; struct CBlock { UInt32 Type; UInt64 UnpPos; UInt64 UnpSize; UInt64 PackPos; UInt64 PackSize; UInt64 GetNextPackOffset() const { return PackPos + PackSize; } UInt64 GetNextUnpPos() const { return UnpPos + UnpSize; } bool IsZeroMethod() const { return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; } bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; } }; static const UInt32 kCheckSumType_CRC = 2; static const size_t 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 CChecksum::Parse(const Byte *p) { Type = Get32(p); NumBits = Get32(p + 4); memcpy(Data, p + 8, kChecksumSize_Max); }; struct CFile { UInt64 Size; UInt64 PackSize; UInt64 StartPos; AString Name; CRecordVector Blocks; CChecksum Checksum; bool FullFileChecksum; HRESULT Parse(const Byte *p, UInt32 size); }; #ifdef DMG_SHOW_RAW struct CExtraFile { CByteBuffer Data; AString Name; }; #endif class CHandler: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { CMyComPtr _inStream; CObjectVector _files; bool _masterCrcError; UInt64 _startPos; UInt64 _phySize; #ifdef DMG_SHOW_RAW CObjectVector _extras; #endif HRESULT Open2(IInStream *stream); HRESULT Extract(IInStream *stream); public: MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) INTERFACE_IInArchive(;) STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; // that limit can be increased, if there are such dmg files static const size_t kXmlSizeMax = 0xFFFF0000; // 4 GB - 64 KB; struct CMethods { CRecordVector Types; CRecordVector ChecksumTypes; void Update(const CFile &file); void GetString(AString &s) const; }; void CMethods::Update(const CFile &file) { ChecksumTypes.AddToUniqueSorted(file.Checksum.Type); FOR_VECTOR (i, file.Blocks) Types.AddToUniqueSorted(file.Blocks[i].Type); } void CMethods::GetString(AString &res) const { res.Empty(); unsigned i; for (i = 0; i < Types.Size(); i++) { UInt32 type = Types[i]; if (type == METHOD_COMMENT || type == METHOD_END) continue; char buf[16]; const char *s; switch (type) { 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; default: ConvertUInt32ToString(type, buf); s = buf; } res.Add_Space_if_NotEmpty(); res += s; } for (i = 0; i < ChecksumTypes.Size(); i++) { UInt32 type = ChecksumTypes[i]; char buf[32]; const char *s; switch (type) { case kCheckSumType_CRC: s = "CRC"; break; default: ConvertUInt32ToString(type, MyStpCpy(buf, "Check")); s = buf; } res.Add_Space_if_NotEmpty(); res += 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" }, { false, "free", "Apple_Free" }, { false, "ddm", "DDM" }, { false, NULL, "Apple_partition_map" }, { false, NULL, " GPT " }, { false, NULL, "MBR" }, { false, NULL, "Driver" }, { false, NULL, "Patches" } }; static const unsigned kNumAppleNames = ARRAY_SIZE(k_Names); static const Byte kProps[] = { kpidPath, kpidSize, kpidPackSize, kpidCRC, kpidComment, kpidMethod }; IMP_IInArchive_Props static const Byte kArcProps[] = { kpidMethod, kpidNumBlocks }; STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; switch (propID) { case kpidMethod: { CMethods m; FOR_VECTOR (i, _files) m.Update(_files[i]); AString s; m.GetString(s); if (!s.IsEmpty()) prop = s; break; } case kpidNumBlocks: { UInt64 numBlocks = 0; FOR_VECTOR (i, _files) numBlocks += _files[i].Blocks.Size(); prop = numBlocks; break; } case kpidMainSubfile: { int mainIndex = -1; unsigned numFS = 0; unsigned numUnknown = 0; FOR_VECTOR (i, _files) { const AString &name = _files[i].Name; unsigned n; for (n = 0; n < kNumAppleNames; n++) { const CAppleName &appleName = k_Names[n]; // if (name.Find(appleName.AppleName) >= 0) if (strstr(name, appleName.AppleName)) { if (appleName.IsFs) { numFS++; mainIndex = i; } break; } } if (n == kNumAppleNames) { mainIndex = i; numUnknown++; } } if (numFS + numUnknown == 1) prop = (UInt32)mainIndex; break; } case kpidWarning: if (_masterCrcError) prop = "Master CRC error"; break; case kpidOffset: prop = _startPos; break; case kpidPhySize: prop = _phySize; break; } prop.Detach(value); return S_OK; COM_TRY_END } IMP_IInArchive_ArcProps HRESULT CFile::Parse(const Byte *p, UInt32 size) { const UInt32 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; // UInt64 firstSectorNumber = Get64(p + 8); UInt64 numSectors = Get64(p + 0x10); StartPos = Get64(p + 0x18); // UInt32 decompressedBufRequested = Get32(p + 0x20); // ??? // UInt32 blocksDescriptor = Get32(p + 0x24); // number starting from -1? // char Reserved1[24]; Checksum.Parse(p + 0x40); PRF(printf("\n\nChecksum Type = %2d", Checksum.Type)); UInt32 numBlocks = Get32(p + 0xC8); if (numBlocks > ((UInt32)1 << 28)) return S_FALSE; const UInt32 kRecordSize = 40; if (numBlocks * kRecordSize + kHeadSize != size) return S_FALSE; PackSize = 0; Size = 0; Blocks.ClearAndReserve(numBlocks); FullFileChecksum = true; p += kHeadSize; UInt32 i; for (i = 0; i < numBlocks; i++, p += kRecordSize) { CBlock b; b.Type = Get32(p); b.UnpPos = Get64(p + 0x08) << 9; b.UnpSize = Get64(p + 0x10) << 9; 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 (!Blocks.IsEmpty()) if (b.UnpPos != Blocks.Back().GetNextUnpPos()) return S_FALSE; PRF(printf("\nType=%8x m[1]=%8x uPos=%8x uSize=%7x pPos=%8x pSize=%7x", b.Type, Get32(p + 4), (UInt32)b.UnpPos, (UInt32)b.UnpSize, (UInt32)b.PackPos, (UInt32)b.PackSize)); if (b.Type == METHOD_COMMENT) continue; if (b.Type == METHOD_END) break; PackSize += b.PackSize; if (b.UnpSize != 0) { if (b.Type == METHOD_ZERO_2) FullFileChecksum = false; Blocks.AddInReserved(b); } } if (i != numBlocks - 1) return S_FALSE; if (!Blocks.IsEmpty()) Size = Blocks.Back().GetNextUnpPos(); if (Size != (numSectors << 9)) return S_FALSE; return S_OK; } static int FindKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag) { for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++) { const CXmlItem &si = item.SubItems[i]; if (si.IsTagged("key") && si.GetSubString() == key && item.SubItems[i + 1].IsTagged(nextTag)) return i + 1; } return -1; } static const AString *GetStringFromKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag) { int index = FindKeyPair(item, key, nextTag); if (index >= 0) return item.SubItems[index].GetSubStringPtr(); return NULL; } static const unsigned HEADER_SIZE = 0x200; 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, 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::Open2(IInStream *stream) { RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_startPos)); Byte buf[HEADER_SIZE]; RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)); UInt64 headerPos; if (IsKoly(buf)) headerPos = _startPos; else { RINOK(stream->Seek(0, STREAM_SEEK_END, &headerPos)); if (headerPos < HEADER_SIZE) return S_FALSE; headerPos -= HEADER_SIZE; RINOK(stream->Seek(headerPos, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)); if (!IsKoly(buf)) return S_FALSE; } // UInt32 flags = Get32(buf + 12); // UInt64 runningDataForkOffset = Get64(buf + 0x10); UInt64 dataForkOffset = Get64(buf + 0x18); UInt64 dataForkLen = Get64(buf + 0x20); UInt64 rsrcOffset = Get64(buf + 0x28); UInt64 rsrcLen = Get64(buf + 0x30); // UInt32 segmentNumber = Get32(buf + 0x38); // UInt32 segmentCount = Get32(buf + 0x3C); // Byte segmentGUID[16]; // CChecksum dataForkChecksum; // dataForkChecksum.Parse(buf + 0x50); UInt64 xmlOffset = Get64(buf + 0xD8); UInt64 xmlLen = Get64(buf + 0xE0); if ( headerPos < dataForkOffset || headerPos - dataForkOffset < dataForkLen || headerPos < rsrcOffset || headerPos - rsrcOffset < rsrcLen || headerPos < xmlOffset || headerPos - xmlOffset < xmlLen) return S_FALSE; UInt64 totalLen = dataForkLen + rsrcLen + xmlLen; if (totalLen > headerPos) return S_FALSE; _startPos = headerPos - totalLen; _phySize = totalLen + HEADER_SIZE; headerPos = totalLen; // Byte reserved[0x78] CChecksum masterChecksum; masterChecksum.Parse(buf + 0x160); // UInt32 imageVariant = Get32(buf + 0x1E8); // UInt64 numSectors = Get64(buf + 0x1EC); // Byte reserved[0x12] const UInt32 RSRC_HEAD_SIZE = 0x100; // 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 rsrcLen < (1 << 24). bool useRsrc = (rsrcLen > RSRC_HEAD_SIZE && rsrcLen < ((UInt32)1 << 24)); // useRsrc = false; if (useRsrc) { #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "rsrc.bin"; CByteBuffer &rsrcBuf = extra.Data; #else CByteBuffer rsrcBuf; #endif size_t rsrcLenT = (size_t)rsrcLen; rsrcBuf.Alloc(rsrcLenT); RINOK(stream->Seek(_startPos + rsrcOffset, STREAM_SEEK_SET, NULL)); RINOK(ReadStream_FALSE(stream, rsrcBuf, rsrcLenT)); const Byte *p = rsrcBuf; UInt32 headSize = Get32(p + 0); UInt32 footerOffset = Get32(p + 4); UInt32 mainDataSize = Get32(p + 8); UInt32 footerSize = Get32(p + 12); if (headSize != RSRC_HEAD_SIZE || footerOffset >= rsrcLenT || mainDataSize >= rsrcLenT || footerOffset + footerSize != rsrcLenT || footerOffset != headSize + mainDataSize) return S_FALSE; if (footerSize < 16) return S_FALSE; if (memcmp(p, p + footerOffset, 16) != 0) return S_FALSE; p += footerOffset; if ((UInt32)Get16(p + 0x18) != 0x1C) return S_FALSE; UInt32 namesOffset = Get16(p + 0x1A); if (namesOffset > footerSize) return S_FALSE; 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 + i * 8; UInt32 typeId = Get32(p2); if (typeId != 0x626C6B78) // blkx continue; UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1; 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); UInt32 namePos = Get16(p3 + 2); // Byte attributes = p3[4]; // = 0x50 for blkx // 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; UInt32 blockSize = Get32(pBlock); #ifdef DMG_SHOW_RAW { CExtraFile &extra = _extras.AddNew(); { char extraName[16]; ConvertUInt32ToString(_files.Size(), extraName); extra.Name = extraName; } extra.Data.CopyFrom(pBlock + 4, blockSize); } #endif CFile &file = _files.AddNew(); if (namePos != 0xFFFF) { UInt32 namesBlockSize = footerSize - namesOffset; if (namePos >= namesBlockSize) return S_FALSE; const Byte *namePtr = p + namesOffset + namePos; UInt32 nameLen = *namePtr; if (namesBlockSize - namePos <= nameLen) return S_FALSE; for (UInt32 r = 1; r <= nameLen; r++) { Byte c = namePtr[r]; if (c < 0x20 || c >= 0x80) break; file.Name += (char)c; } } RINOK(file.Parse(pBlock + 4, blockSize)); } } } else { if (xmlLen >= kXmlSizeMax || xmlLen == 0) return S_FALSE; size_t size = (size_t)xmlLen; if (size != xmlLen) return S_FALSE; RINOK(stream->Seek(_startPos + dataForkLen, STREAM_SEEK_SET, NULL)); CXml xml; { CObjArray xmlStr(size + 1); RINOK(ReadStream_FALSE(stream, xmlStr, size)); xmlStr[size] = 0; // if (strlen(xmlStr) != size) return S_FALSE; if (!xml.Parse(xmlStr)) return S_FALSE; #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "a.xml"; extra.Data.CopyFrom((const Byte *)(const char *)xmlStr, size); #endif } if (xml.Root.Name != "plist") return S_FALSE; int dictIndex = xml.Root.FindSubTag("dict"); if (dictIndex < 0) return S_FALSE; const CXmlItem &dictItem = xml.Root.SubItems[dictIndex]; int rfDictIndex = FindKeyPair(dictItem, "resource-fork", "dict"); if (rfDictIndex < 0) return S_FALSE; const CXmlItem &rfDictItem = dictItem.SubItems[rfDictIndex]; int arrIndex = FindKeyPair(rfDictItem, "blkx", "array"); if (arrIndex < 0) return S_FALSE; const CXmlItem &arrItem = rfDictItem.SubItems[arrIndex]; 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 - rawBuf); } #ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); { char extraName[16]; ConvertUInt32ToString(_files.Size(), extraName); extra.Name = extraName; } extra.Data.CopyFrom(rawBuf, destLen); #endif } CFile &file = _files.AddNew(); { const AString *name = GetStringFromKeyPair(item, "Name", "string"); if (!name || name->IsEmpty()) name = GetStringFromKeyPair(item, "CFName", "string"); if (name) file.Name = *name; } RINOK(file.Parse(rawBuf, destLen)); } } 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; 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()); } return S_OK; } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback * /* openArchiveCallback */) { COM_TRY_BEGIN { Close(); if (Open2(stream) != S_OK) return S_FALSE; _inStream = stream; } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _phySize = 0; _inStream.Release(); _files.Clear(); _masterCrcError = false; #ifdef DMG_SHOW_RAW _extras.Clear(); #endif return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _files.Size() #ifdef DMG_SHOW_RAW + _extras.Size() #endif ; return S_OK; } #define RAW_PREFIX "raw" STRING_PATH_SEPARATOR STDMETHODIMP 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 kpidMethod: { CMethods m; m.Update(item); AString s; m.GetString(s); if (!s.IsEmpty()) prop = s; break; } case kpidPath: { UString name; wchar_t s[16]; ConvertUInt32ToString(index, s); name = s; 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++; 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); } } subName.Trim(); if (!subName.IsEmpty()) { for (unsigned n = 0; n < kNumAppleNames; n++) { const CAppleName &appleName = k_Names[n]; if (appleName.Ext) { if (subName == appleName.AppleName) { subName = appleName.Ext; break; } } } UString name2; ConvertUTF8ToUnicode(subName, name2); name += L'.'; name += name2; } else { UString name2; ConvertUTF8ToUnicode(item.Name, name2); if (!name2.IsEmpty()) name.AddAscii(" - "); name += name2; } prop = name; break; } case kpidComment: { UString name; ConvertUTF8ToUnicode(item.Name, name); prop = name; break; } } } prop.Detach(value); return S_OK; COM_TRY_END } class CAdcDecoder: public ICompressCoder, public CMyUnknownImp { CLzOutWindow m_OutWindowStream; CInBuffer m_InStream; /* void ReleaseStreams() { m_OutWindowStream.ReleaseStream(); m_InStream.ReleaseStream(); } */ class CCoderReleaser { CAdcDecoder *m_Coder; public: bool NeedFlush; CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {} ~CCoderReleaser() { if (NeedFlush) m_Coder->m_OutWindowStream.Flush(); // m_Coder->ReleaseStreams(); } }; friend class CCoderReleaser; public: MY_UNKNOWN_IMP STDMETHOD(CodeReal)(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); }; STDMETHODIMP CAdcDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) { if (!m_OutWindowStream.Create(1 << 18)) 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 << 20); UInt64 nextLimit = kStep; UInt64 pos = 0; while (pos < *outSize) { if (pos > nextLimit && progress) { UInt64 packSize = m_InStream.GetProcessedSize(); RINOK(progress->SetRatioInfo(&packSize, &pos)); nextLimit += kStep; } Byte b; if (!m_InStream.ReadByte(b)) return S_FALSE; UInt64 rem = *outSize - pos; if (b & 0x80) { unsigned num = (b & 0x7F) + 1; if (num > rem) return S_FALSE; for (unsigned i = 0; i < num; i++) { if (!m_InStream.ReadByte(b)) return S_FALSE; m_OutWindowStream.PutByte(b); } pos += num; continue; } Byte b1; if (!m_InStream.ReadByte(b1)) return S_FALSE; UInt32 len, distance; if (b & 0x40) { len = ((UInt32)b & 0x3F) + 4; Byte b2; if (!m_InStream.ReadByte(b2)) return S_FALSE; distance = ((UInt32)b1 << 8) + b2; } else { b &= 0x3F; len = ((UInt32)b >> 2) + 3; distance = (((UInt32)b & 3) << 8) + b1; } if (distance >= pos || len > rem) return S_FALSE; m_OutWindowStream.CopyBlock(distance, len); pos += len; } if (*inSize != m_InStream.GetProcessedSize()) return S_FALSE; coderReleaser.NeedFlush = false; return m_OutWindowStream.Flush(); } STDMETHODIMP CAdcDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) { try { return CodeReal(inStream, outStream, inSize, outSize, progress);} catch(const CInBufferException &e) { return e.ErrorCode; } catch(const CLzOutWindowException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _files.Size(); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { 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; } extractCallback->SetTotal(totalSize); UInt64 currentPackTotal = 0; UInt64 currentUnpTotal = 0; UInt64 currentPackSize = 0; UInt64 currentUnpSize = 0; const UInt32 kZeroBufSize = (1 << 14); CByteBuffer zeroBuf(kZeroBufSize); memset(zeroBuf, 0, kZeroBufSize); NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); CMyComPtr copyCoder = copyCoderSpec; NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); CMyComPtr bzip2Coder = bzip2CoderSpec; NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); CMyComPtr zlibCoder = zlibCoderSpec; CAdcDecoder *adcCoderSpec = new CAdcDecoder(); CMyComPtr adcCoder = adcCoderSpec; CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, false); CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr inStream(streamSpec); streamSpec->SetStream(_inStream); for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize) { lps->InSize = currentPackTotal; lps->OutSize = currentUnpTotal; currentPackSize = 0; currentUnpSize = 0; RINOK(lps->SetCur()); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; UInt32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); COutStreamWithCRC *outCrcStreamSpec = new COutStreamWithCRC; CMyComPtr outCrcStream = outCrcStreamSpec; outCrcStreamSpec->SetStream(realOutStream); bool needCrc = false; outCrcStreamSpec->Init(needCrc); CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream; CMyComPtr outStream(outStreamSpec); outStreamSpec->SetStream(outCrcStream); realOutStream.Release(); Int32 opRes = NExtract::NOperationResult::kOK; #ifdef DMG_SHOW_RAW if (index >= _files.Size()) { const CByteBuffer &buf = _extras[index - _files.Size()].Data; outStreamSpec->Init(buf.Size()); RINOK(WriteStream(outStream, buf, buf.Size())); currentPackSize = currentUnpSize = buf.Size(); } else #endif { const CFile &item = _files[index]; currentPackSize = item.PackSize; currentUnpSize = item.Size; needCrc = item.Checksum.IsCrc32(); UInt64 unpPos = 0; UInt64 packPos = 0; { FOR_VECTOR (j, item.Blocks) { lps->InSize = currentPackTotal + packPos; lps->OutSize = currentUnpTotal + unpPos; RINOK(lps->SetCur()); const CBlock &block = item.Blocks[j]; if (!block.ThereAreDataInBlock()) continue; packPos += block.PackSize; if (block.UnpPos != unpPos) { opRes = NExtract::NOperationResult::kDataError; break; } RINOK(_inStream->Seek(_startPos + item.StartPos + block.PackPos, STREAM_SEEK_SET, NULL)); streamSpec->Init(block.PackSize); bool realMethod = true; outStreamSpec->Init(block.UnpSize); HRESULT res = S_OK; outCrcStreamSpec->EnableCalc(needCrc); switch (block.Type) { case METHOD_ZERO_0: case METHOD_ZERO_2: realMethod = false; if (block.PackSize != 0) opRes = NExtract::NOperationResult::kUnsupportedMethod; outCrcStreamSpec->EnableCalc(block.Type == METHOD_ZERO_0); break; case METHOD_COPY: if (block.UnpSize != block.PackSize) { opRes = NExtract::NOperationResult::kUnsupportedMethod; break; } res = copyCoder->Code(inStream, outStream, NULL, NULL, progress); break; case METHOD_ADC: { res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, progress); break; } case METHOD_ZLIB: { res = zlibCoder->Code(inStream, outStream, NULL, NULL, progress); if (res == S_OK) if (zlibCoderSpec->GetInputProcessedSize() != block.PackSize) opRes = NExtract::NOperationResult::kDataError; break; } case METHOD_BZIP2: { res = bzip2Coder->Code(inStream, outStream, NULL, NULL, progress); if (res == S_OK) if (bzip2CoderSpec->GetInputProcessedSize() != block.PackSize) opRes = NExtract::NOperationResult::kDataError; break; } default: opRes = NExtract::NOperationResult::kUnsupportedMethod; break; } if (res != S_OK) { if (res != S_FALSE) return res; if (opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; } unpPos += block.UnpSize; if (!outStreamSpec->IsFinishedOK()) { if (realMethod && opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; while (outStreamSpec->GetRem() != 0) { UInt64 rem = outStreamSpec->GetRem(); UInt32 size = (UInt32)MyMin(rem, (UInt64)kZeroBufSize); RINOK(WriteStream(outStream, zeroBuf, size)); } } } } if (needCrc && opRes == NExtract::NOperationResult::kOK) { if (outCrcStreamSpec->GetCRC() != item.Checksum.GetCrc32()) opRes = NExtract::NOperationResult::kCRCError; } } outStream.Release(); RINOK(extractCallback->SetOperationResult(opRes)); } return S_OK; COM_TRY_END } struct CChunk { int BlockIndex; UInt64 AccessMark; CByteBuffer Buf; }; class CInStream: public IInStream, public CMyUnknownImp { UInt64 _virtPos; int _latestChunk; int _latestBlock; UInt64 _accessMark; CObjectVector _chunks; NCompress::NBZip2::CDecoder *bzip2CoderSpec; CMyComPtr bzip2Coder; NCompress::NZlib::CDecoder *zlibCoderSpec; CMyComPtr zlibCoder; CAdcDecoder *adcCoderSpec; CMyComPtr adcCoder; CBufPtrSeqOutStream *outStreamSpec; CMyComPtr outStream; CLimitedSequentialInStream *limitedStreamSpec; CMyComPtr inStream; public: CMyComPtr Stream; UInt64 Size; const CFile *File; UInt64 _startPos; HRESULT InitAndSeek(UInt64 startPos) { _startPos = startPos; _virtPos = 0; _latestChunk = -1; _latestBlock = -1; _accessMark = 0; limitedStreamSpec = new CLimitedSequentialInStream; inStream = limitedStreamSpec; limitedStreamSpec->SetStream(Stream); outStreamSpec = new CBufPtrSeqOutStream; outStream = outStreamSpec; return S_OK; } MY_UNKNOWN_IMP1(IInStream) STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; unsigned FindBlock(const CRecordVector &blocks, UInt64 pos) { unsigned left = 0, right = blocks.Size(); for (;;) { unsigned mid = (left + right) / 2; if (mid == left) return left; if (pos < blocks[mid].UnpPos) right = mid; else left = mid; } } STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) { COM_TRY_BEGIN if (processedSize) *processedSize = 0; if (size == 0) return S_OK; if (_virtPos >= Size) return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL; { UInt64 rem = Size - _virtPos; if (size > rem) size = (UInt32)rem; } if (_latestBlock >= 0) { const CBlock &block = File->Blocks[_latestBlock]; if (_virtPos < block.UnpPos || (_virtPos - block.UnpPos) >= block.UnpSize) _latestBlock = -1; } if (_latestBlock < 0) { _latestChunk = -1; unsigned blockIndex = FindBlock(File->Blocks, _virtPos); const CBlock &block = File->Blocks[blockIndex]; if (!block.IsZeroMethod() && block.Type != METHOD_COPY) { unsigned i; for (i = 0; i < _chunks.Size(); i++) if (_chunks[i].BlockIndex == (int)blockIndex) break; if (i != _chunks.Size()) _latestChunk = i; else { const unsigned kNumChunksMax = 128; unsigned chunkIndex; if (_chunks.Size() != kNumChunksMax) chunkIndex = _chunks.Add(CChunk()); else { chunkIndex = 0; for (i = 0; i < _chunks.Size(); i++) if (_chunks[i].AccessMark < _chunks[chunkIndex].AccessMark) chunkIndex = i; } CChunk &chunk = _chunks[chunkIndex]; chunk.BlockIndex = -1; chunk.AccessMark = 0; if (chunk.Buf.Size() < block.UnpSize) { chunk.Buf.Free(); if (block.UnpSize > ((UInt32)1 << 31)) return E_FAIL; chunk.Buf.Alloc((size_t)block.UnpSize); } outStreamSpec->Init(chunk.Buf, (size_t)block.UnpSize); RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos, STREAM_SEEK_SET, NULL)); limitedStreamSpec->Init(block.PackSize); HRESULT res = S_OK; switch (block.Type) { case METHOD_COPY: if (block.PackSize != block.UnpSize) return E_FAIL; res = ReadStream_FAIL(inStream, chunk.Buf, (size_t)block.UnpSize); break; case METHOD_ADC: if (!adcCoder) { adcCoderSpec = new CAdcDecoder(); adcCoder = adcCoderSpec; } res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, NULL); break; case METHOD_ZLIB: if (!zlibCoder) { zlibCoderSpec = new NCompress::NZlib::CDecoder(); zlibCoder = zlibCoderSpec; } res = zlibCoder->Code(inStream, outStream, NULL, NULL, NULL); if (res == S_OK && zlibCoderSpec->GetInputProcessedSize() != block.PackSize) res = S_FALSE; break; case METHOD_BZIP2: if (!bzip2Coder) { bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); bzip2Coder = bzip2CoderSpec; } res = bzip2Coder->Code(inStream, outStream, NULL, NULL, NULL); if (res == S_OK && bzip2CoderSpec->GetInputProcessedSize() != block.PackSize) res = S_FALSE; break; default: return E_FAIL; } if (res != S_OK) return res; if (block.Type != METHOD_COPY && outStreamSpec->GetPos() != block.UnpSize) return E_FAIL; chunk.BlockIndex = blockIndex; _latestChunk = chunkIndex; } _chunks[_latestChunk].AccessMark = _accessMark++; } _latestBlock = blockIndex; } const CBlock &block = File->Blocks[_latestBlock]; UInt64 offset = _virtPos - block.UnpPos; UInt64 rem = block.UnpSize - offset; if (size > rem) size = (UInt32)rem; HRESULT res = S_OK; if (block.Type == METHOD_COPY) { RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos + offset, STREAM_SEEK_SET, NULL)); res = Stream->Read(data, size, &size); } else if (block.IsZeroMethod()) memset(data, 0, size); else if (size != 0) memcpy(data, _chunks[_latestChunk].Buf + offset, size); _virtPos += size; if (processedSize) *processedSize = size; return res; COM_TRY_END } STDMETHODIMP 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 = offset; if (newPosition) *newPosition = offset; return S_OK; } STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { COM_TRY_BEGIN #ifdef DMG_SHOW_RAW if (index >= (UInt32)_files.Size()) return S_FALSE; #endif CInStream *spec = new CInStream; CMyComPtr specStream = spec; spec->File = &_files[index]; const CFile &file = *spec->File; FOR_VECTOR (i, file.Blocks) { const CBlock &block = file.Blocks[i]; switch (block.Type) { case METHOD_ZERO_0: case METHOD_ZERO_2: case METHOD_COPY: case METHOD_ADC: case METHOD_ZLIB: case METHOD_BZIP2: case METHOD_END: break; default: return S_FALSE; } } spec->Stream = _inStream; spec->Size = spec->File->Size; RINOK(spec->InitAndSeek(_startPos)); *stream = specStream.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( "Dmg", "dmg", 0, 0xE4, k_Signature, 0, NArcInfoFlags::kBackwardOpen | NArcInfoFlags::kUseGlobalOffset, NULL) }}