text: #include "rar.hpp"
  
  CmdExtract::CmdExtract(CommandData *Cmd)
  {
    CmdExtract::Cmd=Cmd;
  
    *ArcName=0;
  
    *DestFileName=0;
  
    TotalFileCount=0;
  
    //next two lines added by me
    Buffer = NULL;
    BufferSize = 0;
  
    Unp=new Unpack(&DataIO);
  #ifdef RAR_SMP
    Unp->SetThreads(Cmd->Threads);
  #endif
  }
  
  
  CmdExtract::~CmdExtract()
  {
    delete Unp;
  }
  
  
  void CmdExtract::DoExtract()
  {
  #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
    Fat32=NotFat32=false;
  #endif
    PasswordCancelled=false;
    DataIO.SetCurrentCommand(Cmd->Command[0]);
  
    FindData FD;
    while (Cmd->GetArcName(ArcName,ASIZE(ArcName)))
      if (FindFile::FastFind(ArcName,&FD))
        DataIO.TotalArcSize+=FD.Size;
  
    Cmd->ArcNames.Rewind();
    while (Cmd->GetArcName(ArcName,ASIZE(ArcName)))
    {
      if (Cmd->ManualPassword)
        Cmd->Password.Clean(); // Clean user entered password before processing next archive.
      while (true)
      {
        EXTRACT_ARC_CODE Code=ExtractArchive();
        if (Code!=EXTRACT_ARC_REPEAT)
          break;
      }
      if (FindFile::FastFind(ArcName,&FD))
        DataIO.ProcessedArcSize+=FD.Size;
    }
  
    // Clean user entered password. Not really required, just for extra safety.
    if (Cmd->ManualPassword)
      Cmd->Password.Clean();
  
    if (TotalFileCount==0 && Cmd->Command[0]!='I' && 
        ErrHandler.GetErrorCode()!=RARX_BADPWD) // Not in case of wrong archive password.
    {
      if (!PasswordCancelled)
        uiMsg(UIERROR_NOFILESTOEXTRACT,ArcName);
      ErrHandler.SetErrorCode(RARX_NOFILES);
    }
    else
      if (!Cmd->DisableDone)
        if (Cmd->Command[0]=='I')
          mprintf(St(MDone));
        else
          if (ErrHandler.GetErrorCount()==0)
            mprintf(St(MExtrAllOk));
          else
            mprintf(St(MExtrTotalErr),ErrHandler.GetErrorCount());
  }
  
  
  void CmdExtract::ExtractArchiveInit(Archive &Arc)
  {
    DataIO.UnpArcSize=Arc.FileLength();
  
    FileCount=0;
    MatchedArgs=0;
  #ifndef SFX_MODULE
    FirstFile=true;
  #endif
  
    PasswordAll=(Cmd->Password.IsSet());
  
    DataIO.UnpVolume=false;
  
    PrevProcessed=false;
    AllMatchesExact=true;
    ReconstructDone=false;
    AnySolidDataUnpackedWell=false;
  
    StartTime.SetCurrentTime();
  }
  
  
  EXTRACT_ARC_CODE CmdExtract::ExtractArchive()
  {
    Archive Arc(Cmd);
    if (!Arc.WOpen(ArcName))
      return EXTRACT_ARC_NEXT;
  
    if (!Arc.IsArchive(true))
    {
  #if !defined(SFX_MODULE) && !defined(RARDLL)
      if (CmpExt(ArcName,L"rev"))
      {
        wchar FirstVolName[NM];
        VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),true);
  
        // If several volume names from same volume set are specified
        // and current volume is not first in set and first volume is present
        // and specified too, let's skip the current volume.
        if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) &&
            Cmd->ArcNames.Search(FirstVolName,false))
          return EXTRACT_ARC_NEXT;
        RecVolumesTest(Cmd,NULL,ArcName);
        TotalFileCount++; // Suppress "No files to extract" message.
        return EXTRACT_ARC_NEXT;
      }
  #endif
  
      mprintf(St(MNotRAR),ArcName);
  
  #ifndef SFX_MODULE
      if (CmpExt(ArcName,L"rar"))
  #endif
        ErrHandler.SetErrorCode(RARX_WARNING);
      return EXTRACT_ARC_NEXT;
    }
  
    if (Arc.FailedHeaderDecryption) // Bad archive password.
      return EXTRACT_ARC_NEXT;
  
  #ifndef SFX_MODULE
    if (Arc.Volume && !Arc.FirstVolume)
    {
      wchar FirstVolName[NM];
      VolNameToFirstName(ArcName,FirstVolName,ASIZE(FirstVolName),Arc.NewNumbering);
  
      // If several volume names from same volume set are specified
      // and current volume is not first in set and first volume is present
      // and specified too, let's skip the current volume.
      if (wcsicomp(ArcName,FirstVolName)!=0 && FileExist(FirstVolName) &&
          Cmd->ArcNames.Search(FirstVolName,false))
        return EXTRACT_ARC_NEXT;
    }
  #endif
  
    int64 VolumeSetSize=0; // Total size of volumes after the current volume.
  
    if (Arc.Volume)
    {
      // Calculate the total size of all accessible volumes.
      // This size is necessary to display the correct total progress indicator.
  
      wchar NextName[NM];
      wcscpy(NextName,Arc.FileName);
  
      while (true)
      {
        // First volume is already added to DataIO.TotalArcSize 
        // in initial TotalArcSize calculation in DoExtract.
        // So we skip it and start from second volume.
        NextVolumeName(NextName,ASIZE(NextName),!Arc.NewNumbering);
        FindData FD;
        if (FindFile::FastFind(NextName,&FD))
          VolumeSetSize+=FD.Size;
        else
          break;
      }
      DataIO.TotalArcSize+=VolumeSetSize;
    }
  
    ExtractArchiveInit(Arc);
  
    if (*Cmd->Command=='T' || *Cmd->Command=='I')
      Cmd->Test=true;
  
  
    if (*Cmd->Command=='I')
    {
      Cmd->DisablePercentage=true;
    }
    else
      uiStartArchiveExtract(!Cmd->Test,ArcName);
  
    Arc.ViewComment();
  
  
    while (1)
    {
      size_t Size=Arc.ReadHeader();
  
  
      bool Repeat=false;
      if (!ExtractCurrentFile(Arc,Size,Repeat))
        if (Repeat)
        {
          // If we started extraction from not first volume and need to
          // restart it from first, we must correct DataIO.TotalArcSize
          // for correct total progress display. We subtract the size
          // of current volume and all volumes after it and add the size
          // of new (first) volume.
          FindData OldArc,NewArc;
          if (FindFile::FastFind(Arc.FileName,&OldArc) &&
              FindFile::FastFind(ArcName,&NewArc))
            DataIO.TotalArcSize-=VolumeSetSize+OldArc.Size-NewArc.Size;
          return EXTRACT_ARC_REPEAT;
        }
        else
          break;
    }
  
  
  #if !defined(SFX_MODULE) && !defined(RARDLL)
    if (Cmd->Test && Arc.Volume)
      RecVolumesTest(Cmd,&Arc,ArcName);
  #endif
  
    return EXTRACT_ARC_NEXT;
  }
  
  
  bool CmdExtract::ExtractCurrentFile(Archive &Arc,size_t HeaderSize,bool &Repeat)
  {
    // We can get negative sizes in corrupt archive and it is unacceptable
    // for size comparisons in CmdExtract::UnstoreFile and ComprDataIO::UnpRead,
    // where we cast sizes to size_t and can exceed another read or available
    // size. We could fix it when reading an archive. But we prefer to do it
    // here, because this function is called directly in unrar.dll, so we fix
    // bad parameters passed to dll. Also we want to see real negative sizes
    // in the listing of corrupt archive.
    if (Arc.FileHead.PackSize<0)
      Arc.FileHead.PackSize=0;
    if (Arc.FileHead.UnpSize<0)
      Arc.FileHead.UnpSize=0;
  
    wchar Command=Cmd->Command[0];
    if (HeaderSize==0)
      if (DataIO.UnpVolume)
      {
  #ifdef NOVOLUME
        return false;
  #else
        // Supposing we unpack an old RAR volume without end of archive record
        // and last file is not split between volumes.
        if (!MergeArchive(Arc,&DataIO,false,Command))
        {
          ErrHandler.SetErrorCode(RARX_WARNING);
          return false;
        }
  #endif
      }
      else
        return false;
    HEADER_TYPE HeaderType=Arc.GetHeaderType();
    if (HeaderType!=HEAD_FILE)
    {
  #ifndef SFX_MODULE
      if (HeaderType==HEAD3_OLDSERVICE && PrevProcessed)
        SetExtraInfo20(Cmd,Arc,DestFileName);
  #endif
      if (HeaderType==HEAD_SERVICE && PrevProcessed)
        SetExtraInfo(Cmd,Arc,DestFileName);
      if (HeaderType==HEAD_ENDARC)
        if (Arc.EndArcHead.NextVolume)
        {
  #ifndef NOVOLUME
          if (!MergeArchive(Arc,&DataIO,false,Command))
          {
            ErrHandler.SetErrorCode(RARX_WARNING);
            return false;
          }
  #endif
          Arc.Seek(Arc.CurBlockPos,SEEK_SET);
          return true;
        }
        else
          return false;
      Arc.SeekToNext();
      return true;
    }
    PrevProcessed=false;
  
    if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact)
      return false;
  
    int MatchType=MATCH_WILDSUBPATH;
  
    bool EqualNames=false;
    wchar MatchedArg[NM];
    int MatchNumber=Cmd->IsProcessFile(Arc.FileHead,&EqualNames,MatchType,MatchedArg,ASIZE(MatchedArg));
    bool MatchFound=MatchNumber!=0;
  #ifndef SFX_MODULE
    if (Cmd->ExclPath==EXCL_BASEPATH)
    {
      wcsncpyz(Cmd->ArcPath,MatchedArg,ASIZE(Cmd->ArcPath));
      *PointToName(Cmd->ArcPath)=0;
      if (IsWildcard(Cmd->ArcPath)) // Cannot correctly process path*\* masks here.
      *Cmd->ArcPath=0;
      }
  #endif
    if (MatchFound && !EqualNames)
      AllMatchesExact=false;
  
    Arc.ConvertAttributes();
  
  #if !defined(SFX_MODULE) && !defined(RARDLL)
    if (Arc.FileHead.SplitBefore && FirstFile)
    {
      wchar CurVolName[NM];
      wcsncpyz(CurVolName,ArcName,ASIZE(CurVolName));
      VolNameToFirstName(ArcName,ArcName,ASIZE(ArcName),Arc.NewNumbering);
  
      if (wcsicomp(ArcName,CurVolName)!=0 && FileExist(ArcName))
      {
        // If first volume name does not match the current name and if such
        // volume name really exists, let's unpack from this first volume.
        Repeat=true;
        return false;
      }
  #ifndef RARDLL
      if (!ReconstructDone)
      {
        ReconstructDone=true;
        if (RecVolumesRestore(Cmd,Arc.FileName,true))
        {
          Repeat=true;
          return false;
        }
      }
  #endif
      wcsncpyz(ArcName,CurVolName,ASIZE(ArcName));
    }
  #endif
  
    wchar ArcFileName[NM];
    ConvertPath(Arc.FileHead.FileName,ArcFileName);
  
    if (Arc.FileHead.Version)
    {
      if (Cmd->VersionControl!=1 && !EqualNames)
      {
        if (Cmd->VersionControl==0)
          MatchFound=false;
        int Version=ParseVersionFileName(ArcFileName,false);
        if (Cmd->VersionControl-1==Version)
          ParseVersionFileName(ArcFileName,true);
        else
          MatchFound=false;
      }
    }
    else
      if (!Arc.IsArcDir() && Cmd->VersionControl>1)
        MatchFound=false;
  
    DataIO.UnpVolume=Arc.FileHead.SplitAfter;
    DataIO.NextVolumeMissing=false;
  
    Arc.Seek(Arc.NextBlockPos-Arc.FileHead.PackSize,SEEK_SET);
  
    bool ExtrFile=false;
    bool SkipSolid=false;
  
  #ifndef SFX_MODULE
    if (FirstFile && (MatchFound || Arc.Solid) && Arc.FileHead.SplitBefore)
    {
      if (MatchFound)
      {
        uiMsg(UIERROR_NEEDPREVVOL,Arc.FileName,ArcFileName);
  #ifdef RARDLL
        Cmd->DllError=ERAR_BAD_DATA;
  #endif
        ErrHandler.SetErrorCode(RARX_OPEN);
      }
      MatchFound=false;
    }
  
    FirstFile=false;
  #endif
  
    if (MatchFound || (SkipSolid=Arc.Solid)!=0)
    {
      // First common call of uiStartFileExtract. It is done before overwrite
      // prompts, so if SkipSolid state is changed below, we'll need to make
      // additional uiStartFileExtract calls with updated parameters.
      if (!uiStartFileExtract(ArcFileName,!Cmd->Test,Cmd->Test && Command!='I',SkipSolid))
        return false;
  
      ExtrPrepareName(Arc,ArcFileName,DestFileName,ASIZE(DestFileName));
  
      // DestFileName can be set empty in case of excessive -ap switch.
      ExtrFile=!SkipSolid && *DestFileName!=0 && !Arc.FileHead.SplitBefore;
  
      if ((Cmd->FreshFiles || Cmd->UpdateFiles) && (Command=='E' || Command=='X'))
      {
        FindData FD;
        if (FindFile::FastFind(DestFileName,&FD))
        {
          if (FD.mtime >= Arc.FileHead.mtime)
          {
            // If directory already exists and its modification time is newer 
            // than start of extraction, it is likely it was created 
            // when creating a path to one of already extracted items. 
            // In such case we'll better update its time even if archived 
            // directory is older.
  
            if (!FD.IsDir || FD.mtime<StartTime)
              ExtrFile=false;
          }
        }
        else
          if (Cmd->FreshFiles)
            ExtrFile=false;
      }
  
      if (!CheckUnpVer(Arc,ArcFileName))
      {
        ErrHandler.SetErrorCode(RARX_FATAL);
  #ifdef RARDLL
        Cmd->DllError=ERAR_UNKNOWN_FORMAT;
  #endif
        Arc.SeekToNext();
        return !Arc.Solid; // Can try extracting next file only in non-solid archive.
      }
  
      while (true) // Repeat password prompt in case of wrong password here.
      {
      if (Arc.FileHead.Encrypted)
      {
          // Stop archive extracting if user cancelled a password prompt.
  #ifdef RARDLL
        if (!ExtrDllGetPassword())
          {
            Cmd->DllError=ERAR_MISSING_PASSWORD;
          return false;
          }
  #else
          if (!ExtrGetPassword(Arc,ArcFileName))
        {
            PasswordCancelled=true;
            return false;
          }
  #endif
          // Skip only the current encrypted file if empty password is entered.
          // Actually our "cancel" code above intercepts empty passwords too now,
          // so we keep the code below just in case we'll decide process empty
          // and cancelled passwords differently sometimes.
          if (!Cmd->Password.IsSet())
          {
          ErrHandler.SetErrorCode(RARX_WARNING);
          Cmd->DllError=ERAR_MISSING_PASSWORD;
          ExtrFile=false;
        }
      }
  
  
        // Set a password before creating the file, so we can skip creating
        // in case of wrong password.
        SecPassword FilePassword=Cmd->Password;
  #if defined(_WIN_ALL) && !defined(SFX_MODULE)
        ConvertDosPassword(Arc,FilePassword);
  #endif
  
        byte PswCheck[SIZE_PSWCHECK];
        DataIO.SetEncryption(false,Arc.FileHead.CryptMethod,&FilePassword,
               Arc.FileHead.SaltSet ? Arc.FileHead.Salt:NULL,
               Arc.FileHead.InitV,Arc.FileHead.Lg2Count,
               Arc.FileHead.HashKey,PswCheck);
  
        // If header is damaged, we cannot rely on password check value,
        // because it can be damaged too.
        if (Arc.FileHead.Encrypted && Arc.FileHead.UsePswCheck &&
            memcmp(Arc.FileHead.PswCheck,PswCheck,SIZE_PSWCHECK)!=0 &&
            !Arc.BrokenHeader)
      {
          uiMsg(UIWAIT_BADPSW,ArcFileName);
  
          if (!PasswordAll) // If entered manually and not through -p<pwd>.
          {
            Cmd->Password.Clean();
            continue; // Request a password again.
      }
  #ifdef RARDLL
          // If we already have ERAR_EOPEN as result of missing volume,
          // we should not replace it with less precise ERAR_BAD_PASSWORD.
          if (Cmd->DllError!=ERAR_EOPEN)
            Cmd->DllError=ERAR_BAD_PASSWORD;
  #endif
          ErrHandler.SetErrorCode(RARX_BADPWD);
          ExtrFile=false;
        }
        break;
      }
  
  #ifdef RARDLL
      if (*Cmd->DllDestName!=0)
        wcsncpyz(DestFileName,Cmd->DllDestName,ASIZE(DestFileName));
  #endif
  
      File CurFile;
  
      bool LinkEntry=Arc.FileHead.RedirType!=FSREDIR_NONE;
      if (LinkEntry && Arc.FileHead.RedirType!=FSREDIR_FILECOPY)
      {
        if (ExtrFile && Command!='P' && !Cmd->Test)
        {
          // Overwrite prompt for symbolic and hard links.
          bool UserReject=false;
          if (FileExist(DestFileName) && !UserReject)
            FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime);
          if (UserReject)
            ExtrFile=false;
        }
      }
      else
        if (Arc.IsArcDir())
        {
          if (!ExtrFile || Command=='P' || Command=='I' || Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH)
            return true;
          TotalFileCount++;
          ExtrCreateDir(Arc,ArcFileName);
          // It is important to not increment MatchedArgs here, so we extract
          // dir with its entire contents and not dir record only even if
          // dir record precedes files.
          return true;
        }
        else
          if (ExtrFile) // Create files and file copies (FSREDIR_FILECOPY).
            ExtrFile=ExtrCreateFile(Arc,CurFile);
  
      if (!ExtrFile && Arc.Solid)
      {
        SkipSolid=true;
        ExtrFile=true;
  
        // We changed SkipSolid, so we need to call uiStartFileExtract
        // with "Skip" parameter to change the operation status 
        // from "extracting" to "skipping". For example, it can be necessary
        // if user answered "No" to overwrite prompt when unpacking
        // a solid archive.
        if (!uiStartFileExtract(ArcFileName,false,false,true))
          return false;
      }
      if (ExtrFile)
      {
        // Set it in test mode, so we also test subheaders such as NTFS streams
        // after tested file.
        if (Cmd->Test)
          PrevProcessed=true;
  
        bool TestMode=Cmd->Test || SkipSolid; // Unpack to memory, not to disk.
  
        if (!SkipSolid)
        {
          if (!TestMode && Command!='P' && CurFile.IsDevice())
          {
            uiMsg(UIERROR_INVALIDNAME,Arc.FileName,DestFileName);
            ErrHandler.WriteError(Arc.FileName,DestFileName);
          }
          TotalFileCount++;
        }
        FileCount++;
        if (Command!='I')
          if (SkipSolid)
            mprintf(St(MExtrSkipFile),ArcFileName);
          else
            switch(Cmd->Test ? 'T':Command) // "Test" can be also enabled by -t switch.
            {
              case 'T':
                mprintf(St(MExtrTestFile),ArcFileName);
                break;
  #ifndef SFX_MODULE
              case 'P':
                mprintf(St(MExtrPrinting),ArcFileName);
                break;
  #endif
              case 'X':
              case 'E':
                mprintf(St(MExtrFile),DestFileName);
                break;
            }
        if (!Cmd->DisablePercentage)
          mprintf(L"     ");
  
        DataIO.CurUnpRead=0;
        DataIO.CurUnpWrite=0;
        DataIO.UnpHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads);
        DataIO.PackedDataHash.Init(Arc.FileHead.FileHash.Type,Cmd->Threads);
        DataIO.SetPackedSizeToRead(Arc.FileHead.PackSize);
        DataIO.SetFiles(&Arc,&CurFile);
        DataIO.SetTestMode(TestMode);
        DataIO.SetSkipUnpCRC(SkipSolid);
  
  #if defined(_WIN_ALL) && !defined(SFX_MODULE) && !defined(SILENT)
        if (!TestMode && !Arc.BrokenHeader &&
            Arc.FileHead.UnpSize>0xffffffff && (Fat32 || !NotFat32))
        {
          if (!Fat32) // Not detected yet.
            NotFat32=!(Fat32=IsFAT(Cmd->ExtrPath));
          if (Fat32)
            uiMsg(UIMSG_FAT32SIZE); // Inform user about FAT32 size limit.
        }
  #endif
  
        if (!TestMode && !Arc.BrokenHeader &&
            (Arc.FileHead.PackSize<<11)>Arc.FileHead.UnpSize &&
            (Arc.FileHead.UnpSize<100000000 || Arc.FileLength()>Arc.FileHead.PackSize))
          CurFile.Prealloc(Arc.FileHead.UnpSize);
  
        CurFile.SetAllowDelete(!Cmd->KeepBroken);
  
        bool FileCreateMode=!TestMode && !SkipSolid && Command!='P';
        bool ShowChecksum=true; // Display checksum verification result.
  
        bool LinkSuccess=true; // Assume success for test mode.
        if (LinkEntry)
        {
          FILE_SYSTEM_REDIRECT Type=Arc.FileHead.RedirType;
  
          if (Type==FSREDIR_HARDLINK || Type==FSREDIR_FILECOPY)
          {
            wchar NameExisting[NM];
            ExtrPrepareName(Arc,Arc.FileHead.RedirName,NameExisting,ASIZE(NameExisting));
            if (FileCreateMode && *NameExisting!=0) // *NameExisting can be 0 in case of excessive -ap switch.
              if (Type==FSREDIR_HARDLINK)
                LinkSuccess=ExtractHardlink(DestFileName,NameExisting,ASIZE(NameExisting));
              else
                LinkSuccess=ExtractFileCopy(CurFile,Arc.FileName,DestFileName,NameExisting,ASIZE(NameExisting));
          }
          else
            if (Type==FSREDIR_UNIXSYMLINK || Type==FSREDIR_WINSYMLINK || Type==FSREDIR_JUNCTION)
            {
              if (FileCreateMode)
                LinkSuccess=ExtractSymlink(Cmd,DataIO,Arc,DestFileName);
            }
            else
            {
              uiMsg(UIERROR_UNKNOWNEXTRA, Arc.FileName, DestFileName);
              LinkSuccess=false;
            }
            
            if (!LinkSuccess || Arc.Format==RARFMT15 && !FileCreateMode)
            {
              // RAR 5.x links have a valid data checksum even in case of
              // failure, because they do not store any data.
              // We do not want to display "OK" in this case.
              // For 4.x symlinks we verify the checksum only when extracting,
              // but not when testing an archive.
              ShowChecksum=false;
            }
            PrevProcessed=FileCreateMode && LinkSuccess;
        }
        else
          if (!Arc.FileHead.SplitBefore)
            if (Arc.FileHead.Method==0)
              UnstoreFile(DataIO,Arc.FileHead.UnpSize);
            else
            {
              Unp->Init(Arc.FileHead.WinSize,Arc.FileHead.Solid);
              Unp->SetDestSize(Arc.FileHead.UnpSize);
  #ifndef SFX_MODULE
              if (Arc.Format!=RARFMT50 && Arc.FileHead.UnpVer<=15)
                Unp->DoUnpack(15,FileCount>1 && Arc.Solid);
              else
  #endif
                Unp->DoUnpack(Arc.FileHead.UnpVer,Arc.FileHead.Solid);
            }
  
        Arc.SeekToNext();
  
        // We check for "split after" flag to detect partially extracted files
        // from incomplete volume sets. For them file header contains packed
        // data hash, which must not be compared against unpacked data hash
        // to prevent accidental match. Moreover, for -m0 volumes packed data
        // hash would match truncated unpacked data hash and lead to fake "OK"
        // in incomplete volume set.
        bool ValidCRC=!Arc.FileHead.SplitAfter && DataIO.UnpHash.Cmp(&Arc.FileHead.FileHash,Arc.FileHead.UseHashKey ? Arc.FileHead.HashKey:NULL);
  
        // We set AnySolidDataUnpackedWell to true if we found at least one
        // valid non-zero solid file in preceding solid stream. If it is true
        // and if current encrypted file is broken, we do not need to hint
        // about a wrong password and can report CRC error only.
        if (!Arc.FileHead.Solid)
          AnySolidDataUnpackedWell=false; // Reset the flag, because non-solid file is found.
        else
          if (Arc.FileHead.Method!=0 && Arc.FileHead.UnpSize>0 && ValidCRC)
            AnySolidDataUnpackedWell=true;
   
        bool BrokenFile=false;
        
        // Checksum is not calculated in skip solid mode for performance reason.
        if (!SkipSolid && ShowChecksum)
        {
          if (ValidCRC)
          {
            if (Command!='P' && Command!='I')
              mprintf(L"%s%s ",Cmd->DisablePercentage ? L" ":L"\b\b\b\b\b ",
                Arc.FileHead.FileHash.Type==HASH_NONE ? L"  ?":St(MOk));
          }
          else
          {
              if (Arc.FileHead.Encrypted && (!Arc.FileHead.UsePswCheck || 
                  Arc.BrokenHeader) && !AnySolidDataUnpackedWell)
                uiMsg(UIERROR_CHECKSUMENC,Arc.FileName,ArcFileName);
              else
                uiMsg(UIERROR_CHECKSUM,Arc.FileName,ArcFileName);
            BrokenFile=true;
            ErrHandler.SetErrorCode(RARX_CRC);
  #ifdef RARDLL
            // If we already have ERAR_EOPEN as result of missing volume
            // or ERAR_BAD_PASSWORD for RAR5 wrong password,
            // we should not replace it with less precise ERAR_BAD_DATA.
            if (Cmd->DllError!=ERAR_EOPEN && Cmd->DllError!=ERAR_BAD_PASSWORD)
              Cmd->DllError=ERAR_BAD_DATA;
  #endif
          }
        }
        else
          mprintf(L"\b\b\b\b\b     ");
  
        if (!TestMode && (Command=='X' || Command=='E') &&
            (!LinkEntry || Arc.FileHead.RedirType==FSREDIR_FILECOPY && LinkSuccess) && 
            (!BrokenFile || Cmd->KeepBroken))
        {
          // We could preallocate more space that really written to broken file.
          if (BrokenFile)
            CurFile.Truncate();
  
  #if defined(_WIN_ALL) || defined(_EMX)
          if (Cmd->ClearArc)
            Arc.FileHead.FileAttr&=~FILE_ATTRIBUTE_ARCHIVE;
  #endif
  
  
          CurFile.SetOpenFileTime(
            Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime,
            Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime,
            Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime);
          CurFile.Close();
  #if defined(_WIN_ALL) && !defined(SFX_MODULE)
          if (Cmd->SetCompressedAttr &&
              (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0)
            SetFileCompression(CurFile.FileName,true);
  #endif
          SetFileHeaderExtra(Cmd,Arc,CurFile.FileName);
  
          CurFile.SetCloseFileTime(
            Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime,
            Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime);
          if (!Cmd->IgnoreGeneralAttr && !SetFileAttr(CurFile.FileName,Arc.FileHead.FileAttr))
            uiMsg(UIERROR_FILEATTR,Arc.FileName,CurFile.FileName);
  
          PrevProcessed=true;
        }
      }
    }
    // It is important to increment it for files, but not dirs. So we extract
    // dir with its entire contents, not just dir record only even if dir
    // record precedes files.
    if (MatchFound)
      MatchedArgs++;
    if (DataIO.NextVolumeMissing)
      return false;
    if (!ExtrFile)
      if (!Arc.Solid)
        Arc.SeekToNext();
      else
        if (!SkipSolid)
          return false;
    return true;
  }
  
  
  void CmdExtract::UnstoreFile(ComprDataIO &DataIO,int64 DestUnpSize)
  {
    // 512 KB and larger buffer reported to reduce performance on old XP
    // computers with WDC WD2000JD HDD. According to test made by user
    // 256 KB buffer is optimal.
    Array<byte> Buffer(0x40000);
    while (1)
    {
      uint Code=DataIO.UnpRead(&Buffer[0],Buffer.Size());
      if (Code==0 || (int)Code==-1)
        break;
  	/* basically Code = MIN(Code, (uint) DestUnpSize); */
      Code=Code<DestUnpSize ? Code:(uint)DestUnpSize;
      DataIO.UnpWrite(&Buffer[0],Code);
      if (DestUnpSize>=0)
        DestUnpSize-=Code;
    }
  }
  
  
  bool CmdExtract::ExtractFileCopy(File &New,wchar *ArcName,wchar *NameNew,wchar *NameExisting,size_t NameExistingSize)
  {
    SlashToNative(NameExisting,NameExisting,NameExistingSize); // Not needed for RAR 5.1+ archives.
  
    File Existing;
    if (!Existing.WOpen(NameExisting))
    {
      uiMsg(UIERROR_FILECOPY,ArcName,NameExisting,NameNew);
      uiMsg(UIERROR_FILECOPYHINT,ArcName);
  #ifdef RARDLL
      Cmd->DllError=ERAR_EREFERENCE;
  #endif
      return false;
    }
  
    Array<char> Buffer(0x100000);
    int64 CopySize=0;
  
    while (true)
    {
      Wait();
      int ReadSize=Existing.Read(&Buffer[0],Buffer.Size());
      if (ReadSize==0)
        break;
      New.Write(&Buffer[0],ReadSize);
      CopySize+=ReadSize;
    }
  
    return true;
  }
  
  
  void CmdExtract::ExtrPrepareName(Archive &Arc,const wchar *ArcFileName,wchar *DestName,size_t DestSize)
  {
    wcsncpyz(DestName,Cmd->ExtrPath,DestSize);
  
    if (*Cmd->ExtrPath!=0)
    {
       wchar LastChar=*PointToLastChar(Cmd->ExtrPath);
    // We need IsPathDiv check here to correctly handle Unix forward slash
    // in the end of destination path in Windows: rar x arc dest/
      // IsDriveDiv is needed for current drive dir: rar x arc d:
      if (!IsPathDiv(LastChar) && !IsDriveDiv(LastChar))
    {
      // Destination path can be without trailing slash if it come from GUI shell.
      AddEndSlash(DestName,DestSize);
    }
    }
  
  #ifndef SFX_MODULE
    if (Cmd->AppendArcNameToPath)
    {
      wcsncatz(DestName,PointToName(Arc.FirstVolumeName),DestSize);
      SetExt(DestName,NULL,DestSize);
      AddEndSlash(DestName,DestSize);
    }
  #endif
  
  #ifndef SFX_MODULE
    size_t ArcPathLength=wcslen(Cmd->ArcPath);
    if (ArcPathLength>0)
    {
      size_t NameLength=wcslen(ArcFileName);
  
      // Earlier we compared lengths only here, but then noticed a cosmetic bug
      // in WinRAR. When extracting a file reference from subfolder with
      // "Extract relative paths", so WinRAR sets ArcPath, if reference target
      // is missing, error message removed ArcPath both from reference and target
      // names. If target was stored in another folder, its name looked wrong.
      if (NameLength>=ArcPathLength && 
          wcsnicompc(Cmd->ArcPath,ArcFileName,ArcPathLength)==0 &&
          (IsPathDiv(Cmd->ArcPath[ArcPathLength-1]) || 
           IsPathDiv(ArcFileName[ArcPathLength]) || ArcFileName[ArcPathLength]==0))
      {
      ArcFileName+=Min(ArcPathLength,NameLength);
        while (IsPathDiv(*ArcFileName))
        ArcFileName++;
      if (*ArcFileName==0) // Excessive -ap switch.
      {
        *DestName=0;
        return;
      }
    }
    }
  #endif
  
    wchar Command=Cmd->Command[0];
    // Use -ep3 only in systems, where disk letters are exist, not in Unix.
    bool AbsPaths=Cmd->ExclPath==EXCL_ABSPATH && Command=='X' && IsDriveDiv(':');
  
    // We do not use any user specified destination paths when extracting
    // absolute paths in -ep3 mode.
    if (AbsPaths)
      *DestName=0;
  
    if (Command=='E' || Cmd->ExclPath==EXCL_SKIPWHOLEPATH)
      wcsncatz(DestName,PointToName(ArcFileName),DestSize);
    else
      wcsncatz(DestName,ArcFileName,DestSize);
  
  #ifdef _WIN_ALL
    // Must do after Cmd->ArcPath processing above, so file name and arc path
    // trailing spaces are in sync.
    if (!Cmd->AllowIncompatNames)
      MakeNameCompatible(DestName);
  #endif
  
    wchar DiskLetter=toupperw(DestName[0]);
  
    if (AbsPaths)
    {
      if (DestName[1]=='_' && IsPathDiv(DestName[2]) &&
          DiskLetter>='A' && DiskLetter<='Z')
        DestName[1]=':';
      else
        if (DestName[0]=='_' && DestName[1]=='_')
        {
          // Convert __server\share to \\server\share.
          DestName[0]=CPATHDIVIDER;
          DestName[1]=CPATHDIVIDER;
        }
    }
  }
  
  
  #ifdef RARDLL
  bool CmdExtract::ExtrDllGetPassword()
  {
    if (!Cmd->Password.IsSet())
    {
      if (Cmd->Callback!=NULL)
      {
        wchar PasswordW[MAXPASSWORD];
        *PasswordW=0;
        if (Cmd->Callback(UCM_NEEDPASSWORDW,Cmd->UserData,(LPARAM)PasswordW,ASIZE(PasswordW))==-1)
          *PasswordW=0;
        if (*PasswordW==0)
        {
          char PasswordA[MAXPASSWORD];
          *PasswordA=0;
          if (Cmd->Callback(UCM_NEEDPASSWORD,Cmd->UserData,(LPARAM)PasswordA,ASIZE(PasswordA))==-1)
            *PasswordA=0;
          GetWideName(PasswordA,NULL,PasswordW,ASIZE(PasswordW));
          cleandata(PasswordA,sizeof(PasswordA));
        }
        Cmd->Password.Set(PasswordW);
        cleandata(PasswordW,sizeof(PasswordW));
        Cmd->ManualPassword=true;
      }
      if (!Cmd->Password.IsSet())
        return false;
    }
    return true;
  }
  #endif
  
  
  #ifndef RARDLL
  bool CmdExtract::ExtrGetPassword(Archive &Arc,const wchar *ArcFileName)
  {
    if (!Cmd->Password.IsSet())
    {
      if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password) || !Cmd->Password.IsSet())
      {
        // Suppress "test is ok" message in GUI if user entered
        // an empty password or cancelled a password prompt.
        uiMsg(UIERROR_INCERRCOUNT);
  
        return false;
      }
      Cmd->ManualPassword=true;
    }
  #if !defined(SILENT)
    else
      if (!PasswordAll && !Arc.FileHead.Solid)
      {
        eprintf(St(MUseCurPsw),ArcFileName);
        switch(Cmd->AllYes ? 1 : Ask(St(MYesNoAll)))
        {
          case -1:
            ErrHandler.Exit(RARX_USERBREAK);
          case 2:
            if (!uiGetPassword(UIPASSWORD_FILE,ArcFileName,&Cmd->Password))
              return false;
            break;
          case 3:
            PasswordAll=true;
            break;
        }
      }
  #endif
    return true;
  }
  #endif
  
  
  #if defined(_WIN_ALL) && !defined(SFX_MODULE)
  void CmdExtract::ConvertDosPassword(Archive &Arc,SecPassword &DestPwd)
  {
    if (Arc.Format==RARFMT15 && Arc.FileHead.HostOS==HOST_MSDOS)
    {
      // We need the password in OEM encoding if file was encrypted by
      // native RAR/DOS (not extender based). Let's make the conversion.
      wchar PlainPsw[MAXPASSWORD];
      Cmd->Password.Get(PlainPsw,ASIZE(PlainPsw));
      char PswA[MAXPASSWORD];
      CharToOemBuffW(PlainPsw,PswA,ASIZE(PswA));
      PswA[ASIZE(PswA)-1]=0;
      CharToWide(PswA,PlainPsw,ASIZE(PlainPsw));
      DestPwd.Set(PlainPsw);
      cleandata(PlainPsw,sizeof(PlainPsw));
      cleandata(PswA,sizeof(PswA));
    }
  }
  #endif
  
  
  void CmdExtract::ExtrCreateDir(Archive &Arc,const wchar *ArcFileName)
  {
    if (Cmd->Test)
    {
      mprintf(St(MExtrTestFile),ArcFileName);
      mprintf(L" %s",St(MOk));
      return;
    }
  
    MKDIR_CODE MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr);
    bool DirExist=false;
    if (MDCode!=MKDIR_SUCCESS)
    {
      DirExist=FileExist(DestFileName);
      if (DirExist && !IsDir(GetFileAttr(DestFileName)))
      {
        // File with name same as this directory exists. Propose user
        // to overwrite it.
        bool UserReject;
        FileCreate(Cmd,NULL,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime);
        DirExist=false;
      }
      if (!DirExist)
      {
        CreatePath(DestFileName,true);
        MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr);
        if (MDCode!=MKDIR_SUCCESS)
        {
          wchar OrigName[ASIZE(DestFileName)];
          wcsncpyz(OrigName,DestFileName,ASIZE(OrigName));
          MakeNameUsable(DestFileName,true);
          CreatePath(DestFileName,true);
          MDCode=MakeDir(DestFileName,!Cmd->IgnoreGeneralAttr,Arc.FileHead.FileAttr);
  #ifndef SFX_MODULE
          if (MDCode==MKDIR_SUCCESS)
            uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName);
  #endif
        }
      }
    }
    if (MDCode==MKDIR_SUCCESS)
    {
      mprintf(St(MCreatDir),DestFileName);
      mprintf(L" %s",St(MOk));
      PrevProcessed=true;
    }
    else
      if (DirExist)
      {
        if (!Cmd->IgnoreGeneralAttr)
          SetFileAttr(DestFileName,Arc.FileHead.FileAttr);
        PrevProcessed=true;
      }
      else
      {
        uiMsg(UIERROR_DIRCREATE,Arc.FileName,DestFileName);
        ErrHandler.SysErrMsg();
  #ifdef RARDLL
        Cmd->DllError=ERAR_ECREATE;
  #endif
        ErrHandler.SetErrorCode(RARX_CREATE);
      }
    if (PrevProcessed)
    {
  #if defined(_WIN_ALL) && !defined(SFX_MODULE)
      if (Cmd->SetCompressedAttr &&
          (Arc.FileHead.FileAttr & FILE_ATTRIBUTE_COMPRESSED)!=0 && WinNT())
        SetFileCompression(DestFileName,true);
  #endif
      SetFileHeaderExtra(Cmd,Arc,DestFileName);
      SetDirTime(DestFileName,
        Cmd->xmtime==EXTTIME_NONE ? NULL:&Arc.FileHead.mtime,
        Cmd->xctime==EXTTIME_NONE ? NULL:&Arc.FileHead.ctime,
        Cmd->xatime==EXTTIME_NONE ? NULL:&Arc.FileHead.atime);
    }
  }
  
  
  bool CmdExtract::ExtrCreateFile(Archive &Arc,File &CurFile)
  {
    bool Success=true;
    wchar Command=Cmd->Command[0];
  #if !defined(SFX_MODULE)
    if (Command=='P')
      CurFile.SetHandleType(FILE_HANDLESTD);
  #endif
    if ((Command=='E' || Command=='X') && !Cmd->Test)
    {
      bool UserReject;
      // Specify "write only" mode to avoid OpenIndiana NAS problems
      // with SetFileTime and read+write files.
      if (!FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true))
      {
        Success=false;
        if (!UserReject)
        {
          ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName);
  #ifdef RARDLL
          Cmd->DllError=ERAR_ECREATE;
  #endif
          if (!IsNameUsable(DestFileName))
          {
            uiMsg(UIMSG_CORRECTINGNAME,Arc.FileName);
  
            wchar OrigName[ASIZE(DestFileName)];
            wcsncpyz(OrigName,DestFileName,ASIZE(OrigName));
  
            MakeNameUsable(DestFileName,true);
  
            CreatePath(DestFileName,true);
            if (FileCreate(Cmd,&CurFile,DestFileName,ASIZE(DestFileName),&UserReject,Arc.FileHead.UnpSize,&Arc.FileHead.mtime,true))
            {
  #ifndef SFX_MODULE
              uiMsg(UIERROR_RENAMING,Arc.FileName,OrigName,DestFileName);
  #endif
              Success=true;
            }
            else
              ErrHandler.CreateErrorMsg(Arc.FileName,DestFileName);
          }
        }
      }
    }
    return Success;
  }
  
  
  bool CmdExtract::CheckUnpVer(Archive &Arc,const wchar *ArcFileName)
  {
    bool WrongVer;
    if (Arc.Format==RARFMT50) // Both SFX and RAR can unpack RAR 5.0 archives.
      WrongVer=Arc.FileHead.UnpVer>VER_UNPACK5;
    else
    {
  #ifdef SFX_MODULE   // SFX can unpack only RAR 2.9 archives.
      WrongVer=Arc.FileHead.UnpVer!=VER_UNPACK;
  #else               // All formats since 1.3 for RAR.
      WrongVer=Arc.FileHead.UnpVer<13 || Arc.FileHead.UnpVer>VER_UNPACK;
  #endif
    }
  
    // We can unpack stored files regardless of compression version field.
    if (Arc.FileHead.Method==0)
      WrongVer=false;
  
    if (WrongVer)
    {
      ErrHandler.UnknownMethodMsg(Arc.FileName,ArcFileName);
      uiMsg(UIERROR_NEWERRAR,Arc.FileName);
    }
    return !WrongVer;
  }
  