926 lines
22 KiB
C++
926 lines
22 KiB
C++
// 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 {
|
|
|
|
#define SIGNATURE { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 }
|
|
|
|
static const unsigned kSignatureSize = 10;
|
|
static const Byte kSignature[kSignatureSize] = SIGNATURE;
|
|
|
|
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; }
|
|
AString GetTypeString() const;
|
|
bool Parse(const Byte *p);
|
|
};
|
|
|
|
AString CFooter::GetTypeString() const
|
|
{
|
|
if (Type < ARRAY_SIZE(kDiskTypes))
|
|
return kDiskTypes[Type];
|
|
char s[16];
|
|
ConvertUInt32ToString(Type, s);
|
|
return s;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
class CHandler: public CHandlerImg
|
|
{
|
|
UInt64 _posInArcLimit;
|
|
UInt64 _startOffset;
|
|
UInt64 _phySize;
|
|
|
|
CFooter Footer;
|
|
CDynHeader Dyn;
|
|
CRecordVector<UInt32> Bat;
|
|
CByteBuffer BitMap;
|
|
UInt32 BitMapTag;
|
|
UInt32 NumUsedBlocks;
|
|
// CMyComPtr<IInStream> Stream;
|
|
CMyComPtr<IInStream> ParentStream;
|
|
CHandler *Parent;
|
|
UString _errorMessage;
|
|
// bool _unexpectedEnd;
|
|
|
|
void AddErrorMessage(const wchar_t *s)
|
|
{
|
|
if (!_errorMessage.IsEmpty())
|
|
_errorMessage.Add_LF();
|
|
_errorMessage += s;
|
|
}
|
|
void UpdatePhySize(UInt64 value)
|
|
{
|
|
if (_phySize < value)
|
|
_phySize = value;
|
|
}
|
|
|
|
void Reset_PosInArc() { _posInArc = (UInt64)0 - 1; }
|
|
HRESULT Seek(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.AddAscii(" -> ");
|
|
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 += L'(';
|
|
res += anotherName;
|
|
res += L')';
|
|
}
|
|
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)
|
|
{
|
|
return Open2(stream, NULL, openArchiveCallback, 0);
|
|
}
|
|
void CloseAtError();
|
|
|
|
public:
|
|
INTERFACE_IInArchive_Img(;)
|
|
|
|
STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
|
|
STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
|
|
};
|
|
|
|
HRESULT CHandler::Seek(UInt64 offset) { return Stream->Seek(_startOffset + offset, STREAM_SEEK_SET, NULL); }
|
|
|
|
HRESULT CHandler::InitAndSeek()
|
|
{
|
|
if (ParentStream)
|
|
{
|
|
RINOK(Parent->InitAndSeek());
|
|
}
|
|
_virtPos = _posInArc = 0;
|
|
BitMapTag = kUnusedBlock;
|
|
BitMap.Alloc(Dyn.NumBitMapSectors() << kSectorSize_Log);
|
|
return Seek(0);
|
|
}
|
|
|
|
HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size)
|
|
{
|
|
if (offset + size > _posInArcLimit)
|
|
return S_FALSE;
|
|
if (offset != _posInArc)
|
|
{
|
|
_posInArc = offset;
|
|
RINOK(Seek(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(Stream->Seek(0, STREAM_SEEK_CUR, &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(Stream->Seek(0, STREAM_SEEK_END, &fileSize));
|
|
if (fileSize < kHeaderSize)
|
|
return S_FALSE;
|
|
|
|
const UInt32 kDynSize = 1024;
|
|
Byte buf[kDynSize];
|
|
|
|
RINOK(Stream->Seek(fileSize - kHeaderSize, STREAM_SEEK_SET, NULL));
|
|
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.
|
|
_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(L"Can't find footer");
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP 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;
|
|
UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog);
|
|
UInt32 blockSectIndex = Bat[blockIndex];
|
|
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(ParentStream->Seek(_virtPos, STREAM_SEEK_SET, NULL));
|
|
res = ParentStream->Read(data, size, &size);
|
|
}
|
|
else
|
|
memset(data, 0, size);
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
UInt32 bmi = offsetInBlock >> kSectorSize_Log;
|
|
if (((BitMap[bmi >> 3] >> (7 - (bmi & 7))) & 1) == 0)
|
|
{
|
|
if (ParentStream)
|
|
{
|
|
RINOK(ParentStream->Seek(_virtPos + cur, STREAM_SEEK_SET, NULL));
|
|
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, kpidSize, VT_UI8},
|
|
{ NULL, kpidOffset, VT_UI8},
|
|
{ NULL, kpidCTime, VT_FILETIME},
|
|
{ NULL, kpidClusterSize, VT_UI8},
|
|
{ NULL, kpidMethod, VT_BSTR},
|
|
{ "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)
|
|
{
|
|
Byte b = (Byte)((val >> i) & 0xFF);
|
|
if (b < 0x20 || b > 0x7F)
|
|
break;
|
|
*dest++ = b;
|
|
}
|
|
*dest = 0;
|
|
}
|
|
|
|
static void ConvertByteToHex(unsigned value, char *s)
|
|
{
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
unsigned t = value & 0xF;
|
|
value >>= 4;
|
|
s[1 - i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10)));
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP 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.GetTypeString();
|
|
if (NeedParent())
|
|
{
|
|
s += " -> ";
|
|
const CHandler *p = this;
|
|
while (p && p->NeedParent())
|
|
p = p->Parent;
|
|
if (!p)
|
|
s += '?';
|
|
else
|
|
s += p->Footer.GetTypeString();
|
|
}
|
|
prop = s;
|
|
break;
|
|
}
|
|
case kpidCreatorApp:
|
|
{
|
|
char s[16];
|
|
StringToAString(s, Footer.CreatorApp);
|
|
AString res = s;
|
|
res.Trim();
|
|
ConvertUInt32ToString(Footer.CreatorVersion >> 16, s);
|
|
res.Add_Space();
|
|
res += s;
|
|
res += '.';
|
|
ConvertUInt32ToString(Footer.CreatorVersion & 0xFFFF, s);
|
|
res += s;
|
|
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[32 + 4];
|
|
for (int i = 0; i < 16; i++)
|
|
ConvertByteToHex(Footer.Id[i], s + i * 2);
|
|
s[32] = 0;
|
|
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 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());
|
|
|
|
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;
|
|
|
|
CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
|
|
openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);
|
|
|
|
if (openVolumeCallback)
|
|
{
|
|
CMyComPtr<IInStream> 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)
|
|
{
|
|
UString s;
|
|
s.SetFromAscii("Missing volume : ");
|
|
s += name;
|
|
AddErrorMessage(s);
|
|
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
|
|
}
|
|
}
|
|
}
|
|
{
|
|
const CHandler *p = this;
|
|
while (p->NeedParent())
|
|
{
|
|
p = p->Parent;
|
|
if (!p)
|
|
{
|
|
AddErrorMessage(L"Can't open parent VHD file:");
|
|
AddErrorMessage(Dyn.ParentName);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
void CHandler::CloseAtError()
|
|
{
|
|
_phySize = 0;
|
|
Bat.Clear();
|
|
NumUsedBlocks = 0;
|
|
Parent = NULL;
|
|
Stream.Release();
|
|
ParentStream.Release();
|
|
Dyn.Clear();
|
|
_errorMessage.Empty();
|
|
// _unexpectedEnd = false;
|
|
_imgExt = NULL;
|
|
}
|
|
|
|
STDMETHODIMP CHandler::Close()
|
|
{
|
|
CloseAtError();
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP 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
|
|
}
|
|
|
|
|
|
STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)
|
|
{
|
|
COM_TRY_BEGIN
|
|
*stream = 0;
|
|
if (Footer.IsFixed())
|
|
{
|
|
CLimitedInStream *streamSpec = new CLimitedInStream;
|
|
CMyComPtr<ISequentialInStream> streamTemp = streamSpec;
|
|
streamSpec->SetStream(Stream);
|
|
streamSpec->InitAndSeek(0, Footer.CurrentSize);
|
|
RINOK(streamSpec->SeekToStart());
|
|
*stream = streamTemp.Detach();
|
|
return S_OK;
|
|
}
|
|
if (!Footer.ThereIsDynamic() || !AreParentsOK())
|
|
return S_FALSE;
|
|
CMyComPtr<ISequentialInStream> 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)
|
|
|
|
}}
|