// DmgHandler.cpp

#include "StdAfx.h"

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

#include "../../Common/ComTry.h"
#include "../../Common/IntToString.h"
#include "../../Common/MyXml.h"
#include "../../Common/UTFConvert.h"

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

#include "../Common/LimitedStreams.h"
#include "../Common/ProgressUtils.h"
#include "../Common/RegisterArc.h"
#include "../Common/StreamObjects.h"
#include "../Common/StreamUtils.h"

#include "../Compress/BZip2Decoder.h"
#include "../Compress/CopyCoder.h"
#include "../Compress/ZlibDecoder.h"

#include "Common/OutStreamWithCRC.h"

// #define DMG_SHOW_RAW

// #include <stdio.h>
#define PRF(x) // x

#define Get16(p) GetBe16(p)
#define Get32(p) GetBe32(p)
#define Get64(p) GetBe64(p)

static const Byte k_Base64Table[256] =
{
  66,77,77,77,77,77,77,77,77,65,65,77,77,65,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  65,77,77,77,77,77,77,77,77,77,77,62,77,77,77,63,
  52,53,54,55,56,57,58,59,60,61,77,77,77,64,77,77,
  77, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
  15,16,17,18,19,20,21,22,23,24,25,77,77,77,77,77,
  77,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
  41,42,43,44,45,46,47,48,49,50,51,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,
  77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77
};

static Byte *Base64ToBin(Byte *dest, const char *src)
{
  UInt32 val = 1;
  
  for (;;)
  {
    UInt32 c = k_Base64Table[(Byte)(*src++)];

    if (c < 64)
    {
      val = (val << 6) | c;
      if ((val & ((UInt32)1 << 24)) == 0)
        continue;
      dest[0] = (Byte)(val >> 16);
      dest[1] = (Byte)(val >> 8);
      dest[2] = (Byte)(val);
      dest += 3;
      val = 1;
      continue;
    }
    
    if (c == 65) // space
      continue;
    
    if (c == 64) // '='
      break;
    
    if (c == 66 && val == 1) // end of string
      return dest;
    
    return NULL;
  }

  if (val < (1 << 12))
    return NULL;

  if (val & (1 << 18))
  {
    *dest++ = (Byte)(val >> 10);
    *dest++ = (Byte)(val >> 2);
  }
  else if (k_Base64Table[(Byte)(*src++)] != 64) // '='
    return NULL;
  else
    *dest++ = (Byte)(val >> 4);

  for (;;)
  {
    Byte c = k_Base64Table[(Byte)(*src++)];
    if (c == 65) // space
      continue;
    if (c == 66) // end of string
      return dest;
    return NULL;
  }
}


namespace NArchive {
namespace NDmg {

enum
{
  METHOD_ZERO_0 = 0,
  METHOD_COPY   = 1,
  METHOD_ZERO_2 = 2, // without file CRC calculation
  METHOD_ADC    = 0x80000004,
  METHOD_ZLIB   = 0x80000005,
  METHOD_BZIP2  = 0x80000006,
  METHOD_COMMENT = 0x7FFFFFFE, // is used to comment "+beg" and "+end" in extra field.
  METHOD_END    = 0xFFFFFFFF
};

struct CBlock
{
  UInt32 Type;
  UInt64 UnpPos;
  UInt64 UnpSize;
  UInt64 PackPos;
  UInt64 PackSize;
  
  UInt64 GetNextPackOffset() const { return PackPos + PackSize; }
  UInt64 GetNextUnpPos() const { return UnpPos + UnpSize; }

  bool IsZeroMethod() const { return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; }
  bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; }
};

static const UInt32 kCheckSumType_CRC = 2;

static const size_t kChecksumSize_Max = 0x80;

struct CChecksum
{
  UInt32 Type;
  UInt32 NumBits;
  Byte Data[kChecksumSize_Max];

  bool IsCrc32() const { return Type == kCheckSumType_CRC && NumBits == 32; }
  UInt32 GetCrc32() const { return Get32(Data); }
  void Parse(const Byte *p);
};

void CChecksum::Parse(const Byte *p)
{
  Type = Get32(p);
  NumBits = Get32(p + 4);
  memcpy(Data, p + 8, kChecksumSize_Max);
};

struct CFile
{
  UInt64 Size;
  UInt64 PackSize;
  UInt64 StartPos;
  AString Name;
  CRecordVector<CBlock> Blocks;
  CChecksum Checksum;
  bool FullFileChecksum;

  HRESULT Parse(const Byte *p, UInt32 size);
};

#ifdef DMG_SHOW_RAW
struct CExtraFile
{
  CByteBuffer Data;
  AString Name;
};
#endif

class CHandler:
  public IInArchive,
  public IInArchiveGetStream,
  public CMyUnknownImp
{
  CMyComPtr<IInStream> _inStream;
  CObjectVector<CFile> _files;
  bool _masterCrcError;

  UInt64 _startPos;
  UInt64 _phySize;
  
  #ifdef DMG_SHOW_RAW
  CObjectVector<CExtraFile> _extras;
  #endif

  HRESULT Open2(IInStream *stream);
  HRESULT Extract(IInStream *stream);
public:
  MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream)
  INTERFACE_IInArchive(;)
  STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);
};

// that limit can be increased, if there are such dmg files
static const size_t kXmlSizeMax = 0xFFFF0000; // 4 GB - 64 KB;

struct CMethods
{
  CRecordVector<UInt32> Types;
  CRecordVector<UInt32> ChecksumTypes;

  void Update(const CFile &file);
  void GetString(AString &s) const;
};

void CMethods::Update(const CFile &file)
{
  ChecksumTypes.AddToUniqueSorted(file.Checksum.Type);
  FOR_VECTOR (i, file.Blocks)
    Types.AddToUniqueSorted(file.Blocks[i].Type);
}

void CMethods::GetString(AString &res) const
{
  res.Empty();

  unsigned i;
  
  for (i = 0; i < Types.Size(); i++)
  {
    UInt32 type = Types[i];
    if (type == METHOD_COMMENT || type == METHOD_END)
      continue;
    char buf[16];
    const char *s;
    switch (type)
    {
      case METHOD_ZERO_0: s = "Zero0"; break;
      case METHOD_ZERO_2: s = "Zero2"; break;
      case METHOD_COPY:   s = "Copy";  break;
      case METHOD_ADC:    s = "ADC";   break;
      case METHOD_ZLIB:   s = "ZLIB";  break;
      case METHOD_BZIP2:  s = "BZip2"; break;
      default: ConvertUInt32ToString(type, buf); s = buf;
    }
    res.Add_Space_if_NotEmpty();
    res += s;
  }
  
  for (i = 0; i < ChecksumTypes.Size(); i++)
  {
    UInt32 type = ChecksumTypes[i];
    char buf[32];
    const char *s;
    switch (type)
    {
      case kCheckSumType_CRC: s = "CRC"; break;
      default:
        ConvertUInt32ToString(type, MyStpCpy(buf, "Check"));
        s = buf;
    }
    res.Add_Space_if_NotEmpty();
    res += s;
  }
}

struct CAppleName
{
  bool IsFs;
  const char *Ext;
  const char *AppleName;
};

static const CAppleName k_Names[] =
{
  { true,  "hfs",  "Apple_HFS" },
  { true,  "hfsx", "Apple_HFSX" },
  { true,  "ufs",  "Apple_UFS" },
  { false, "free", "Apple_Free" },
  { false, "ddm",  "DDM" },
  { false, NULL,   "Apple_partition_map" },
  { false, NULL,   " GPT " },
  { false, NULL,   "MBR" },
  { false, NULL,   "Driver" },
  { false, NULL,   "Patches" }
};
  
static const unsigned kNumAppleNames = ARRAY_SIZE(k_Names);

static const Byte kProps[] =
{
  kpidPath,
  kpidSize,
  kpidPackSize,
  kpidCRC,
  kpidComment,
  kpidMethod
};

IMP_IInArchive_Props

static const Byte kArcProps[] =
{
  kpidMethod,
  kpidNumBlocks
};

STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NWindows::NCOM::CPropVariant prop;
  switch (propID)
  {
    case kpidMethod:
    {
      CMethods m;
      FOR_VECTOR (i, _files)
        m.Update(_files[i]);
      AString s;
      m.GetString(s);
      if (!s.IsEmpty())
        prop = s;
      break;
    }
    case kpidNumBlocks:
    {
      UInt64 numBlocks = 0;
      FOR_VECTOR (i, _files)
        numBlocks += _files[i].Blocks.Size();
      prop = numBlocks;
      break;
    }
    case kpidMainSubfile:
    {
      int mainIndex = -1;
      unsigned numFS = 0;
      unsigned numUnknown = 0;
      FOR_VECTOR (i, _files)
      {
        const AString &name = _files[i].Name;
        unsigned n;
        for (n = 0; n < kNumAppleNames; n++)
        {
          const CAppleName &appleName = k_Names[n];
          // if (name.Find(appleName.AppleName) >= 0)
          if (strstr(name, appleName.AppleName))
          {
            if (appleName.IsFs)
            {
              numFS++;
              mainIndex = i;
            }
            break;
          }
        }
        if (n == kNumAppleNames)
        {
          mainIndex = i;
          numUnknown++;
        }
      }
      if (numFS + numUnknown == 1)
        prop = (UInt32)mainIndex;
      break;
    }
    case kpidWarning:
      if (_masterCrcError)
        prop = "Master CRC error";
      break;
    case kpidOffset: prop = _startPos; break;
    case kpidPhySize: prop = _phySize; break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

IMP_IInArchive_ArcProps

HRESULT CFile::Parse(const Byte *p, UInt32 size)
{
  const UInt32 kHeadSize = 0xCC;
  if (size < kHeadSize)
    return S_FALSE;
  if (Get32(p) != 0x6D697368) // "mish" signature
    return S_FALSE;
  if (Get32(p + 4) != 1) // version
    return S_FALSE;
  // UInt64 firstSectorNumber = Get64(p + 8);
  UInt64 numSectors = Get64(p + 0x10);

  StartPos = Get64(p + 0x18);

  // UInt32 decompressedBufRequested = Get32(p + 0x20); // ???
  // UInt32 blocksDescriptor = Get32(p + 0x24); // number starting from -1?
  // char Reserved1[24];
  
  Checksum.Parse(p + 0x40);
  PRF(printf("\n\nChecksum Type = %2d", Checksum.Type));

  UInt32 numBlocks = Get32(p + 0xC8);
  if (numBlocks > ((UInt32)1 << 28))
    return S_FALSE;

  const UInt32 kRecordSize = 40;
  if (numBlocks * kRecordSize + kHeadSize != size)
    return S_FALSE;

  PackSize = 0;
  Size = 0;
  Blocks.ClearAndReserve(numBlocks);
  FullFileChecksum = true;

  p += kHeadSize;
  UInt32 i;
 
  for (i = 0; i < numBlocks; i++, p += kRecordSize)
  {
    CBlock b;
    b.Type = Get32(p);
    b.UnpPos   = Get64(p + 0x08) << 9;
    b.UnpSize  = Get64(p + 0x10) << 9;
    b.PackPos  = Get64(p + 0x18);
    b.PackSize = Get64(p + 0x20);
    
    // b.PackPos can be 0 for some types. So we don't check it
    if (!Blocks.IsEmpty())
      if (b.UnpPos != Blocks.Back().GetNextUnpPos())
        return S_FALSE;
    
    PRF(printf("\nType=%8x  m[1]=%8x  uPos=%8x  uSize=%7x  pPos=%8x  pSize=%7x",
        b.Type, Get32(p + 4), (UInt32)b.UnpPos, (UInt32)b.UnpSize, (UInt32)b.PackPos, (UInt32)b.PackSize));
    
    if (b.Type == METHOD_COMMENT)
      continue;
    if (b.Type == METHOD_END)
      break;
    PackSize += b.PackSize;
  
    if (b.UnpSize != 0)
    {
      if (b.Type == METHOD_ZERO_2)
        FullFileChecksum = false;
      Blocks.AddInReserved(b);
    }
  }
  
  if (i != numBlocks - 1)
    return S_FALSE;
  if (!Blocks.IsEmpty())
    Size = Blocks.Back().GetNextUnpPos();
  if (Size != (numSectors << 9))
    return S_FALSE;
  
  return S_OK;
}

static int FindKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag)
{
  for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++)
  {
    const CXmlItem &si = item.SubItems[i];
    if (si.IsTagged("key") && si.GetSubString() == key && item.SubItems[i + 1].IsTagged(nextTag))
      return i + 1;
  }
  return -1;
}

static const AString *GetStringFromKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag)
{
  int index = FindKeyPair(item, key, nextTag);
  if (index >= 0)
    return item.SubItems[index].GetSubStringPtr();
  return NULL;
}

static const unsigned HEADER_SIZE = 0x200;

static const Byte k_Signature[] = { 'k','o','l','y', 0, 0, 0, 4, 0, 0, 2, 0 };

static inline bool IsKoly(const Byte *p)
{
  return memcmp(p, k_Signature, ARRAY_SIZE(k_Signature)) == 0;
  /*
  if (Get32(p) != 0x6B6F6C79) // "koly" signature
    return false;
  if (Get32(p + 4) != 4) // version
    return false;
  if (Get32(p + 8) != HEADER_SIZE)
    return false;
  return true;
  */
}

HRESULT CHandler::Open2(IInStream *stream)
{
  RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_startPos));

  Byte buf[HEADER_SIZE];
  RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE));

  UInt64 headerPos;
  if (IsKoly(buf))
    headerPos = _startPos;
  else
  {
    RINOK(stream->Seek(0, STREAM_SEEK_END, &headerPos));
    if (headerPos < HEADER_SIZE)
      return S_FALSE;
    headerPos -= HEADER_SIZE;
    RINOK(stream->Seek(headerPos, STREAM_SEEK_SET, NULL));
    RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE));
    if (!IsKoly(buf))
      return S_FALSE;
  }

  // UInt32 flags = Get32(buf + 12);
  // UInt64 runningDataForkOffset = Get64(buf + 0x10);
  UInt64 dataForkOffset = Get64(buf + 0x18);
  UInt64 dataForkLen = Get64(buf + 0x20);
  UInt64 rsrcOffset = Get64(buf + 0x28);
  UInt64 rsrcLen = Get64(buf + 0x30);
  // UInt32 segmentNumber = Get32(buf + 0x38);
  // UInt32 segmentCount = Get32(buf + 0x3C);
  // Byte segmentGUID[16];
  // CChecksum dataForkChecksum;
  // dataForkChecksum.Parse(buf + 0x50);
  UInt64 xmlOffset = Get64(buf + 0xD8);
  UInt64 xmlLen = Get64(buf + 0xE0);

  if (   headerPos < dataForkOffset
      || headerPos - dataForkOffset < dataForkLen
      || headerPos < rsrcOffset
      || headerPos - rsrcOffset < rsrcLen
      || headerPos < xmlOffset
      || headerPos - xmlOffset < xmlLen)
    return S_FALSE;

  UInt64 totalLen = dataForkLen + rsrcLen + xmlLen;
  if (totalLen > headerPos)
    return S_FALSE;
  _startPos = headerPos - totalLen;
  _phySize = totalLen + HEADER_SIZE;
  headerPos = totalLen;

  // Byte reserved[0x78]

  CChecksum masterChecksum;
  masterChecksum.Parse(buf + 0x160);

  // UInt32 imageVariant = Get32(buf + 0x1E8);
  // UInt64 numSectors = Get64(buf + 0x1EC);
  // Byte reserved[0x12]

  const UInt32 RSRC_HEAD_SIZE = 0x100;

  // We don't know the size of the field "offset" in rsrc.
  // We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcLen < (1 << 24).
  bool useRsrc = (rsrcLen > RSRC_HEAD_SIZE && rsrcLen < ((UInt32)1 << 24));
  // useRsrc = false;

  if (useRsrc)
  {
    #ifdef DMG_SHOW_RAW
    CExtraFile &extra = _extras.AddNew();
    extra.Name = "rsrc.bin";
    CByteBuffer &rsrcBuf = extra.Data;
    #else
    CByteBuffer rsrcBuf;
    #endif

    size_t rsrcLenT = (size_t)rsrcLen;
    rsrcBuf.Alloc(rsrcLenT);
    RINOK(stream->Seek(_startPos + rsrcOffset, STREAM_SEEK_SET, NULL));
    RINOK(ReadStream_FALSE(stream, rsrcBuf, rsrcLenT));

    const Byte *p = rsrcBuf;
    UInt32 headSize = Get32(p + 0);
    UInt32 footerOffset = Get32(p + 4);
    UInt32 mainDataSize = Get32(p + 8);
    UInt32 footerSize = Get32(p + 12);
    if (headSize != RSRC_HEAD_SIZE ||
        footerOffset >= rsrcLenT ||
        mainDataSize >= rsrcLenT ||
        footerOffset + footerSize != rsrcLenT ||
        footerOffset != headSize + mainDataSize)
      return S_FALSE;
    if (footerSize < 16)
      return S_FALSE;
    if (memcmp(p, p + footerOffset, 16) != 0)
      return S_FALSE;

    p += footerOffset;

    if ((UInt32)Get16(p + 0x18) != 0x1C)
      return S_FALSE;
    UInt32 namesOffset = Get16(p + 0x1A);
    if (namesOffset > footerSize)
      return S_FALSE;
    
    UInt32 numItems = (UInt32)Get16(p + 0x1C) + 1;
    if (numItems * 8 + 0x1E > namesOffset)
      return S_FALSE;
    
    for (UInt32 i = 0; i < numItems; i++)
    {
      const Byte *p2 = p + 0x1E + i * 8;
    
      UInt32 typeId = Get32(p2);
      if (typeId != 0x626C6B78) // blkx
        continue;
      
      UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1;
      UInt32 offs = Get16(p2 + 6);
      if (0x1C + offs + 12 * numFiles > namesOffset)
        return S_FALSE;

      for (UInt32 k = 0; k < numFiles; k++)
      {
        const Byte *p3 = p + 0x1C + offs + k * 12;
        // UInt32 id = Get16(p3);
        UInt32 namePos = Get16(p3 + 2);
        // Byte attributes = p3[4]; // = 0x50 for blkx
        // we don't know how many bits we can use. So we use 24 bits only
        UInt32 blockOffset = Get32(p3 + 4);
        blockOffset &= (((UInt32)1 << 24) - 1);
        // UInt32 unknown2 = Get32(p3 + 8); // ???
        if (blockOffset + 4 >= mainDataSize)
          return S_FALSE;
        const Byte *pBlock = rsrcBuf + headSize + blockOffset;
        UInt32 blockSize = Get32(pBlock);
              
        #ifdef DMG_SHOW_RAW
        {
          CExtraFile &extra = _extras.AddNew();
          {
            char extraName[16];
            ConvertUInt32ToString(_files.Size(), extraName);
            extra.Name = extraName;
          }
          extra.Data.CopyFrom(pBlock + 4, blockSize);
        }
        #endif
        
        CFile &file = _files.AddNew();
        if (namePos != 0xFFFF)
        {
          UInt32 namesBlockSize = footerSize - namesOffset;
          if (namePos >= namesBlockSize)
            return S_FALSE;
          const Byte *namePtr = p + namesOffset + namePos;
          UInt32 nameLen = *namePtr;
          if (namesBlockSize - namePos <= nameLen)
            return S_FALSE;
          for (UInt32 r = 1; r <= nameLen; r++)
          {
            Byte c = namePtr[r];
            if (c < 0x20 || c >= 0x80)
              break;
            file.Name += (char)c;
          }
        }
        RINOK(file.Parse(pBlock + 4, blockSize));
      }
    }
  }
  else
  {
    if (xmlLen >= kXmlSizeMax || xmlLen == 0)
      return S_FALSE;
    size_t size = (size_t)xmlLen;
    if (size != xmlLen)
      return S_FALSE;

    RINOK(stream->Seek(_startPos + dataForkLen, STREAM_SEEK_SET, NULL));
    
    CXml xml;
    {
      CObjArray<char> xmlStr(size + 1);
      RINOK(ReadStream_FALSE(stream, xmlStr, size));
      xmlStr[size] = 0;
      // if (strlen(xmlStr) != size) return S_FALSE;
      if (!xml.Parse(xmlStr))
        return S_FALSE;

      #ifdef DMG_SHOW_RAW
      CExtraFile &extra = _extras.AddNew();
      extra.Name = "a.xml";
      extra.Data.CopyFrom((const Byte *)(const char *)xmlStr, size);
      #endif
    }
    
    if (xml.Root.Name != "plist")
      return S_FALSE;
    
    int dictIndex = xml.Root.FindSubTag("dict");
    if (dictIndex < 0)
      return S_FALSE;
    
    const CXmlItem &dictItem = xml.Root.SubItems[dictIndex];
    int rfDictIndex = FindKeyPair(dictItem, "resource-fork", "dict");
    if (rfDictIndex < 0)
      return S_FALSE;
    
    const CXmlItem &rfDictItem = dictItem.SubItems[rfDictIndex];
    int arrIndex = FindKeyPair(rfDictItem, "blkx", "array");
    if (arrIndex < 0)
      return S_FALSE;
    
    const CXmlItem &arrItem = rfDictItem.SubItems[arrIndex];
    
    FOR_VECTOR (i, arrItem.SubItems)
    {
      const CXmlItem &item = arrItem.SubItems[i];
      if (!item.IsTagged("dict"))
        continue;
      
      CByteBuffer rawBuf;
      unsigned destLen = 0;
      {
        const AString *dataString = GetStringFromKeyPair(item, "Data", "data");
        if (!dataString)
          return S_FALSE;
        destLen = dataString->Len() / 4 * 3 + 4;
        rawBuf.Alloc(destLen);
        {
          const Byte *endPtr = Base64ToBin(rawBuf, *dataString);
          if (!endPtr)
            return S_FALSE;
          destLen = (unsigned)(endPtr - rawBuf);
        }
        
        #ifdef DMG_SHOW_RAW
        CExtraFile &extra = _extras.AddNew();
        {
          char extraName[16];
          ConvertUInt32ToString(_files.Size(), extraName);
          extra.Name = extraName;
        }
        extra.Data.CopyFrom(rawBuf, destLen);
        #endif
      }
      CFile &file = _files.AddNew();
      {
        const AString *name = GetStringFromKeyPair(item, "Name", "string");
        if (!name || name->IsEmpty())
          name = GetStringFromKeyPair(item, "CFName", "string");
        if (name)
          file.Name = *name;
      }
      RINOK(file.Parse(rawBuf, destLen));
    }
  }

  if (masterChecksum.IsCrc32())
  {
    UInt32 crc = CRC_INIT_VAL;
    unsigned i;
    for (i = 0; i < _files.Size(); i++)
    {
      const CChecksum &cs = _files[i].Checksum;
      if ((cs.NumBits & 0x7) != 0)
        break;
      UInt32 len = cs.NumBits >> 3;
      if (len > kChecksumSize_Max)
        break;
      crc = CrcUpdate(crc, cs.Data, (size_t)len);
    }
    if (i == _files.Size())
      _masterCrcError = (CRC_GET_DIGEST(crc) != masterChecksum.GetCrc32());
  }

  return S_OK;
}

STDMETHODIMP CHandler::Open(IInStream *stream,
    const UInt64 * /* maxCheckStartPosition */,
    IArchiveOpenCallback * /* openArchiveCallback */)
{
  COM_TRY_BEGIN
  {
    Close();
    if (Open2(stream) != S_OK)
      return S_FALSE;
    _inStream = stream;
  }
  return S_OK;
  COM_TRY_END
}

STDMETHODIMP CHandler::Close()
{
  _phySize = 0;
  _inStream.Release();
  _files.Clear();
  _masterCrcError = false;
  #ifdef DMG_SHOW_RAW
  _extras.Clear();
  #endif
  return S_OK;
}

STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
  *numItems = _files.Size()
    #ifdef DMG_SHOW_RAW
    + _extras.Size()
    #endif
  ;
  return S_OK;
}

#define RAW_PREFIX "raw" STRING_PATH_SEPARATOR

STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NWindows::NCOM::CPropVariant prop;
  
  #ifdef DMG_SHOW_RAW
  if (index >= _files.Size())
  {
    const CExtraFile &extra = _extras[index - _files.Size()];
    switch (propID)
    {
      case kpidPath:
        prop = (AString)RAW_PREFIX + extra.Name;
        break;
      case kpidSize:
      case kpidPackSize:
        prop = (UInt64)extra.Data.Size();
        break;
    }
  }
  else
  #endif
  {
    const CFile &item = _files[index];
    switch (propID)
    {
      case kpidSize:  prop = item.Size; break;
      case kpidPackSize:  prop = item.PackSize; break;
      case kpidCRC:
      {
        if (item.Checksum.IsCrc32() && item.FullFileChecksum)
          prop = item.Checksum.GetCrc32();
        break;
      }

      case kpidMethod:
      {
        CMethods m;
        m.Update(item);
        AString s;
        m.GetString(s);
        if (!s.IsEmpty())
          prop = s;
        break;
      }
      
      case kpidPath:
      {
        UString name;
        wchar_t s[16];
        ConvertUInt32ToString(index, s);
        name = s;
        unsigned num = 10;
        unsigned numDigits;
        for (numDigits = 1; num < _files.Size(); numDigits++)
          num *= 10;
        while (name.Len() < numDigits)
          name.InsertAtFront(L'0');

        AString subName;
        int pos1 = item.Name.Find('(');
        if (pos1 >= 0)
        {
          pos1++;
          int pos2 = item.Name.Find(')', pos1);
          if (pos2 >= 0)
          {
            subName.SetFrom(item.Name.Ptr(pos1), pos2 - pos1);
            pos1 = subName.Find(':');
            if (pos1 >= 0)
              subName.DeleteFrom(pos1);
          }
        }
        subName.Trim();
        if (!subName.IsEmpty())
        {
          for (unsigned n = 0; n < kNumAppleNames; n++)
          {
            const CAppleName &appleName = k_Names[n];
            if (appleName.Ext)
            {
              if (subName == appleName.AppleName)
              {
                subName = appleName.Ext;
                break;
              }
            }
          }
          UString name2;
          ConvertUTF8ToUnicode(subName, name2);
          name += L'.';
          name += name2;
        }
        else
        {
          UString name2;
          ConvertUTF8ToUnicode(item.Name, name2);
          if (!name2.IsEmpty())
            name.AddAscii(" - ");
          name += name2;
        }
        prop = name;
        break;
      }
      
      case kpidComment:
      {
        UString name;
        ConvertUTF8ToUnicode(item.Name, name);
        prop = name;
        break;
      }
    }
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

class CAdcDecoder:
  public ICompressCoder,
  public CMyUnknownImp
{
  CLzOutWindow m_OutWindowStream;
  CInBuffer m_InStream;

  /*
  void ReleaseStreams()
  {
    m_OutWindowStream.ReleaseStream();
    m_InStream.ReleaseStream();
  }
  */

  class CCoderReleaser
  {
    CAdcDecoder *m_Coder;
  public:
    bool NeedFlush;
    CCoderReleaser(CAdcDecoder *coder): m_Coder(coder), NeedFlush(true) {}
    ~CCoderReleaser()
    {
      if (NeedFlush)
        m_Coder->m_OutWindowStream.Flush();
      // m_Coder->ReleaseStreams();
    }
  };
  friend class CCoderReleaser;

public:
  MY_UNKNOWN_IMP

  STDMETHOD(CodeReal)(ISequentialInStream *inStream,
      ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
      ICompressProgressInfo *progress);

  STDMETHOD(Code)(ISequentialInStream *inStream,
      ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
      ICompressProgressInfo *progress);
};

STDMETHODIMP CAdcDecoder::CodeReal(ISequentialInStream *inStream,
    ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
    ICompressProgressInfo *progress)
{
  if (!m_OutWindowStream.Create(1 << 18))
    return E_OUTOFMEMORY;
  if (!m_InStream.Create(1 << 18))
    return E_OUTOFMEMORY;

  m_OutWindowStream.SetStream(outStream);
  m_OutWindowStream.Init(false);
  m_InStream.SetStream(inStream);
  m_InStream.Init();
  
  CCoderReleaser coderReleaser(this);

  const UInt32 kStep = (1 << 20);
  UInt64 nextLimit = kStep;

  UInt64 pos = 0;
  while (pos < *outSize)
  {
    if (pos > nextLimit && progress)
    {
      UInt64 packSize = m_InStream.GetProcessedSize();
      RINOK(progress->SetRatioInfo(&packSize, &pos));
      nextLimit += kStep;
    }
    Byte b;
    if (!m_InStream.ReadByte(b))
      return S_FALSE;
    UInt64 rem = *outSize - pos;
    if (b & 0x80)
    {
      unsigned num = (b & 0x7F) + 1;
      if (num > rem)
        return S_FALSE;
      for (unsigned i = 0; i < num; i++)
      {
        if (!m_InStream.ReadByte(b))
          return S_FALSE;
        m_OutWindowStream.PutByte(b);
      }
      pos += num;
      continue;
    }
    Byte b1;
    if (!m_InStream.ReadByte(b1))
      return S_FALSE;

    UInt32 len, distance;

    if (b & 0x40)
    {
      len = ((UInt32)b & 0x3F) + 4;
      Byte b2;
      if (!m_InStream.ReadByte(b2))
        return S_FALSE;
      distance = ((UInt32)b1 << 8) + b2;
    }
    else
    {
      b &= 0x3F;
      len = ((UInt32)b >> 2) + 3;
      distance = (((UInt32)b & 3) << 8) + b1;
    }

    if (distance >= pos || len > rem)
      return S_FALSE;
    m_OutWindowStream.CopyBlock(distance, len);
    pos += len;
  }
  if (*inSize != m_InStream.GetProcessedSize())
    return S_FALSE;
  coderReleaser.NeedFlush = false;
  return m_OutWindowStream.Flush();
}

STDMETHODIMP CAdcDecoder::Code(ISequentialInStream *inStream,
    ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize,
    ICompressProgressInfo *progress)
{
  try { return CodeReal(inStream, outStream, inSize, outSize, progress);}
  catch(const CInBufferException &e) { return e.ErrorCode; }
  catch(const CLzOutWindowException &e) { return e.ErrorCode; }
  catch(...) { return S_FALSE; }
}


STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
    Int32 testMode, IArchiveExtractCallback *extractCallback)
{
  COM_TRY_BEGIN
  bool allFilesMode = (numItems == (UInt32)(Int32)-1);
  if (allFilesMode)
    numItems = _files.Size();
  if (numItems == 0)
    return S_OK;
  UInt64 totalSize = 0;
  UInt32 i;
  
  for (i = 0; i < numItems; i++)
  {
    UInt32 index = (allFilesMode ? i : indices[i]);
    #ifdef DMG_SHOW_RAW
    if (index >= _files.Size())
      totalSize += _extras[index - _files.Size()].Data.Size();
    else
    #endif
      totalSize += _files[index].Size;
  }
  extractCallback->SetTotal(totalSize);

  UInt64 currentPackTotal = 0;
  UInt64 currentUnpTotal = 0;
  UInt64 currentPackSize = 0;
  UInt64 currentUnpSize = 0;

  const UInt32 kZeroBufSize = (1 << 14);
  CByteBuffer zeroBuf(kZeroBufSize);
  memset(zeroBuf, 0, kZeroBufSize);
  
  NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
  CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;

  NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
  CMyComPtr<ICompressCoder> bzip2Coder = bzip2CoderSpec;

  NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder();
  CMyComPtr<ICompressCoder> zlibCoder = zlibCoderSpec;

  CAdcDecoder *adcCoderSpec = new CAdcDecoder();
  CMyComPtr<ICompressCoder> adcCoder = adcCoderSpec;

  CLocalProgress *lps = new CLocalProgress;
  CMyComPtr<ICompressProgressInfo> progress = lps;
  lps->Init(extractCallback, false);

  CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
  CMyComPtr<ISequentialInStream> inStream(streamSpec);
  streamSpec->SetStream(_inStream);

  for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize)
  {
    lps->InSize = currentPackTotal;
    lps->OutSize = currentUnpTotal;
    currentPackSize = 0;
    currentUnpSize = 0;
    RINOK(lps->SetCur());
    CMyComPtr<ISequentialOutStream> realOutStream;
    Int32 askMode = testMode ?
        NExtract::NAskMode::kTest :
        NExtract::NAskMode::kExtract;
    UInt32 index = allFilesMode ? i : indices[i];
    RINOK(extractCallback->GetStream(index, &realOutStream, askMode));

    if (!testMode && !realOutStream)
      continue;
    RINOK(extractCallback->PrepareOperation(askMode));


    COutStreamWithCRC *outCrcStreamSpec = new COutStreamWithCRC;
    CMyComPtr<ISequentialOutStream> outCrcStream = outCrcStreamSpec;
    outCrcStreamSpec->SetStream(realOutStream);
    bool needCrc = false;
    outCrcStreamSpec->Init(needCrc);

    CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;
    CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
    outStreamSpec->SetStream(outCrcStream);
    
    realOutStream.Release();

    Int32 opRes = NExtract::NOperationResult::kOK;
    #ifdef DMG_SHOW_RAW
    if (index >= _files.Size())
    {
      const CByteBuffer &buf = _extras[index - _files.Size()].Data;
      outStreamSpec->Init(buf.Size());
      RINOK(WriteStream(outStream, buf, buf.Size()));
      currentPackSize = currentUnpSize = buf.Size();
    }
    else
    #endif
    {
      const CFile &item = _files[index];
      currentPackSize = item.PackSize;
      currentUnpSize = item.Size;

      needCrc = item.Checksum.IsCrc32();

      UInt64 unpPos = 0;
      UInt64 packPos = 0;
      {
        FOR_VECTOR (j, item.Blocks)
        {
          lps->InSize = currentPackTotal + packPos;
          lps->OutSize = currentUnpTotal + unpPos;
          RINOK(lps->SetCur());

          const CBlock &block = item.Blocks[j];
          if (!block.ThereAreDataInBlock())
            continue;

          packPos += block.PackSize;
          if (block.UnpPos != unpPos)
          {
            opRes = NExtract::NOperationResult::kDataError;
            break;
          }

          RINOK(_inStream->Seek(_startPos + item.StartPos + block.PackPos, STREAM_SEEK_SET, NULL));
          streamSpec->Init(block.PackSize);
          bool realMethod = true;
          outStreamSpec->Init(block.UnpSize);
          HRESULT res = S_OK;

          outCrcStreamSpec->EnableCalc(needCrc);

          switch (block.Type)
          {
            case METHOD_ZERO_0:
            case METHOD_ZERO_2:
              realMethod = false;
              if (block.PackSize != 0)
                opRes = NExtract::NOperationResult::kUnsupportedMethod;
              outCrcStreamSpec->EnableCalc(block.Type == METHOD_ZERO_0);
              break;

            case METHOD_COPY:
              if (block.UnpSize != block.PackSize)
              {
                opRes = NExtract::NOperationResult::kUnsupportedMethod;
                break;
              }
              res = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
              break;
            
            case METHOD_ADC:
            {
              res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, progress);
              break;
            }
            
            case METHOD_ZLIB:
            {
              res = zlibCoder->Code(inStream, outStream, NULL, NULL, progress);
              if (res == S_OK)
                if (zlibCoderSpec->GetInputProcessedSize() != block.PackSize)
                  opRes = NExtract::NOperationResult::kDataError;
              break;
            }

            case METHOD_BZIP2:
            {
              res = bzip2Coder->Code(inStream, outStream, NULL, NULL, progress);
              if (res == S_OK)
                if (bzip2CoderSpec->GetInputProcessedSize() != block.PackSize)
                  opRes = NExtract::NOperationResult::kDataError;
              break;
            }
            
            default:
              opRes = NExtract::NOperationResult::kUnsupportedMethod;
              break;
          }

          if (res != S_OK)
          {
            if (res != S_FALSE)
              return res;
            if (opRes == NExtract::NOperationResult::kOK)
              opRes = NExtract::NOperationResult::kDataError;
          }
          
          unpPos += block.UnpSize;
          
          if (!outStreamSpec->IsFinishedOK())
          {
            if (realMethod && opRes == NExtract::NOperationResult::kOK)
              opRes = NExtract::NOperationResult::kDataError;

            while (outStreamSpec->GetRem() != 0)
            {
              UInt64 rem = outStreamSpec->GetRem();
              UInt32 size = (UInt32)MyMin(rem, (UInt64)kZeroBufSize);
              RINOK(WriteStream(outStream, zeroBuf, size));
            }
          }
        }
      }
  
      if (needCrc && opRes == NExtract::NOperationResult::kOK)
      {
        if (outCrcStreamSpec->GetCRC() != item.Checksum.GetCrc32())
          opRes = NExtract::NOperationResult::kCRCError;
      }
    }
    outStream.Release();
    RINOK(extractCallback->SetOperationResult(opRes));
  }

  return S_OK;
  COM_TRY_END
}

struct CChunk
{
  int BlockIndex;
  UInt64 AccessMark;
  CByteBuffer Buf;
};

class CInStream:
  public IInStream,
  public CMyUnknownImp
{
  UInt64 _virtPos;
  int _latestChunk;
  int _latestBlock;
  UInt64 _accessMark;
  CObjectVector<CChunk> _chunks;

  NCompress::NBZip2::CDecoder *bzip2CoderSpec;
  CMyComPtr<ICompressCoder> bzip2Coder;

  NCompress::NZlib::CDecoder *zlibCoderSpec;
  CMyComPtr<ICompressCoder> zlibCoder;

  CAdcDecoder *adcCoderSpec;
  CMyComPtr<ICompressCoder> adcCoder;

  CBufPtrSeqOutStream *outStreamSpec;
  CMyComPtr<ISequentialOutStream> outStream;

  CLimitedSequentialInStream *limitedStreamSpec;
  CMyComPtr<ISequentialInStream> inStream;

public:
  CMyComPtr<IInStream> Stream;
  UInt64 Size;
  const CFile *File;
  UInt64 _startPos;

  HRESULT InitAndSeek(UInt64 startPos)
  {
    _startPos = startPos;
    _virtPos = 0;
    _latestChunk = -1;
    _latestBlock = -1;
    _accessMark = 0;

    limitedStreamSpec = new CLimitedSequentialInStream;
    inStream = limitedStreamSpec;
    limitedStreamSpec->SetStream(Stream);

    outStreamSpec = new CBufPtrSeqOutStream;
    outStream = outStreamSpec;
    return S_OK;
  }

  MY_UNKNOWN_IMP1(IInStream)

  STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize);
  STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition);
};


unsigned FindBlock(const CRecordVector<CBlock> &blocks, UInt64 pos)
{
  unsigned left = 0, right = blocks.Size();
  for (;;)
  {
    unsigned mid = (left + right) / 2;
    if (mid == left)
      return left;
    if (pos < blocks[mid].UnpPos)
      right = mid;
    else
      left = mid;
  }
}

STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)
{
  COM_TRY_BEGIN

  if (processedSize)
    *processedSize = 0;
  if (size == 0)
    return S_OK;
  if (_virtPos >= Size)
    return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL;
  {
    UInt64 rem = Size - _virtPos;
    if (size > rem)
      size = (UInt32)rem;
  }

  if (_latestBlock >= 0)
  {
    const CBlock &block = File->Blocks[_latestBlock];
    if (_virtPos < block.UnpPos || (_virtPos - block.UnpPos) >= block.UnpSize)
      _latestBlock = -1;
  }
  
  if (_latestBlock < 0)
  {
    _latestChunk = -1;
    unsigned blockIndex = FindBlock(File->Blocks, _virtPos);
    const CBlock &block = File->Blocks[blockIndex];
    
    if (!block.IsZeroMethod() && block.Type != METHOD_COPY)
    {
      unsigned i;
      for (i = 0; i < _chunks.Size(); i++)
        if (_chunks[i].BlockIndex == (int)blockIndex)
          break;
      
      if (i != _chunks.Size())
        _latestChunk = i;
      else
      {
        const unsigned kNumChunksMax = 128;
        unsigned chunkIndex;
      
        if (_chunks.Size() != kNumChunksMax)
          chunkIndex = _chunks.Add(CChunk());
        else
        {
          chunkIndex = 0;
          for (i = 0; i < _chunks.Size(); i++)
            if (_chunks[i].AccessMark < _chunks[chunkIndex].AccessMark)
              chunkIndex = i;
        }
        
        CChunk &chunk = _chunks[chunkIndex];
        chunk.BlockIndex = -1;
        chunk.AccessMark = 0;
        
        if (chunk.Buf.Size() < block.UnpSize)
        {
          chunk.Buf.Free();
          if (block.UnpSize > ((UInt32)1 << 31))
            return E_FAIL;
          chunk.Buf.Alloc((size_t)block.UnpSize);
        }
        
        outStreamSpec->Init(chunk.Buf, (size_t)block.UnpSize);
          
        RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos, STREAM_SEEK_SET, NULL));

        limitedStreamSpec->Init(block.PackSize);
        HRESULT res = S_OK;
        
        switch (block.Type)
        {
          case METHOD_COPY:
            if (block.PackSize != block.UnpSize)
              return E_FAIL;
            res = ReadStream_FAIL(inStream, chunk.Buf, (size_t)block.UnpSize);
            break;
            
          case METHOD_ADC:
            if (!adcCoder)
            {
              adcCoderSpec = new CAdcDecoder();
              adcCoder = adcCoderSpec;
            }
            res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, NULL);
            break;
            
          case METHOD_ZLIB:
            if (!zlibCoder)
            {
              zlibCoderSpec = new NCompress::NZlib::CDecoder();
              zlibCoder = zlibCoderSpec;
            }
            res = zlibCoder->Code(inStream, outStream, NULL, NULL, NULL);
            if (res == S_OK && zlibCoderSpec->GetInputProcessedSize() != block.PackSize)
              res = S_FALSE;
            break;
            
          case METHOD_BZIP2:
            if (!bzip2Coder)
            {
              bzip2CoderSpec = new NCompress::NBZip2::CDecoder();
              bzip2Coder = bzip2CoderSpec;
            }
            res = bzip2Coder->Code(inStream, outStream, NULL, NULL, NULL);
            if (res == S_OK && bzip2CoderSpec->GetInputProcessedSize() != block.PackSize)
              res = S_FALSE;
            break;
            
          default:
            return E_FAIL;
        }
        
        if (res != S_OK)
          return res;
        if (block.Type != METHOD_COPY && outStreamSpec->GetPos() != block.UnpSize)
          return E_FAIL;
        chunk.BlockIndex = blockIndex;
        _latestChunk = chunkIndex;
      }
      
      _chunks[_latestChunk].AccessMark = _accessMark++;
    }
  
    _latestBlock = blockIndex;
  }

  const CBlock &block = File->Blocks[_latestBlock];
  UInt64 offset = _virtPos - block.UnpPos;
  UInt64 rem = block.UnpSize - offset;
  if (size > rem)
    size = (UInt32)rem;
  
  HRESULT res = S_OK;
  
  if (block.Type == METHOD_COPY)
  {
    RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos + offset, STREAM_SEEK_SET, NULL));
    res = Stream->Read(data, size, &size);
  }
  else if (block.IsZeroMethod())
    memset(data, 0, size);
  else if (size != 0)
    memcpy(data, _chunks[_latestChunk].Buf + offset, size);
  
  _virtPos += size;
  if (processedSize)
    *processedSize = size;
  
  return res;
  COM_TRY_END
}
 
STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)
{
  switch (seekOrigin)
  {
    case STREAM_SEEK_SET: break;
    case STREAM_SEEK_CUR: offset += _virtPos; break;
    case STREAM_SEEK_END: offset += Size; break;
    default: return STG_E_INVALIDFUNCTION;
  }
  if (offset < 0)
    return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
  _virtPos = offset;
  if (newPosition)
    *newPosition = offset;
  return S_OK;
}

STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
{
  COM_TRY_BEGIN
  
  #ifdef DMG_SHOW_RAW
  if (index >= (UInt32)_files.Size())
    return S_FALSE;
  #endif
  
  CInStream *spec = new CInStream;
  CMyComPtr<ISequentialInStream> specStream = spec;
  spec->File = &_files[index];
  const CFile &file = *spec->File;
  
  FOR_VECTOR (i, file.Blocks)
  {
    const CBlock &block = file.Blocks[i];
    switch (block.Type)
    {
      case METHOD_ZERO_0:
      case METHOD_ZERO_2:
      case METHOD_COPY:
      case METHOD_ADC:
      case METHOD_ZLIB:
      case METHOD_BZIP2:
      case METHOD_END:
        break;
      default:
        return S_FALSE;
    }
  }
  
  spec->Stream = _inStream;
  spec->Size = spec->File->Size;
  RINOK(spec->InitAndSeek(_startPos));
  *stream = specStream.Detach();
  return S_OK;
  
  COM_TRY_END
}

REGISTER_ARC_I(
  "Dmg", "dmg", 0, 0xE4,
  k_Signature,
  0,
  NArcInfoFlags::kBackwardOpen |
  NArcInfoFlags::kUseGlobalOffset,
  NULL)

}}
