5 #include "../../../C/CpuArch.h"
\r
7 #include "Common/Buffer.h"
\r
8 #include "Common/ComTry.h"
\r
9 #include "Common/IntToString.h"
\r
10 #include "Common/MyString.h"
\r
12 #include "Windows/PropVariant.h"
\r
14 #include "../Common/LimitedStreams.h"
\r
15 #include "../Common/ProgressUtils.h"
\r
16 #include "../Common/RegisterArc.h"
\r
17 #include "../Common/StreamUtils.h"
\r
19 #include "../Compress/CopyCoder.h"
\r
21 #define Get16(p) GetBe16(p)
\r
22 #define Get32(p) GetBe32(p)
\r
23 #define Get64(p) GetBe64(p)
\r
25 #define G32(p, dest) dest = Get32(p);
\r
26 #define G64(p, dest) dest = Get64(p);
\r
28 using namespace NWindows;
\r
30 namespace NArchive {
\r
33 static const UInt32 kUnusedBlock = 0xFFFFFFFF;
\r
35 static const UInt32 kDiskType_Fixed = 2;
\r
36 static const UInt32 kDiskType_Dynamic = 3;
\r
37 static const UInt32 kDiskType_Diff = 4;
\r
39 static const char *kDiskTypes[] =
\r
51 // UInt32 FormatVersion;
\r
55 UInt32 CreatorVersion;
\r
56 UInt32 CreatorHostOS;
\r
57 // UInt64 OriginalSize;
\r
59 UInt32 DiskGeometry;
\r
64 bool IsFixed() const { return Type == kDiskType_Fixed; }
\r
65 bool ThereIsDynamic() const { return Type == kDiskType_Dynamic || Type == kDiskType_Diff; }
\r
66 // bool IsSupported() const { return Type == kDiskType_Fixed || Type == kDiskType_Dynamic || Type == kDiskType_Diff; }
\r
67 UInt32 NumCyls() const { return DiskGeometry >> 16; }
\r
68 UInt32 NumHeads() const { return (DiskGeometry >> 8) & 0xFF; }
\r
69 UInt32 NumSectorsPerTrack() const { return DiskGeometry & 0xFF; }
\r
70 AString GetTypeString() const;
\r
71 bool Parse(const Byte *p);
\r
74 AString CFooter::GetTypeString() const
\r
76 if (Type < sizeof(kDiskTypes) / sizeof(kDiskTypes[0]))
\r
77 return kDiskTypes[Type];
\r
79 ConvertUInt32ToString(Type, s);
\r
83 static bool CheckBlock(const Byte *p, unsigned size, unsigned checkSumOffset, unsigned zeroOffset)
\r
87 for (i = 0; i < checkSumOffset; i++)
\r
89 for (i = checkSumOffset + 4; i < size; i++)
\r
91 if (~sum != Get32(p + checkSumOffset))
\r
93 for (i = zeroOffset; i < size; i++)
\r
99 bool CFooter::Parse(const Byte *p)
\r
101 if (memcmp(p, "conectix", 8) != 0)
\r
103 // G32(p + 0x08, Features);
\r
104 // G32(p + 0x0C, FormatVersion);
\r
105 G64(p + 0x10, DataOffset);
\r
106 G32(p + 0x18, CTime);
\r
107 G32(p + 0x1C, CreatorApp);
\r
108 G32(p + 0x20, CreatorVersion);
\r
109 G32(p + 0x24, CreatorHostOS);
\r
110 // G64(p + 0x28, OriginalSize);
\r
111 G64(p + 0x30, CurrentSize);
\r
112 G32(p + 0x38, DiskGeometry);
\r
113 G32(p + 0x3C, Type);
\r
114 memcpy(Id, p + 0x44, 16);
\r
115 SavedState = p[0x54];
\r
116 return CheckBlock(p, 512, 0x40, 0x55);
\r
120 struct CParentLocatorEntry
\r
127 bool Parse(const Byte *p);
\r
129 bool CParentLocatorEntry::Parse(const Byte *p)
\r
131 G32(p + 0x00, Code);
\r
132 G32(p + 0x04, DataSpace);
\r
133 G32(p + 0x08, DataLen);
\r
134 G32(p + 0x10, DataOffset);
\r
135 return (Get32(p + 0x0C) == 0); // Resrved
\r
141 // UInt64 DataOffset;
\r
142 UInt64 TableOffset;
\r
143 // UInt32 HeaderVersion;
\r
148 UString ParentName;
\r
149 // CParentLocatorEntry ParentLocators[8];
\r
151 bool Parse(const Byte *p);
\r
152 UInt32 NumBitMapSectors() const
\r
154 UInt32 numSectorsInBlock = (1 << (BlockSizeLog - 9));
\r
155 return (numSectorsInBlock + 512 * 8 - 1) / (512 * 8);
\r
159 static int GetLog(UInt32 num)
\r
161 for (int i = 0; i < 31; i++)
\r
162 if (((UInt32)1 << i) == num)
\r
167 bool CDynHeader::Parse(const Byte *p)
\r
169 if (memcmp(p, "cxsparse", 8) != 0)
\r
171 // G64(p + 0x08, DataOffset);
\r
172 G64(p + 0x10, TableOffset);
\r
173 // G32(p + 0x18, HeaderVersion);
\r
174 G32(p + 0x1C, NumBlocks);
\r
175 BlockSizeLog = GetLog(Get32(p + 0x20));
\r
176 if (BlockSizeLog < 9 || BlockSizeLog > 30)
\r
178 G32(p + 0x38, ParentTime);
\r
179 if (Get32(p + 0x3C) != 0) // reserved
\r
181 memcpy(ParentId, p + 0x28, 16);
\r
183 const int kNameLength = 256;
\r
184 wchar_t *s = ParentName.GetBuffer(kNameLength);
\r
185 for (unsigned i = 0; i < kNameLength; i++)
\r
186 s[i] = Get16(p + 0x40 + i * 2);
\r
187 s[kNameLength] = 0;
\r
188 ParentName.ReleaseBuffer();
\r
191 for (int i = 0; i < 8; i++)
\r
192 if (!ParentLocators[i].Parse(p + 0x240 + i * 24))
\r
195 return CheckBlock(p, 1024, 0x24, 0x240 + 8 * 24);
\r
201 public IInArchiveGetStream,
\r
202 public CMyUnknownImp
\r
210 CRecordVector<UInt32> Bat;
\r
211 CByteBuffer BitMap;
\r
213 UInt32 NumUsedBlocks;
\r
214 CMyComPtr<IInStream> Stream;
\r
215 CMyComPtr<IInStream> ParentStream;
\r
218 HRESULT Seek(UInt64 offset);
\r
219 HRESULT InitAndSeek();
\r
220 HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size);
\r
222 bool NeedParent() const { return Footer.Type == kDiskType_Diff; }
\r
223 UInt64 GetPackSize() const
\r
224 { return Footer.ThereIsDynamic() ? ((UInt64)NumUsedBlocks << Dyn.BlockSizeLog) : Footer.CurrentSize; }
\r
226 UString GetParentName() const
\r
228 const CHandler *p = this;
\r
230 while (p && p->NeedParent())
\r
232 if (!res.IsEmpty())
\r
234 res += p->Dyn.ParentName;
\r
242 const CHandler *p = this;
\r
243 while (p->NeedParent())
\r
253 HRESULT Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, int level);
\r
256 MY_UNKNOWN_IMP3(IInArchive, IInArchiveGetStream, IInStream)
\r
258 INTERFACE_IInArchive(;)
\r
259 STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
\r
260 STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
\r
261 STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
\r
264 HRESULT CHandler::Seek(UInt64 offset) { return Stream->Seek(offset, STREAM_SEEK_SET, NULL); }
\r
266 HRESULT CHandler::InitAndSeek()
\r
270 RINOK(Parent->InitAndSeek());
\r
272 _virtPos = _phyPos = 0;
\r
273 BitMapTag = kUnusedBlock;
\r
274 BitMap.SetCapacity(Dyn.NumBitMapSectors() << 9);
\r
278 HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size)
\r
280 if (offset + size > _phyLimit)
\r
282 if (offset != _phyPos)
\r
285 RINOK(Seek(offset));
\r
287 HRESULT res = ReadStream_FALSE(Stream, data, size);
\r
292 HRESULT CHandler::Open3()
\r
294 RINOK(Stream->Seek(0, STREAM_SEEK_END, &_phyPos));
\r
297 const UInt32 kDynSize = 1024;
\r
298 Byte buf[kDynSize];
\r
300 _phyLimit = _phyPos;
\r
301 RINOK(ReadPhy(_phyLimit - 512, buf, 512));
\r
302 if (!Footer.Parse(buf))
\r
306 if (!Footer.ThereIsDynamic())
\r
309 RINOK(ReadPhy(0, buf + 512, 512));
\r
310 if (memcmp(buf, buf + 512, 512) != 0)
\r
313 RINOK(ReadPhy(Footer.DataOffset, buf, kDynSize));
\r
314 if (!Dyn.Parse(buf))
\r
317 if (Dyn.NumBlocks >= (UInt32)1 << 31)
\r
319 if (Footer.CurrentSize == 0)
\r
321 if (Dyn.NumBlocks != 0)
\r
324 else if (((Footer.CurrentSize - 1) >> Dyn.BlockSizeLog) + 1 != Dyn.NumBlocks)
\r
327 Bat.Reserve(Dyn.NumBlocks);
\r
328 while ((UInt32)Bat.Size() < Dyn.NumBlocks)
\r
330 RINOK(ReadPhy(Dyn.TableOffset + (UInt64)Bat.Size() * 4, buf, 512));
\r
331 for (UInt32 j = 0; j < 512; j += 4)
\r
333 UInt32 v = Get32(buf + j);
\r
334 if (v != kUnusedBlock)
\r
337 if ((UInt32)Bat.Size() >= Dyn.NumBlocks)
\r
344 STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)
\r
346 if (processedSize != NULL)
\r
347 *processedSize = 0;
\r
348 if (_virtPos >= Footer.CurrentSize)
\r
349 return (Footer.CurrentSize == _virtPos) ? S_OK: E_FAIL;
\r
350 UInt64 rem = Footer.CurrentSize - _virtPos;
\r
352 size = (UInt32)rem;
\r
355 UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog);
\r
356 UInt32 blockSectIndex = Bat[blockIndex];
\r
357 UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog;
\r
358 UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1);
\r
359 size = MyMin(blockSize - offsetInBlock, size);
\r
361 HRESULT res = S_OK;
\r
362 if (blockSectIndex == kUnusedBlock)
\r
366 RINOK(ParentStream->Seek(_virtPos, STREAM_SEEK_SET, NULL));
\r
367 res = ParentStream->Read(data, size, &size);
\r
370 memset(data, 0, size);
\r
374 UInt64 newPos = (UInt64)blockSectIndex << 9;
\r
375 if (BitMapTag != blockIndex)
\r
377 RINOK(ReadPhy(newPos, BitMap, (UInt32)BitMap.GetCapacity()));
\r
378 BitMapTag = blockIndex;
\r
380 RINOK(ReadPhy(newPos + BitMap.GetCapacity() + offsetInBlock, data, size));
\r
381 for (UInt32 cur = 0; cur < size;)
\r
383 UInt32 rem = MyMin(0x200 - (offsetInBlock & 0x1FF), size - cur);
\r
384 UInt32 bmi = offsetInBlock >> 9;
\r
385 if (((BitMap[bmi >> 3] >> (7 - (bmi & 7))) & 1) == 0)
\r
389 RINOK(ParentStream->Seek(_virtPos + cur, STREAM_SEEK_SET, NULL));
\r
390 RINOK(ReadStream_FALSE(ParentStream, (Byte *)data + cur, rem));
\r
394 const Byte *p = (const Byte *)data + cur;
\r
395 for (UInt32 i = 0; i < rem; i++)
\r
400 offsetInBlock += rem;
\r
404 if (processedSize != NULL)
\r
405 *processedSize = size;
\r
410 STDMETHODIMP CHandler::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
\r
414 case STREAM_SEEK_SET: _virtPos = offset; break;
\r
415 case STREAM_SEEK_CUR: _virtPos += offset; break;
\r
416 case STREAM_SEEK_END: _virtPos = Footer.CurrentSize + offset; break;
\r
417 default: return STG_E_INVALIDFUNCTION;
\r
420 *newPosition = _virtPos;
\r
426 kpidParent = kpidUserDefined,
\r
430 STATPROPSTG kArcProps[] =
\r
432 { NULL, kpidSize, VT_UI8},
\r
433 { NULL, kpidCTime, VT_FILETIME},
\r
434 { NULL, kpidClusterSize, VT_UI8},
\r
435 { NULL, kpidMethod, VT_BSTR},
\r
436 { L"Parent", kpidParent, VT_BSTR},
\r
437 { NULL, kpidCreatorApp, VT_BSTR},
\r
438 { NULL, kpidHostOS, VT_BSTR},
\r
439 { L"Saved State", kpidSavedState, VT_BOOL},
\r
440 { NULL, kpidId, VT_BSTR}
\r
443 STATPROPSTG kProps[] =
\r
445 { NULL, kpidSize, VT_UI8},
\r
446 { NULL, kpidPackSize, VT_UI8},
\r
447 { NULL, kpidCTime, VT_FILETIME}
\r
450 { NULL, kpidNumCyls, VT_UI4},
\r
451 { NULL, kpidNumHeads, VT_UI4},
\r
452 { NULL, kpidSectorsPerTrack, VT_UI4}
\r
456 IMP_IInArchive_Props
\r
457 IMP_IInArchive_ArcProps_WITH_NAME
\r
459 // VHD start time: 2000-01-01
\r
460 static const UInt64 kVhdTimeStartValue = (UInt64)3600 * 24 * (399 * 365 + 24 * 4);
\r
462 static void VhdTimeToFileTime(UInt32 vhdTime, NCOM::CPropVariant &prop)
\r
465 UInt64 v = (kVhdTimeStartValue + vhdTime) * 10000000;
\r
466 ft.dwLowDateTime = (DWORD)v;
\r
467 ft.dwHighDateTime = (DWORD)(v >> 32);
\r
468 // specification says that it's UTC time, but Virtual PC 6 writes local time. Why?
\r
469 LocalFileTimeToFileTime(&ft, &utc);
\r
473 static void StringToAString(char *dest, UInt32 s)
\r
475 for (int i = 24; i >= 0; i -= 8)
\r
477 Byte b = (Byte)((s >> i) & 0xFF);
\r
478 if (b < 0x20 || b > 0x7F)
\r
485 static void ConvertByteToHex(unsigned value, char *s)
\r
487 for (int i = 0; i < 2; i++)
\r
489 unsigned t = value & 0xF;
\r
491 s[1 - i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10)));
\r
495 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
\r
498 NCOM::CPropVariant prop;
\r
501 case kpidMainSubfile: prop = (UInt32)0; break;
\r
502 case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break;
\r
503 case kpidClusterSize: if (Footer.ThereIsDynamic()) prop = (UInt32)1 << Dyn.BlockSizeLog; break;
\r
506 AString s = Footer.GetTypeString();
\r
510 const CHandler *p = this;
\r
511 while (p != 0 && p->NeedParent())
\r
516 s += p->Footer.GetTypeString();
\r
521 case kpidCreatorApp:
\r
524 StringToAString(s, Footer.CreatorApp);
\r
527 ConvertUInt32ToString(Footer.CreatorVersion >> 16, s);
\r
531 ConvertUInt32ToString(Footer.CreatorVersion & 0xFFFF, s);
\r
538 if (Footer.CreatorHostOS == 0x5769326b)
\r
543 StringToAString(s, Footer.CreatorHostOS);
\r
551 for (int i = 0; i < 16; i++)
\r
552 ConvertByteToHex(Footer.Id[i], s + i * 2);
\r
557 case kpidSavedState: prop = Footer.SavedState ? true : false; break;
\r
558 case kpidParent: if (NeedParent()) prop = GetParentName(); break;
\r
560 prop.Detach(value);
\r
565 HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, int level)
\r
572 if (child && memcmp(child->Dyn.ParentId, Footer.Id, 16) != 0)
\r
574 if (Footer.Type != kDiskType_Diff)
\r
576 CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
\r
577 if (openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback) != S_OK)
\r
579 CMyComPtr<IInStream> nextStream;
\r
580 HRESULT res = openVolumeCallback->GetStream(Dyn.ParentName, &nextStream);
\r
581 if (res == S_FALSE)
\r
585 Parent = new CHandler;
\r
586 ParentStream = Parent;
\r
587 return Parent->Open2(nextStream, this, openArchiveCallback, level + 1);
\r
590 STDMETHODIMP CHandler::Open(IInStream *stream,
\r
591 const UInt64 * /* maxCheckStartPosition */,
\r
592 IArchiveOpenCallback * openArchiveCallback)
\r
599 res = Open2(stream, NULL, openArchiveCallback, 0);
\r
614 STDMETHODIMP CHandler::Close()
\r
620 ParentStream.Release();
\r
624 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
\r
630 STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
\r
633 NWindows::NCOM::CPropVariant prop;
\r
637 case kpidSize: prop = Footer.CurrentSize; break;
\r
638 case kpidPackSize: prop = GetPackSize(); break;
\r
639 case kpidCTime: VhdTimeToFileTime(Footer.CTime, prop); break;
\r
641 case kpidNumCyls: prop = Footer.NumCyls(); break;
\r
642 case kpidNumHeads: prop = Footer.NumHeads(); break;
\r
643 case kpidSectorsPerTrack: prop = Footer.NumSectorsPerTrack(); break;
\r
646 prop.Detach(value);
\r
651 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
\r
652 Int32 testMode, IArchiveExtractCallback *extractCallback)
\r
657 if (numItems != (UInt32)-1 && (numItems != 1 || indices[0] != 0))
\r
658 return E_INVALIDARG;
\r
660 RINOK(extractCallback->SetTotal(Footer.CurrentSize));
\r
661 CMyComPtr<ISequentialOutStream> outStream;
\r
662 Int32 askMode = testMode ?
\r
663 NExtract::NAskMode::kTest :
\r
664 NExtract::NAskMode::kExtract;
\r
665 RINOK(extractCallback->GetStream(0, &outStream, askMode));
\r
666 if (!testMode && !outStream)
\r
668 RINOK(extractCallback->PrepareOperation(askMode));
\r
670 NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
\r
671 CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
\r
673 CLocalProgress *lps = new CLocalProgress;
\r
674 CMyComPtr<ICompressProgressInfo> progress = lps;
\r
675 lps->Init(extractCallback, false);
\r
677 int res = NExtract::NOperationResult::kDataError;
\r
678 CMyComPtr<ISequentialInStream> inStream;
\r
679 HRESULT hres = GetStream(0, &inStream);
\r
680 if (hres == S_FALSE)
\r
681 res = NExtract::NOperationResult::kUnSupportedMethod;
\r
685 HRESULT hres = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
\r
688 if (copyCoderSpec->TotalSize == Footer.CurrentSize)
\r
689 res = NExtract::NOperationResult::kOK;
\r
693 if (hres != S_FALSE)
\r
699 outStream.Release();
\r
700 return extractCallback->SetOperationResult(res);
\r
704 STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)
\r
708 if (Footer.IsFixed())
\r
710 CLimitedInStream *streamSpec = new CLimitedInStream;
\r
711 CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
\r
712 streamSpec->SetStream(Stream);
\r
713 streamSpec->InitAndSeek(0, Footer.CurrentSize);
\r
714 RINOK(streamSpec->SeekToStart());
\r
715 *stream = streamTemp.Detach();
\r
718 if (!Footer.ThereIsDynamic() || !IsOK())
\r
720 CMyComPtr<ISequentialInStream> streamTemp = this;
\r
721 RINOK(InitAndSeek());
\r
722 *stream = streamTemp.Detach();
\r
727 static IInArchive *CreateArc() { return new CHandler; }
\r
729 static CArcInfo g_ArcInfo =
\r
730 { L"VHD", L"vhd", L".mbr", 0xDC, { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 }, 10, false, CreateArc, 0 };
\r