// 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"

#ifndef _SFX
#include "SetProperties.h"
#endif

#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 << 22;

/*
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;

/*
#ifdef _SFX
#define OPEN_PROPS_PARAM
#else
#define OPEN_PROPS_PARAM  , props
#endif
*/

/*
CArc::~CArc()
{
  GetRawProps.Release();
  Archive.Release();
  printf("\nCArc::~CArc()\n");
}
*/

#ifndef _SFX

namespace NArchive {
namespace NParser {

struct CParseItem
{
  UInt64 Offset;
  UInt64 Size;
  // UInt64 OkSize;
  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)
      // OkSize(0)
    {}

  /*
  bool IsEqualTo(const CParseItem &item) const
  {
    return Offset == item.Offset && Size == item.Size;
  }
  */

  void NormalizeOffset()
  {
    if ((Int64)Offset < 0)
    {
      Size += Offset;
      // OkSize += 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);
  void AddItem(const CParseItem &item);
  // void Init();
  
  CHandler()
  {
    _maxEndOffset = 0;
  }
};

int CHandler::FindInsertPos(const CParseItem &item)
{
  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 -1;
    }
  }
  return left;
}

void CHandler::AddUnknownItem(UInt64 next)
{
  /*
  UInt64 prevEnd = 0;
  if (!_items.IsEmpty())
  {
    const CParseItem &back = _items.Back();
    prevEnd = back.Offset + back.Size;
  }
  */
  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 STATPROPSTG kProps[] =
{
  { NULL, kpidPath, VT_BSTR},
  { NULL, kpidSize, VT_UI8},
  { NULL, kpidMTime, VT_FILETIME},
  { NULL, kpidType, VT_BSTR},
  { NULL, kpidComment, VT_BSTR},
  { NULL, kpidOffset, VT_UI8},
  { NULL, kpidUnpackSize, VT_UI8},
//   { NULL, kpidNumSubDirs, VT_UI8},
};
*/

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
}

}}

#endif

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_Folder(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)
{
  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 = 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;
  UInt32 curIndex = index;
  bool prevWasAltStream = false;
  for (;;)
  {
    UString s;
    
    #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)
        s = prop.bstrVal;
      else if (prop.vt == VT_EMPTY)
        s = L"[Content]";
      else
        return E_FAIL;
    }
    
    if (prevWasAltStream)
      parts[0] = s + L":" + parts[0];
    else
      parts.Insert(0, s);

    UInt32 curParent = (UInt32)(Int32)-1;
    UInt32 parentType = 0;
    RINOK(GetRawProps->GetParent(curIndex, &curParent, &parentType));
    if (parent == curParent)
      return S_OK;
    if (curParent == (UInt32)(Int32)-1)
      return E_FAIL;
    prevWasAltStream = (parentType == NParentType::kAltStream);
    curIndex = curParent;
  }
}

HRESULT CArc::GetItemPath(UInt32 index, UString &result) const
{
  #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.GetBuffer(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;
        }
        result.ReleaseBuffer(len);
        if (len != 0)
          return S_OK;
      }
    }
    /*
    else if (GetRawProps->GetRawProp(index, kpidName, &p, &size, &propType) == S_OK &&
        p && propType == NPropDataType::kUtf16z)
    {
      UInt32 totalSize = size;
      bool isOK = false;
      {
        UInt32 index2 = index;
        for (;;)
        {
          UInt32 parent = (UInt32)(Int32)-1;
          UInt32 parentType = 0;
          if (GetRawProps->GetParent(index2, &parent, &parentType) != S_OK)
            break;
          if (parent == (UInt32)(Int32)-1)
          {
            isOK = true;
            break;
          }
          index2 = parent;
          UInt32 size2;
          const void *p2;
          if (GetRawProps->GetRawProp(index2, kpidName, &p2, &size2, &propType) != S_OK)
            break;
          totalSize += size2;
        }
      }

      if (isOK)
      {
        wchar_t *sz = result.GetBuffer(totalSize / 2);
        UInt32 pos = totalSize - size;
        memcpy((Byte *)sz + pos, p, size - 2);
        UInt32 index2 = index;
        for (;;)
        {
          UInt32 parent = (UInt32)(Int32)-1;
          UInt32 parentType = 0;
          if (GetRawProps->GetParent(index2, &parent, &parentType) != S_OK)
            break;
          if (parent == (UInt32)(Int32)-1)
            break;
          index2 = parent;
          UInt32 size2;
          const void *p2;
          if (GetRawProps->GetRawProp(index2, kpidName, &p2, &size2, &propType) != S_OK)
            break;
          pos -= size2;
          memcpy((Byte *)sz + pos, p2, size2);
          sz[(pos + size2 - 2) / 2] = (parentType == 0) ? WCHAR_PATH_SEPARATOR : L':';
        }
        result.ReleaseBuffer((totalSize - 2) / 2);
        #ifdef _WIN32
        // result.Replace(L'/', WCHAR_PATH_SEPARATOR);
        #endif
        return S_OK;
      }
    }
    */
  }
  #endif
  
  {
    NCOM::CPropVariant prop;
    RINOK(Archive->GetProperty(index, kpidPath, &prop));
    if (prop.vt == VT_BSTR)
      result = prop.bstrVal;
    else if (prop.vt == VT_EMPTY)
      result.Empty();
    else
      return E_FAIL;
  }
  
  if (result.IsEmpty())
  {
    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;
}

#ifndef _SFX

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;
}

#endif

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;
}

#ifndef _SFX

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;
      }
    }
  }
}

#endif

#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


#ifndef _SFX

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

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

static bool IsNameFromList(const UString &s, const char *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 *k_Formats_with_simple_signuature[] =
{
    "7z"
  , "xz"
  , "rar"
  , "bzip2"
  , "gzip"
  , "cab"
  , "wim"
  , "rpm"
  , "vhd"
  , "xar"
};

static bool IsNewStyleSignature(const CArcInfoEx &ai)
{
  // if (ai.Version >= 0x91F)
  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,
  #ifndef _NO_CRYPTO
  public ICryptoGetTextPassword,
  #endif
  public CMyUnknownImp
{
public:
  CMyComPtr<IArchiveOpenCallback> Callback;
  UInt64 Files;
  UInt64 Offset;
  
  #ifndef _NO_CRYPTO
  CMyComPtr<ICryptoGetTextPassword> GetTextPassword;
  MY_UNKNOWN_IMP2(
      IArchiveOpenCallback,
      ICryptoGetTextPassword)
  #else
  MY_UNKNOWN_IMP1(IArchiveOpenCallback)
  #endif
  STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes);
  STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes);
  #ifndef _NO_CRYPTO
  STDMETHOD(CryptoGetTextPassword)(BSTR *password);
  #endif
};

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

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

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

#endif

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()
{
  // ErrorFormatIndex = -1; // we don't need to clear ErrorFormatIndex here !!!

  ThereIsTail = false;
  UnexpecedEnd = false;
  IgnoreTail = false;
  // NonZerosTail = false;
  ErrorFlags_Defined = false;
  ErrorFlags = 0;
  WarningFlags = 0;
  TailSize = 0;

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

HRESULT CArc::ReadBasicProps(IInArchive *archive, UInt64 startPos, HRESULT openRes)
{
  // OkPhySize_Defined = false;
  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));
    /*
    RINOK(Archive_GetArcProp_UInt(archive, kpidOkPhySize, OkPhySize, OkPhySize_Defined));
    if (!OkPhySize_Defined)
    {
      OkPhySize_Defined = PhySizeDefined;
      OkPhySize = PhySize;
    }
    */

    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;
}

/*
static PrintNumber(const char *s, int n)
{
  char temp[100];
  sprintf(temp, "%s %d", s, n);
  OutputDebugStringA(temp);
}
*/

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

  #ifdef EXTERNAL_CODECS
  {
    CMyComPtr<ISetCompressCodecsInfo> setCompressCodecsInfo;
    archive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo);
    if (setCompressCodecsInfo)
    {
      RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(op.codecs));
    }
  }
  #endif
  
  // OutputDebugStringW(ai.Name);
  // OutputDebugStringW(L"a3");
  
  #ifndef _SFX
  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)
  {
    /*
    FOR_VECTOR (y, op.props)
    {
      const COptionalOpenProperties &optProps = (*op.props)[y];
      if (optProps.FormatName.IsEmpty() || optProps.FormatName.CompareNoCase(ai.Name) == 0)
      {
        RINOK(SetProperties(archive, optProps.Props));
        break;
      }
    }
    */
    RINOK(SetProperties(archive, *op.props));
  }
  #endif
  return S_OK;
}

#ifndef _SFX

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));

  // RINOK(Archive_GetArcBoolProp(archive, kpidIsSelfExe, pi.IsSelfExe));
  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 = prop.bstrVal;
      pi.Extension.Empty();
    }
    else
    {
      RINOK(archive->GetArchiveProperty(kpidExtension, &prop));
      if (prop.vt == VT_BSTR)
        pi.Extension = prop.bstrVal;
    }
  }
  
  {
    NCOM::CPropVariant prop;
    RINOK(archive->GetArchiveProperty(kpidShortComment, &prop));
    if (prop.vt == VT_BSTR)
      pi.Comment = prop.bstrVal;
  }


  UInt32 numItems;
  RINOK(archive->GetNumberOfItems(&numItems));
  
  // pi.NumSubFiles = numItems;
  // RINOK(Archive_GetArcProp_UInt(archive, kpidUnpackSize, pi.UnpackSize, pi.UnpackSize_Defined));
  // if (!pi.UnpackSize_Defined)
  {
    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_Folder(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;
}

#endif

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.NonZerosTail = false;
      ErrorInfo.IgnoreTail = true;
      return S_OK;
    }
    for (size_t i = 0; i < processed; i++)
    {
      if (buf[i] != 0)
      {
        // ErrorInfo.IgnoreTail = false;
        // ErrorInfo.NonZerosTail = true;
        return S_OK;
      }
    }
  }
}

#ifndef _SFX

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)
{
  /*
  if (needPhySize)
  {
    CMyComPtr<IArchiveOpen2> open2;
    archive->QueryInterface(IID_IArchiveOpen2, (void **)&open2);
    if (open2)
      return open2->ArcOpen2(stream, kOpenFlags_RealPhySize, openCallback);
  }
  */
  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;
}

#endif

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;
  
  // OutputDebugStringW(L"1");
  // OutputDebugStringW(Path);

  const UString fileName = ExtractFileNameFromPath(Path);
  UString extension;
  {
    int dotPos = fileName.ReverseFind(L'.');
    if (dotPos >= 0)
      extension = fileName.Ptr(dotPos + 1);
  }
  
  CIntVector orderIndices;
  
  bool searchMarkerInHandler = false;
  #ifdef _SFX
    searchMarkerInHandler = true;
  #endif

  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;

  #ifndef _SFX
  bool isUnknownExt = false;
  #endif

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

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

    searchMarkerInHandler = true;
  }
  else
  {
    unsigned numFinded = 0;
    #ifndef _SFX
    bool isPrearcExt = false;
    #endif
    
    {
      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;

        #ifndef _SFX
        if (IsPreArcFormat(ai))
          isPrearcExt = true;
        #endif

        if (ai.FindExtension(extension) >= 0)
        {
          // 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);
    }
    // PrintNumber("numFinded", numFinded );

    /*
    if (op.openOnlySpecifiedByExtension)
    {
      if (numFinded != 0 && !IsExeExt(extension))
        orderIndices.DeleteFrom(numFinded);
    }
    */

    #ifndef _SFX

      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 == L"000" || extension == L"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);
          // MakeCheckOrder(op.codecs, orderIndices, orderIndices.Size(), orderIndices2, NULL, 0);
          // MakeCheckOrder(op.codecs, orderIndices, orderIndices.Size(), 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;

    #else // _SFX

      numMainTypes = orderIndices.Size();
    
    #endif
  }

  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;


  #ifndef _SFX

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

  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];
      const CArcInfoEx &ai = op.codecs->Formats[FormatIndex];
      // OutputDebugStringW(ai.Name);
      
      bool exactOnly = false;
      if (i >= numMainTypes)
      {
        if (!ai.Flags_BackwardOpen()
            // && !ai.Flags_PureStartOpen()
            )
          continue;
        exactOnly = true;
      }
      
      // Some handlers do not set total bytes. So we set it here
      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();

        #ifndef _SFX
        // if it's archive, we allow another open attempt for parser
        if (!mode.CanReturnParser || !isArc)
          skipFrontalFormat[FormatIndex] = true;
        #endif
        
        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
              #ifndef _SFX
              if (!IsPreArcFormat(ai) /* || !mode.SkipSfxStub */)
              #endif
                return S_FALSE;
            }
          }
        }
        
        /*
        #ifndef _SFX
        if (IsExeExt(extension) || ai.Flags_PreArc())
        {
        // openOnlyFullArc = false;
        // canReturnTailArc = true;
        // limitSignatureSearch = true;
        }
        #endif
        */
        
        continue;
      }
      
      RINOK(result);
      
      #ifndef _SFX

      bool isMainFormat = isMainFormatArr[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))
          {
            // openOnlyFullArc = false;
            // canReturnTailArc = true;
            /*
            if (mode.SkipSfxStub)
            limitSignatureSearch = true;
            */
            // if (mode.SkipSfxStub)
            {
              // skipFrontalFormat[FormatIndex] = true;
              continue;
            }
          }
        }
      }
     
      #endif

      Archive = archive;
      return S_OK;
    }
  }

  

  #ifndef _SFX

  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)
      {
        const CArcInfoEx &ai = op.codecs->Formats[formatIndex];
        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];

      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;
        /*
        if (mode.CanReturnArc)
          result = archive->Open(op.stream, &searchLimit, op.callback);
        else
        */
        result = OpenArchiveSpec(archive, !mode.CanReturnArc, op.stream, &searchLimit, op.callback, extractCallback_To_OpenCallback);
      }
      
      if (result == S_FALSE)
      {
        skipFrontalFormat[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;
      
      // bool needScan = false;

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

      /*
      if (OkPhySize_Defined)
        pi.OkSize = pi.OkPhySize;
      else
        pi.OkSize = pi.Size;
      */

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


      if (mode.CanReturnArc)
      {
        bool isMainFormat = isMainFormatArr[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[FormatIndex] = true;


      // if (!mode.CanReturnArc)
      /*
      if (!ErrorInfo.ThereIsTail)
          continue;
      */
      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);
    }
  }
  
  {
    CArchiveOpenCallback_Offset *openCallback_Offset_Spec = new CArchiveOpenCallback_Offset;
    CMyComPtr<IArchiveOpenCallback> openCallback_Offset = openCallback_Offset_Spec;

    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];
          /*
          UInt32 signatureEnd = ai.SignatureOffset + (UInt32)sig.Size();
          if (maxSignatureEnd < signatureEnd)
            maxSignatureEnd = signatureEnd;
          */
          if (sig.Size() < kNumHashBytes)
          {
            isDifficult = true;
            continue;
          }
          thereAreHandlersForSearch = true;
          UInt32 v = HASH_VAL(sig, 0);
          unsigned sigIndex = arc2sig[index] + k;
          prevs[sigIndex] = hash[v];
          hash[v] = (Byte)sigIndex;
        }
      }
      if (isDifficult)
      {
        difficultFormats.Add(index);
        difficultBools[index] = true;
      }
    }
    
    if (!thereAreHandlersForSearch)
    {
      // openOnlyFullArc = true;
      // canReturnTailArc = true;
    }
    
    RINOK(op.stream->Seek(0, STREAM_SEEK_SET, NULL));

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

    openCallback_Offset_Spec->Callback = op.callback;

    #ifndef _NO_CRYPTO
    if (op.callback)
    {
      openCallback_Offset_Spec->Callback.QueryInterface(IID_ICryptoGetTextPassword, &openCallback_Offset_Spec->GetTextPassword);
    }
    #endif

    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;
    // UInt64 prevPos = 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 (pos > ((UInt64)1 << 35)) break;
      
      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));
          // RINOK(op.stream->Seek(skipSize - kBeforeSize, STREAM_SEEK_CUR, NULL));
          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;
        }
      }

      if (pos >= callbackPrev + (1 << 23))
      {
        openCallback_Offset_Spec->Files = handlerSpec->_items.Size();
        openCallback_Offset_Spec->Offset = pos;
        RINOK(openCallback_Offset->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);
  
      {
        /*
        UInt64 scanLimit = openOnlyFullArc ?
            maxSignatureEnd :
            op.openType.ScanSize + maxSignatureEnd;
        */
        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;

          /*
          if (openOnlyFullArc)
            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));
          // prevPos = pos;
          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;
          /*
          // we don't need the check for Z files
          if (startArcPos < handlerSpec->GetLastEnd())
            continue;
          */
        }
        
        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;
            /*
            if (isArcRes == k_IsArc_Res_YES_LOW_PROB)
            {
              // if (pos != ai.SignatureOffset)
              continue;
            }
            */
          }
          // printf("\nIsArc OK: %S", (const wchar_t *)ai.Name);
        }
        
        /*
        if (pos == 67109888)
          pos = pos;
        */
        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;
        openCallback_Offset_Spec->Files = handlerSpec->_items.Size();
        openCallback_Offset_Spec->Offset = startArcPos;
        // HRESULT result = archive->Open(limitedStream, &maxCheckStartPosition, openCallback_Offset);
        extractCallback_To_OpenCallback_Spec->Files = 0;
        extractCallback_To_OpenCallback_Spec->Offset = startArcPos;

        HRESULT result = OpenArchiveSpec(archive, true, limitedStream, &maxCheckStartPosition, openCallback_Offset, 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;

        /*
        if (!ai.UseGlobalOffset)
        {
          if (phySize > arcRem)
          {
            ThereIsTail = true;
            phySize = arcRem;
          }
        }
        */
        
        bool needScan = false;

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

        pi.Size = phySize;
        /*
        if (OkPhySize_Defined)
          pi.OkSize = OkPhySize;
        */
        pi.NormalizeOffset();
        // printf("  phySize = %8d", (unsigned)phySize);

        /*
        if (needSkipFullArc)
          if (pi.Offset == 0 && phySizeDefined && pi.Size >= fileSize)
            continue;
        */
        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++;
          /*
          if (!OkPhySize_Defined)
            pos++;
          else
            pos = pi.Offset + pi.OkSize;
          */
        }
        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;
          }
        }

        /*
        if (openOnlyFullArc)
        {
          ErrorInfo.ClearErrors();
          return S_FALSE;
        }
        */

        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 (handlerSpec->_items.Size() == 1)
    {
      const NArchive::NParser::CParseItem &pi = handlerSpec->_items[0];
      if (pi.Size == fileSize && pi.Offset == 0)
      {
        Archive = archive;
        FormatIndex2 = pi.FormatIndex;
        return S_OK;
      }
    }
    */

    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;
      }
    }
  }

  #endif

  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));

    const UString fileName = ExtractFileNameFromPath(Path);
    UString extension;
    {
      int dotPos = fileName.ReverseFind(L'.');
      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, L"", L"");
      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;
}

#ifdef _SFX

#ifdef _WIN32
  static const wchar_t *k_ExeExt = L".exe";
  static const unsigned k_ExeExt_Len = 4;
#else
  static const wchar_t *k_ExeExt = L"";
  static const unsigned k_ExeExt_Len = 0;
#endif

#endif

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;
    fileStream = fileStreamSpec;
    Path = filePath;
    if (!fileStreamSpec->Open(us2fs(Path)))
    {
      return GetLastError();
    }
    op.stream = fileStream;
    #ifdef _SFX
    IgnoreSplit = true;
    #endif
  }

  /*
  if (callback)
  {
    UInt64 fileSize;
    RINOK(op.stream->Seek(0, STREAM_SEEK_END, &fileSize));
    RINOK(op.callback->SetTotal(NULL, &fileSize))
  }
  */

  HRESULT res = OpenStream(op);
  IgnoreSplit = false;
  
  #ifdef _SFX
  
  if (res != S_FALSE
      || !fileStreamSpec
      || !op.callbackSpec
      || NonOpen_ErrorInfo.IsArc_After_NonOpen())
    return res;
  {
    if (filePath.Len() > k_ExeExt_Len
        && MyStringCompareNoCase(filePath.RightPtr(k_ExeExt_Len), k_ExeExt) == 0)
    {
      const UString path2 = filePath.Left(filePath.Len() - k_ExeExt_Len);
      FOR_VECTOR (i, op.codecs->Formats)
      {
        const CArcInfoEx &ai = op.codecs->Formats[i];
        if (ai.IsSplit())
          continue;
        UString path3 = path2;
        path3 += L".";
        path3 += ai.GetMainExt(); // "7z"  for SFX.
        Path = path3 + L".001";
        bool isOk = op.callbackSpec->SetSecondFileInfo(us2fs(Path));
        if (!isOk)
        {
          Path = path3;
          isOk = op.callbackSpec->SetSecondFileInfo(us2fs(Path));
        }
        if (isOk)
        {
          if (fileStreamSpec->Open(us2fs(Path)))
          {
            op.stream = fileStream;
            NonOpen_ErrorInfo.ClearErrors_Full();
            if (OpenStream(op) == S_OK)
              return S_OK;
          }
        }
      }
    }
  }
  
  #endif

  return res;
}

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

HRESULT CArchiveLink::Close()
{
  for (int i = Arcs.Size() - 1; i >= 0; i--)
  {
    RINOK(Arcs[i].Close());
  }
  IsOpen = false;
  // ErrorsText.Empty();
  return S_OK;
}

void CArchiveLink::Release()
{
  // NonOpenErrorFormatIndex = -1;
  NonOpen_ErrorInfo.ClearErrors();
  NonOpen_ArcPath.Empty();
  while (!Arcs.IsEmpty())
    Arcs.DeleteBack();
}

/*
void CArchiveLink::Set_ErrorsText()
{
  FOR_VECTOR(i, Arcs)
  {
    const CArc &arc = Arcs[i];
    if (!arc.ErrorFlagsText.IsEmpty())
    {
      if (!ErrorsText.IsEmpty())
        ErrorsText += L'\n';
      ErrorsText += GetUnicodeString(arc.ErrorFlagsText);
    }
    if (!arc.ErrorMessage.IsEmpty())
    {
      if (!ErrorsText.IsEmpty())
        ErrorsText += L'\n';
      ErrorsText += arc.ErrorMessage;
    }

    if (!arc.WarningMessage.IsEmpty())
    {
      if (!ErrorsText.IsEmpty())
        ErrorsText += L'\n';
      ErrorsText += arc.WarningMessage;
    }
  }
}
*/

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;

    /*
    op.formatIndex = -1;
    if (op.types->Size() >= 1)
    {
      int latest;
      if (Arcs.Size() < op.types->Size())
        latest = (*op.types)[op.types->Size() - Arcs.Size() - 1];
      else
      {
        latest = (*op.types)[0];
        if (latest != -2 && latest != -3)
          break;
      }
      if (latest >= 0)
        op.formatIndex = latest;
      else if (latest == -1 || latest == -2)
      {
        // default
      }
      else if (latest == -3)
        op.formatIndex = -2;
      else
        op.formatIndex = latest + 2;
    }
    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));

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

    // CIntVector incl;
    CIntVector excl;

    COpenOptions op2;
    #ifndef _SFX
    op2.props = op.props;
    #endif
    op2.codecs = op.codecs;
    // op2.types = &incl;
    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;
}

static void SetCallback(const FString &filePath,
    IOpenCallbackUI *callbackUI,
    IArchiveOpenCallback *reOpenCallback,
    CMyComPtr<IArchiveOpenCallback> &callback)
{
  COpenCallbackImp *openCallbackSpec = new COpenCallbackImp;
  callback = openCallbackSpec;
  openCallbackSpec->Callback = callbackUI;
  openCallbackSpec->ReOpenCallback = reOpenCallback;

  FString dirPrefix, fileName;
  NFile::NDir::GetFullPathAndSplit(filePath, dirPrefix, fileName);
  openCallbackSpec->Init(dirPrefix, fileName);
}

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;
  RINOK(Open(op));
  // VolumePaths.Add(fs2us(prefix + name));

  FOR_VECTOR (i, openCallbackSpec->FileNames_WasUsed)
  {
    if (openCallbackSpec->FileNames_WasUsed[i])
    {
      VolumePaths.Add(fs2us(prefix) + openCallbackSpec->FileNames[i]);
      VolumesSize += openCallbackSpec->FileSizes[i];
    }
  }
  // VolumesSize = openCallbackSpec->TotalSize;
  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::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);

  CMyComPtr<IArchiveOpenCallback> openCallbackNew;
  SetCallback(us2fs(op.filePath), NULL, op.callback, openCallbackNew);

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

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

#ifndef _SFX

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(':');
  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(':', i);
    if (next < 0)
      next = s.Len();
    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('.', 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;
}

#endif