// VhdHandler.cpp #include "StdAfx.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../../Windows/PropVariant.h" #include "../Common/LimitedStreams.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "HandlerCont.h" #define Get16(p) GetBe16(p) #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) #define G32(_offs_, dest) dest = Get32(p + (_offs_)) #define G64(_offs_, dest) dest = Get64(p + (_offs_)) using namespace NWindows; namespace NArchive { namespace NVhd { static const unsigned kSignatureSize = 10; static const Byte kSignature[kSignatureSize] = { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 }; static const UInt32 kUnusedBlock = 0xFFFFFFFF; static const UInt32 kDiskType_Fixed = 2; static const UInt32 kDiskType_Dynamic = 3; static const UInt32 kDiskType_Diff = 4; static const char * const kDiskTypes[] = { "0" , "1" , "Fixed" , "Dynamic" , "Differencing" }; struct CFooter { // UInt32 Features; // UInt32 FormatVersion; UInt64 DataOffset; UInt32 CTime; UInt32 CreatorApp; UInt32 CreatorVersion; UInt32 CreatorHostOS; // UInt64 OriginalSize; UInt64 CurrentSize; UInt32 DiskGeometry; UInt32 Type; Byte Id[16]; Byte SavedState; bool IsFixed() const { return Type == kDiskType_Fixed; } bool ThereIsDynamic() const { return Type == kDiskType_Dynamic || Type == kDiskType_Diff; } // bool IsSupported() const { return Type == kDiskType_Fixed || Type == kDiskType_Dynamic || Type == kDiskType_Diff; } UInt32 NumCyls() const { return DiskGeometry >> 16; } UInt32 NumHeads() const { return (DiskGeometry >> 8) & 0xFF; } UInt32 NumSectorsPerTrack() const { return DiskGeometry & 0xFF; } void AddTypeString(AString &s) const; bool Parse(const Byte *p); }; void CFooter::AddTypeString(AString &s) const { if (Type < Z7_ARRAY_SIZE(kDiskTypes)) s += kDiskTypes[Type]; else s.Add_UInt32(Type); } static bool CheckBlock(const Byte *p, unsigned size, unsigned checkSumOffset, unsigned zeroOffset) { UInt32 sum = 0; unsigned i; for (i = 0; i < checkSumOffset; i++) sum += p[i]; for (i = checkSumOffset + 4; i < size; i++) sum += p[i]; if (~sum != Get32(p + checkSumOffset)) return false; for (i = zeroOffset; i < size; i++) if (p[i] != 0) return false; return true; } static const unsigned kSectorSize_Log = 9; static const unsigned kSectorSize = 1 << kSectorSize_Log; static const unsigned kHeaderSize = 512; bool CFooter::Parse(const Byte *p) { if (memcmp(p, kSignature, kSignatureSize) != 0) return false; // G32(0x08, Features); // G32(0x0C, FormatVersion); G64(0x10, DataOffset); G32(0x18, CTime); G32(0x1C, CreatorApp); G32(0x20, CreatorVersion); G32(0x24, CreatorHostOS); // G64(0x28, OriginalSize); G64(0x30, CurrentSize); G32(0x38, DiskGeometry); G32(0x3C, Type); if (Type < kDiskType_Fixed || Type > kDiskType_Diff) return false; memcpy(Id, p + 0x44, 16); SavedState = p[0x54]; // if (DataOffset > ((UInt64)1 << 62)) return false; // if (CurrentSize > ((UInt64)1 << 62)) return false; return CheckBlock(p, kHeaderSize, 0x40, 0x55); } struct CParentLocatorEntry { UInt32 Code; UInt32 DataSpace; UInt32 DataLen; UInt64 DataOffset; bool Parse(const Byte *p) { G32(0x00, Code); G32(0x04, DataSpace); G32(0x08, DataLen); G64(0x10, DataOffset); return Get32(p + 0x0C) == 0; // Reserved } }; struct CDynHeader { // UInt64 DataOffset; UInt64 TableOffset; // UInt32 HeaderVersion; UInt32 NumBlocks; unsigned BlockSizeLog; UInt32 ParentTime; Byte ParentId[16]; bool RelativeNameWasUsed; UString ParentName; UString RelativeParentNameFromLocator; CParentLocatorEntry ParentLocators[8]; bool Parse(const Byte *p); UInt32 NumBitMapSectors() const { UInt32 numSectorsInBlock = (1 << (BlockSizeLog - kSectorSize_Log)); return (numSectorsInBlock + kSectorSize * 8 - 1) / (kSectorSize * 8); } void Clear() { RelativeNameWasUsed = false; ParentName.Empty(); RelativeParentNameFromLocator.Empty(); } }; bool CDynHeader::Parse(const Byte *p) { if (memcmp(p, "cxsparse", 8) != 0) return false; // G64(0x08, DataOffset); G64(0x10, TableOffset); // G32(0x18, HeaderVersion); G32(0x1C, NumBlocks); { UInt32 blockSize = Get32(p + 0x20); unsigned i; for (i = kSectorSize_Log;; i++) { if (i > 31) return false; if (((UInt32)1 << i) == blockSize) break; } BlockSizeLog = i; } G32(0x38, ParentTime); if (Get32(p + 0x3C) != 0) // reserved return false; memcpy(ParentId, p + 0x28, 16); { const unsigned kNameLen = 256; wchar_t *s = ParentName.GetBuf(kNameLen); unsigned i; for (i = 0; i < kNameLen; i++) { wchar_t c = Get16(p + 0x40 + i * 2); if (c == 0) break; s[i] = c; } s[i] = 0; ParentName.ReleaseBuf_SetLen(i); } for (unsigned i = 0; i < 8; i++) if (!ParentLocators[i].Parse(p + 0x240 + i * 24)) return false; return CheckBlock(p, 1024, 0x24, 0x240 + 8 * 24); } Z7_class_CHandler_final: public CHandlerImg { UInt64 _posInArcLimit; UInt64 _startOffset; UInt64 _phySize; CFooter Footer; CDynHeader Dyn; CRecordVector Bat; CByteBuffer BitMap; UInt32 BitMapTag; UInt32 NumUsedBlocks; CMyComPtr ParentStream; CHandler *Parent; UInt64 NumLevels; UString _errorMessage; // bool _unexpectedEnd; void AddErrorMessage(const char *message, const wchar_t *name = NULL) { if (!_errorMessage.IsEmpty()) _errorMessage.Add_LF(); _errorMessage += message; if (name) _errorMessage += name; } void UpdatePhySize(UInt64 value) { if (_phySize < value) _phySize = value; } HRESULT Seek2(UInt64 offset); HRESULT InitAndSeek(); HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size); bool NeedParent() const { return Footer.Type == kDiskType_Diff; } UInt64 GetPackSize() const { return Footer.ThereIsDynamic() ? ((UInt64)NumUsedBlocks << Dyn.BlockSizeLog) : Footer.CurrentSize; } UString GetParentSequence() const { const CHandler *p = this; UString res; while (p && p->NeedParent()) { if (!res.IsEmpty()) res += " -> "; UString mainName; UString anotherName; if (Dyn.RelativeNameWasUsed) { mainName = p->Dyn.RelativeParentNameFromLocator; anotherName = p->Dyn.ParentName; } else { mainName = p->Dyn.ParentName; anotherName = p->Dyn.RelativeParentNameFromLocator; } res += mainName; if (mainName != anotherName && !anotherName.IsEmpty()) { res.Add_Space(); res.Add_Char('('); res += anotherName; res.Add_Char(')'); } p = p->Parent; } return res; } bool AreParentsOK() const { const CHandler *p = this; while (p->NeedParent()) { p = p->Parent; if (!p) return false; } return true; } HRESULT Open3(); HRESULT Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level); HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override { return Open2(stream, NULL, openArchiveCallback, 0); } void CloseAtError() Z7_override; public: Z7_IFACE_COM7_IMP(IInArchive_Img) Z7_IFACE_COM7_IMP(IInArchiveGetStream) Z7_IFACE_COM7_IMP(ISequentialInStream) }; HRESULT CHandler::Seek2(UInt64 offset) { return InStream_SeekSet(Stream, _startOffset + offset); } HRESULT CHandler::InitAndSeek() { if (ParentStream) { RINOK(Parent->InitAndSeek()) } _virtPos = _posInArc = 0; BitMapTag = kUnusedBlock; BitMap.Alloc(Dyn.NumBitMapSectors() << kSectorSize_Log); return Seek2(0); } HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size) { if (offset + size > _posInArcLimit) return S_FALSE; if (offset != _posInArc) { _posInArc = offset; RINOK(Seek2(offset)) } HRESULT res = ReadStream_FALSE(Stream, data, size); if (res == S_OK) _posInArc += size; else Reset_PosInArc(); return res; } HRESULT CHandler::Open3() { // Fixed archive uses only footer UInt64 startPos; RINOK(InStream_GetPos(Stream, startPos)) _startOffset = startPos; Byte header[kHeaderSize]; RINOK(ReadStream_FALSE(Stream, header, kHeaderSize)) bool headerIsOK = Footer.Parse(header); _size = Footer.CurrentSize; if (headerIsOK && !Footer.ThereIsDynamic()) { // fixed archive if (startPos < Footer.CurrentSize) return S_FALSE; _posInArcLimit = Footer.CurrentSize; _phySize = Footer.CurrentSize + kHeaderSize; _startOffset = startPos - Footer.CurrentSize; _posInArc = _phySize; return S_OK; } UInt64 fileSize; RINOK(InStream_GetSize_SeekToEnd(Stream, fileSize)) if (fileSize < kHeaderSize) return S_FALSE; const UInt32 kDynSize = 1024; Byte buf[kDynSize]; RINOK(InStream_SeekSet(Stream, fileSize - kHeaderSize)) RINOK(ReadStream_FALSE(Stream, buf, kHeaderSize)) if (!headerIsOK) { if (!Footer.Parse(buf)) return S_FALSE; _size = Footer.CurrentSize; if (Footer.ThereIsDynamic()) return S_FALSE; // we can't open Dynamic Archive backward. // fixed archive _posInArcLimit = Footer.CurrentSize; _phySize = Footer.CurrentSize + kHeaderSize; _startOffset = fileSize - kHeaderSize - Footer.CurrentSize; _posInArc = _phySize; return S_OK; } _phySize = kHeaderSize; _posInArc = fileSize - startPos; _posInArcLimit = _posInArc - kHeaderSize; bool headerAndFooterAreEqual = false; if (memcmp(header, buf, kHeaderSize) == 0) { headerAndFooterAreEqual = true; _phySize = fileSize - _startOffset; } RINOK(ReadPhy(Footer.DataOffset, buf, kDynSize)) if (!Dyn.Parse(buf)) return S_FALSE; UpdatePhySize(Footer.DataOffset + kDynSize); for (int i = 0; i < 8; i++) { const CParentLocatorEntry &locator = Dyn.ParentLocators[i]; const UInt32 kNameBufSizeMax = 1024; if (locator.DataLen < kNameBufSizeMax && locator.DataOffset < _posInArcLimit && locator.DataOffset + locator.DataLen <= _posInArcLimit) { if (locator.Code == 0x57327275 && (locator.DataLen & 1) == 0) { // "W2ru" locator // Path is encoded as little-endian UTF-16 Byte nameBuf[kNameBufSizeMax]; UString tempString; unsigned len = (locator.DataLen >> 1); { wchar_t *s = tempString.GetBuf(len); RINOK(ReadPhy(locator.DataOffset, nameBuf, locator.DataLen)) unsigned j; for (j = 0; j < len; j++) { wchar_t c = GetUi16(nameBuf + j * 2); if (c == 0) break; s[j] = c; } s[j] = 0; tempString.ReleaseBuf_SetLen(j); } if (tempString[0] == L'.' && tempString[1] == L'\\') tempString.DeleteFrontal(2); Dyn.RelativeParentNameFromLocator = tempString; } } if (locator.DataLen != 0) UpdatePhySize(locator.DataOffset + locator.DataLen); } if (Dyn.NumBlocks >= (UInt32)1 << 31) return S_FALSE; if (Footer.CurrentSize == 0) { if (Dyn.NumBlocks != 0) return S_FALSE; } else if (((Footer.CurrentSize - 1) >> Dyn.BlockSizeLog) + 1 != Dyn.NumBlocks) return S_FALSE; Bat.ClearAndReserve(Dyn.NumBlocks); UInt32 bitmapSize = Dyn.NumBitMapSectors() << kSectorSize_Log; while ((UInt32)Bat.Size() < Dyn.NumBlocks) { RINOK(ReadPhy(Dyn.TableOffset + (UInt64)Bat.Size() * 4, buf, kSectorSize)) UpdatePhySize(Dyn.TableOffset + kSectorSize); for (UInt32 j = 0; j < kSectorSize; j += 4) { UInt32 v = Get32(buf + j); if (v != kUnusedBlock) { UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog; UpdatePhySize(((UInt64)v << kSectorSize_Log) + bitmapSize + blockSize); NumUsedBlocks++; } Bat.AddInReserved(v); if ((UInt32)Bat.Size() >= Dyn.NumBlocks) break; } } if (headerAndFooterAreEqual) return S_OK; if (_startOffset + _phySize + kHeaderSize > fileSize) { // _unexpectedEnd = true; _posInArcLimit = _phySize; _phySize += kHeaderSize; return S_OK; } RINOK(ReadPhy(_phySize, buf, kHeaderSize)) if (memcmp(header, buf, kHeaderSize) == 0) { _posInArcLimit = _phySize; _phySize += kHeaderSize; return S_OK; } if (_phySize == 0x800) { /* WHY does empty archive contain additional empty sector? We skip that sector and check footer again. */ unsigned i; for (i = 0; i < kSectorSize && buf[i] == 0; i++); if (i == kSectorSize) { RINOK(ReadPhy(_phySize + kSectorSize, buf, kHeaderSize)) if (memcmp(header, buf, kHeaderSize) == 0) { _phySize += kSectorSize; _posInArcLimit = _phySize; _phySize += kHeaderSize; return S_OK; } } } _posInArcLimit = _phySize; _phySize += kHeaderSize; AddErrorMessage("Can't find footer"); return S_OK; } Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; if (_virtPos >= Footer.CurrentSize) return S_OK; { const UInt64 rem = Footer.CurrentSize - _virtPos; if (size > rem) size = (UInt32)rem; } if (size == 0) return S_OK; if (Footer.IsFixed()) { if (_virtPos > _posInArcLimit) return S_FALSE; { const UInt64 rem = _posInArcLimit - _virtPos; if (size > rem) size = (UInt32)rem; } HRESULT res = S_OK; if (_virtPos != _posInArc) { _posInArc = _virtPos; res = Seek2(_virtPos); } if (res == S_OK) { UInt32 processedSize2 = 0; res = Stream->Read(data, size, &processedSize2); if (processedSize) *processedSize = processedSize2; _posInArc += processedSize2; } if (res != S_OK) Reset_PosInArc(); return res; } const UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog); if (blockIndex >= Bat.Size()) return E_FAIL; // it's some unexpected case const UInt32 blockSectIndex = Bat[blockIndex]; const UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog; UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1); size = MyMin(blockSize - offsetInBlock, size); HRESULT res = S_OK; if (blockSectIndex == kUnusedBlock) { if (ParentStream) { RINOK(InStream_SeekSet(ParentStream, _virtPos)) res = ParentStream->Read(data, size, &size); } else memset(data, 0, size); } else { const UInt64 newPos = (UInt64)blockSectIndex << kSectorSize_Log; if (BitMapTag != blockIndex) { RINOK(ReadPhy(newPos, BitMap, (UInt32)BitMap.Size())) BitMapTag = blockIndex; } RINOK(ReadPhy(newPos + BitMap.Size() + offsetInBlock, data, size)) for (UInt32 cur = 0; cur < size;) { const UInt32 rem = MyMin(0x200 - (offsetInBlock & 0x1FF), size - cur); const UInt32 bmi = offsetInBlock >> kSectorSize_Log; if (((BitMap[bmi >> 3] >> (7 - (bmi & 7))) & 1) == 0) { if (ParentStream) { RINOK(InStream_SeekSet(ParentStream, _virtPos + cur)) RINOK(ReadStream_FALSE(ParentStream, (Byte *)data + cur, rem)) } else { const Byte *p = (const Byte *)data + cur; for (UInt32 i = 0; i < rem; i++) if (p[i] != 0) return S_FALSE; } } offsetInBlock += rem; cur += rem; } } if (processedSize) *processedSize = size; _virtPos += size; return res; } enum { kpidParent = kpidUserDefined, kpidSavedState }; static const CStatProp kArcProps[] = { { NULL, kpidOffset, VT_UI8}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidClusterSize, VT_UI8}, { NULL, kpidMethod, VT_BSTR}, { NULL, kpidNumVolumes, VT_UI4}, { NULL, kpidTotalPhySize, VT_UI8}, { "Parent", kpidParent, VT_BSTR}, { NULL, kpidCreatorApp, VT_BSTR}, { NULL, kpidHostOS, VT_BSTR}, { "Saved State", kpidSavedState, VT_BOOL}, { NULL, kpidId, VT_BSTR} }; static const Byte kProps[] = { kpidSize, kpidPackSize, kpidCTime /* { kpidNumCyls, VT_UI4}, { kpidNumHeads, VT_UI4}, { kpidSectorsPerTrack, VT_UI4} */ }; IMP_IInArchive_Props IMP_IInArchive_ArcProps_WITH_NAME // VHD start time: 2000-01-01 static const UInt64 kVhdTimeStartValue = (UInt64)3600 * 24 * (399 * 365 + 24 * 4); static void VhdTimeToFileTime(UInt32 vhdTime, NCOM::CPropVariant &prop) { FILETIME ft, utc; UInt64 v = (kVhdTimeStartValue + vhdTime) * 10000000; ft.dwLowDateTime = (DWORD)v; ft.dwHighDateTime = (DWORD)(v >> 32); // specification says that it's UTC time, but Virtual PC 6 writes local time. Why? LocalFileTimeToFileTime(&ft, &utc); prop = utc; } static void StringToAString(char *dest, UInt32 val) { for (int i = 24; i >= 0; i -= 8) { const Byte b = (Byte)((val >> i) & 0xFF); if (b < 0x20 || b > 0x7F) break; *dest++ = (char)b; } *dest = 0; } Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { case kpidMainSubfile: prop = (UInt32)0; break; case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break; case kpidClusterSize: if (Footer.ThereIsDynamic()) prop = (UInt32)1 << Dyn.BlockSizeLog; break; case kpidShortComment: case kpidMethod: { AString s; Footer.AddTypeString(s); if (NeedParent()) { s += " -> "; const CHandler *p = this; while (p && p->NeedParent()) p = p->Parent; if (!p) s += '?'; else p->Footer.AddTypeString(s); } prop = s; break; } case kpidCreatorApp: { char s[16]; StringToAString(s, Footer.CreatorApp); AString res (s); res.Trim(); res.Add_Space(); res.Add_UInt32(Footer.CreatorVersion >> 16); res.Add_Dot(); res.Add_UInt32(Footer.CreatorVersion & 0xFFFF); prop = res; break; } case kpidHostOS: { if (Footer.CreatorHostOS == 0x5769326B) prop = "Windows"; else { char s[16]; StringToAString(s, Footer.CreatorHostOS); prop = s; } break; } case kpidId: { char s[sizeof(Footer.Id) * 2 + 2]; ConvertDataToHex_Upper(s, Footer.Id, sizeof(Footer.Id)); prop = s; break; } case kpidSavedState: prop = Footer.SavedState ? true : false; break; case kpidParent: if (NeedParent()) prop = GetParentSequence(); break; case kpidOffset: prop = _startOffset; break; case kpidPhySize: prop = _phySize; break; case kpidTotalPhySize: { const CHandler *p = this; UInt64 sum = 0; do { sum += p->_phySize; p = p->Parent; } while (p); prop = sum; break; } case kpidNumVolumes: if (NumLevels != 1) prop = (UInt32)NumLevels; break; /* case kpidErrorFlags: { UInt32 flags = 0; if (_unexpectedEnd) flags |= kpv_ErrorFlags_UnexpectedEndOfArc; if (flags != 0) prop = flags; break; } */ case kpidError: if (!_errorMessage.IsEmpty()) prop = _errorMessage; break; } prop.Detach(value); return S_OK; COM_TRY_END } HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level) { Close(); Stream = stream; if (level > (1 << 12)) // Maybe we need to increase that limit return S_FALSE; RINOK(Open3()) NumLevels = 1; if (child && memcmp(child->Dyn.ParentId, Footer.Id, 16) != 0) return S_FALSE; if (Footer.Type != kDiskType_Diff) return S_OK; bool useRelative; UString name; if (!Dyn.RelativeParentNameFromLocator.IsEmpty()) { useRelative = true; name = Dyn.RelativeParentNameFromLocator; } else { useRelative = false; name = Dyn.ParentName; } Dyn.RelativeNameWasUsed = useRelative; Z7_DECL_CMyComPtr_QI_FROM( IArchiveOpenVolumeCallback, openVolumeCallback, openArchiveCallback) if (openVolumeCallback) { CMyComPtr nextStream; HRESULT res = openVolumeCallback->GetStream(name, &nextStream); if (res == S_FALSE) { if (useRelative && Dyn.ParentName != Dyn.RelativeParentNameFromLocator) { res = openVolumeCallback->GetStream(Dyn.ParentName, &nextStream); if (res == S_OK) Dyn.RelativeNameWasUsed = false; } } if (res != S_OK && res != S_FALSE) return res; if (res == S_FALSE || !nextStream) { AddErrorMessage("Missing volume : ", name); return S_OK; } Parent = new CHandler; ParentStream = Parent; res = Parent->Open2(nextStream, this, openArchiveCallback, level + 1); if (res != S_OK) { Parent = NULL; ParentStream.Release(); if (res == E_ABORT) return res; if (res != S_FALSE) { // we must show that error code } } if (res == S_OK) { NumLevels = Parent->NumLevels + 1; } } { const CHandler *p = this; while (p->NeedParent()) { p = p->Parent; if (!p) { AddErrorMessage("Can't open parent VHD file : ", Dyn.ParentName); break; } } } return S_OK; } void CHandler::CloseAtError() { // CHandlerImg: Stream.Release(); Clear_HandlerImg_Vars(); _phySize = 0; NumLevels = 0; Bat.Clear(); NumUsedBlocks = 0; Parent = NULL; ParentStream.Release(); Dyn.Clear(); _errorMessage.Empty(); // _unexpectedEnd = false; } Z7_COM7F_IMF(CHandler::Close()) { CloseAtError(); return S_OK; } Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { case kpidSize: prop = Footer.CurrentSize; break; case kpidPackSize: prop = GetPackSize(); break; case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break; case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break; /* case kpidNumCyls: prop = Footer.NumCyls(); break; case kpidNumHeads: prop = Footer.NumHeads(); break; case kpidSectorsPerTrack: prop = Footer.NumSectorsPerTrack(); break; */ } prop.Detach(value); return S_OK; COM_TRY_END } Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) { COM_TRY_BEGIN *stream = NULL; if (Footer.IsFixed()) { CMyComPtr2 streamSpec; streamSpec.Create_if_Empty(); streamSpec->SetStream(Stream); // fixme : check (startOffset = 0) streamSpec->InitAndSeek(_startOffset, Footer.CurrentSize); RINOK(streamSpec->SeekToStart()) *stream = streamSpec.Detach(); return S_OK; } if (!Footer.ThereIsDynamic() || !AreParentsOK()) return S_FALSE; CMyComPtr streamTemp = this; RINOK(InitAndSeek()) *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( "VHD", "vhd", NULL, 0xDC, kSignature, 0, NArcInfoFlags::kUseGlobalOffset, NULL) }}