// OpenArchive.cpp

#include "StdAfx.h"

// #define SHOW_DEBUG_INFO

#ifdef SHOW_DEBUG_INFO
#include <stdio.h>
#endif

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

#include "../../../Common/ComTry.h"
#include "../../../Common/IntToString.h"
#include "../../../Common/StringConvert.h"
#include "../../../Common/StringToInt.h"
#include "../../../Common/Wildcard.h"

#include "../../../Windows/FileDir.h"

#include "../../Common/FileStreams.h"
#include "../../Common/LimitedStreams.h"
#include "../../Common/ProgressUtils.h"
#include "../../Common/StreamUtils.h"

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

#include "DefaultName.h"
#include "OpenArchive.h"

#include "SetProperties.h"

#ifdef SHOW_DEBUG_INFO
#define PRF(x) x
#else
#define PRF(x)
#endif

// increase it, if you need to support larger SFX stubs
static const UInt64 kMaxCheckStartPosition = 1 << 23;

/*
Open:
  - formatIndex >= 0 (exact Format)
       1) Open with main type. Archive handler is allowed to use archive start finder.
          Warning, if there is tail.

  - formatIndex = -1 (Parser:0) (default)
    - same as #1 but doesn't return Parser

  - formatIndex = -2 (#1)
    - file has supported extension (like a.7z)
      Open with that main type (only starting from start of file).
        - open OK:
            - if there is no tail - return OK
            - if there is tail:
              - archive is not "Self Exe" - return OK with Warning, that there is tail
              - archive is "Self Exe"
                ignore "Self Exe" stub, and tries to open tail
                  - tail can be open as archive - shows that archive and stub size property.
                  - tail can't be open as archive - shows Parser ???
        - open FAIL:
           Try to open with all other types from offset 0 only.
           If some open type is OK and physical archive size is uequal or larger
           than file size, then return that archive with warning that can not be open as [extension type].
           If extension was EXE, it will try to open as unknown_extension case
    - file has unknown extension (like a.hhh)
       It tries to open via parser code.
         - if there is full archive or tail archive and unknown block or "Self Exe"
           at front, it shows tail archive and stub size property.
         - in another cases, if there is some archive inside file, it returns parser/
         - in another cases, it retuens S_FALSE


  - formatIndex = -3 (#2)
    - same as #1, but
    - stub (EXE) + archive is open in Parser

  - formatIndex = -4 (#3)
    - returns only Parser. skip full file archive. And show other sub-archives

  - formatIndex = -5 (#4)
    - returns only Parser. skip full file archive. And show other sub-archives for each byte pos

*/

using namespace NWindows;

namespace NArchive {
namespace NParser {

struct CParseItem
{
  UInt64 Offset;
  UInt64 Size;
  UString Name;
  UString Extension;
  FILETIME FileTime;
  UString Comment;
  UString ArcType;

  bool FileTime_Defined;
  bool UnpackSize_Defined;
  bool NumSubDirs_Defined;
  bool NumSubFiles_Defined;

  bool IsSelfExe;
  bool IsNotArcType;

  UInt64 UnpackSize;
  UInt64 NumSubDirs;
  UInt64 NumSubFiles;

  int FormatIndex;

  bool LenIsUnknown;

  CParseItem():
      LenIsUnknown(false),
      FileTime_Defined(false),
      UnpackSize_Defined(false),
      NumSubFiles_Defined(false),
      NumSubDirs_Defined(false),
      IsSelfExe(false),
      IsNotArcType(false)
    {}

  void NormalizeOffset()
  {
    if ((Int64)Offset < 0)
    {
      Size += Offset;
      Offset = 0;
    }
  }
};

class CHandler:
  public IInArchive,
  public IInArchiveGetStream,
  public CMyUnknownImp
{
public:
  CObjectVector<CParseItem> _items;
  UInt64 _maxEndOffset;
  CMyComPtr<IInStream> _stream;

  MY_UNKNOWN_IMP2(
    IInArchive,
    IInArchiveGetStream)

  INTERFACE_IInArchive(;)
  STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream);

  UInt64 GetLastEnd() const
  {
    if (_items.IsEmpty())
      return 0;
    const CParseItem &back = _items.Back();
    return back.Offset + back.Size;
  }

  void AddUnknownItem(UInt64 next);
  int FindInsertPos(const CParseItem &item) const;
  void AddItem(const CParseItem &item);

  CHandler(): _maxEndOffset(0) {}
};

int CHandler::FindInsertPos(const CParseItem &item) const
{
  unsigned left = 0, right = _items.Size();
  while (left != right)
  {
    unsigned mid = (left + right) / 2;
    const CParseItem & midItem = _items[mid];
    if (item.Offset < midItem.Offset)
      right = mid;
    else if (item.Offset > midItem.Offset)
      left = mid + 1;
    else if (item.Size < midItem.Size)
      right = mid;
    else if (item.Size > midItem.Size)
      left = mid + 1;
    else
    {
      left = mid + 1;
    }
  }
  return left;
}

void CHandler::AddUnknownItem(UInt64 next)
{
  if (_maxEndOffset < next)
  {
    CParseItem item2;
    item2.Offset = _maxEndOffset;
    item2.Size = next - _maxEndOffset;
    _maxEndOffset = next;
    _items.Add(item2);
  }
  else if (_maxEndOffset > next && !_items.IsEmpty())
  {
    CParseItem &back = _items.Back();
    if (back.LenIsUnknown)
    {
      back.Size = next - back.Offset;
      _maxEndOffset = next;
    }
  }
}

void CHandler::AddItem(const CParseItem &item)
{
  AddUnknownItem(item.Offset);
  int pos = FindInsertPos(item);
  if (pos >= 0)
  {
    _items.Insert(pos, item);
    UInt64 next = item.Offset + item.Size;
    if (_maxEndOffset < next)
      _maxEndOffset = next;
  }
}

static const Byte kProps[] =
{
  kpidPath,
  kpidSize,
  kpidMTime,
  kpidType,
  kpidComment,
  kpidOffset,
  kpidUnpackSize
};

IMP_IInArchive_Props
IMP_IInArchive_ArcProps_NO

STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback * /* openArchiveCallback */)
{
  COM_TRY_BEGIN
  {
    Close();
    _stream = stream;
  }
  return S_OK;
  COM_TRY_END
}

STDMETHODIMP CHandler::Close()
{
  _items.Clear();
  _stream.Release();
  return S_OK;
}

STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
{
  *numItems = _items.Size();
  return S_OK;
}

STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
{
  COM_TRY_BEGIN
  NCOM::CPropVariant prop;

  const CParseItem &item = _items[index];

  switch (propID)
  {
    case kpidPath:
    {
      wchar_t sz[32];
      ConvertUInt32ToString(index + 1, sz);
      UString s = sz;
      if (!item.Name.IsEmpty())
      {
        s += L'.';
        s += item.Name;
      }
      if (!item.Extension.IsEmpty())
      {
        s += L'.';
        s += item.Extension;
      }
      prop = s; break;
    }
    case kpidSize:
    case kpidPackSize: prop = item.Size; break;
    case kpidOffset: prop = item.Offset; break;
    case kpidUnpackSize: if (item.UnpackSize_Defined) prop = item.UnpackSize; break;
    case kpidNumSubFiles: if (item.NumSubFiles_Defined) prop = item.NumSubFiles; break;
    case kpidNumSubDirs: if (item.NumSubDirs_Defined) prop = item.NumSubDirs; break;
    case kpidMTime: if (item.FileTime_Defined) prop = item.FileTime; break;
    case kpidComment: if (!item.Comment.IsEmpty()) prop = item.Comment; break;
    case kpidType: if (!item.ArcType.IsEmpty()) prop = item.ArcType; break;
  }
  prop.Detach(value);
  return S_OK;
  COM_TRY_END
}

HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems,
    Int32 testMode, IArchiveExtractCallback *extractCallback)
{
  COM_TRY_BEGIN

  bool allFilesMode = (numItems == (UInt32)(Int32)-1);
  if (allFilesMode)
    numItems = _items.Size();
  if (_stream && numItems == 0)
    return S_OK;
  UInt64 totalSize = 0;
  UInt32 i;
  for (i = 0; i < numItems; i++)
    totalSize += _items[allFilesMode ? i : indices[i]].Size;
  extractCallback->SetTotal(totalSize);

  totalSize = 0;

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

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

  CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream;
  CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);

  NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
  CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;

  for (i = 0; i < numItems; i++)
  {
    lps->InSize = totalSize;
    lps->OutSize = totalSize;
    RINOK(lps->SetCur());
    CMyComPtr<ISequentialOutStream> realOutStream;
    Int32 askMode = testMode ?
        NExtract::NAskMode::kTest :
        NExtract::NAskMode::kExtract;
    Int32 index = allFilesMode ? i : indices[i];
    const CParseItem &item = _items[index];

    RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
    UInt64 unpackSize = item.Size;
    totalSize += unpackSize;
    bool skipMode = false;
    if (!testMode && !realOutStream)
      continue;
    RINOK(extractCallback->PrepareOperation(askMode));

    outStreamSpec->SetStream(realOutStream);
    realOutStream.Release();
    outStreamSpec->Init(skipMode ? 0 : unpackSize, true);

    Int32 opRes = NExtract::NOperationResult::kOK;
    RINOK(_stream->Seek(item.Offset, STREAM_SEEK_SET, NULL));
    streamSpec->Init(unpackSize);
    RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress));

    if (outStreamSpec->GetRem() != 0)
      opRes = NExtract::NOperationResult::kDataError;
    outStreamSpec->ReleaseStream();
    RINOK(extractCallback->SetOperationResult(opRes));
  }

  return S_OK;

  COM_TRY_END
}


STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream)
{
  COM_TRY_BEGIN
  const CParseItem &item = _items[index];
  return CreateLimitedInStream(_stream, item.Offset, item.Size, stream);
  COM_TRY_END
}

}}

HRESULT Archive_GetItemBoolProp(IInArchive *arc, UInt32 index, PROPID propID, bool &result) throw()
{
  NCOM::CPropVariant prop;
  result = false;
  RINOK(arc->GetProperty(index, propID, &prop));
  if (prop.vt == VT_BOOL)
    result = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt != VT_EMPTY)
    return E_FAIL;
  return S_OK;
}

HRESULT Archive_IsItem_Dir(IInArchive *arc, UInt32 index, bool &result) throw()
{
  return Archive_GetItemBoolProp(arc, index, kpidIsDir, result);
}

HRESULT Archive_IsItem_Aux(IInArchive *arc, UInt32 index, bool &result) throw()
{
  return Archive_GetItemBoolProp(arc, index, kpidIsAux, result);
}

HRESULT Archive_IsItem_AltStream(IInArchive *arc, UInt32 index, bool &result) throw()
{
  return Archive_GetItemBoolProp(arc, index, kpidIsAltStream, result);
}

HRESULT Archive_IsItem_Deleted(IInArchive *arc, UInt32 index, bool &result) throw()
{
  return Archive_GetItemBoolProp(arc, index, kpidIsDeleted, result);
}

static HRESULT Archive_GetArcBoolProp(IInArchive *arc, PROPID propid, bool &result) throw()
{
  NCOM::CPropVariant prop;
  result = false;
  RINOK(arc->GetArchiveProperty(propid, &prop));
  if (prop.vt == VT_BOOL)
    result = VARIANT_BOOLToBool(prop.boolVal);
  else if (prop.vt != VT_EMPTY)
    return E_FAIL;
  return S_OK;
}

static HRESULT Archive_GetArcProp_UInt(IInArchive *arc, PROPID propid, UInt64 &result, bool &defined)
{
  defined = false;
  NCOM::CPropVariant prop;
  RINOK(arc->GetArchiveProperty(propid, &prop));
  switch (prop.vt)
  {
    case VT_UI4: result = prop.ulVal; defined = true; break;
    case VT_I4: result = (Int64)prop.lVal; defined = true; break;
    case VT_UI8: result = (UInt64)prop.uhVal.QuadPart; defined = true; break;
    case VT_I8: result = (UInt64)prop.hVal.QuadPart; defined = true; break;
    case VT_EMPTY: break;
    default: return E_FAIL;
  }
  return S_OK;
}

static HRESULT Archive_GetArcProp_Int(IInArchive *arc, PROPID propid, Int64 &result, bool &defined)
{
  defined = false;
  NCOM::CPropVariant prop;
  RINOK(arc->GetArchiveProperty(propid, &prop));
  switch (prop.vt)
  {
    case VT_UI4: result = prop.ulVal; defined = true; break;
    case VT_I4: result = prop.lVal; defined = true; break;
    case VT_UI8: result = (Int64)prop.uhVal.QuadPart; defined = true; break;
    case VT_I8: result = (Int64)prop.hVal.QuadPart; defined = true; break;
    case VT_EMPTY: break;
    default: return E_FAIL;
  }
  return S_OK;
}

HRESULT CArc::GetItemPathToParent(UInt32 index, UInt32 parent, UStringVector &parts) const
{
  if (!GetRawProps)
    return E_FAIL;
  if (index == parent)
    return S_OK;
  UInt32 curIndex = index;

  UString s;

  bool prevWasAltStream = false;

  for (;;)
  {
    #ifdef MY_CPU_LE
    const void *p;
    UInt32 size;
    UInt32 propType;
    RINOK(GetRawProps->GetRawProp(curIndex, kpidName, &p, &size, &propType));
    if (p && propType == PROP_DATA_TYPE_wchar_t_PTR_Z_LE)
      s = (const wchar_t *)p;
    else
    #endif
    {
      NCOM::CPropVariant prop;
      RINOK(Archive->GetProperty(curIndex, kpidName, &prop));
      if (prop.vt == VT_BSTR && prop.bstrVal)
        s.SetFromBstr(prop.bstrVal);
      else if (prop.vt == VT_EMPTY)
        s.Empty();
      else
        return E_FAIL;
    }

    UInt32 curParent = (UInt32)(Int32)-1;
    UInt32 parentType = 0;
    RINOK(GetRawProps->GetParent(curIndex, &curParent, &parentType));

    if (parentType != NParentType::kAltStream)
    {
      for (;;)
      {
        int pos = s.ReverseFind_PathSepar();
        if (pos < 0)
        {
          break;
        }
        parts.Insert(0, s.Ptr(pos + 1));
        s.DeleteFrom(pos);
      }
    }

    parts.Insert(0, s);

    if (prevWasAltStream)
    {
      {
        UString &s2 = parts[parts.Size() - 2];
        s2 += L':';
        s2 += parts.Back();
      }
      parts.DeleteBack();
    }

    if (parent == curParent)
      return S_OK;

    prevWasAltStream = false;
    if (parentType == NParentType::kAltStream)
      prevWasAltStream = true;

    if (curParent == (UInt32)(Int32)-1)
      return E_FAIL;
    curIndex = curParent;
  }
}

HRESULT CArc::GetItemPath(UInt32 index, UString &result) const
{
#ifdef _LIB_FOR_CHERRYTREE
    UStringVector pathParts;
    SplitPathToParts(filePath, pathParts);
    result = pathParts.Back();
    //wprintf(L"GetItemPath archiveName=%S\n", result.Ptr());
    if (result.Back() == (wchar_t)'z') {
        result.ReplaceOneCharAtPos(result.Len()-1, (wchar_t)'d');
        return S_OK;
    }
    if (result.Back() == (wchar_t)'x') {
        result.ReplaceOneCharAtPos(result.Len()-1, (wchar_t)'b');
        return S_OK;
    }
#endif // !_LIB_FOR_CHERRYTREE

  #ifdef MY_CPU_LE
  if (GetRawProps)
  {
    const void *p;
    UInt32 size;
    UInt32 propType;
    if (!IsTree)
    {
      if (GetRawProps->GetRawProp(index, kpidPath, &p, &size, &propType) == S_OK &&
          propType == NPropDataType::kUtf16z)
      {
        unsigned len = size / 2 - 1;
        wchar_t *s = result.GetBuf(len);
        for (unsigned i = 0; i < len; i++)
        {
          wchar_t c = GetUi16(p);
          p = (const void *)((const Byte *)p + 2);
          #if WCHAR_PATH_SEPARATOR != L'/'
          if (c == L'/')
            c = WCHAR_PATH_SEPARATOR;
          #endif
          *s++ = c;
        }
        *s = 0;
        result.ReleaseBuf_SetLen(len);
        if (len != 0)
          return S_OK;
      }
    }
  }
  #endif // MY_CPU_LE

  {
    NCOM::CPropVariant prop;
    RINOK(Archive->GetProperty(index, kpidPath, &prop));
    if (prop.vt == VT_BSTR && prop.bstrVal)
      result.SetFromBstr(prop.bstrVal);
    else if (prop.vt == VT_EMPTY)
      result.Empty();
    else
      return E_FAIL;
  }

  if (result.IsEmpty())
    return GetDefaultItemPath(index, result);
  return S_OK;
}

HRESULT CArc::GetDefaultItemPath(UInt32 index, UString &result) const
{
  result.Empty();
  bool isDir;
  RINOK(Archive_IsItem_Dir(Archive, index, isDir));
  if (!isDir)
  {
    result = DefaultName;
    NCOM::CPropVariant prop;
    RINOK(Archive->GetProperty(index, kpidExtension, &prop));
    if (prop.vt == VT_BSTR)
    {
      result += L'.';
      result += prop.bstrVal;
    }
    else if (prop.vt != VT_EMPTY)
      return E_FAIL;
  }
  return S_OK;
}

HRESULT CArc::GetItemPath2(UInt32 index, UString &result) const
{
  RINOK(GetItemPath(index, result));
  if (Ask_Deleted)
  {
    bool isDeleted = false;
    RINOK(Archive_IsItem_Deleted(Archive, index, isDeleted));
    if (isDeleted)
      result.Insert(0, L"[DELETED]" WSTRING_PATH_SEPARATOR);
  }
  return S_OK;
}

#ifdef SUPPORT_ALT_STREAMS

int FindAltStreamColon_in_Path(const wchar_t *path)
{
  unsigned i = 0;
  int colonPos = -1;
  for (;; i++)
  {
    wchar_t c = path[i];
    if (c == 0)
      return colonPos;
    if (c == ':')
    {
      if (colonPos < 0)
        colonPos = i;
      continue;
    }
    if (c == WCHAR_PATH_SEPARATOR)
      colonPos = -1;
  }
}

#endif

HRESULT CArc::GetItem(UInt32 index, CReadArcItem &item) const
{
  #ifdef SUPPORT_ALT_STREAMS
  item.IsAltStream = false;
  item.AltStreamName.Empty();
  item.MainPath.Empty();
  #endif

  item.IsDir = false;
  item.Path.Empty();
  item.ParentIndex = (UInt32)(Int32)-1;

  item.PathParts.Clear();

  RINOK(Archive_IsItem_Dir(Archive, index, item.IsDir));
  item.MainIsDir = item.IsDir;

  RINOK(GetItemPath2(index, item.Path));

  UInt32 mainIndex = index;

  #ifdef SUPPORT_ALT_STREAMS

  item.MainPath = item.Path;
  if (Ask_AltStream)
  {
    RINOK(Archive_IsItem_AltStream(Archive, index, item.IsAltStream));
  }

  bool needFindAltStream = false;

  if (item.IsAltStream)
  {
    needFindAltStream = true;
    if (GetRawProps)
    {
      UInt32 parentType = 0;
      UInt32 parentIndex;
      RINOK(GetRawProps->GetParent(index, &parentIndex, &parentType));
      if (parentType == NParentType::kAltStream)
      {
        NCOM::CPropVariant prop;
        RINOK(Archive->GetProperty(index, kpidName, &prop));
        if (prop.vt == VT_BSTR && prop.bstrVal)
          item.AltStreamName.SetFromBstr(prop.bstrVal);
        else if (prop.vt != VT_EMPTY)
          return E_FAIL;

        needFindAltStream = false;
        item.ParentIndex = parentIndex;
        mainIndex = parentIndex;

        if (parentIndex == (UInt32)(Int32)-1)
        {
          item.MainPath.Empty();
          item.MainIsDir = true;
        }
        else
        {
          RINOK(GetItemPath2(parentIndex, item.MainPath));
          RINOK(Archive_IsItem_Dir(Archive, parentIndex, item.MainIsDir));
        }
      }
    }
  }

  if (item.WriteToAltStreamIfColon || needFindAltStream)
  {
    /* Good handler must support GetRawProps::GetParent for alt streams./
       So the following code currently is not used */
    int colon = FindAltStreamColon_in_Path(item.Path);
    if (colon >= 0)
    {
      item.MainPath.DeleteFrom(colon);
      item.AltStreamName = item.Path.Ptr(colon + 1);
      item.MainIsDir = (colon == 0 || IsPathSepar(item.Path[(unsigned)colon - 1]));
      item.IsAltStream = true;
    }
  }

  #endif

  if (item._use_baseParentFolder_mode)
  {
    RINOK(GetItemPathToParent(mainIndex, item._baseParentFolder, item.PathParts));

    #ifdef SUPPORT_ALT_STREAMS
    if ((item.WriteToAltStreamIfColon || needFindAltStream) && !item.PathParts.IsEmpty())
    {
      int colon;
      {
        UString &s = item.PathParts.Back();
        colon = FindAltStreamColon_in_Path(s);
        if (colon >= 0)
        {
          item.AltStreamName = s.Ptr(colon + 1);
          item.MainIsDir = (colon == 0 || IsPathSepar(s[(unsigned)colon - 1]));
          item.IsAltStream = true;
          s.DeleteFrom(colon);
        }
      }
      if (colon == 0)
        item.PathParts.DeleteBack();
    }
    #endif

  }
  else
    SplitPathToParts(
          #ifdef SUPPORT_ALT_STREAMS
            item.MainPath
          #else
            item.Path
          #endif
      , item.PathParts);

  return S_OK;
}

static HRESULT Archive_GetItem_Size(IInArchive *archive, UInt32 index, UInt64 &size, bool &defined)
{
  NCOM::CPropVariant prop;
  defined = false;
  size = 0;
  RINOK(archive->GetProperty(index, kpidSize, &prop));
  switch (prop.vt)
  {
    case VT_UI1: size = prop.bVal; break;
    case VT_UI2: size = prop.uiVal; break;
    case VT_UI4: size = prop.ulVal; break;
    case VT_UI8: size = (UInt64)prop.uhVal.QuadPart; break;
    case VT_EMPTY: return S_OK;
    default: return E_FAIL;
  }
  defined = true;
  return S_OK;
}

HRESULT CArc::GetItemSize(UInt32 index, UInt64 &size, bool &defined) const
{
  NCOM::CPropVariant prop;
  defined = false;
  size = 0;
  RINOK(Archive->GetProperty(index, kpidSize, &prop));
  switch (prop.vt)
  {
    case VT_UI1: size = prop.bVal; break;
    case VT_UI2: size = prop.uiVal; break;
    case VT_UI4: size = prop.ulVal; break;
    case VT_UI8: size = (UInt64)prop.uhVal.QuadPart; break;
    case VT_EMPTY: return S_OK;
    default: return E_FAIL;
  }
  defined = true;
  return S_OK;
}

HRESULT CArc::GetItemMTime(UInt32 index, FILETIME &ft, bool &defined) const
{
  NCOM::CPropVariant prop;
  defined = false;
  ft.dwHighDateTime = ft.dwLowDateTime = 0;
  RINOK(Archive->GetProperty(index, kpidMTime, &prop));
  if (prop.vt == VT_FILETIME)
  {
    ft = prop.filetime;
    defined = true;
  }
  else if (prop.vt != VT_EMPTY)
    return E_FAIL;
  else if (MTimeDefined)
  {
    ft = MTime;
    defined = true;
  }
  return S_OK;
}

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

static void MakeCheckOrder(CCodecs *codecs,
    CIntVector &orderIndices, unsigned numTypes, CIntVector &orderIndices2,
    const Byte *data, size_t dataSize)
{
  for (unsigned i = 0; i < numTypes; i++)
  {
    int index = orderIndices[i];
    if (index < 0)
      continue;
    const CArcInfoEx &ai = codecs->Formats[index];
    if (ai.SignatureOffset != 0)
    {
      orderIndices2.Add(index);
      orderIndices[i] = -1;
      continue;
    }

    const CObjectVector<CByteBuffer> &sigs = ai.Signatures;
    FOR_VECTOR (k, sigs)
    {
      const CByteBuffer &sig = sigs[k];
      if ((sig.Size() == 0 && dataSize == 0) ||
          (sig.Size() != 0 && sig.Size() <= dataSize &&
          TestSignature(data, sig, sig.Size())))
      {
        orderIndices2.Add(index);
        orderIndices[i] = -1;
        break;
      }
    }
  }
}

#ifdef UNDER_CE
  static const unsigned kNumHashBytes = 1;
  #define HASH_VAL(buf, pos) ((buf)[pos])
#else
  static const unsigned kNumHashBytes = 2;
  #define HASH_VAL(buf, pos) ((buf)[pos] | ((UInt32)(buf)[pos + 1] << 8))
#endif


static bool IsExeExt(const UString &ext)
{
  return ext.IsEqualTo_Ascii_NoCase("exe");
}

static const char * const k_PreArcFormats[] =
{
    "pe"
  , "elf"
  , "macho"
  , "mub"
  , "te"
};

static bool IsNameFromList(const UString &s, const char * const names[], size_t num)
{
  for (unsigned i = 0; i < num; i++)
    if (StringsAreEqualNoCase_Ascii(s, names[i]))
      return true;
  return false;
}


static bool IsPreArcFormat(const CArcInfoEx &ai)
{
  if (ai.Flags_PreArc())
    return true;
  return IsNameFromList(ai.Name, k_PreArcFormats, ARRAY_SIZE(k_PreArcFormats));
}

static const char * const k_Formats_with_simple_signuature[] =
{
    "7z"
  , "xz"
  , "rar"
  , "bzip2"
  , "gzip"
  , "cab"
  , "wim"
  , "rpm"
  , "vhd"
  , "xar"
};

static bool IsNewStyleSignature(const CArcInfoEx &ai)
{
  if (ai.NewInterface)
    return true;
  return IsNameFromList(ai.Name, k_Formats_with_simple_signuature, ARRAY_SIZE(k_Formats_with_simple_signuature));
}

class CArchiveOpenCallback_Offset:
  public IArchiveOpenCallback,
  public IArchiveOpenVolumeCallback,
  public ICryptoGetTextPassword,
  public CMyUnknownImp
{
public:
  CMyComPtr<IArchiveOpenCallback> Callback;
  CMyComPtr<IArchiveOpenVolumeCallback> OpenVolumeCallback;
  UInt64 Files;
  UInt64 Offset;

  CMyComPtr<ICryptoGetTextPassword> GetTextPassword;

  MY_QUERYINTERFACE_BEGIN2(IArchiveOpenCallback)
  MY_QUERYINTERFACE_ENTRY(IArchiveOpenVolumeCallback)
  MY_QUERYINTERFACE_ENTRY(ICryptoGetTextPassword)
  MY_QUERYINTERFACE_END
  MY_ADDREF_RELEASE

  INTERFACE_IArchiveOpenCallback(;)
  INTERFACE_IArchiveOpenVolumeCallback(;)
  STDMETHOD(CryptoGetTextPassword)(BSTR *password);
};

STDMETHODIMP CArchiveOpenCallback_Offset::CryptoGetTextPassword(BSTR *password)
{
  COM_TRY_BEGIN
  if (GetTextPassword)
    return GetTextPassword->CryptoGetTextPassword(password);
  return E_NOTIMPL;
  COM_TRY_END
}

STDMETHODIMP CArchiveOpenCallback_Offset::SetTotal(const UInt64 *, const UInt64 *)
{
  return S_OK;
}

STDMETHODIMP CArchiveOpenCallback_Offset::SetCompleted(const UInt64 *, const UInt64 *bytes)
{
  if (!Callback)
    return S_OK;
  UInt64 value = Offset;
  if (bytes)
    value += *bytes;
  return Callback->SetCompleted(&Files, &value);
}

STDMETHODIMP CArchiveOpenCallback_Offset::GetProperty(PROPID propID, PROPVARIANT *value)
{
  if (OpenVolumeCallback)
    return OpenVolumeCallback->GetProperty(propID, value);
  NCOM::PropVariant_Clear(value);
  return S_OK;
}

STDMETHODIMP CArchiveOpenCallback_Offset::GetStream(const wchar_t *name, IInStream **inStream)
{
  if (OpenVolumeCallback)
    return OpenVolumeCallback->GetStream(name, inStream);
  return S_FALSE;
}


UInt32 GetOpenArcErrorFlags(const NCOM::CPropVariant &prop, bool *isDefinedProp)
{
  if (isDefinedProp != NULL)
    *isDefinedProp = false;

  switch (prop.vt)
  {
    case VT_UI8: if (isDefinedProp) *isDefinedProp = true; return (UInt32)prop.uhVal.QuadPart;
    case VT_UI4: if (isDefinedProp) *isDefinedProp = true; return prop.ulVal;
    case VT_EMPTY: return 0;
    default: throw 151199;
  }
}

void CArcErrorInfo::ClearErrors()
{
  ThereIsTail = false;
  UnexpecedEnd = false;
  IgnoreTail = false;
  ErrorFlags_Defined = false;
  ErrorFlags = 0;
  WarningFlags = 0;
  TailSize = 0;

  ErrorMessage.Empty();
  WarningMessage.Empty();
}

HRESULT CArc::ReadBasicProps(IInArchive *archive, UInt64 startPos, HRESULT openRes)
{
  PhySizeDefined = false;
  PhySize = 0;
  Offset = 0;
  AvailPhySize = FileSize - startPos;

  ErrorInfo.ClearErrors();
  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidErrorFlags, &prop));
    ErrorInfo.ErrorFlags = GetOpenArcErrorFlags(prop, &ErrorInfo.ErrorFlags_Defined);
  }
  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidWarningFlags, &prop));
    ErrorInfo.WarningFlags = GetOpenArcErrorFlags(prop);
  }

  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidError, &prop));
    if (prop.vt != VT_EMPTY)
      ErrorInfo.ErrorMessage = (prop.vt == VT_BSTR ? prop.bstrVal : L"Unknown error");
  }

  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidWarning, &prop));
    if (prop.vt != VT_EMPTY)
      ErrorInfo.WarningMessage = (prop.vt == VT_BSTR ? prop.bstrVal : L"Unknown warning");
  }

  if (openRes == S_OK || ErrorInfo.IsArc_After_NonOpen())
  {
    RINOK(Archive_GetArcProp_UInt(archive, kpidPhySize, PhySize, PhySizeDefined));

    bool offsetDefined;
    RINOK(Archive_GetArcProp_Int(archive, kpidOffset, Offset, offsetDefined));

    Int64 globalOffset = startPos + Offset;
    AvailPhySize = FileSize - globalOffset;
    if (PhySizeDefined)
    {
      UInt64 endPos = globalOffset + PhySize;
      if (endPos < FileSize)
      {
        AvailPhySize = PhySize;
        ErrorInfo.ThereIsTail = true;
        ErrorInfo.TailSize = FileSize - endPos;
      }
      else if (endPos > FileSize)
        ErrorInfo.UnexpecedEnd = true;
    }
  }

  return S_OK;
}

HRESULT CArc::PrepareToOpen(const COpenOptions &op, unsigned formatIndex, CMyComPtr<IInArchive> &archive)
{
  RINOK(op.codecs->CreateInArchive(formatIndex, archive));
  if (!archive)
    return S_OK;

  const CArcInfoEx &ai = op.codecs->Formats[formatIndex];

  if (ai.Flags_PreArc())
  {
    /* we notify parsers that extract executables, that they don't need
       to open archive, if there is tail after executable (for SFX cases) */
    CMyComPtr<IArchiveAllowTail> allowTail;
    archive.QueryInterface(IID_IArchiveAllowTail, (void **)&allowTail);
    if (allowTail)
      allowTail->AllowTail(BoolToInt(true));
  }

  if (op.props)
  {
    RINOK(SetProperties(archive, *op.props));
  }
  return S_OK;
}

static HRESULT ReadParseItemProps(IInArchive *archive, const CArcInfoEx &ai, NArchive::NParser::CParseItem &pi)
{
  pi.Extension = ai.GetMainExt();
  pi.FileTime_Defined = false;
  pi.ArcType = ai.Name;

  RINOK(Archive_GetArcBoolProp(archive, kpidIsNotArcType, pi.IsNotArcType));

  pi.IsSelfExe = ai.Flags_PreArc();

  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidMTime, &prop));
    if (prop.vt == VT_FILETIME)
    {
      pi.FileTime_Defined = true;
      pi.FileTime = prop.filetime;
    }
  }

  if (!pi.FileTime_Defined)
  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidCTime, &prop));
    if (prop.vt == VT_FILETIME)
    {
      pi.FileTime_Defined = true;
      pi.FileTime = prop.filetime;
    }
  }

  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidName, &prop));
    if (prop.vt == VT_BSTR)
    {
      pi.Name.SetFromBstr(prop.bstrVal);
      pi.Extension.Empty();
    }
    else
    {
      RINOK(archive->GetArchiveProperty(kpidExtension, &prop));
      if (prop.vt == VT_BSTR)
        pi.Extension.SetFromBstr(prop.bstrVal);
    }
  }

  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidShortComment, &prop));
    if (prop.vt == VT_BSTR)
      pi.Comment.SetFromBstr(prop.bstrVal);
  }


  UInt32 numItems;
  RINOK(archive->GetNumberOfItems(&numItems));

  {
    pi.NumSubFiles = 0;
    pi.NumSubDirs = 0;
    pi.UnpackSize = 0;
    for (UInt32 i = 0; i < numItems; i++)
    {
      UInt64 size = 0;
      bool defined = false;
      Archive_GetItem_Size(archive, i, size, defined);
      if (defined)
      {
        pi.UnpackSize_Defined = true;
        pi.UnpackSize += size;
      }

      bool isDir = false;
      Archive_IsItem_Dir(archive, i, isDir);
      if (isDir)
        pi.NumSubDirs++;
      else
        pi.NumSubFiles++;
    }
    if (pi.NumSubDirs != 0)
      pi.NumSubDirs_Defined = true;
    pi.NumSubFiles_Defined = true;
  }

  return S_OK;
}

HRESULT CArc::CheckZerosTail(const COpenOptions &op, UInt64 offset)
{
  if (!op.stream)
    return S_OK;
  RINOK(op.stream->Seek(offset, STREAM_SEEK_SET, NULL));
  const UInt32 kBufSize = 1 << 11;
  Byte buf[kBufSize];

  for (;;)
  {
    UInt32 processed = 0;
    RINOK(op.stream->Read(buf, kBufSize, &processed));
    if (processed == 0)
    {
      ErrorInfo.IgnoreTail = true;
      return S_OK;
    }
    for (size_t i = 0; i < processed; i++)
    {
      if (buf[i] != 0)
      {
        return S_OK;
      }
    }
  }
}

class CExtractCallback_To_OpenCallback:
  public IArchiveExtractCallback,
  public ICompressProgressInfo,
  public CMyUnknownImp
{
public:
  CMyComPtr<IArchiveOpenCallback> Callback;
  UInt64 Files;
  UInt64 Offset;

  MY_UNKNOWN_IMP2(IArchiveExtractCallback, ICompressProgressInfo)
  INTERFACE_IArchiveExtractCallback(;)
  STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize);
  void Init(IArchiveOpenCallback *callback)
  {
    Callback = callback;
    Files = 0;
    Offset = 0;
  }
};

STDMETHODIMP CExtractCallback_To_OpenCallback::SetTotal(UInt64 /* size */)
{
  return S_OK;
}

STDMETHODIMP CExtractCallback_To_OpenCallback::SetCompleted(const UInt64 * /* completeValue */)
{
  return S_OK;
}

STDMETHODIMP CExtractCallback_To_OpenCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)
{
  if (Callback)
  {
    UInt64 value = Offset;
    if (inSize)
      value += *inSize;
    return Callback->SetCompleted(&Files, &value);
  }
  return S_OK;
}

STDMETHODIMP CExtractCallback_To_OpenCallback::GetStream(UInt32 /* index */, ISequentialOutStream **outStream, Int32 /* askExtractMode */)
{
  *outStream = 0;
  return S_OK;
}

STDMETHODIMP CExtractCallback_To_OpenCallback::PrepareOperation(Int32 /* askExtractMode */)
{
  return S_OK;
}

STDMETHODIMP CExtractCallback_To_OpenCallback::SetOperationResult(Int32 /* operationResult */)
{
  return S_OK;
}

static HRESULT OpenArchiveSpec(IInArchive *archive, bool needPhySize,
    IInStream *stream, const UInt64 *maxCheckStartPosition,
    IArchiveOpenCallback *openCallback,
    IArchiveExtractCallback *extractCallback)
{
  RINOK(archive->Open(stream, maxCheckStartPosition, openCallback));
  if (needPhySize)
  {
    bool phySize_Defined = false;
    UInt64 phySize = 0;
    RINOK(Archive_GetArcProp_UInt(archive, kpidPhySize, phySize, phySize_Defined));
    if (phySize_Defined)
      return S_OK;

    bool phySizeCantBeDetected = false;;
    RINOK(Archive_GetArcBoolProp(archive, kpidPhySizeCantBeDetected, phySizeCantBeDetected));

    if (!phySizeCantBeDetected)
    {
      RINOK(archive->Extract(0, (UInt32)(Int32)-1, BoolToInt(true), extractCallback));
    }
  }
  return S_OK;
}

static int FindFormatForArchiveType(CCodecs *codecs, CIntVector orderIndices, const char *name)
{
  FOR_VECTOR (i, orderIndices)
    if (StringsAreEqualNoCase_Ascii(codecs->Formats[orderIndices[i]].Name, name))
      return i;
  return -1;
}

HRESULT CArc::OpenStream2(const COpenOptions &op)
{
  // fprintf(stdout, "\nOpen: %S", Path); fflush(stdout);

  Archive.Release();
  GetRawProps.Release();
  GetRootProps.Release();

  ErrorInfo.ClearErrors();
  ErrorInfo.ErrorFormatIndex = -1;

  IsParseArc = false;
  ArcStreamOffset = 0;

  // OutputDebugStringA("1");
  // OutputDebugStringW(Path);

  const UString fileName = ExtractFileNameFromPath(Path);
  UString extension;
  {
    int dotPos = fileName.ReverseFind_Dot();
    if (dotPos >= 0)
      extension = fileName.Ptr(dotPos + 1);
  }

  CIntVector orderIndices;

  bool searchMarkerInHandler = false;

  CBoolArr isMainFormatArr(op.codecs->Formats.Size());
  {
    FOR_VECTOR(i, op.codecs->Formats)
      isMainFormatArr[i] = false;
  }

  UInt64 maxStartOffset =
      op.openType.MaxStartOffset_Defined ?
      op.openType.MaxStartOffset :
      kMaxCheckStartPosition;

  bool isUnknownExt = false;

  bool isForced = false;
  unsigned numMainTypes = 0;
  int formatIndex = op.openType.FormatIndex;

  if (formatIndex >= 0)
  {
    isForced = true;
    orderIndices.Add(formatIndex);
    numMainTypes = 1;
    isMainFormatArr[(unsigned)formatIndex] = true;

    searchMarkerInHandler = true;
  }
  else
  {
    unsigned numFinded = 0;
    bool isPrearcExt = false;

    {
      bool isZip = false;
      bool isRar = false;

      const wchar_t c = extension[0];
      if (c == 'z' || c == 'Z' || c == 'r' || c == 'R')
      {
        bool isNumber = false;
        for (unsigned k = 1;; k++)
        {
          const wchar_t d = extension[k];
          if (d == 0)
            break;
          if (d < '0' || d > '9')
          {
            isNumber = false;
            break;
          }
          isNumber = true;
        }
        if (isNumber) {
          if (c == 'z' || c == 'Z')
            isZip = true;
          else
            isRar = true;
        }
      }

      FOR_VECTOR (i, op.codecs->Formats)
      {
        const CArcInfoEx &ai = op.codecs->Formats[i];

        if (IgnoreSplit || !op.openType.CanReturnArc)
          if (ai.IsSplit())
            continue;
        if (op.excludedFormats->FindInSorted(i) >= 0)
          continue;

        if (IsPreArcFormat(ai))
          isPrearcExt = true;

        if (ai.FindExtension(extension) >= 0
            || (isZip && StringsAreEqualNoCase_Ascii(ai.Name, "zip"))
            || (isRar && StringsAreEqualNoCase_Ascii(ai.Name, "rar"))
            )
        {
          // PrintNumber("orderIndices.Insert", i);
          orderIndices.Insert(numFinded++, i);
          isMainFormatArr[i] = true;
        }
        else
          orderIndices.Add(i);
      }
    }

    if (!op.stream)
    {
      if (numFinded != 1)
        return E_NOTIMPL;
      orderIndices.DeleteFrom(1);
    }

      if (op.stream && orderIndices.Size() >= 2)
      {
        RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));
        CByteBuffer byteBuffer;
        CIntVector orderIndices2;
        if (numFinded == 0 || IsExeExt(extension))
        {
          // signature search was here
        }
        else if (extension.IsEqualTo("000") || extension.IsEqualTo("001"))
        {
          int i = FindFormatForArchiveType(op.codecs, orderIndices, "rar");
          if (i >= 0)
          {
            const size_t kBufSize = (1 << 10);
            byteBuffer.Alloc(kBufSize);
            size_t processedSize = kBufSize;
            RINOK(ReadStream(op.stream, byteBuffer, &processedSize));
            if (processedSize >= 16)
            {
              const Byte *buf = byteBuffer;
              const Byte kRarHeader[] = { 0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 };
              if (TestSignature(buf, kRarHeader, 7) && buf[9] == 0x73 && (buf[10] & 1) != 0)
              {
                orderIndices2.Add(orderIndices[i]);
                orderIndices[i] = -1;
                if (i >= (int)numFinded)
                  numFinded++;
              }
            }
          }
        }
        else
        {
          const size_t kBufSize = (1 << 10);
          byteBuffer.Alloc(kBufSize);
          size_t processedSize = kBufSize;
          RINOK(ReadStream(op.stream, byteBuffer, &processedSize));
          if (processedSize == 0)
            return S_FALSE;

          /*
          check type order:
            1) matched extension, no signuature
            2) matched extension, matched signuature
            3) no signuature
            4) matched signuature
          */

          MakeCheckOrder(op.codecs, orderIndices, numFinded, orderIndices2, NULL, 0);
          MakeCheckOrder(op.codecs, orderIndices, numFinded, orderIndices2, byteBuffer, processedSize);
        }

        FOR_VECTOR (i, orderIndices)
        {
          int val = orderIndices[i];
          if (val != -1)
            orderIndices2.Add(val);
        }
        orderIndices = orderIndices2;
      }

      if (orderIndices.Size() >= 2)
      {
        int iIso = FindFormatForArchiveType(op.codecs, orderIndices, "iso");
        int iUdf = FindFormatForArchiveType(op.codecs, orderIndices, "udf");
        if (iUdf > iIso && iIso >= 0)
        {
          int isoIndex = orderIndices[iIso];
          int udfIndex = orderIndices[iUdf];
          orderIndices[iUdf] = isoIndex;
          orderIndices[iIso] = udfIndex;
        }
      }

      numMainTypes = numFinded;
      isUnknownExt = (numMainTypes == 0) || isPrearcExt;
  }

  UInt64 fileSize = 0;
  if (op.stream)
  {
    RINOK(op.stream->Seek(0, STREAM_SEEK_END, &fileSize));
    RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));
  }
  FileSize = fileSize;

  CBoolArr skipFrontalFormat(op.codecs->Formats.Size());
  {
    FOR_VECTOR(i, op.codecs->Formats)
      skipFrontalFormat[i] = false;
  }

  const COpenType &mode = op.openType;

  if (mode.CanReturnArc)
  {
    // ---------- OPEN main type by extenssion ----------

    unsigned numCheckTypes = orderIndices.Size();
    if (formatIndex >= 0)
      numCheckTypes = numMainTypes;

    for (unsigned i = 0; i < numCheckTypes; i++)
    {
      FormatIndex = orderIndices[i];

      bool exactOnly = false;

      const CArcInfoEx &ai = op.codecs->Formats[FormatIndex];
      // OutputDebugStringW(ai.Name);
      if (i >= numMainTypes)
      {
        if (!ai.Flags_BackwardOpen())
          continue;
        exactOnly = true;
      }

      // Some handlers do not set total bytes. So we set it here
      if (op.callback)
        RINOK(op.callback->SetTotal(NULL, &fileSize));

      if (op.stream)
      {
        RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));
      }

      CMyComPtr<IInArchive> archive;

      RINOK(PrepareToOpen(op, FormatIndex, archive));
      if (!archive)
        continue;

      HRESULT result;
      if (op.stream)
      {
        UInt64 searchLimit = (!exactOnly && searchMarkerInHandler) ? maxStartOffset: 0;
        result = archive->Open(op.stream, &searchLimit, op.callback);
      }
      else
      {
        CMyComPtr<IArchiveOpenSeq> openSeq;
        archive.QueryInterface(IID_IArchiveOpenSeq, (void **)&openSeq);
        if (!openSeq)
          return E_NOTIMPL;
        result = openSeq->OpenSeq(op.seqStream);
      }

      RINOK(ReadBasicProps(archive, 0, result));

      if (result == S_FALSE)
      {
        bool isArc = ErrorInfo.IsArc_After_NonOpen();

        // if it's archive, we allow another open attempt for parser
        if (!mode.CanReturnParser || !isArc)
          skipFrontalFormat[(unsigned)FormatIndex] = true;

        if (exactOnly)
          continue;

        if (i == 0 && numMainTypes == 1)
        {
          // we set NonOpenErrorInfo, only if there is only one main format (defined by extension).
          ErrorInfo.ErrorFormatIndex = FormatIndex;
          NonOpen_ErrorInfo = ErrorInfo;

          if (!mode.CanReturnParser && isArc)
          {
            // if (formatIndex < 0 && !searchMarkerInHandler)
            {
              // if bad archive was detected, we don't need additional open attempts
              if (!IsPreArcFormat(ai) /* || !mode.SkipSfxStub */)
                return S_FALSE;
            }
          }
        }

        continue;
      }

      RINOK(result);

      bool isMainFormat = isMainFormatArr[(unsigned)FormatIndex];
      const COpenSpecFlags &specFlags = mode.GetSpec(isForced, isMainFormat, isUnknownExt);

      bool thereIsTail = ErrorInfo.ThereIsTail;
      if (thereIsTail && mode.ZerosTailIsAllowed)
      {
        RINOK(CheckZerosTail(op, Offset + PhySize));
        if (ErrorInfo.IgnoreTail)
          thereIsTail = false;
      }

      if (Offset > 0)
      {
        if (exactOnly
            || !searchMarkerInHandler
            || !specFlags.CanReturn_NonStart()
            || (mode.MaxStartOffset_Defined && (UInt64)Offset > mode.MaxStartOffset))
          continue;
      }
      if (thereIsTail)
      {
        if (Offset > 0)
        {
          if (!specFlags.CanReturnMid)
            continue;
        }
        else if (!specFlags.CanReturnFrontal)
          continue;
      }

      if (Offset > 0 || thereIsTail)
      {
        if (formatIndex < 0)
        {
          if (IsPreArcFormat(ai))
          {
            {
              continue;
            }
          }
        }
      }

      Archive = archive;
      return S_OK;
    }
  }

  if (!op.stream)
    return S_FALSE;

  if (formatIndex >= 0 && !mode.CanReturnParser)
  {
    if (mode.MaxStartOffset_Defined)
    {
      if (mode.MaxStartOffset == 0)
        return S_FALSE;
    }
    else
    {
      const CArcInfoEx &ai = op.codecs->Formats[formatIndex];
      if (ai.FindExtension(extension) >= 0)
      {
        if (ai.Flags_FindSignature() && searchMarkerInHandler)
          return S_FALSE;
      }
    }
  }

  NArchive::NParser::CHandler *handlerSpec = new NArchive::NParser::CHandler;
  CMyComPtr<IInArchive> handler = handlerSpec;

  CExtractCallback_To_OpenCallback *extractCallback_To_OpenCallback_Spec = new CExtractCallback_To_OpenCallback;
  CMyComPtr<IArchiveExtractCallback> extractCallback_To_OpenCallback = extractCallback_To_OpenCallback_Spec;
  extractCallback_To_OpenCallback_Spec->Init(op.callback);

  {
    // ---------- Check all possible START archives ----------
    // this code is better for full file archives than Parser's code.

    CByteBuffer byteBuffer;
    bool endOfFile = false;
    size_t processedSize;
    {
      size_t bufSize = 1 << 20; // it must be larger than max signature offset or IsArcFunc offset ((1 << 19) + x for UDF)
      if (bufSize > fileSize)
      {
        bufSize = (size_t)fileSize;
        endOfFile = true;
      }
      byteBuffer.Alloc(bufSize);
      RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));
      processedSize = bufSize;
      RINOK(ReadStream(op.stream, byteBuffer, &processedSize));
      if (processedSize == 0)
        return S_FALSE;
      if (processedSize < bufSize)
        endOfFile = true;
    }
    CUIntVector sortedFormats;

    unsigned i;

    int splitIndex = -1;

    for (i = 0; i < orderIndices.Size(); i++)
    {
      unsigned form = orderIndices[i];
      if (skipFrontalFormat[form])
        continue;
      const CArcInfoEx &ai = op.codecs->Formats[form];
      if (ai.IsSplit())
      {
        splitIndex = form;
        continue;
      }

      if (ai.IsArcFunc)
      {
        UInt32 isArcRes = ai.IsArcFunc(byteBuffer, processedSize);
        if (isArcRes == k_IsArc_Res_NO)
          continue;
        if (isArcRes == k_IsArc_Res_NEED_MORE && endOfFile)
          continue;
        // if (isArcRes == k_IsArc_Res_YES_LOW_PROB) continue;
        sortedFormats.Insert(0, form);
        continue;
      }

      bool isNewStyleSignature = IsNewStyleSignature(ai);
      bool needCheck = !isNewStyleSignature
          || ai.Signatures.IsEmpty()
          || ai.Flags_PureStartOpen()
          || ai.Flags_StartOpen()
          || ai.Flags_BackwardOpen();

      if (isNewStyleSignature && !ai.Signatures.IsEmpty())
      {
        unsigned k;
        for (k = 0; k < ai.Signatures.Size(); k++)
        {
          const CByteBuffer &sig = ai.Signatures[k];
          UInt32 signatureEnd = ai.SignatureOffset + (UInt32)sig.Size();
          if (processedSize < signatureEnd)
          {
            if (!endOfFile)
              needCheck = true;
          }
          else if (memcmp(sig, byteBuffer + ai.SignatureOffset, sig.Size()) == 0)
            break;
        }
        if (k != ai.Signatures.Size())
        {
          sortedFormats.Insert(0, form);
          continue;
        }
      }
      if (needCheck)
        sortedFormats.Add(form);
    }

    if (splitIndex >= 0)
      sortedFormats.Insert(0, splitIndex);

    for (i = 0; i < sortedFormats.Size(); i++)
    {
      FormatIndex = sortedFormats[i];
      const CArcInfoEx &ai = op.codecs->Formats[FormatIndex];

      if (op.callback)
        RINOK(op.callback->SetTotal(NULL, &fileSize));

      RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));

      CMyComPtr<IInArchive> archive;
      RINOK(PrepareToOpen(op, FormatIndex, archive));
      if (!archive)
        continue;

      PRF(printf("\nSorted Open %S", (const wchar_t *)ai.Name));
      HRESULT result;
      {
        UInt64 searchLimit = 0;

        result = OpenArchiveSpec(archive, !mode.CanReturnArc, op.stream, &searchLimit, op.callback, extractCallback_To_OpenCallback);
      }

      if (result == S_FALSE)
      {
        skipFrontalFormat[(unsigned)FormatIndex] = true;
        // FIXME: maybe we must use LenIsUnknown.
        // printf("  OpenForSize Error");
        continue;
      }
      RINOK(result);

      RINOK(ReadBasicProps(archive, 0, result));

      if (Offset > 0)
      {
        continue; // good handler doesn't return such Offset > 0
        // but there are some cases like false prefixed PK00 archive, when
        // we can support it?
      }

      NArchive::NParser::CParseItem pi;
      pi.Offset = Offset;
      pi.Size = AvailPhySize;

      if (!PhySizeDefined)
      {
        // it's for Z format
        pi.LenIsUnknown = true;
        // needScan = true;
        // phySize = arcRem;
        // nextNeedCheckStartOpen = false;
      }

      pi.NormalizeOffset();
      // printf("  phySize = %8d", (unsigned)phySize);

      if (mode.CanReturnArc)
      {
        bool isMainFormat = isMainFormatArr[(unsigned)FormatIndex];
        const COpenSpecFlags &specFlags = mode.GetSpec(isForced, isMainFormat, isUnknownExt);
        bool openCur = false;

        if (!ErrorInfo.ThereIsTail)
          openCur = true;
        else
        {
          if (mode.ZerosTailIsAllowed)
          {
            RINOK(CheckZerosTail(op, Offset + PhySize));
            if (ErrorInfo.IgnoreTail)
              openCur = true;
          }
          if (!openCur)
          {
            openCur = specFlags.CanReturnFrontal;
            if (formatIndex < 0) // format is not forced
            {
              if (IsPreArcFormat(ai))
              {
                // if (mode.SkipSfxStub)
                {
                  openCur = false;
                }
              }
            }
          }
        }

        if (openCur)
        {
          InStream = op.stream;
          Archive = archive;
          return S_OK;
        }
      }

      skipFrontalFormat[(unsigned)FormatIndex] = true;

      if (pi.Offset == 0 && !pi.LenIsUnknown && pi.Size >= FileSize)
        continue;

      // printf("\nAdd offset = %d", (int)pi.Offset);
      RINOK(ReadParseItemProps(archive, ai, pi));
      handlerSpec->AddItem(pi);
    }
  }

  // ---------- PARSER ----------

  CUIntVector arc2sig; // formatIndex to signatureIndex
  CUIntVector sig2arc; // signatureIndex to formatIndex;
  {
    unsigned sum = 0;
    FOR_VECTOR (i, op.codecs->Formats)
    {
      arc2sig.Add(sum);
      const CObjectVector<CByteBuffer> &sigs = op.codecs->Formats[i].Signatures;
      sum += sigs.Size();
      FOR_VECTOR (k, sigs)
        sig2arc.Add(i);
    }
  }

  {
    const size_t kBeforeSize = 1 << 16;
    const size_t kAfterSize  = 1 << 20;
    const size_t kBufSize = 1 << 22; // it must be more than kBeforeSize + kAfterSize

    const UInt32 kNumVals = (UInt32)1 << (kNumHashBytes * 8);
    CByteArr hashBuffer(kNumVals);
    Byte *hash = hashBuffer;
    memset(hash, 0xFF, kNumVals);
    Byte prevs[256];
    memset(prevs, 0xFF, sizeof(prevs));
    if (sig2arc.Size() >= 0xFF)
      return S_FALSE;

    CUIntVector difficultFormats;
    CBoolArr difficultBools(256);
    {
      for (unsigned i = 0; i < 256; i++)
        difficultBools[i] = false;
    }

    bool thereAreHandlersForSearch = false;

    // UInt32 maxSignatureEnd = 0;

    FOR_VECTOR (i, orderIndices)
    {
      int index = orderIndices[i];
      if (index < 0)
        continue;
      const CArcInfoEx &ai = op.codecs->Formats[index];
      bool isDifficult = false;
      // if (ai.Version < 0x91F) // we don't use parser with old DLL (before 9.31)
      if (!ai.NewInterface)
        isDifficult = true;
      else
      {
        if (ai.Flags_StartOpen())
          isDifficult = true;
        FOR_VECTOR (k, ai.Signatures)
        {
          const CByteBuffer &sig = ai.Signatures[k];

          if (sig.Size() < kNumHashBytes)
          {
            isDifficult = true;
            continue;
          }
          thereAreHandlersForSearch = true;
          UInt32 v = HASH_VAL(sig, 0);
          unsigned sigIndex = arc2sig[(unsigned)index] + k;
          prevs[sigIndex] = hash[v];
          hash[v] = (Byte)sigIndex;
        }
      }
      if (isDifficult)
      {
        difficultFormats.Add(index);
        difficultBools[index] = true;
      }
    }

    RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));

    CLimitedCachedInStream *limitedStreamSpec = new CLimitedCachedInStream;
    CMyComPtr<IInStream> limitedStream = limitedStreamSpec;
    limitedStreamSpec->SetStream(op.stream);

    CArchiveOpenCallback_Offset *openCallback_Offset_Spec = NULL;
    CMyComPtr<IArchiveOpenCallback> openCallback_Offset;
    if (op.callback)
    {
      openCallback_Offset_Spec = new CArchiveOpenCallback_Offset;
      openCallback_Offset = openCallback_Offset_Spec;
      openCallback_Offset_Spec->Callback = op.callback;
      openCallback_Offset_Spec->Callback.QueryInterface(IID_IArchiveOpenVolumeCallback, &openCallback_Offset_Spec->OpenVolumeCallback);
      openCallback_Offset_Spec->Callback.QueryInterface(IID_ICryptoGetTextPassword, &openCallback_Offset_Spec->GetTextPassword);
    }

    if (op.callback)
      RINOK(op.callback->SetTotal(NULL, &fileSize));

    CByteBuffer &byteBuffer = limitedStreamSpec->Buffer;
    byteBuffer.Alloc(kBufSize);

    UInt64 callbackPrev = 0;
    bool needCheckStartOpen = true; // = true, if we need to test all archives types for current pos.

    bool endOfFile = false;
    UInt64 bufPhyPos = 0;
    size_t bytesInBuf = 0;

    // ---------- Main Scan Loop ----------

    UInt64 pos = 0;

    if (!mode.EachPos && handlerSpec->_items.Size() == 1)
    {
      NArchive::NParser::CParseItem &pi = handlerSpec->_items[0];
      if (!pi.LenIsUnknown && pi.Offset == 0)
        pos = pi.Size;
    }

    for (;;)
    {
      // printf("\nPos = %d", (int)pos);
      UInt64 posInBuf = pos - bufPhyPos;

      if (!endOfFile)
      {
        if (bytesInBuf < kBufSize)
        {
          size_t processedSize = kBufSize - bytesInBuf;
          // printf("\nRead ask = %d", (unsigned)processedSize);
          UInt64 seekPos = bufPhyPos + bytesInBuf;
          RINOK(op.stream->Seek(bufPhyPos + bytesInBuf, STREAM_SEEK_SET, NULL));
          RINOK(ReadStream(op.stream, byteBuffer + bytesInBuf, &processedSize));
          // printf("   processed = %d", (unsigned)processedSize);
          if (processedSize == 0)
          {
            fileSize = seekPos;
            endOfFile = true;
          }
          else
          {
            bytesInBuf += processedSize;
            limitedStreamSpec->SetCache(processedSize, (size_t)bufPhyPos);
          }
          continue;
        }

        if (bytesInBuf < posInBuf)
        {
          UInt64 skipSize = posInBuf - bytesInBuf;
          if (skipSize <= kBeforeSize)
          {
            size_t keepSize = (size_t)(kBeforeSize - skipSize);
            // printf("\nmemmove skip = %d", (int)keepSize);
            memmove(byteBuffer, byteBuffer + bytesInBuf - keepSize, keepSize);
            bytesInBuf = keepSize;
            bufPhyPos = pos - keepSize;
            continue;
          }
          // printf("\nSkip %d", (int)(skipSize - kBeforeSize));
          bytesInBuf = 0;
          bufPhyPos = pos - kBeforeSize;
          continue;
        }

        if (bytesInBuf - posInBuf < kAfterSize)
        {
          size_t beg = (size_t)posInBuf - kBeforeSize;
          // printf("\nmemmove for after beg = %d", (int)beg);
          memmove(byteBuffer, byteBuffer + beg, bytesInBuf - beg);
          bufPhyPos += beg;
          bytesInBuf -= beg;
          continue;
        }
      }

      bool useOffsetCallback = false;
      if (openCallback_Offset)
      {
        openCallback_Offset_Spec->Files = handlerSpec->_items.Size();
        openCallback_Offset_Spec->Offset = pos;

        useOffsetCallback = (!op.openType.CanReturnArc || handlerSpec->_items.Size() > 1);

        if (pos >= callbackPrev + (1 << 23))
        {
          RINOK(openCallback_Offset_Spec->SetCompleted(NULL, NULL));
          callbackPrev = pos;
        }
      }

      {
        UInt64 endPos = bufPhyPos + bytesInBuf;
        if (fileSize < endPos)
        {
          FileSize = fileSize; // why ????
          fileSize = endPos;
        }
      }

      size_t availSize = bytesInBuf - (size_t)posInBuf;
      if (availSize < kNumHashBytes)
        break;
      size_t scanSize = availSize -
          ((availSize >= kAfterSize) ? kAfterSize : kNumHashBytes);

      {
        if (!mode.CanReturnParser)
        {
          if (pos > maxStartOffset)
            break;
          UInt64 remScan = maxStartOffset - pos;
          if (scanSize > remScan)
            scanSize = (size_t)remScan;
        }
      }

      scanSize++;

      const Byte *buf = byteBuffer + (size_t)posInBuf;
      size_t ppp = 0;

      if (!needCheckStartOpen)
      {
        for (; ppp < scanSize && hash[HASH_VAL(buf, ppp)] == 0xFF; ppp++);
        pos += ppp;
        if (ppp == scanSize)
          continue;
      }

      UInt32 v = HASH_VAL(buf, ppp);
      bool nextNeedCheckStartOpen = true;
      unsigned i = hash[v];
      unsigned indexOfDifficult = 0;

      // ---------- Open Loop for Current Pos ----------
      bool wasOpen = false;

      for (;;)
      {
        unsigned index;
        bool isDifficult;
        if (needCheckStartOpen && indexOfDifficult < difficultFormats.Size())
        {
          index = difficultFormats[indexOfDifficult++];
          isDifficult = true;
        }
        else
        {
          if (i == 0xFF)
            break;
          index = sig2arc[i];
          unsigned sigIndex = i - arc2sig[index];
          i = prevs[i];
          if (needCheckStartOpen && difficultBools[index])
            continue;
          const CArcInfoEx &ai = op.codecs->Formats[index];

          if (pos < ai.SignatureOffset)
            continue;

          const CByteBuffer &sig = ai.Signatures[sigIndex];

          if (ppp + sig.Size() > availSize
              || !TestSignature(buf + ppp, sig, sig.Size()))
            continue;
          // printf("\nSignature OK: %10S %8x %5d", (const wchar_t *)ai.Name, (int)pos, (int)(pos - prevPos));
          isDifficult = false;
        }

        const CArcInfoEx &ai = op.codecs->Formats[index];


        if ((isDifficult && pos == 0) || ai.SignatureOffset == pos)
        {
          // we don't check same archive second time */
          if (skipFrontalFormat[index])
            continue;
        }

        UInt64 startArcPos = pos;
        if (!isDifficult)
        {
          if (pos < ai.SignatureOffset)
            continue;
          startArcPos = pos - ai.SignatureOffset;
        }

        if (ai.IsArcFunc && startArcPos >= bufPhyPos)
        {
          size_t offsetInBuf = (size_t)(startArcPos - bufPhyPos);
          if (offsetInBuf < bytesInBuf)
          {
            UInt32 isArcRes = ai.IsArcFunc(byteBuffer + offsetInBuf, bytesInBuf - offsetInBuf);
            if (isArcRes == k_IsArc_Res_NO)
              continue;
            if (isArcRes == k_IsArc_Res_NEED_MORE && endOfFile)
              continue;
          }
          // printf("\nIsArc OK: %S", (const wchar_t *)ai.Name);
        }

        PRF(printf("\npos = %9I64d : %S", pos, (const wchar_t *)ai.Name));

        bool isMainFormat = isMainFormatArr[index];
        const COpenSpecFlags &specFlags = mode.GetSpec(isForced, isMainFormat, isUnknownExt);

        CMyComPtr<IInArchive> archive;
        RINOK(PrepareToOpen(op, index, archive));
        if (!archive)
          return E_FAIL;

        // OutputDebugStringW(ai.Name);

        UInt64 rem = fileSize - startArcPos;

        UInt64 arcStreamOffset = 0;

        if (ai.Flags_UseGlobalOffset())
        {
          limitedStreamSpec->InitAndSeek(0, fileSize);
          limitedStream->Seek(startArcPos, STREAM_SEEK_SET, NULL);
        }
        else
        {
          limitedStreamSpec->InitAndSeek(startArcPos, rem);
          arcStreamOffset = startArcPos;
        }

        UInt64 maxCheckStartPosition = 0;

        if (openCallback_Offset)
        {
          openCallback_Offset_Spec->Files = handlerSpec->_items.Size();
          openCallback_Offset_Spec->Offset = startArcPos;
        }

        extractCallback_To_OpenCallback_Spec->Files = 0;
        extractCallback_To_OpenCallback_Spec->Offset = startArcPos;

        HRESULT result = OpenArchiveSpec(archive, true, limitedStream, &maxCheckStartPosition,
            useOffsetCallback ? (IArchiveOpenCallback *)openCallback_Offset : (IArchiveOpenCallback *)op.callback,
            extractCallback_To_OpenCallback);

        RINOK(ReadBasicProps(archive, ai.Flags_UseGlobalOffset() ? 0 : startArcPos, result));

        bool isOpen = false;
        if (result == S_FALSE)
        {
          if (!mode.CanReturnParser)
          {
            if (formatIndex < 0 && ErrorInfo.IsArc_After_NonOpen())
            {
              ErrorInfo.ErrorFormatIndex = index;
              NonOpen_ErrorInfo = ErrorInfo;
              // if archive was detected, we don't need additional open attempts
              return S_FALSE;
            }
            continue;
          }
          if (!ErrorInfo.IsArc_After_NonOpen() || !PhySizeDefined || PhySize == 0)
            continue;
        }
        else
        {
          isOpen = true;
          RINOK(result);
          PRF(printf("  OK "));
        }

        // fprintf(stderr, "\n %8X  %S", startArcPos, Path);
        // printf("\nOpen OK: %S", ai.Name);

        NArchive::NParser::CParseItem pi;
        pi.Offset = startArcPos;

        if (ai.Flags_UseGlobalOffset())
          pi.Offset = Offset;
        else if (Offset != 0)
          return E_FAIL;
        UInt64 arcRem = FileSize - pi.Offset;
        UInt64 phySize = arcRem;
        bool phySizeDefined = PhySizeDefined;
        if (phySizeDefined)
        {
          if (pi.Offset + PhySize > FileSize)
          {
            // ErrorInfo.ThereIsTail = true;
            PhySize = FileSize - pi.Offset;
          }
          phySize = PhySize;
        }
        if (phySize == 0 || (UInt64)phySize > ((UInt64)1 << 63))
          return E_FAIL;

        bool needScan = false;


        if (isOpen && !phySizeDefined)
        {
          // it's for Z format
          pi.LenIsUnknown = true;
          needScan = true;
          phySize = arcRem;
          nextNeedCheckStartOpen = false;
        }

        pi.Size = phySize;

        pi.NormalizeOffset();
        // printf("  phySize = %8d", (unsigned)phySize);

        if (pi.Offset == 0 && !pi.LenIsUnknown && pi.Size >= FileSize)
        {
          // it's possible for dmg archives
          if (!mode.CanReturnArc)
            continue;
        }

        if (mode.EachPos)
          pos++;
        else if (needScan)
        {
          pos++;
        }
        else
          pos = pi.Offset + pi.Size;


        RINOK(ReadParseItemProps(archive, ai, pi));

        if (pi.Offset < startArcPos && !mode.EachPos /* && phySizeDefined */)
        {
          /* It's for DMG format.
          This code deletes all previous items that are included to current item */

          while (!handlerSpec->_items.IsEmpty())
          {
            {
              const NArchive::NParser::CParseItem &back = handlerSpec->_items.Back();
              if (back.Offset < pi.Offset)
                break;
              if (back.Offset + back.Size > pi.Offset + pi.Size)
                break;
            }
            handlerSpec->_items.DeleteBack();
          }
        }


        if (isOpen && mode.CanReturnArc && phySizeDefined)
        {
          // if (pi.Offset + pi.Size >= fileSize)
          bool openCur = false;

          bool thereIsTail = ErrorInfo.ThereIsTail;
          if (thereIsTail && mode.ZerosTailIsAllowed)
          {
            RINOK(CheckZerosTail(op, arcStreamOffset + Offset + PhySize));
            if (ErrorInfo.IgnoreTail)
              thereIsTail = false;
          }

          if (pi.Offset != 0)
          {
            if (!pi.IsNotArcType) {
              if (thereIsTail)
                openCur = specFlags.CanReturnMid;
              else
                openCur = specFlags.CanReturnTail;
            }
          }
          else
          {
            if (!thereIsTail)
              openCur = true;
            else
              openCur = specFlags.CanReturnFrontal;


            if (formatIndex >= -2)
              openCur = true;
          }
          if (formatIndex < 0 && pi.IsSelfExe /* && mode.SkipSfxStub */)
            openCur = false;

          // We open file as SFX, if there is front archive or first archive is "Self Executable"
          if (!openCur && !pi.IsSelfExe && !thereIsTail &&
              (!pi.IsNotArcType || pi.Offset == 0))
          {
            if (handlerSpec->_items.IsEmpty())
            {
              if (specFlags.CanReturnTail)
                openCur = true;
            }
            else if (handlerSpec->_items.Size() == 1)
            {
              if (handlerSpec->_items[0].IsSelfExe)
              {
                if (mode.SpecUnknownExt.CanReturnTail)
                  openCur = true;
              }
            }
          }

          if (openCur)
          {
            InStream = op.stream;
            Archive = archive;
            FormatIndex = index;
            ArcStreamOffset = arcStreamOffset;
            return S_OK;
          }
        }

        pi.FormatIndex = index;

        // printf("\nAdd offset = %d", (int)pi.Offset);
        handlerSpec->AddItem(pi);
        wasOpen = true;
        break;
      }
      // ---------- End of Open Loop for Current Pos ----------

      if (!wasOpen)
        pos++;
      needCheckStartOpen = (nextNeedCheckStartOpen && wasOpen);
    }
    // ---------- End of Main Scan Loop ----------

    if (mode.CanReturnParser)
    {
      bool returnParser = (handlerSpec->_items.Size() == 1); // it's possible if fileSize was not correct at start of parsing
      handlerSpec->AddUnknownItem(fileSize);
      if (handlerSpec->_items.Size() == 0)
        return S_FALSE;
      if (returnParser || handlerSpec->_items.Size() != 1)
      {
        // return S_FALSE;
        handlerSpec->_stream = op.stream;
        Archive = handler;
        ErrorInfo.ClearErrors();
        IsParseArc = true;
        FormatIndex = -1; // It's parser
        Offset = 0;
        return S_OK;
      }
    }
  }

  if (!Archive)
    return S_FALSE;
  return S_OK;
}

HRESULT CArc::OpenStream(const COpenOptions &op)
{
  RINOK(OpenStream2(op));
  // PrintNumber("op.formatIndex 3", op.formatIndex);

  if (Archive)
  {
    GetRawProps.Release();
    GetRootProps.Release();
    Archive->QueryInterface(IID_IArchiveGetRawProps, (void **)&GetRawProps);
    Archive->QueryInterface(IID_IArchiveGetRootProps, (void **)&GetRootProps);

    RINOK(Archive_GetArcBoolProp(Archive, kpidIsTree, IsTree));
    RINOK(Archive_GetArcBoolProp(Archive, kpidIsDeleted, Ask_Deleted));
    RINOK(Archive_GetArcBoolProp(Archive, kpidIsAltStream, Ask_AltStream));
    RINOK(Archive_GetArcBoolProp(Archive, kpidIsAux, Ask_Aux));
    RINOK(Archive_GetArcBoolProp(Archive, kpidINode, Ask_INode));
    RINOK(Archive_GetArcBoolProp(Archive, kpidReadOnly, IsReadOnly));

    const UString fileName = ExtractFileNameFromPath(Path);
    UString extension;
    {
      int dotPos = fileName.ReverseFind_Dot();
      if (dotPos >= 0)
        extension = fileName.Ptr(dotPos + 1);
    }

    DefaultName.Empty();
    if (FormatIndex >= 0)
    {
      const CArcInfoEx &ai = op.codecs->Formats[FormatIndex];
      if (ai.Exts.Size() == 0)
        DefaultName = GetDefaultName2(fileName, UString(), UString());
      else
      {
        int subExtIndex = ai.FindExtension(extension);
        if (subExtIndex < 0)
          subExtIndex = 0;
        const CArcExtInfo &extInfo = ai.Exts[subExtIndex];
        DefaultName = GetDefaultName2(fileName, extInfo.Ext, extInfo.AddExt);
      }
    }
  }

  return S_OK;
}

HRESULT CArc::OpenStreamOrFile(COpenOptions &op)
{
  CMyComPtr<IInStream> fileStream;
  CMyComPtr<ISequentialInStream> seqStream;
  CInFileStream *fileStreamSpec = NULL;

  if (op.stdInMode)
  {
    seqStream = new CStdInFileStream;
    op.seqStream = seqStream;
  }
  else if (!op.stream)
  {
    fileStreamSpec = new CInFileStream(true);
    fileStream = fileStreamSpec;
    Path = filePath;
    if (!fileStreamSpec->Open(us2fs(Path)))
    {
      return GetLastError();
    }
    op.stream = fileStream;
  }

  HRESULT res = OpenStream(op);
  IgnoreSplit = false;

  return res;
}

void CArchiveLink::KeepModeForNextOpen()
{
  for (unsigned i = Arcs.Size(); i != 0;)
  {
    i--;
    CMyComPtr<IArchiveKeepModeForNextOpen> keep;
    Arcs[i].Archive->QueryInterface(IID_IArchiveKeepModeForNextOpen, (void **)&keep);
    if (keep)
      keep->KeepModeForNextOpen();
  }
}

HRESULT CArchiveLink::Close()
{
  for (unsigned i = Arcs.Size(); i != 0;)
  {
    i--;
    RINOK(Arcs[i].Close());
  }
  IsOpen = false;
  return S_OK;
}

void CArchiveLink::Release()
{
  NonOpen_ErrorInfo.ClearErrors();
  NonOpen_ArcPath.Empty();
  while (!Arcs.IsEmpty())
    Arcs.DeleteBack();
}

HRESULT CArchiveLink::Open(COpenOptions &op)
{
  Release();
  if (op.types->Size() >= 32)
    return E_NOTIMPL;

  HRESULT resSpec;

  for (;;)
  {
    resSpec = S_OK;

    op.openType = COpenType();
    if (op.types->Size() >= 1)
    {
      COpenType latest;
      if (Arcs.Size() < op.types->Size())
        latest = (*op.types)[op.types->Size() - Arcs.Size() - 1];
      else
      {
        latest = (*op.types)[0];
        if (!latest.Recursive)
          break;
      }
      op.openType = latest;
    }
    else if (Arcs.Size() >= 32)
      break;

    if (Arcs.IsEmpty())
    {
      CArc arc;
      arc.filePath = op.filePath;
      arc.Path = op.filePath;
      arc.SubfileIndex = (UInt32)(Int32)-1;
      HRESULT result = arc.OpenStreamOrFile(op);
      if (result != S_OK)
      {
        if (result == S_FALSE)
        {
          NonOpen_ErrorInfo = arc.NonOpen_ErrorInfo;
          // NonOpenErrorFormatIndex = arc.ErrorFormatIndex;
          NonOpen_ArcPath = arc.Path;
        }
        return result;
      }
      Arcs.Add(arc);
      continue;
    }

    // PrintNumber("op.formatIndex 11", op.formatIndex);

    const CArc &arc = Arcs.Back();

    if (op.types->Size() > Arcs.Size())
      resSpec = E_NOTIMPL;

    UInt32 mainSubfile;
    {
      NCOM::CPropVariant prop;
      RINOK(arc.Archive->GetArchiveProperty(kpidMainSubfile, &prop));
      if (prop.vt == VT_UI4)
        mainSubfile = prop.ulVal;
      else
        break;
      UInt32 numItems;
      RINOK(arc.Archive->GetNumberOfItems(&numItems));
      if (mainSubfile >= numItems)
        break;
    }


    CMyComPtr<IInArchiveGetStream> getStream;
    if (arc.Archive->QueryInterface(IID_IInArchiveGetStream, (void **)&getStream) != S_OK || !getStream)
      break;

    CMyComPtr<ISequentialInStream> subSeqStream;
    if (getStream->GetStream(mainSubfile, &subSeqStream) != S_OK || !subSeqStream)
      break;

    CMyComPtr<IInStream> subStream;
    if (subSeqStream.QueryInterface(IID_IInStream, &subStream) != S_OK || !subStream)
      break;

    CArc arc2;
    RINOK(arc.GetItemPath(mainSubfile, arc2.Path));

    bool zerosTailIsAllowed;
    RINOK(Archive_GetItemBoolProp(arc.Archive, mainSubfile, kpidZerosTailIsAllowed, zerosTailIsAllowed));


    if (op.callback)
    {
      CMyComPtr<IArchiveOpenSetSubArchiveName> setSubArchiveName;
      op.callback->QueryInterface(IID_IArchiveOpenSetSubArchiveName, (void **)&setSubArchiveName);
      if (setSubArchiveName)
        setSubArchiveName->SetSubArchiveName(arc2.Path);
    }

    arc2.SubfileIndex = mainSubfile;

    CIntVector excl;

    COpenOptions op2;
    op2.props = op.props;
    op2.codecs = op.codecs;
    op2.openType = op.openType;
    op2.openType.ZerosTailIsAllowed = zerosTailIsAllowed;
    op2.excludedFormats = &excl;
    op2.stdInMode = false;
    op2.stream = subStream;
    op2.filePath = arc2.Path;
    op2.callback = op.callback;
    op2.callbackSpec = op.callbackSpec;

    HRESULT result = arc2.OpenStream(op2);
    resSpec = (op.types->Size() == 0 ? S_OK : S_FALSE);
    if (result == S_FALSE)
    {
      NonOpen_ErrorInfo = arc2.ErrorInfo;
      NonOpen_ArcPath = arc2.Path;
      break;
    }
    RINOK(result);
    RINOK(arc.GetItemMTime(mainSubfile, arc2.MTime, arc2.MTimeDefined));
    Arcs.Add(arc2);
  }
  IsOpen = !Arcs.IsEmpty();
  return resSpec;
}

HRESULT CArchiveLink::Open2(COpenOptions &op, IOpenCallbackUI *callbackUI)
{
  VolumesSize = 0;
  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
  CMyComPtr<IArchiveOpenCallback> callback = openCallbackSpec;
  openCallbackSpec->Callback = callbackUI;

  FString prefix, name;

  if (!op.stream && !op.stdInMode)
  {
    NFile::NDir::GetFullPathAndSplit(us2fs(op.filePath), prefix, name);
    openCallbackSpec->Init(prefix, name);
  }
  else
  {
    openCallbackSpec->SetSubArchiveName(op.filePath);
  }

  op.callback = callback;
  op.callbackSpec = openCallbackSpec;

  HRESULT res = Open(op);

  PasswordWasAsked = openCallbackSpec->PasswordWasAsked;

  RINOK(res);

  FOR_VECTOR (i, openCallbackSpec->FileNames_WasUsed)
  {
    if (openCallbackSpec->FileNames_WasUsed[i])
    {
      VolumePaths.Add(fs2us(prefix) + openCallbackSpec->FileNames[i]);
      VolumesSize += openCallbackSpec->FileSizes[i];
    }
  }
  return S_OK;
}

HRESULT CArc::ReOpen(const COpenOptions &op)
{
  ErrorInfo.ClearErrors();
  ErrorInfo.ErrorFormatIndex = -1;

  UInt64 fileSize = 0;
  if (op.stream)
  {
    RINOK(op.stream->Seek(0, STREAM_SEEK_END, &fileSize));
    RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));
  }
  FileSize = fileSize;

  CMyComPtr<IInStream> stream2;
  Int64 globalOffset = GetGlobalOffset();
  if (globalOffset <= 0)
    stream2 = op.stream;
  else
  {
    CTailInStream *tailStreamSpec = new CTailInStream;
    stream2 = tailStreamSpec;
    tailStreamSpec->Stream = op.stream;
    tailStreamSpec->Offset = globalOffset;
    tailStreamSpec->Init();
    RINOK(tailStreamSpec->SeekToStart());
  }

  // There are archives with embedded STUBs (like ZIP), so we must support signature scanning
  // But for another archives we can use 0 here. So the code can be fixed !!!
  UInt64 maxStartPosition = kMaxCheckStartPosition;
  HRESULT res = Archive->Open(stream2, &maxStartPosition, op.callback);

  if (res == S_OK)
  {
    RINOK(ReadBasicProps(Archive, globalOffset, res));
    ArcStreamOffset = globalOffset;
    if (ArcStreamOffset != 0)
      InStream = op.stream;
  }
  return res;
}

HRESULT CArchiveLink::Open3(COpenOptions &op, IOpenCallbackUI *callbackUI)
{
  HRESULT res = Open2(op, callbackUI);
  if (callbackUI)
  {
    RINOK(callbackUI->Open_Finished());
  }
  return res;
}

HRESULT CArchiveLink::ReOpen(COpenOptions &op)
{
  if (Arcs.Size() > 1)
    return E_NOTIMPL;

  CObjectVector<COpenType> inc;
  CIntVector excl;

  op.types = &inc;
  op.excludedFormats = &excl;
  op.stdInMode = false;
  op.stream = NULL;
  if (Arcs.Size() == 0) // ???
    return Open2(op, NULL);

  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
  CMyComPtr<IArchiveOpenCallback> openCallbackNew = openCallbackSpec;

  openCallbackSpec->Callback = NULL;
  openCallbackSpec->ReOpenCallback = op.callback;
  {
    FString dirPrefix, fileName;
    NFile::NDir::GetFullPathAndSplit(us2fs(op.filePath), dirPrefix, fileName);
    openCallbackSpec->Init(dirPrefix, fileName);
  }


  CInFileStream *fileStreamSpec = new CInFileStream(true);
  CMyComPtr<IInStream> stream(fileStreamSpec);
  if (!fileStreamSpec->Open(us2fs(op.filePath)))
    return GetLastError();
  op.stream = stream;

  CArc &arc = Arcs[0];
  HRESULT res = arc.ReOpen(op);

  PasswordWasAsked = openCallbackSpec->PasswordWasAsked;

  IsOpen = (res == S_OK);
  return res;
}

bool ParseComplexSize(const wchar_t *s, UInt64 &result)
{
  result = 0;
  const wchar_t *end;
  UInt64 number = ConvertStringToUInt64(s, &end);
  if (end == s)
    return false;
  if (*end == 0)
  {
    result = number;
    return true;
  }
  if (end[1] != 0)
    return false;
  unsigned numBits;
  switch (MyCharLower_Ascii(*end))
  {
    case 'b': result = number; return true;
    case 'k': numBits = 10; break;
    case 'm': numBits = 20; break;
    case 'g': numBits = 30; break;
    case 't': numBits = 40; break;
    default: return false;
  }
  if (number >= ((UInt64)1 << (64 - numBits)))
    return false;
  result = number << numBits;
  return true;
}

static bool ParseTypeParams(const UString &s, COpenType &type)
{
  if (s[0] == 0)
    return true;
  if (s[1] == 0)
  {
    switch ((unsigned)(Byte)s[0])
    {
      case 'e': type.EachPos = true; return true;
      case 'a': type.CanReturnArc = true; return true;
      case 'r': type.Recursive = true; return true;
    }
    return false;
  }
  if (s[0] == 's')
  {
    UInt64 result;
    if (!ParseComplexSize(s.Ptr(1), result))
      return false;
    type.MaxStartOffset = result;
    type.MaxStartOffset_Defined = true;
    return true;
  }

  return false;
}

bool ParseType(CCodecs &codecs, const UString &s, COpenType &type)
{
  int pos2 = s.Find(L':');

  {
  UString name;
  if (pos2 < 0)
  {
    name = s;
    pos2 = s.Len();
  }
  else
  {
    name = s.Left(pos2);
    pos2++;
  }

  int index = codecs.FindFormatForArchiveType(name);
  type.Recursive = false;

  if (index < 0)
  {
    if (name[0] == '*')
    {
      if (name[1] != 0)
        return false;
    }
    else if (name[0] == '#')
    {
      if (name[1] != 0)
        return false;
      type.CanReturnArc = false;
      type.CanReturnParser = true;
    }
    else
      return false;
  }

  type.FormatIndex = index;

  }

  for (unsigned i = pos2; i < s.Len();)
  {
    int next = s.Find(L':', i);
    if (next < 0)
      next = s.Len();
    const UString name = s.Mid(i, next - i);
    if (name.IsEmpty())
      return false;
    if (!ParseTypeParams(name, type))
      return false;
    i = next + 1;
  }

  return true;
}

bool ParseOpenTypes(CCodecs &codecs, const UString &s, CObjectVector<COpenType> &types)
{
  types.Clear();
  for (unsigned pos = 0; pos < s.Len();)
  {
    int pos2 = s.Find(L'.', pos);
    if (pos2 < 0)
      pos2 = s.Len();
    UString name = s.Mid(pos, pos2 - pos);
    if (name.IsEmpty())
      return false;
    COpenType type;
    if (!ParseType(codecs, name, type))
      return false;
    types.Add(type);
    pos = pos2 + 1;
  }
  return true;
}
