text: // ArjHandler.cpp
  
  
  
  #include "../../../C/CpuArch.h"
  
  #include "../../Common/ComTry.h"
  #include "../../Common/IntToString.h"
  #include "../../Common/StringConvert.h"
  
  #include "../../Windows/PropVariant.h"
  #include "../../Windows/TimeUtils.h"
  
  #include "../Common/LimitedStreams.h"
  #include "../Common/ProgressUtils.h"
  #include "../Common/RegisterArc.h"
  #include "../Common/StreamObjects.h"
  #include "../Common/StreamUtils.h"
  
  #include "../Compress/CopyCoder.h"
  #include "../Compress/LzhDecoder.h"
  
  #include "Common/ItemNameUtils.h"
  #include "Common/OutStreamWithCRC.h"
  
  namespace NCompress {
  namespace NArj {
  namespace NDecoder {
  
  static const unsigned kMatchMinLen = 3;
  
  static const UInt32 kWindowSize = 1 << 15; // must be >= (1 << 14)
  
  class CCoder:
    public ICompressCoder,
    public CMyUnknownImp
  {
    CLzOutWindow _outWindow;
    NBitm::CDecoder<CInBuffer> _inBitStream;
  
    class CCoderReleaser
    {
      CCoder *_coder;
    public:
      CCoderReleaser(CCoder *coder): _coder(coder) {}
      void Disable() { _coder = NULL; }
      ~CCoderReleaser() { if (_coder) _coder->_outWindow.Flush(); }
    };
    friend class CCoderReleaser;
  
    HRESULT CodeReal(UInt64 outSize, ICompressProgressInfo *progress);
  public:
    MY_UNKNOWN_IMP
  
    bool FinishMode;
    CCoder(): FinishMode(false) {}
  
    STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream,
        const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress);
    UInt64 GetInputProcessedSize() const { return _inBitStream.GetProcessedSize(); }
  };
  
  HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress)
  {
    const UInt32 kStep = 1 << 20;
    UInt64 next = 0;
    if (rem > kStep && progress)
      next = rem - kStep;
  
    while (rem != 0)
    {
      if (rem <= next)
      {
        if (_inBitStream.ExtraBitsWereRead())
          return S_FALSE;
  
        UInt64 packSize = _inBitStream.GetProcessedSize();
        UInt64 pos = _outWindow.GetProcessedSize();
        RINOK(progress->SetRatioInfo(&packSize, &pos));
        next = 0;
        if (rem > kStep)
          next = rem - kStep;
      }
  
      UInt32 len;
      
      {
        const unsigned kNumBits = 7 + 7;
        UInt32 val = _inBitStream.GetValue(kNumBits);
        
        if ((val & (1 << (kNumBits - 1))) == 0)
        {
          _outWindow.PutByte((Byte)(val >> 5));
          _inBitStream.MovePos(1 + 8);
          rem--;
          continue;
        }
  
        UInt32 mask = 1 << (kNumBits - 2);
        unsigned w;
  
        for (w = 1; w < 7; w++, mask >>= 1)
          if ((val & mask) == 0)
            break;
        
        unsigned readBits = (w != 7 ? 1 : 0);
        readBits += w + w;
        len = (1 << w) - 1 + kMatchMinLen - 1 +
            (((val >> (kNumBits - readBits)) & ((1 << w) - 1)));
        _inBitStream.MovePos(readBits);
      }
      
      {
        const unsigned kNumBits = 4 + 13;
        UInt32 val = _inBitStream.GetValue(kNumBits);
  
        unsigned readBits = 1;
        unsigned w;
       
             if ((val & ((UInt32)1 << 16)) == 0) w = 9;
        else if ((val & ((UInt32)1 << 15)) == 0) w = 10;
        else if ((val & ((UInt32)1 << 14)) == 0) w = 11;
        else if ((val & ((UInt32)1 << 13)) == 0) w = 12;
        else { w = 13; readBits = 0; }
  
        readBits += w + w - 9;
  
        UInt32 dist = ((UInt32)1 << w) - (1 << 9) +
            (((val >> (kNumBits - readBits)) & ((1 << w) - 1)));
        _inBitStream.MovePos(readBits);
  
        if (len > rem)
          len = (UInt32)rem;
  
        if (!_outWindow.CopyBlock(dist, len))
          return S_FALSE;
        rem -= len;
      }
    }
  
    if (FinishMode)
    {
      if (_inBitStream.ReadAlignBits() != 0)
        return S_FALSE;
    }
  
    if (_inBitStream.ExtraBitsWereRead())
      return S_FALSE;
  
    return S_OK;
  }
  
  
  
  STDMETHODIMP CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream,
      const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)
  {
    try
    {
      if (!outSize)
        return E_INVALIDARG;
      
      if (!_outWindow.Create(kWindowSize))
        return E_OUTOFMEMORY;
      if (!_inBitStream.Create(1 << 17))
        return E_OUTOFMEMORY;
      
      _outWindow.SetStream(outStream);
      _outWindow.Init(false);
      _inBitStream.SetStream(inStream);
      _inBitStream.Init();
      
      CCoderReleaser coderReleaser(this);
      HRESULT res;
      {
        res = CodeReal(*outSize, progress);
        if (res != S_OK)
          return res;
      }
      
      coderReleaser.Disable();
      return _outWindow.Flush();
    }
    catch(const CInBufferException &e) { return e.ErrorCode; }
    catch(const CLzOutWindowException &e) { return e.ErrorCode; }
    catch(...) { return S_FALSE; }
  }
  
  }}}
  
  
  
  
  using namespace NWindows;
  
  #define Get16(p) GetUi16(p)
  #define Get32(p) GetUi32(p)
  
  namespace NArchive {
  namespace NArj {
  
  static const unsigned kBlockSizeMin = 30;
  static const unsigned kBlockSizeMax = 2600;
  
  static const Byte kSig0 = 0x60;
  static const Byte kSig1 = 0xEA;
  
  namespace NCompressionMethod
  {
    enum
    {
      kStored = 0,
      kCompressed1a = 1,
      kCompressed1b = 2,
      kCompressed1c = 3,
      kCompressed2 = 4,
      kNoDataNoCRC = 8,
      kNoData = 9
    };
  }
  
  namespace NFileType
  {
    enum
    {
      kBinary = 0,
      k7BitText,
      kArchiveHeader,
      kDirectory,
      kVolumeLablel,
      kChapterLabel
    };
  }
  
  namespace NFlags
  {
    const Byte kGarbled  = 1 << 0;
    const Byte kAnsiPage = 1 << 1; // or (OLD_SECURED_FLAG) obsolete
    const Byte kVolume   = 1 << 2;
    const Byte kExtFile  = 1 << 3;
    const Byte kPathSym  = 1 << 4;
    const Byte kBackup   = 1 << 5; // obsolete
    const Byte kSecured  = 1 << 6;
    const Byte kDualName = 1 << 7;
  }
  
  namespace NHostOS
  {
    enum EEnum
    {
      kMSDOS = 0,  // MS-DOS, OS/2, Win32, pkarj 2.50 (FAT / VFAT / FAT32)
      kPRIMOS,
      kUnix,
      kAMIGA,
      kMac,
      kOS_2,
      kAPPLE_GS,
      kAtari_ST,
      kNext,
      kVAX_VMS,
      kWIN95
    };
  }
  
  static const char * const kHostOS[] =
  {
      "MSDOS"
    , "PRIMOS"
    , "UNIX"
    , "AMIGA"
    , "MAC"
    , "OS/2"
    , "APPLE GS"
    , "ATARI ST"
    , "NEXT"
    , "VAX VMS"
    , "WIN95"
  };
  
  struct CArcHeader
  {
    // Byte ArchiverVersion;
    // Byte ExtractVersion;
    Byte HostOS;
    // Byte Flags;
    // Byte SecuryVersion;
    // Byte FileType;
    // Byte Reserved;
    UInt32 CTime;
    UInt32 MTime;
    UInt32 ArchiveSize;
    // UInt32 SecurPos;
    // UInt16 FilespecPosInFilename;
    UInt16 SecurSize;
    // Byte EncryptionVersion;
    // Byte LastChapter;
    AString Name;
    AString Comment;
    
    HRESULT Parse(const Byte *p, unsigned size);
  };
  
  API_FUNC_static_IsArc IsArc_Arj(const Byte *p, size_t size)
  {
    if (size < kBlockSizeMin + 4)
      return k_IsArc_Res_NEED_MORE;
    if (p[0] != kSig0 || p[1] != kSig1)
      return k_IsArc_Res_NO;
    UInt32 blockSize = Get16(p + 2);
    if (blockSize < kBlockSizeMin ||
        blockSize > kBlockSizeMax)
      return k_IsArc_Res_NO;
  
    p += 4;
    size -= 4;
  
    Byte headerSize = p[0];
    if (headerSize < kBlockSizeMin ||
        headerSize > blockSize ||
        p[6] != NFileType::kArchiveHeader ||
        p[28] > 8) // EncryptionVersion
      return k_IsArc_Res_NO;
  
    if (blockSize + 4 <= size)
      if (Get32(p + blockSize) != CrcCalc(p, blockSize))
        return k_IsArc_Res_NO;
  
    return k_IsArc_Res_YES;
  }
  }
  
  static HRESULT ReadString(const Byte *p, unsigned &size, AString &res)
  {
    unsigned num = size;
    for (unsigned i = 0; i < num;)
    {
      if (p[i++] == 0)
      {
        size = i;
        res = (const char *)p;
        return S_OK;
      }
    }
    return S_FALSE;
  }
  
  HRESULT CArcHeader::Parse(const Byte *p, unsigned size)
  {
    Byte headerSize = p[0];
    if (headerSize < kBlockSizeMin || headerSize > size)
      return S_FALSE;
    // ArchiverVersion = p[1];
    // ExtractVersion = p[2];
    HostOS = p[3];
    // Flags = p[4];
    // SecuryVersion = p[5];
    if (p[6] != NFileType::kArchiveHeader)
      return S_FALSE;
    // Reserved = p[7];
    CTime = Get32(p + 8);
    MTime = Get32(p + 12);
    ArchiveSize = Get32(p + 16); // it can be zero. (currently used only for secured archives)
    // SecurPos = Get32(p + 20);
    // UInt16 filespecPositionInFilename = Get16(p + 24);
    SecurSize = Get16(p + 26);
    // EncryptionVersion = p[28];
    // LastChapter = p[29];
    unsigned pos = headerSize;
    unsigned size1 = size - pos;
    RINOK(ReadString(p + pos, size1, Name));
    pos += size1;
    size1 = size - pos;
    RINOK(ReadString(p + pos, size1, Comment));
    pos += size1;
    return S_OK;
  }
  
  struct CItem
  {
    AString Name;
    AString Comment;
  
    UInt32 MTime;
    UInt32 PackSize;
    UInt32 Size;
    UInt32 FileCRC;
    UInt32 SplitPos;
  
    Byte Version;
    Byte ExtractVersion;
    Byte HostOS;
    Byte Flags;
    Byte Method;
    Byte FileType;
  
    // UInt16 FilespecPosInFilename;
    UInt16 FileAccessMode;
    // Byte FirstChapter;
    // Byte LastChapter;
    
    UInt64 DataPosition;
    
    bool IsEncrypted() const { return (Flags & NFlags::kGarbled) != 0; }
    bool IsDir() const { return (FileType == NFileType::kDirectory); }
    bool IsSplitAfter() const { return (Flags & NFlags::kVolume) != 0; }
    bool IsSplitBefore() const { return (Flags & NFlags::kExtFile) != 0; }
    UInt32 GetWinAttrib() const
    {
      UInt32 atrrib = 0;
      switch (HostOS)
      {
        case NHostOS::kMSDOS:
        case NHostOS::kWIN95:
          atrrib = FileAccessMode;
          break;
      }
      if (IsDir())
        atrrib |= FILE_ATTRIBUTE_DIRECTORY;
      return atrrib;
    }
  
    HRESULT Parse(const Byte *p, unsigned size);
  };
  
  HRESULT CItem::Parse(const Byte *p, unsigned size)
  {
    Byte headerSize = p[0];
    if (headerSize < kBlockSizeMin || headerSize > size)
      return S_FALSE;
    Version = p[1];
    ExtractVersion = p[2];
    HostOS = p[3];
    Flags = p[4];
    Method = p[5];
    FileType = p[6];
    // Reserved = p[7];
    MTime = Get32(p + 8);
    PackSize = Get32(p + 12);
    Size = Get32(p + 16);
    FileCRC = Get32(p + 20);
    // FilespecPosInFilename = Get16(p + 24);
    FileAccessMode = Get16(p + 26);
    // FirstChapter = p[28];
    // FirstChapter = p[29];
  
    SplitPos = 0;
    if (IsSplitBefore() && headerSize >= 34)
      SplitPos = Get32(p + 30);
  
    unsigned pos = headerSize;
    unsigned size1 = size - pos;
    RINOK(ReadString(p + pos, size1, Name));
    pos += size1;
    size1 = size - pos;
    RINOK(ReadString(p + pos, size1, Comment));
    pos += size1;
  
    return S_OK;
  }
  
  enum EErrorType
  {
    k_ErrorType_OK,
    k_ErrorType_Corrupted,
    k_ErrorType_UnexpectedEnd,
  };
  
  class CArc
  {
  public:
    UInt64 Processed;
    EErrorType Error;
    bool IsArc;
    IInStream *Stream;
    IArchiveOpenCallback *Callback;
    UInt64 NumFiles;
    CArcHeader Header;
  
    HRESULT Open();
    HRESULT GetNextItem(CItem &item, bool &filled);
    void Close()
    {
      IsArc = false;
      Error = k_ErrorType_OK;
    }
  private:
    UInt32 _blockSize;
    Byte _block[kBlockSizeMax + 4];
  
    HRESULT ReadBlock(bool &filled, bool readSignature);
    HRESULT SkipExtendedHeaders();
    HRESULT Read(void *data, size_t *size);
  };
  
  HRESULT CArc::Read(void *data, size_t *size)
  {
    HRESULT res = ReadStream(Stream, data, size);
    Processed += *size;
    return res;
  }
  
  #define READ_STREAM(_dest_, _size_) \
    { size_t _processed_ = (_size_); RINOK(Read(_dest_, &_processed_)); \
    if (_processed_ != (_size_)) { Error = k_ErrorType_UnexpectedEnd; return S_OK; } }
  
  HRESULT CArc::ReadBlock(bool &filled, bool readSignature)
  {
    Error = k_ErrorType_OK;
    filled = false;
    Byte buf[4];
    unsigned signSize = readSignature ? 2 : 0;
    READ_STREAM(buf, signSize + 2)
    if (readSignature)
      if (buf[0] != kSig0 || buf[1] != kSig1)
      {
        Error = k_ErrorType_Corrupted;
        return S_OK;
      }
    _blockSize = Get16(buf + signSize);
    if (_blockSize == 0) // end of archive
      return S_OK;
    if (_blockSize < kBlockSizeMin ||
        _blockSize > kBlockSizeMax)
    {
      Error = k_ErrorType_Corrupted;
      return S_OK;
    }
    READ_STREAM(_block, _blockSize + 4);
    if (Get32(_block + _blockSize) != CrcCalc(_block, _blockSize))
    {
      Error = k_ErrorType_Corrupted;
      return S_OK;
    }
    filled = true;
    return S_OK;
  }
  
  HRESULT CArc::SkipExtendedHeaders()
  {
    for (UInt32 i = 0;; i++)
    {
      bool filled;
      RINOK(ReadBlock(filled, false));
      if (!filled)
        return S_OK;
      if (Callback && (i & 0xFF) == 0)
        RINOK(Callback->SetCompleted(&NumFiles, &Processed));
    }
  }
  
  HRESULT CArc::Open()
  {
    bool filled;
    RINOK(ReadBlock(filled, true));
    if (!filled)
      return S_FALSE;
    RINOK(Header.Parse(_block, _blockSize));
    IsArc = true;
    return SkipExtendedHeaders();
  }
  
  HRESULT CArc::GetNextItem(CItem &item, bool &filled)
  {
    RINOK(ReadBlock(filled, true));
    if (!filled)
      return S_OK;
    filled = false;
    if (item.Parse(_block, _blockSize) != S_OK)
    {
      Error = k_ErrorType_Corrupted;
      return S_OK;
    }
    /*
    UInt32 extraData;
    if ((header.Flags & NFlags::kExtFile) != 0)
      extraData = GetUi32(_block + pos);
    */
  
    RINOK(SkipExtendedHeaders());
    filled = true;
    return S_OK;
  }
  
  class CHandler:
    public IInArchive,
    public CMyUnknownImp
  {
    CObjectVector<CItem> _items;
    CMyComPtr<IInStream> _stream;
    UInt64 _phySize;
    CArc _arc;
  public:
    MY_UNKNOWN_IMP1(IInArchive)
  
    INTERFACE_IInArchive(;)
  
    HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *callback);
  };
  
  static const Byte kArcProps[] =
  {
    kpidName,
    kpidCTime,
    kpidMTime,
    kpidHostOS,
    kpidComment
  };
  
  static const Byte kProps[] =
  {
    kpidPath,
    kpidIsDir,
    kpidSize,
    kpidPosition,
    kpidPackSize,
    kpidMTime,
    kpidAttrib,
    kpidEncrypted,
    kpidCRC,
    kpidMethod,
    kpidHostOS,
    kpidComment
  };
  
  IMP_IInArchive_Props
  IMP_IInArchive_ArcProps
  
  static void SetTime(UInt32 dosTime, NCOM::CPropVariant &prop)
  {
    if (dosTime == 0)
      return;
    FILETIME localFileTime, utc;
    if (NTime::DosTimeToFileTime(dosTime, localFileTime))
    {
      if (!LocalFileTimeToFileTime(&localFileTime, &utc))
        utc.dwHighDateTime = utc.dwLowDateTime = 0;
    }
    else
      utc.dwHighDateTime = utc.dwLowDateTime = 0;
    prop = utc;
  }
  
  static void SetHostOS(Byte hostOS, NCOM::CPropVariant &prop)
  {
    char temp[16];
    const char *s = NULL;
    if (hostOS < ARRAY_SIZE(kHostOS))
      s = kHostOS[hostOS];
    else
    {
      ConvertUInt32ToString(hostOS, temp);
      s = temp;
    }
    prop = s;
  }
  
  static void SetUnicodeString(const AString &s, NCOM::CPropVariant &prop)
  {
    if (!s.IsEmpty())
      prop = MultiByteToUnicodeString(s, CP_OEMCP);
  }
   
  STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
  {
    COM_TRY_BEGIN
    NCOM::CPropVariant prop;
    switch (propID)
    {
      case kpidPhySize: prop = _phySize; break;
      case kpidName: SetUnicodeString(_arc.Header.Name, prop); break;
      case kpidCTime: SetTime(_arc.Header.CTime, prop); break;
      case kpidMTime: SetTime(_arc.Header.MTime, prop); break;
      case kpidHostOS: SetHostOS(_arc.Header.HostOS, prop); break;
      case kpidComment: SetUnicodeString(_arc.Header.Comment, prop); break;
      case kpidErrorFlags:
      {
        UInt32 v = 0;
        if (!_arc.IsArc) v |= kpv_ErrorFlags_IsNotArc;
        switch (_arc.Error)
        {
          case k_ErrorType_UnexpectedEnd: v |= kpv_ErrorFlags_UnexpectedEnd; break;
          case k_ErrorType_Corrupted: v |= kpv_ErrorFlags_HeadersError; break;
        }
        prop = v;
        break;
      }
    }
    prop.Detach(value);
    return S_OK;
    COM_TRY_END
  }
  
  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 CItem &item = _items[index];
    switch (propID)
    {
      case kpidPath:  prop = NItemName::GetOSName(MultiByteToUnicodeString(item.Name, CP_OEMCP)); break;
      case kpidIsDir:  prop = item.IsDir(); break;
      case kpidSize:  prop = item.Size; break;
      case kpidPackSize:  prop = item.PackSize; break;
      case kpidPosition:  if (item.IsSplitBefore() || item.IsSplitAfter()) prop = (UInt64)item.SplitPos; break;
      case kpidAttrib:  prop = item.GetWinAttrib(); break;
      case kpidEncrypted:  prop = item.IsEncrypted(); break;
      case kpidCRC:  prop = item.FileCRC; break;
      case kpidMethod:  prop = item.Method; break;
      case kpidHostOS:  SetHostOS(item.HostOS, prop); break;
      case kpidMTime:  SetTime(item.MTime, prop); break;
      case kpidComment:  SetUnicodeString(item.Comment, prop); break;
    }
    prop.Detach(value);
    return S_OK;
    COM_TRY_END
  }
  
  HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback)
  {
    Close();
    
    UInt64 endPos = 0;
    RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos));
    RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL));
    
    _arc.Stream = inStream;
    _arc.Callback = callback;
    _arc.NumFiles = 0;
    _arc.Processed = 0;
  
    RINOK(_arc.Open());
  
    _phySize = _arc.Processed;
    if (_arc.Header.ArchiveSize != 0)
      _phySize = (UInt64)_arc.Header.ArchiveSize + _arc.Header.SecurSize;
  
    for (;;)
    {
      CItem item;
      bool filled;
  
      _arc.Error = k_ErrorType_OK;
      RINOK(_arc.GetNextItem(item, filled));
  
      if (_arc.Error != k_ErrorType_OK)
        break;
      
      if (!filled)
      {
        if (_arc.Error == k_ErrorType_OK)
          if (_arc.Header.ArchiveSize == 0)
            _phySize = _arc.Processed;
        break;
      }
      item.DataPosition = _arc.Processed;
      _items.Add(item);
      
      UInt64 pos = item.DataPosition + item.PackSize;
      if (_arc.Header.ArchiveSize == 0)
        _phySize = pos;
      if (pos > endPos)
      {
        _arc.Error = k_ErrorType_UnexpectedEnd;
        break;
      }
  
      RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL));
      _arc.NumFiles = _items.Size();
      _arc.Processed = pos;
      
      if (callback && (_items.Size() & 0xFF) == 0)
      {
        RINOK(callback->SetCompleted(&_arc.NumFiles, &_arc.Processed));
      }
    }
    return S_OK;
  }
  
  STDMETHODIMP CHandler::Open(IInStream *inStream,
      const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback)
  {
    COM_TRY_BEGIN
    HRESULT res;
    {
      res = Open2(inStream, callback);
      if (res == S_OK)
      {
        _stream = inStream;
        return S_OK;
      }
    }
    return res;
    COM_TRY_END
  }
  
  STDMETHODIMP CHandler::Close()
  {
    _arc.Close();
    _phySize = 0;
    _items.Clear();
    _stream.Release();
    return S_OK;
  }
  
  STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
      Int32 testMode, IArchiveExtractCallback *extractCallback)
  {
    COM_TRY_BEGIN
    UInt64 totalUnpacked = 0, totalPacked = 0;
    bool allFilesMode = (numItems == (UInt32)(Int32)-1);
    if (allFilesMode)
      numItems = _items.Size();
    if (numItems == 0)
      return S_OK;
    UInt32 i;
    for (i = 0; i < numItems; i++)
    {
      const CItem &item = _items[allFilesMode ? i : indices[i]];
      totalUnpacked += item.Size;
      // totalPacked += item.PackSize;
    }
    extractCallback->SetTotal(totalUnpacked);
  
    totalUnpacked = totalPacked = 0;
    UInt64 curUnpacked, curPacked;
    
    NCompress::NLzh::NDecoder::CCoder *lzhDecoderSpec = NULL;
    CMyComPtr<ICompressCoder> lzhDecoder;
  
    NCompress::NArj::NDecoder::CCoder *arjDecoderSpec = NULL;
    CMyComPtr<ICompressCoder> arjDecoder;
  
    NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
    CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
  
    CLocalProgress *lps = new CLocalProgress;
    CMyComPtr<ICompressProgressInfo> progress = lps;
    lps->Init(extractCallback, false);
  
    CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream;
    CMyComPtr<ISequentialInStream> inStream(inStreamSpec);
    inStreamSpec->SetStream(_stream);
  
    for (i = 0; i < numItems; i++, totalUnpacked += curUnpacked, totalPacked += curPacked)
    {
      lps->InSize = totalPacked;
      lps->OutSize = totalUnpacked;
      RINOK(lps->SetCur());
  
      curUnpacked = curPacked = 0;
  
      CMyComPtr<ISequentialOutStream> realOutStream;
      Int32 askMode = testMode ?
          NExtract::NAskMode::kTest :
          NExtract::NAskMode::kExtract;
      Int32 index = allFilesMode ? i : indices[i];
      const CItem &item = _items[index];
      RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
  
      if (item.IsDir())
      {
        // if (!testMode)
        {
          RINOK(extractCallback->PrepareOperation(askMode));
          RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
        }
        continue;
      }
  
      if (!testMode && !realOutStream)
        continue;
  
      RINOK(extractCallback->PrepareOperation(askMode));
      curUnpacked = item.Size;
      curPacked = item.PackSize;
  
      {
        COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC;
        CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
        outStreamSpec->SetStream(realOutStream);
        realOutStream.Release();
        outStreamSpec->Init();
    
        inStreamSpec->Init(item.PackSize);
        
        UInt64 pos;
        _stream->Seek(item.DataPosition, STREAM_SEEK_SET, &pos);
  
        HRESULT result = S_OK;
        Int32 opRes = NExtract::NOperationResult::kOK;
  
        if (item.IsEncrypted())
          opRes = NExtract::NOperationResult::kUnsupportedMethod;
        else
        {
          switch (item.Method)
          {
            case NCompressionMethod::kStored:
            {
              result = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
              if (result == S_OK && copyCoderSpec->TotalSize != item.PackSize)
                result = S_FALSE;
              break;
            }
            case NCompressionMethod::kCompressed1a:
            case NCompressionMethod::kCompressed1b:
            case NCompressionMethod::kCompressed1c:
            {
              if (!lzhDecoder)
              {
                lzhDecoderSpec = new NCompress::NLzh::NDecoder::CCoder;
                lzhDecoder = lzhDecoderSpec;
              }
              lzhDecoderSpec->FinishMode = true;
              const UInt32 kHistorySize = 26624;
              lzhDecoderSpec->SetDictSize(kHistorySize);
              result = lzhDecoder->Code(inStream, outStream, NULL, &curUnpacked, progress);
              if (result == S_OK && lzhDecoderSpec->GetInputProcessedSize() != item.PackSize)
                result = S_FALSE;
              break;
            }
            case NCompressionMethod::kCompressed2:
            {
              if (!arjDecoder)
              {
                arjDecoderSpec = new NCompress::NArj::NDecoder::CCoder;
                arjDecoder = arjDecoderSpec;
              }
              arjDecoderSpec->FinishMode = true;
              result = arjDecoder->Code(inStream, outStream, NULL, &curUnpacked, progress);
              if (result == S_OK && arjDecoderSpec->GetInputProcessedSize() != item.PackSize)
                result = S_FALSE;
              break;
            }
            default:
              opRes = NExtract::NOperationResult::kUnsupportedMethod;
          }
        }
        
        if (opRes == NExtract::NOperationResult::kOK)
        {
          if (result == S_FALSE)
            opRes = NExtract::NOperationResult::kDataError;
          else
          {
            RINOK(result);
            opRes = (outStreamSpec->GetCRC() == item.FileCRC) ?
                NExtract::NOperationResult::kOK:
                NExtract::NOperationResult::kCRCError;
          }
        }
        
        outStream.Release();
        RINOK(extractCallback->SetOperationResult(opRes));
      }
    }
    
    return S_OK;
    COM_TRY_END
  }
  
  static const Byte k_Signature[] = { kSig0, kSig1 };
  
  REGISTER_ARC_I(
    "Arj", "arj", 0, 4,
    k_Signature,
    0,
    0,
    IsArc_Arj)
  
  }}
  