// VhdxHandler.cpp

#include "StdAfx.h"

// #include <stdio.h>

#include "../../../C/CpuArch.h"

#include "../../Common/ComTry.h"
#include "../../Common/MyBuffer.h"

#include "../../Windows/PropVariant.h"

#include "../Common/RegisterArc.h"
#include "../Common/StreamUtils.h"

#include "HandlerCont.h"

#define Get16(p) GetUi16(p)
#define Get32(p) GetUi32(p)
#define Get64(p) GetUi64(p)

#define G32(_offs_, dest) dest = Get32(p + (_offs_));
#define G64(_offs_, dest) dest = Get64(p + (_offs_));

using namespace NWindows;


EXTERN_C_BEGIN

// CRC-32C (Castagnoli) : reversed for poly 0x1EDC6F41
#define k_Crc32c_Poly 0x82f63b78

static UInt32 g_Crc32c_Table[256];

static void MY_FAST_CALL Crc32c_GenerateTable()
{
  UInt32 i;
  for (i = 0; i < 256; i++)
  {
    UInt32 r = i;
    unsigned j;
    for (j = 0; j < 8; j++)
      r = (r >> 1) ^ (k_Crc32c_Poly & ((UInt32)0 - (r & 1)));
    g_Crc32c_Table[i] = r;
  }
}

UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table);

#define CRC32C_INIT_VAL 0xFFFFFFFF

static UInt32 MY_FAST_CALL Crc32c_Calc(const void *data, size_t size)
{
  return CrcUpdateT1(CRC32C_INIT_VAL, data, size, g_Crc32c_Table) ^ CRC32C_INIT_VAL;
}

EXTERN_C_END


namespace NArchive {
namespace NVhdx {

static struct C_CRC32c_TableInit { C_CRC32c_TableInit() { Crc32c_GenerateTable(); } } g__CRC32c_TableInit;

#define SIGNATURE { 'v', 'h', 'd', 'x', 'f', 'i', 'l', 'e' }
  
static const unsigned kSignatureSize = 8;
static const Byte kSignature[kSignatureSize] = SIGNATURE;

static const unsigned kBitmapSize_Log = 20;
static const size_t kBitmapSize = (size_t)1 << kBitmapSize_Log;


static bool IsZeroArr(const Byte *p, size_t size)
{
  for (size_t i = 0; i < size; i++)
    if (p[i] != 0)
      return false;
  return true;
}


#define ValToHex(t) ((char)(((t) < 10) ? ('0' + (t)) : ('a' + ((t) - 10))))

static void AddByteToHex2(unsigned val, UString &s)
{
  unsigned t;
  t = val >> 4;
  s += ValToHex(t);
  t = val & 0xF;
  s += ValToHex(t);
}


static int HexToVal(const wchar_t c)
{
  if (c >= '0' && c <= '9')  return c - '0';
  if (c >= 'a' && c <= 'z')  return c - 'a' + 10;
  if (c >= 'A' && c <= 'Z')  return c - 'A' + 10;
  return -1;
}

static int DecodeFrom2HexChars(const wchar_t *s)
{
  const int v0 = HexToVal(s[0]);  if (v0 < 0)  return -1;
  const int v1 = HexToVal(s[1]);  if (v1 < 0)  return -1;
  return ((unsigned)v0 << 4) | (unsigned)v1;
}


struct CGuid
{
  Byte Data[16];

  bool IsZero() const { return IsZeroArr(Data, 16); }
  bool IsEqualTo(const Byte *a) const { return memcmp(Data, a, 16) == 0; }
  bool IsEqualTo(const CGuid &g) const { return IsEqualTo(g.Data); }
  void AddHexToString(UString &s) const;

  void SetFrom(const Byte *p) { memcpy(Data, p, 16); }

  bool ParseFromFormatedHexString(const UString &s)
  {
    const unsigned kLen = 16 * 2 + 4 + 2;
    if (s.Len() != kLen || s[0] != '{' || s[kLen - 1] != '}')
      return false;
    unsigned pos = 0;
    for (unsigned i = 1; i < kLen - 1;)
    {
      if (i == 9 || i == 14 || i == 19 || i == 24)
      {
        if (s[i] != '-')
          return false;
        i++;
        continue;
      }
      const int v = DecodeFrom2HexChars(s.Ptr(i));
      if (v < 0)
        return false;
      unsigned pos2 = pos;
      if (pos < 8)
        pos2 ^= (pos < 4 ? 3 : 1);
      Data[pos2] = (Byte)v;
      pos++;
      i += 2;
    }
    return true; // pos == 16;
  }
};

void CGuid::AddHexToString(UString &s) const
{
  for (unsigned i = 0; i < 16; i++)
    AddByteToHex2(Data[i], s);
}


#define IS_NON_ALIGNED(v) (((v) & 0xFFFFF) != 0)

static const unsigned kHeader_GUID_Index_FileWriteGuid = 0;
static const unsigned kHeader_GUID_Index_DataWriteGuid = 1;
static const unsigned kHeader_GUID_Index_LogGuid = 2;

struct CHeader
{
  UInt64 SequenceNumber;
  // UInt16 LogVersion;
  // UInt16 Version;
  UInt32 LogLength;
  UInt64 LogOffset;
  CGuid Guids[3];

  bool Parse(Byte *p);
};

static const unsigned kHeader2Size = 1 << 12;

bool CHeader::Parse(Byte *p)
{
  if (Get32(p) != 0x64616568) // "head"
    return false;
  const UInt32 crc = Get32(p + 4);
  SetUi32(p + 4, 0);
  if (Crc32c_Calc(p, kHeader2Size) != crc)
    return false;
  G64(8, SequenceNumber);
  for (unsigned i = 0; i < 3; i++)
    Guids[i].SetFrom(p + 0x10 + 0x10 * i);
  // LogVersion = Get16(p + 0x40);
  /* LogVersion MUST be set to zero, for known log format
     but we don't parse log so we ignore it */
  G32(0x44, LogLength);
  G64(0x48, LogOffset);
  if (Get16(p + 0x42) != 1) // Header format Version
    return false;
  if (IS_NON_ALIGNED(LogLength))
    return false;
  if (IS_NON_ALIGNED(LogOffset))
    return false;
  return true;
  // return IsZeroArr(p + 0x50, kHeader2Size - 0x50);
}



static const Byte kBat[16] =
  { 0x66,0x77,0xC2,0x2D,0x23,0xF6,0x00,0x42,0x9D,0x64,0x11,0x5E,0x9B,0xFD,0x4A,0x08 };
static const Byte kMetadataRegion[16] =
  { 0x06,0xA2,0x7C,0x8B,0x90,0x47,0x9A,0x4B,0xB8,0xFE,0x57,0x5F,0x05,0x0F,0x88,0x6E };

struct CRegionEntry
{
  // CGuid Guid;
  UInt64 Offset;
  UInt32 Len;
  UInt32 Required;

  UInt64 GetEndPos() const { return Offset + Len; }
  bool Parse(const Byte *p);
};

bool CRegionEntry::Parse(const Byte *p)
{
  // Guid.SetFrom(p);
  G64(0x10, Offset);
  G32(0x18, Len);
  G32(0x1c, Required);
  if (IS_NON_ALIGNED(Offset))
    return false;
  if (IS_NON_ALIGNED(Len))
    return false;
  if (Offset + Len < Offset)
    return false;
  return true;
}
  

struct CRegion
{
  bool Bat_Defined;
  bool Meta_Defined;
  UInt64 EndPos;
  UInt64 DataSize;

  CRegionEntry BatEntry;
  CRegionEntry MetaEntry;

  bool Parse(Byte *p);
};


static const unsigned kRegionSize = 1 << 16;
static const unsigned kNumRegionEntriesMax = (1 << 11) - 1;

bool CRegion::Parse(Byte *p)
{
  Bat_Defined = false;
  Meta_Defined = false;
  EndPos = 0;
  DataSize = 0;

  if (Get32(p) != 0x69676572) // "regi"
    return false;
  const UInt32 crc = Get32(p + 4);
  SetUi32(p + 4, 0);
  const UInt32 crc_calced = Crc32c_Calc(p, kRegionSize);
  if (crc_calced != crc)
    return false;
  
  const UInt32 EntryCount = Get32(p + 8);
  if (Get32(p + 12) != 0) // reserved field must be set to 0.
    return false;
  if (EntryCount > kNumRegionEntriesMax)
    return false;
  for (UInt32 i = 0; i < EntryCount; i++)
  {
    CRegionEntry e;
    const Byte *p2 = p + 0x10 + 0x20 * (size_t)i;
    if (!e.Parse(p2))
      return false;
    DataSize += e.Len;
    const UInt64 endPos = e.GetEndPos();
    if (EndPos < endPos)
      EndPos = endPos;
    CGuid Guid;
    Guid.SetFrom(p2);
    if (Guid.IsEqualTo(kBat))
    {
      if (Bat_Defined)
        return false;
      BatEntry = e;
      Bat_Defined = true;
    }
    else if (Guid.IsEqualTo(kMetadataRegion))
    {
      if (Meta_Defined)
        return false;
      MetaEntry = e;
      Meta_Defined = true;
    }
    else
    {
      if (e.Required != 0)
        return false;
      // it's allowed to ignore unknown non-required region entries
    }
  }
  /*
  const size_t k = 0x10 + 0x20 * EntryCount;
  return IsZeroArr(p + k, kRegionSize - k);
  */
  return true;
}




struct CMetaEntry
{
  CGuid Guid;
  UInt32 Offset;
  UInt32 Len;
  UInt32 Flags0;
  // UInt32 Flags1;

  bool IsUser()        const { return (Flags0 & 1) != 0; }
  bool IsVirtualDisk() const { return (Flags0 & 2) != 0; }
  bool IsRequired()    const { return (Flags0 & 4) != 0; }

  bool CheckLimit(size_t regionSize) const
  {
    return Offset <= regionSize && Len <= regionSize - Offset;
  }

  bool Parse(const Byte *p);
};


bool CMetaEntry::Parse(const Byte *p)
{
  Guid.SetFrom(p);
  
  G32(0x10, Offset);
  G32(0x14, Len);
  G32(0x18, Flags0);
  UInt32 Flags1;
  G32(0x1C, Flags1);

  if (Offset != 0 && Offset < (1 << 16))
    return false;
  if (Len > (1 << 20))
    return false;
  if (Len == 0 && Offset != 0)
    return false;
  if ((Flags0 >> 3) != 0) // Reserved
    return false;
  if ((Flags1 & 3) != 0) // Reserved2
    return false;
  return true;
};
  

struct CParentPair
{
  UString Key;
  UString Value;
};


struct CMetaHeader
{
  // UInt16 EntryCount;
  bool Guid_Defined;
  bool VirtualDiskSize_Defined;
  bool Locator_Defined;

  unsigned BlockSize_Log;
  unsigned LogicalSectorSize_Log;
  unsigned PhysicalSectorSize_Log;

  UInt32 Flags;
  UInt64 VirtualDiskSize;
  CGuid Guid;
  // CGuid LocatorType;

  CObjectVector<CParentPair> ParentPairs;

  int FindParentKey(const char *name) const
  {
    FOR_VECTOR (i, ParentPairs)
    {
      const CParentPair &pair = ParentPairs[i];
      if (pair.Key.IsEqualTo(name))
        return i;
    }
    return -1;
  }

  bool Is_LeaveBlockAllocated() const { return (Flags & 1) != 0; }
  bool Is_HasParent() const { return (Flags & 2) != 0; }

  void Clear()
  {
    Guid_Defined = false;
    VirtualDiskSize_Defined = false;
    Locator_Defined = false;
    BlockSize_Log = 0;
    LogicalSectorSize_Log = 0;
    PhysicalSectorSize_Log = 0;
    Flags = 0;
    VirtualDiskSize = 0;
    ParentPairs.Clear();
  }

  bool Parse(const Byte *p, size_t size);
};


static unsigned GetLogSize(UInt32 size)
{
  unsigned k;
  for (k = 0; k < 32; k++)
    if (((UInt32)1 << k) == size)
      return k;
  return k;
}

 
static const unsigned kMetadataSize = 8;
static const Byte kMetadata[kMetadataSize] =
  { 'm','e','t','a','d','a','t','a' };

static const unsigned k_Num_MetaEntries_Max = (1 << 11) - 1;

static const Byte kFileParameters[16] =
  { 0x37,0x67,0xa1,0xca,0x36,0xfa,0x43,0x4d,0xb3,0xb6,0x33,0xf0,0xaa,0x44,0xe7,0x6b };
static const Byte kVirtualDiskSize[16] =
  { 0x24,0x42,0xa5,0x2f,0x1b,0xcd,0x76,0x48,0xb2,0x11,0x5d,0xbe,0xd8,0x3b,0xf4,0xb8 };
static const Byte kVirtualDiskID[16] =
  { 0xab,0x12,0xca,0xbe,0xe6,0xb2,0x23,0x45,0x93,0xef,0xc3,0x09,0xe0,0x00,0xc7,0x46 };
static const Byte kLogicalSectorSize[16] =
  { 0x1d,0xbf,0x41,0x81,0x6f,0xa9,0x09,0x47,0xba,0x47,0xf2,0x33,0xa8,0xfa,0xab,0x5f };
static const Byte kPhysicalSectorSize[16] =
  { 0xc7,0x48,0xa3,0xcd,0x5d,0x44,0x71,0x44,0x9c,0xc9,0xe9,0x88,0x52,0x51,0xc5,0x56 };
static const Byte kParentLocator[16] =
  { 0x2d,0x5f,0xd3,0xa8,0x0b,0xb3,0x4d,0x45,0xab,0xf7,0xd3,0xd8,0x48,0x34,0xab,0x0c };

static bool GetString16(UString &s, const Byte *p, size_t size)
{
  s.Empty();
  if (size & 1)
    return false;
  for (size_t i = 0; i < size; i += 2)
  {
    const wchar_t c = Get16(p + i);
    if (c == 0)
      return false;
    s += c;
  }
  return true;
}


bool CMetaHeader::Parse(const Byte *p, size_t size)
{
  if (memcmp(p, kMetadata, kMetadataSize) != 0)
    return false;
  if (Get16(p + 8) != 0) // Reserved
    return false;
  const UInt32 EntryCount = Get16(p + 10);
  if (EntryCount > k_Num_MetaEntries_Max)
    return false;
  if (!IsZeroArr(p + 12, 20)) // Reserved
    return false;

  for (unsigned i = 0; i < EntryCount; i++)
  {
    CMetaEntry e;
    if (!e.Parse(p + 32 + 32 * (size_t)i))
      return false;
    if (!e.CheckLimit(size))
      return false;
    const Byte *p2 = p + e.Offset;
    
    if (e.Guid.IsEqualTo(kFileParameters))
    {
      if (BlockSize_Log != 0)
        return false;
      if (e.Len != 8)
        return false;
      const UInt32 v = Get32(p2);
      Flags = Get32(p2 + 4);
      BlockSize_Log = GetLogSize(v);
      if (BlockSize_Log < 20 || BlockSize_Log > 28) // specification from 1 MB to 256 MB
        return false;
      if ((Flags >> 2) != 0) // reserved
        return false;
    }
    else if (e.Guid.IsEqualTo(kVirtualDiskSize))
    {
      if (VirtualDiskSize_Defined)
        return false;
      if (e.Len != 8)
        return false;
      VirtualDiskSize = Get64(p2);
      VirtualDiskSize_Defined = true;
    }
    else if (e.Guid.IsEqualTo(kVirtualDiskID))
    {
      if (e.Len != 16)
        return false;
      Guid.SetFrom(p2);
      Guid_Defined = true;
    }
    else if (e.Guid.IsEqualTo(kLogicalSectorSize))
    {
      if (LogicalSectorSize_Log != 0)
        return false;
      if (e.Len != 4)
        return false;
      const UInt32 v = Get32(p2);
      LogicalSectorSize_Log = GetLogSize(v);
      if (LogicalSectorSize_Log != 9 && LogicalSectorSize_Log != 12)
        return false;
    }
    else if (e.Guid.IsEqualTo(kPhysicalSectorSize))
    {
      if (PhysicalSectorSize_Log != 0)
        return false;
      if (e.Len != 4)
        return false;
      const UInt32 v = Get32(p2);
      PhysicalSectorSize_Log = GetLogSize(v);
      if (PhysicalSectorSize_Log != 9 && PhysicalSectorSize_Log != 12)
        return false;
    }
    else if (e.Guid.IsEqualTo(kParentLocator))
    {
      if (Locator_Defined)
        return false;
      if (e.Len < 20)
        return false;
      // LocatorType.SetFrom(p2);
      /* Specifies the type of the parent virtual disk.
         is different for each type: VHDX, VHD or iSCSI.
         only "B04AEFB7-D19E-4A81-B789-25B8E9445913" (for VHDX) is supported now
      */
      Locator_Defined = true;
      if (Get16(p2 + 16) != 0) // reserved
        return false;
      const UInt32 KeyValueCount = Get16(p2 + 18);
      if (20 + (UInt32)KeyValueCount * 12 > e.Len)
        return false;
      for (unsigned k = 0; k < KeyValueCount; k++)
      {
        const Byte *p3 = p2 + 20 + (size_t)k * 12;
        const UInt32 KeyOffset   = Get32(p3);
        const UInt32 ValueOffset = Get32(p3 + 4);
        const UInt32 KeyLength   = Get16(p3 + 8);
        const UInt32 ValueLength = Get16(p3 + 10);
        if (KeyOffset > e.Len || KeyLength > e.Len - KeyOffset)
          return false;
        if (ValueOffset > e.Len || ValueLength > e.Len - ValueOffset)
          return false;
        CParentPair pair;
        if (!GetString16(pair.Key, p2 + KeyOffset, KeyLength))
          return false;
        if (!GetString16(pair.Value, p2 + ValueOffset, ValueLength))
          return false;
        ParentPairs.Add(pair);
      }
    }
    else
    {
      if (e.IsRequired())
        return false;
      // return false; // unknown metadata;
    }
  }

  // some properties are required for correct processing

  if (BlockSize_Log == 0)
    return false;
  if (LogicalSectorSize_Log == 0)
    return false;
  if (!VirtualDiskSize_Defined)
    return false;
  if (((UInt32)VirtualDiskSize & ((UInt32)1 << LogicalSectorSize_Log)) != 0)
    return false;

  // vhdx specification sets limit for 64 TB.
  // do we need to check over same limit ?
  const UInt64 kVirtualDiskSize_Max = (UInt64)1 << 46;
  if (VirtualDiskSize > kVirtualDiskSize_Max)
    return false;

  return true;
}



struct CBat
{
  CByteBuffer Data;

  void Clear() { Data.Free(); }
  UInt64 GetItem(size_t n) const
  {
    return Get64(Data + n * 8);
  }
};



class CHandler: public CHandlerImg
{
  UInt64 _phySize;

  CBat Bat;
  CObjectVector<CByteBuffer> BitMaps;

  unsigned ChunkRatio_Log;
  size_t ChunkRatio;
  size_t TotalBatEntries;

  CMetaHeader Meta;
  CHeader Header;

  UInt32 NumUsedBlocks;
  UInt32 NumUsedBitMaps;
  UInt64 HeadersSize;

  UInt32 NumLevels;
  UInt64 PackSize_Total;

  /*
  UInt64 NumUsed_1MB_Blocks; // data and bitmaps
  bool NumUsed_1MB_Blocks_Defined;
  */

  CMyComPtr<IInStream> ParentStream;
  CHandler *Parent;
  UString _errorMessage;
  UString _Creator;

  bool _nonEmptyLog;
  bool _isDataContiguous;
  // bool _BatOverlap;

  CGuid _parentGuid;
  bool _parentGuid_IsDefined;
  UStringVector ParentNames;
  UString ParentName_Used;

  const CHandler *_child;
  unsigned _level;
  bool _isCyclic;
  bool _isCyclic_or_CyclicParent;

  void AddErrorMessage(const char *message);
  void AddErrorMessage(const char *message, const wchar_t *name);

  void UpdatePhySize(UInt64 value)
  {
    if (_phySize < value)
      _phySize = value;
  }

  HRESULT Seek2(UInt64 offset);
  HRESULT Read_FALSE(Byte *data, size_t size)
  {
    return ReadStream_FALSE(Stream, data, size);
  }
  HRESULT ReadToBuf_FALSE(CByteBuffer &buf, size_t size)
  {
    buf.Alloc(size);
    return ReadStream_FALSE(Stream, buf, size);
  }
  
  void InitSeekPositions();
  HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed);

  bool IsDiff() const
  {
    // here we suppose that only HasParent() flag is mandatory for Diff archive type
    return Meta.Is_HasParent();
    // return _parentGuid_IsDefined;
  }

  void AddTypeString(AString &s) const
  {
    if (IsDiff())
      s += "Differencing";
    else
    {
      if (Meta.Is_LeaveBlockAllocated())
        s +=  _isDataContiguous ? "fixed" : "fixed-non-cont";
      else
        s += "dynamic";
    }
  }

  void AddComment(UString &s) const;

  UInt64 GetPackSize() const
  {
    return (UInt64)NumUsedBlocks << Meta.BlockSize_Log;
  }

  UString GetParentSequence() const
  {
    const CHandler *p = this;
    UString res;
    while (p && p->IsDiff())
    {
      if (!res.IsEmpty())
        res += " -> ";
      res += ParentName_Used;
      p = p->Parent;
    }
    return res;
  }

  bool AreParentsOK() const
  {
    if (_isCyclic_or_CyclicParent)
      return false;
    const CHandler *p = this;
    while (p->IsDiff())
    {
      p = p->Parent;
      if (!p)
        return false;
    }
    return true;
  }

  // bool ParseLog(CByteBuffer &log);
  bool ParseBat();
  bool CheckBat();

  HRESULT Open3();
  HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback);
  HRESULT OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen);
  virtual void CloseAtError();

public:
  INTERFACE_IInArchive_Img(;)

  STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
  STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);

  CHandler():
    _child(NULL),
    _level(0),
    _isCyclic(false),
    _isCyclic_or_CyclicParent(false)
    {}
};


HRESULT CHandler::Seek2(UInt64 offset)
{
  return Stream->Seek(offset, STREAM_SEEK_SET, NULL);
}


void CHandler::InitSeekPositions()
{
  /* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()).
     So we must reset these variables before first call of Read() */
  Reset_VirtPos();
  Reset_PosInArc();
  if (ParentStream)
    Parent->InitSeekPositions();
}


HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed)
{
  processed = 0;
  if (offset > _phySize
      || offset + size > _phySize)
  {
    // we don't expect these cases, if (_phySize) was set correctly.
    return S_FALSE;
  }
  if (offset != _posInArc)
  {
    const HRESULT res = Seek2(offset);
    if (res != S_OK)
    {
      Reset_PosInArc(); // we don't trust seek_pos in case of error
      return res;
    }
    _posInArc = offset;
  }
  {
    size_t size2 = size;
    const HRESULT res = ReadStream(Stream, data, &size2);
    processed = (UInt32)size2;
    _posInArc += size2;
    if (res != S_OK)
      Reset_PosInArc(); // we don't trust seek_pos in case of reading error
    return res;
  }
}


#define PAYLOAD_BLOCK_NOT_PRESENT   0
#define PAYLOAD_BLOCK_UNDEFINED     1
#define PAYLOAD_BLOCK_ZERO          2
#define PAYLOAD_BLOCK_UNMAPPED      3
#define PAYLOAD_BLOCK_FULLY_PRESENT 6
#define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7

#define SB_BLOCK_NOT_PRESENT 0
#define SB_BLOCK_PRESENT     6

#define BAT_GET_OFFSET(v) ((v) & ~(UInt64)0xFFFFF);
#define BAT_GET_STATE(v)  ((UInt32)(v) & 7);

/* The log contains only   updates to metadata, bat and region tables
   The log doesn't contain updates to start header, and 2 headers (first 192 KB of file).
   The log is array of 4 KB blocks and each block has 4-byte signature.
   So it's possible to scan whole log to find the latest entry sequence (and header for replay).
*/

/*
struct CLogEntry
{
  UInt32 EntryLength;
  UInt32 Tail;
  UInt64 SequenceNumber;
  CGuid LogGuid;
  UInt32 DescriptorCount;
  UInt64 FlushedFileOffset;
  UInt64 LastFileOffset;
  
  bool Parse(const Byte *p);
};

bool CLogEntry::Parse(const Byte *p)
{
  G32 (8, EntryLength);
  G32 (12,Tail);
  G64 (16, SequenceNumber);
  G32 (24, DescriptorCount); // it's 32-bit, but specification says 64-bit
  if (Get32(p + 28) != 0) // reserved
    return false;
  LogGuid.SetFrom(p + 32);
  G64 (48, FlushedFileOffset);
  G64 (56, LastFileOffset);

  if (SequenceNumber == 0)
    return false;
  if ((Tail & 0xfff) != 0)
    return false;
  if (IS_NON_ALIGNED(FlushedFileOffset))
    return false;
  if (IS_NON_ALIGNED(LastFileOffset))
    return false;
  return true;
}


bool CHandler::ParseLog(CByteBuffer &log)
{
  CLogEntry lastEntry;
  lastEntry.SequenceNumber = 0;
  bool lastEntry_found = false;
  size_t lastEntry_Offset = 0;
  for (size_t i = 0; i < log.Size(); i += 1 << 12)
  {
    Byte *p = (Byte *)(log + i);

    if (Get32(p) != 0x65676F6C) // "loge"
      continue;
    const UInt32 crc = Get32(p + 4);

    CLogEntry e;
    if (!e.Parse(p))
    {
      return false;
      continue;
    }
    const UInt32 entryLength = Get32(p + 8);
    if (e.EntryLength > log.Size() || (e.EntryLength & 0xFFF) != 0 || e.EntryLength == 0)
    {
      return false;
      continue;
    }
    SetUi32(p + 4, 0);
    const UInt32 crc_calced = Crc32c_Calc(p, entryLength);
    SetUi32(p + 4, crc); // we must restore crc if we want same data in log
    if (crc_calced != crc)
      continue;
    if (!lastEntry_found || lastEntry.SequenceNumber < e.SequenceNumber)
    {
      lastEntry = e;
      lastEntry_found = true;
      lastEntry_Offset = i;
    }
  }

  return true;
}
*/


bool CHandler::ParseBat()
{
  ChunkRatio_Log = kBitmapSize_Log + 3 + Meta.LogicalSectorSize_Log - Meta.BlockSize_Log;
  ChunkRatio = (size_t)1 << (ChunkRatio_Log);
  
  UInt64 totalBatEntries64;
  const bool isDiff = IsDiff();
  const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log;
  {
    const UInt64 up = Meta.VirtualDiskSize + blockSize - 1;
    if (up < Meta.VirtualDiskSize)
      return false;
    const UInt64 numDataBlocks = up >> Meta.BlockSize_Log;
    
    if (isDiff)
    {
      // differencing table must be finished with bitmap entry
      const UInt64 numBitmaps = (numDataBlocks + ChunkRatio - 1) >> ChunkRatio_Log;
      totalBatEntries64 = numBitmaps * (ChunkRatio + 1);
    }
    else
    {
      // we don't need last Bitmap entry
      totalBatEntries64 = numDataBlocks + ((numDataBlocks - 1) >> ChunkRatio_Log);
    }
  }
  
  if (totalBatEntries64 > Bat.Data.Size() / 8)
    return false;

  const size_t totalBatEntries = (size_t)totalBatEntries64;
  TotalBatEntries = totalBatEntries;
  
  bool isCont = (!isDiff && Meta.Is_LeaveBlockAllocated());
  UInt64 prevBlockOffset = 0;
  UInt64 maxBlockOffset = 0;

  size_t remEntries = ChunkRatio + 1;
  
  size_t i;
  for (i = 0; i < totalBatEntries; i++)
  {
    const UInt64 v = Bat.GetItem(i);
    if ((v & 0xFFFF8) != 0)
      return false;
    const UInt64 offset = BAT_GET_OFFSET(v);
    const unsigned state = BAT_GET_STATE(v);
    
    /*
    UInt64 index64 = v >> 20;
    printf("\n%7d", i);
    printf("%10d, ", (unsigned)index64);
    printf("%4x, ", (unsigned)state);
    */
    
    remEntries--;
    if (remEntries == 0)
    {
      // printf(" ========");
      // printf("\n");
      remEntries = ChunkRatio + 1;
      if (state == SB_BLOCK_PRESENT)
      {
        isCont = false;
        if (!isDiff)
          return false;
        if (offset == 0)
          return false;
        const UInt64 lim = offset + kBitmapSize;
        if (lim < offset)
          return false;
        if (_phySize < lim)
          _phySize = lim;
        NumUsedBitMaps++;
      }
      else if (state != SB_BLOCK_NOT_PRESENT)
        return false;
    }
    else
    {
      if (state == PAYLOAD_BLOCK_FULLY_PRESENT
          || state == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
      {
        if (offset == 0)
          return false;
        if (maxBlockOffset < offset)
          maxBlockOffset = offset;

        if (state == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
        {
          isCont = false;
          if (!isDiff)
            return false;
        }
        else if (isCont)
        {
          if (prevBlockOffset != 0 && prevBlockOffset + blockSize != offset)
            isCont = false;
          else
            prevBlockOffset = offset;
        }

        NumUsedBlocks++;
      }
      else if (state == PAYLOAD_BLOCK_UNMAPPED)
      {
        isCont = false;
        // non-empty (offset) is allowed
      }
      else if (state == PAYLOAD_BLOCK_NOT_PRESENT
          || state == PAYLOAD_BLOCK_UNDEFINED
          || state == PAYLOAD_BLOCK_ZERO)
      {
        isCont = false;
        /* (offset) is reserved and (offset == 0) is expected here,
           but we ignore (offset) here */
        // if (offset != 0) return false;
      }
      else
        return false;
    }
  }

  _isDataContiguous = isCont;

  if (maxBlockOffset != 0)
  {
    const UInt64 lim = maxBlockOffset + blockSize;
    if (lim < maxBlockOffset)
      return false;
    if (_phySize < lim)
      _phySize = lim;
    const UInt64 kPhyLimit = (UInt64)1 << 62;
    if (maxBlockOffset >= kPhyLimit)
      return false;
  }
  return true;
}
    

bool CHandler::CheckBat()
{
  const UInt64 upSize = _phySize + kBitmapSize * 8 - 1;
  if (upSize < _phySize)
    return false;
  const UInt64 useMapSize64 = upSize >> (kBitmapSize_Log + 3);
  const size_t useMapSize = (size_t)useMapSize64;
  
  const UInt32 blockSizeMB = (UInt32)1 << (Meta.BlockSize_Log - kBitmapSize_Log);

  // we don't check useMap, if it's too big.
  if (useMapSize != useMapSize64)
    return true;
  if (useMapSize == 0 || useMapSize > ((size_t)1 << 28))
    return true;

  CByteArr useMap;
  useMap.Alloc(useMapSize);
  memset(useMap, 0, useMapSize);
  // useMap[0] = (Byte)(1 << 0); // first 1 MB is used by headers
  // we can also update useMap for log, and region data.
  
  const size_t totalBatEntries = TotalBatEntries;
  size_t remEntries = ChunkRatio + 1;
  
  size_t i;
  for (i = 0; i < totalBatEntries; i++)
  {
    const UInt64 v = Bat.GetItem(i);
    const UInt64 offset = BAT_GET_OFFSET(v);
    const unsigned state = BAT_GET_STATE(v);
    const UInt64 index = offset >> kBitmapSize_Log;
    UInt32 numBlocks = 1;
    remEntries--;
    if (remEntries == 0)
    {
      remEntries = ChunkRatio + 1;
      if (state != SB_BLOCK_PRESENT)
        continue;
    }
    else
    {
      if (state != PAYLOAD_BLOCK_FULLY_PRESENT &&
          state != PAYLOAD_BLOCK_PARTIALLY_PRESENT)
        continue;
      numBlocks = blockSizeMB;
    }
    
    for (unsigned k = 0; k < numBlocks; k++)
    {
      const UInt64 index2 = index + k;
      const unsigned flag = (unsigned)1 << ((unsigned)index2 & 7);
      const size_t byteIndex = (size_t)(index2 >> 3);
      if (byteIndex >= useMapSize)
        return false;
      const unsigned m = useMap[byteIndex];
      if (m & flag)
        return false;
      useMap[byteIndex] = (Byte)(m | flag);
    }
  }

  /*
  UInt64 num = 0;
  for (i = 0; i < useMapSize; i++)
  {
    Byte b = useMap[i];
    unsigned t = 0;
    t += (b & 1);  b >>= 1;
    t += (b & 1);  b >>= 1;
    t += (b & 1);  b >>= 1;
    t += (b & 1);  b >>= 1;
    t += (b & 1);  b >>= 1;
    t += (b & 1);  b >>= 1;
    t += (b & 1);  b >>= 1;
    t += (b & 1);
    num += t;
  }
  NumUsed_1MB_Blocks = num;
  NumUsed_1MB_Blocks_Defined = true;
  */
  
  return true;
}



HRESULT CHandler::Open3()
{
  {
    static const unsigned kHeaderSize = 512; // + 8
    Byte header[kHeaderSize];
    
    RINOK(Read_FALSE(header, kHeaderSize));

    if (memcmp(header, kSignature, kSignatureSize) != 0)
      return S_FALSE;

    const Byte *p = &header[0];
    for (unsigned i = kSignatureSize; i < kHeaderSize; i += 2)
    {
      const wchar_t c = Get16(p + i);
      if (c < 0x20 || c > 0x7F)
        break;
      _Creator += c;
    }
  }

  HeadersSize = (UInt32)1 << 20;
  CHeader headers[2];
  {
    Byte header[kHeader2Size];
    for (unsigned i = 0; i < 2; i++)
    {
      RINOK(Seek2((1 << 16) * (1 + i)));
      RINOK(Read_FALSE(header, kHeader2Size));
      bool headerIsOK = headers[i].Parse(header);
      if (!headerIsOK)
        return S_FALSE;
    }
  }
  unsigned mainIndex;
       if (headers[0].SequenceNumber > headers[1].SequenceNumber) mainIndex = 0;
  else if (headers[0].SequenceNumber < headers[1].SequenceNumber) mainIndex = 1;
  else return S_FALSE;

  const CHeader &h = headers[mainIndex];
  Header = h;
  if (h.LogLength != 0)
  {
    HeadersSize += h.LogLength;
    UpdatePhySize(h.LogOffset + h.LogLength);
    if (!h.Guids[kHeader_GUID_Index_LogGuid].IsZero())
    {
      _nonEmptyLog = true;
      AddErrorMessage("non-empty LOG was not replayed");
      /*
      if (h.LogVersion != 0)
        AddErrorMessage("unknown LogVresion");
      else
      {
        CByteBuffer log;
        RINOK(Seek2(h.LogOffset));
        RINOK(ReadToBuf_FALSE(log, h.LogLength));
        if (!ParseLog(log))
        {
          return S_FALSE;
        }
      }
      */
    }
  }
  CRegion regions[2];
  int correctRegionIndex = -1;

  {
    CByteBuffer temp;
    temp.Alloc(kRegionSize * 2);
    RINOK(Seek2((1 << 16) * 3));
    RINOK(Read_FALSE(temp, kRegionSize * 2));
    unsigned numTables = 1;
    if (memcmp(temp, temp + kRegionSize, kRegionSize) != 0)
    {
      AddErrorMessage("Region tables mismatch");
      numTables = 2;
    }

    for (unsigned i = 0; i < numTables; i++)
    {
      // RINOK(Seek2((1 << 16) * (3 + i)));
      // RINOK(Read_FALSE(temp, kRegionSize));
      if (regions[i].Parse(temp))
      {
        if (correctRegionIndex < 0)
          correctRegionIndex = i;
      }
      else
      {
        AddErrorMessage("Incorrect region table");
      }
    }
    if (correctRegionIndex < 0)
      return S_FALSE;
    /*
    if (!regions[0].IsEqualTo(regions[1]))
      return S_FALSE;
    */
  }
  
  // UpdatePhySize((1 << 16) * 5);
  UpdatePhySize(1 << 20);
  
  {
    const CRegion &region = regions[correctRegionIndex];
    HeadersSize += region.DataSize;
    UpdatePhySize(region.EndPos);
    {
      if (!region.Meta_Defined)
        return S_FALSE;
      const CRegionEntry &e = region.MetaEntry;
      if (e.Len == 0)
        return S_FALSE;
      {
        // static const kMetaTableSize = 1 << 16;
        CByteBuffer temp;
        {
          RINOK(Seek2(e.Offset));
          RINOK(ReadToBuf_FALSE(temp, e.Len));
        }
        if (!Meta.Parse(temp, temp.Size()))
          return S_FALSE;
      }
      // UpdatePhySize(e.GetEndPos());
    }
    {
      if (!region.Bat_Defined)
        return S_FALSE;
      const CRegionEntry &e = region.BatEntry;
      if (e.Len == 0)
        return S_FALSE;
      // UpdatePhySize(e.GetEndPos());
      {
        RINOK(Seek2(e.Offset));
        RINOK(ReadToBuf_FALSE(Bat.Data, e.Len));
      }
      if (!ParseBat())
        return S_FALSE;
      if (!CheckBat())
      {
        AddErrorMessage("BAT overlap");
        // _BatOverlap = true;
        // return S_FALSE;
      }
    }
  }

  {
    // do we need to check "parent_linkage2" also?
    FOR_VECTOR (i, Meta.ParentPairs)
    {
      const CParentPair &pair = Meta.ParentPairs[i];
      if (pair.Key.IsEqualTo("parent_linkage"))
      {
        _parentGuid_IsDefined = _parentGuid.ParseFromFormatedHexString(pair.Value);
        break;
      }
    }
  }

  {
    // absolute paths for parent stream can be rejected later in client callback
    // the order of check by specification:
    static const char * const g_ParentKeys[] =
    {
        "relative_path"       // "..\..\path2\sub3\parent.vhdx"
      , "volume_path"         // "\\?\Volume{26A21BDA-A627-11D7-9931-806E6F6E6963}\path2\sub3\parent.vhdx")
      , "absolute_win32_path" // "d:\path2\sub3\parent.vhdx"
    };
    for (unsigned i = 0; i < ARRAY_SIZE(g_ParentKeys); i++)
    {
      const int index = Meta.FindParentKey(g_ParentKeys[i]);
      if (index < 0)
        continue;
      ParentNames.Add(Meta.ParentPairs[index].Value);
    }
  }

  if (Meta.Is_HasParent())
  {
    if (!Meta.Locator_Defined)
      AddErrorMessage("Parent locator is not defined");
    else
    {
      if (!_parentGuid_IsDefined)
        AddErrorMessage("Parent GUID is not defined");
      if (ParentNames.IsEmpty())
        AddErrorMessage("Parent VHDX file name is not defined");
    }
  }
  else
  {
    if (Meta.Locator_Defined)
      AddErrorMessage("Unexpected parent locator");
  }

  // here we suppose that and locator can be used only with HasParent flag
    
  // return S_FALSE;

  _size = Meta.VirtualDiskSize; // CHandlerImg

  // _posInArc = 0;
  // Reset_PosInArc();
  // RINOK(Stream->Seek(0, STREAM_SEEK_SET, NULL));

  return S_OK;
}


/*
static UInt32 g_NumCalls = 0;
static UInt32 g_NumCalls2 = 0;
static struct CCounter { ~CCounter()
{
  printf("\nNumCalls = %10u\n", g_NumCalls);
  printf("NumCalls2 = %10u\n", g_NumCalls2);
} } g_Counter;
*/

STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)
{
  // g_NumCalls++;
  if (processedSize)
    *processedSize = 0;
  if (_virtPos >= Meta.VirtualDiskSize)
    return S_OK;
  {
    const UInt64 rem = Meta.VirtualDiskSize - _virtPos;
    if (size > rem)
      size = (UInt32)rem;
  }
  if (size == 0)
    return S_OK;
  const size_t blockIndex = (size_t)(_virtPos >> Meta.BlockSize_Log);
  const size_t chunkIndex = blockIndex >> ChunkRatio_Log;
  const size_t chunkRatio = (size_t)1 << ChunkRatio_Log;
  const size_t blockIndex2 = chunkIndex * (chunkRatio + 1) + (blockIndex & (chunkRatio - 1));
  const UInt64 blockSectVal = Bat.GetItem(blockIndex2);
  const UInt64 blockOffset = BAT_GET_OFFSET(blockSectVal);
  const UInt32 blockState = BAT_GET_STATE(blockSectVal);

  const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log;
  const UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1);
  size = MyMin(blockSize - offsetInBlock, size);

  bool needParent = false;
  bool needRead = false;
  
  if (blockState == PAYLOAD_BLOCK_FULLY_PRESENT)
    needRead = true;
  else if (blockState == PAYLOAD_BLOCK_NOT_PRESENT)
  {
    /* for a differencing VHDX: parent virtual disk SHOULD be
         inspected to determine the associated contents (SPECIFICATION).
         we suppose that we should not check BitMap.
       for fixed or dynamic VHDX files: the block contents are undefined and
         can contain arbitrary data (SPECIFICATION). NTFS::pagefile.sys can use such state. */
    if (IsDiff())
      needParent = true;
  }
  else if (blockState == PAYLOAD_BLOCK_PARTIALLY_PRESENT)
  {
    // only allowed for differencing VHDX files.
    // associated sector bitmap block MUST be valid
    if (chunkIndex >= BitMaps.Size())
      return S_FALSE;
    // else
    {
      const CByteBuffer &bitmap = BitMaps[(unsigned)chunkIndex];
      const Byte *p = (const Byte *)bitmap;
      if (!p)
        return S_FALSE;
      // else
      {
        // g_NumCalls2++;
        const UInt64 sectorIndex = _virtPos >> Meta.LogicalSectorSize_Log;

        #define BIT_MAP_UNIT_LOG  3 // it's for small block (4 KB)
        // #define BIT_MAP_UNIT_LOG  5 // speed optimization for large blocks (16 KB)

        const size_t offs = (size_t)(sectorIndex >> 3) &
            (
              (kBitmapSize - 1)
              & ~(((UInt32)1 << (BIT_MAP_UNIT_LOG - 3)) - 1)
            );

        unsigned sector2 = (unsigned)sectorIndex & ((1 << BIT_MAP_UNIT_LOG) - 1);
      #if BIT_MAP_UNIT_LOG == 5
        UInt32 v = GetUi32(p + offs) >> sector2;
      #else
        unsigned v = (unsigned)p[offs] >> sector2;
      #endif
        // UInt32 v = GetUi32(p + offs) >> sector2;
        const UInt32 sectorSize = (UInt32)1 << Meta.LogicalSectorSize_Log;
        const UInt32 offsetInSector = (UInt32)_virtPos & (sectorSize - 1);
        const unsigned bit = (unsigned)(v & 1);
        if (bit)
          needRead = true;
        else
          needParent = true; // zero - from the parent VHDX file
        UInt32 rem = sectorSize - offsetInSector;
        for (sector2++; sector2 < (1 << BIT_MAP_UNIT_LOG); sector2++)
        {
          v >>= 1;
          if (bit != (v & 1))
            break;
          rem += sectorSize;
        }
        if (size > rem)
          size = rem;
      }
    }
  }

  bool needZero = true;

  HRESULT res = S_OK;

  if (needParent)
  {
    if (!ParentStream)
      return S_FALSE;
    // if (ParentStream)
    {
      RINOK(ParentStream->Seek(_virtPos, STREAM_SEEK_SET, NULL));
      size_t processed = size;
      res = ReadStream(ParentStream, (Byte *)data, &processed);
      size = (UInt32)processed;
      needZero = false;
    }
  }
  else if (needRead)
  {
    UInt32 processed = 0;
    res = ReadPhy(blockOffset + offsetInBlock, data, size, processed);
    size = processed;
    needZero = false;
  }

  if (needZero)
    memset(data, 0, size);

  if (processedSize)
    *processedSize = size;

  _virtPos += size;
  return res;
}


enum
{
  kpidParent = kpidUserDefined
};

static const CStatProp kArcProps[] =
{
  { NULL, kpidClusterSize, VT_UI4},
  { NULL, kpidSectorSize, VT_UI4},
  { NULL, kpidMethod, VT_BSTR},
  { NULL, kpidNumVolumes, VT_UI4},
  { NULL, kpidTotalPhySize, VT_UI8},
  { "Parent", kpidParent, VT_BSTR},
  { NULL, kpidCreatorApp, VT_BSTR},
  { NULL, kpidComment, VT_BSTR},
  { NULL, kpidId, VT_BSTR}
 };

static const Byte kProps[] =
{
  kpidSize,
  kpidPackSize
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps_WITH_NAME


void CHandler::AddErrorMessage(const char *message)
{
  if (!_errorMessage.IsEmpty())
    _errorMessage.Add_LF();
  _errorMessage += message;
}

void CHandler::AddErrorMessage(const char *message, const wchar_t *name)
{
  AddErrorMessage(message);
  _errorMessage += name;
}


static void AddComment_Name(UString &s, const char *name)
{
  s += name;
  s += ": ";
}

static void AddComment_Bool(UString &s, const char *name, bool val)
{
  AddComment_Name(s, name);
  s += val ? "+" : "-";
  s.Add_LF();
}

static void AddComment_UInt64(UString &s, const char *name, UInt64 v, bool showMB = false)
{
  AddComment_Name(s, name);
  s.Add_UInt64(v);
  if (showMB)
  {
    s += " (";
    s.Add_UInt64(v >> 20);
    s += " MiB)";
  }
  s.Add_LF();
}

static void AddComment_BlockSize(UString &s, const char *name, unsigned logSize)
{
  if (logSize != 0)
    AddComment_UInt64(s, name, ((UInt64)1 << logSize));
}


void CHandler::AddComment(UString &s) const
{
  AddComment_UInt64(s, "PhysicalSize", _phySize);

  if (!_errorMessage.IsEmpty())
  {
    AddComment_Name(s, "Error");
    s += _errorMessage;
    s.Add_LF();
  }

  if (Meta.Guid_Defined)
  {
    AddComment_Name(s, "Id");
    Meta.Guid.AddHexToString(s);
    s.Add_LF();
  }
  
  AddComment_UInt64(s, "SequenceNumber", Header.SequenceNumber);
  AddComment_UInt64(s, "LogLength", Header.LogLength, true);
  
  for (unsigned i = 0; i < 3; i++)
  {
    const CGuid &g = Header.Guids[i];
    if (g.IsZero())
      continue;
    if (i == 0)
      s += "FileWrite";
    else if (i == 1)
      s += "DataWrite";
    else
      s += "Log";
    AddComment_Name(s, "Guid");
    g.AddHexToString(s);
    s.Add_LF();
  }

  AddComment_Bool(s, "HasParent", Meta.Is_HasParent());
  AddComment_Bool(s, "Fixed", Meta.Is_LeaveBlockAllocated());
  if (Meta.Is_LeaveBlockAllocated())
    AddComment_Bool(s, "DataContiguous", _isDataContiguous);
  
  AddComment_BlockSize(s, "BlockSize", Meta.BlockSize_Log);
  AddComment_BlockSize(s, "LogicalSectorSize", Meta.LogicalSectorSize_Log);
  AddComment_BlockSize(s, "PhysicalSectorSize", Meta.PhysicalSectorSize_Log);

  {
    const UInt64 packSize = GetPackSize();
    AddComment_UInt64(s, "PackSize", packSize, true);
    const UInt64 headersSize = HeadersSize + ((UInt64)NumUsedBitMaps << kBitmapSize_Log);
    AddComment_UInt64(s, "HeadersSize", headersSize, true);
    AddComment_UInt64(s, "FreeSpace", _phySize - packSize - headersSize, true);
    /*
    if (NumUsed_1MB_Blocks_Defined)
      AddComment_UInt64(s, "used2", (NumUsed_1MB_Blocks << 20));
    */
  }

  if (Meta.ParentPairs.Size() != 0)
  {
    s += "Parent:";
    s.Add_LF();
    FOR_VECTOR(i, Meta.ParentPairs)
    {
      const CParentPair &pair = Meta.ParentPairs[i];
      s += "  ";
      s += pair.Key;
      s += ": ";
      s += pair.Value;
      s.Add_LF();
    }
    s.Add_LF();
  }
}



STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;
  switch (propID)
  {
    case kpidMainSubfile: prop = (UInt32)0; break;
    case kpidClusterSize: prop = (UInt32)1 << Meta.BlockSize_Log; break;
    case kpidSectorSize: prop = (UInt32)1 << Meta.LogicalSectorSize_Log; break;
    case kpidShortComment:
    case kpidMethod:
    {
      AString s;
      AddTypeString(s);
      if (IsDiff())
      {
        s += " -> ";
        const CHandler *p = this;
        while (p && p->IsDiff())
          p = p->Parent;
        if (!p)
          s += '?';
        else
          p->AddTypeString(s);
      }
      prop = s;
      break;
    }
    case kpidComment:
    {
      UString s;
      {
        if (NumLevels > 1)
        {
          AddComment_UInt64(s, "NumVolumeLevels", NumLevels);
          AddComment_UInt64(s, "PackSizeTotal", PackSize_Total, true);
          s += "----";
          s.Add_LF();
        }

        const CHandler *p = this;
        for (;;)
        {
          if (p->_level != 0 || p->Parent)
            AddComment_UInt64(s, "VolumeLevel", p->_level + 1);
          p->AddComment(s);
          if (!p->Parent)
            break;
          s += "----";
          s.Add_LF();
          {
            s.Add_LF();
            if (!p->ParentName_Used.IsEmpty())
            {
              AddComment_Name(s, "Name");
              s += p->ParentName_Used;
              s.Add_LF();
            }
          }
          p = p->Parent;
        }
      }
      prop = s;
      break;
    }
    case kpidCreatorApp:
    {
      if (!_Creator.IsEmpty())
        prop = _Creator;
      break;
    }
    case kpidId:
    {
      if (Meta.Guid_Defined)
      {
        UString s;
        Meta.Guid.AddHexToString(s);
        prop = s;
      }
      break;
    }
    case kpidName:
    {
      if (Meta.Guid_Defined)
      {
        UString s;
        Meta.Guid.AddHexToString(s);
        s += ".vhdx";
        prop = s;
      }
      break;
    }
    case kpidParent: if (IsDiff()) prop = GetParentSequence(); 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 kpidError:
    {
      UString s;
      const CHandler *p = this;
      do
      {
        if (!p->_errorMessage.IsEmpty())
        {
          if (!s.IsEmpty())
            s.Add_LF();
          s += p->_errorMessage;
        }
        p = p->Parent;
      }
      while (p);
      if (!s.IsEmpty())
        prop = s;
      break;
    }
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}


HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback)
{
  Stream = stream;
  if (_level >= (1 << 20))
    return S_FALSE;

  RINOK(Open3());

  NumLevels = 1;
  PackSize_Total = GetPackSize();

  if (_child)
  {
    if (!_child->_parentGuid.IsEqualTo(Header.Guids[kHeader_GUID_Index_DataWriteGuid]))
      return S_FALSE;
    const CHandler *child = _child;
    do
    {
      /* We suppose that only FileWriteGuid is unique.
         Another IDs must be identical in in difference and parent archives. */
      if (Header.Guids[kHeader_GUID_Index_FileWriteGuid].IsEqualTo(
              child->Header.Guids[kHeader_GUID_Index_FileWriteGuid])
          && _phySize == child->_phySize)
      {
        _isCyclic = true;
        _isCyclic_or_CyclicParent = true;
        AddErrorMessage("Cyclic parent archive was blocked");
        return S_OK;
      }
      child = child->_child;
    }
    while (child);
  }

  if (!Meta.Is_HasParent())
    return S_OK;

  if (!Meta.Locator_Defined
      || !_parentGuid_IsDefined
      || ParentNames.IsEmpty())
  {
    return S_OK;
  }

  ParentName_Used = ParentNames.Front();

  HRESULT res;
  const unsigned kNumLevelsMax = (1 << 8);  // Maybe we need to increase that limit
  if (_level >= kNumLevelsMax - 1)
  {
    AddErrorMessage("Too many parent levels");
    return S_OK;
  }
  
  bool _parentFileWasOpen = false;
  
  if (!openArchiveCallback)
    res = S_FALSE;
  else
    res = OpenParent(openArchiveCallback, _parentFileWasOpen);
  
  if (res != S_OK)
  {
    if (res != S_FALSE)
      return res;
    
    if (_parentFileWasOpen)
      AddErrorMessage("Can't parse parent VHDX file : ", ParentName_Used);
    else
      AddErrorMessage("Missing parent VHDX file : ", ParentName_Used);
  }


  return S_OK;
}


HRESULT CHandler::OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen)
{
  _parentFileWasOpen = false;
  CMyComPtr<IArchiveOpenVolumeCallback> openVolumeCallback;
  openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback);

  if (!openVolumeCallback)
    return S_FALSE;

  {
    CMyComPtr<IInStream> nextStream;
    HRESULT res = S_FALSE;
    UString name;
    
    FOR_VECTOR (i, ParentNames)
    {
      name = ParentNames[i];
      
      // we remove prefix ".\\', but client already can support any variant
      if (name[0] == L'.' && name[1] == L'\\')
        name.DeleteFrontal(2);

      res = openVolumeCallback->GetStream(name, &nextStream);

      if (res == S_OK && nextStream)
        break;
         
      if (res != S_OK && res != S_FALSE)
        return res;
    }
    
    if (res == S_FALSE || !nextStream)
      return S_FALSE;
    
    ParentName_Used = name;
    _parentFileWasOpen = true;

    Parent = new CHandler;
    ParentStream = Parent;

    try
    {
      Parent->_level = _level + 1;
      Parent->_child = this;
      /* we could call CHandlerImg::Open() here.
         but we don't need (_imgExt) in (Parent). So we call Open2() here */
      Parent->Close();
      res = Parent->Open2(nextStream, openArchiveCallback);
    }
    catch(...)
    {
      Parent = NULL;
      ParentStream.Release();
      res = S_FALSE;
      throw;
    }

    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)
    {
      if (Parent->_isCyclic_or_CyclicParent)
        _isCyclic_or_CyclicParent = true;

      NumLevels = Parent->NumLevels + 1;
      PackSize_Total += Parent->GetPackSize();

      // we read BitMaps only if Parent was open

      UInt64 numBytes = (UInt64)NumUsedBitMaps << kBitmapSize_Log;
      if (openArchiveCallback && numBytes != 0)
      {
        RINOK(openArchiveCallback->SetTotal(NULL, &numBytes));
      }
      numBytes = 0;
      for (size_t i = ChunkRatio; i < TotalBatEntries; i += ChunkRatio + 1)
      {
        const UInt64 v = Bat.GetItem(i);
        const UInt64 offset = BAT_GET_OFFSET(v);
        const unsigned state = BAT_GET_STATE(v);

        CByteBuffer &buf = BitMaps.AddNew();
        if (state == SB_BLOCK_PRESENT)
        {
          if (openArchiveCallback)
          {
            RINOK(openArchiveCallback->SetCompleted(NULL, &numBytes));
          }
          numBytes += kBitmapSize;
          buf.Alloc(kBitmapSize);
          RINOK(Seek2(offset));
          RINOK(Read_FALSE(buf, kBitmapSize));
          /*
          for (unsigned i = 0; i < (1 << 20); i+=4)
          {
            UInt32 v = GetUi32(buf + i);
            if (v != 0 && v != (UInt32)(Int32)-1)
              printf("\n%7d %8x", i, v);
          }
          */
        }
      }
    }
  }

  return S_OK;
}


void CHandler::CloseAtError()
{
  // CHandlerImg
  Clear_HandlerImg_Vars();
  Stream.Release();

  _phySize = 0;
  Bat.Clear();
  BitMaps.Clear();
  NumUsedBlocks = 0;
  NumUsedBitMaps = 0;
  HeadersSize = 0;
  /*
  NumUsed_1MB_Blocks = 0;
  NumUsed_1MB_Blocks_Defined = false;
  */

  Parent = NULL;
  ParentStream.Release();
  _errorMessage.Empty();
  _Creator.Empty();
  _nonEmptyLog = false;
  _parentGuid_IsDefined = false;
  _isDataContiguous = false;
  // _BatOverlap = false;

  ParentNames.Clear();
  ParentName_Used.Empty();

  Meta.Clear();

  ChunkRatio_Log = 0;
  ChunkRatio = 0;
  TotalBatEntries = 0;
  NumLevels = 0;
  PackSize_Total = 0;

  _isCyclic = false;
  _isCyclic_or_CyclicParent = false;
}

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 = Meta.VirtualDiskSize; break;
    case kpidPackSize: prop = PackSize_Total; break;
    case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break;
  }

  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}


STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)
{
  COM_TRY_BEGIN
  *stream = NULL;
  // if some prarent is not OK, we don't create stream
  if (!AreParentsOK())
    return S_FALSE;
  InitSeekPositions();
  CMyComPtr<ISequentialInStream> streamTemp = this;
  *stream = streamTemp.Detach();
  return S_OK;
  COM_TRY_END
}

REGISTER_ARC_I(
  "VHDX", "vhdx avhdx", NULL, 0xc4,
  kSignature,
  0,
  0,
  NULL)

}}
