// Main.cpp

#include <time.h>
#include <unistd.h>

#include "StdAfx.h"
#include "../../C/CpuArch.h"
#include "../Common/MyWindows.h"
#include "../Common/MyInitGuid.h"
#include "../Common/CommandLineParser.h"
#include "../Common/IntToString.h"
#include "../Common/MyException.h"
#include "../Common/StdInStream.h"
#include "../Common/StdOutStream.h"
#include "../Common/StringConvert.h"
#include "../Common/StringToInt.h"
#include "../Common/UTFConvert.h"

#include "ErrorMsg.h"
#include "TimeUtils.h"
#include "FileDir.h"

#include "../7zip/Common/RegisterCodec.h"
#include "../7zip/UI/Common/ArchiveCommandLine.h"
#include "../7zip/UI/Common/ExitCode.h"
#include "../7zip/UI/Common/Extract.h"
#include "../7zip/UI/Console/ConsoleClose.h"
#include "../7zip/UI/Console/ExtractCallbackConsole.h"
#include "../7zip/UI/Console/List.h"
#include "../7zip/UI/Console/OpenCallbackConsole.h"
#include "../7zip/UI/Console/UpdateCallbackConsole.h"

#ifndef Z7_NO_HASHCON
#include "../7zip/UI/Console/HashCon.h"
#endif

#ifdef Z7_PROG_VARIANT_R
#include "../../C/7zVersion.h"
#else
#include "../7zip/MyVersion.h"
#endif

#include "version.h"

using namespace NDOS;
using namespace NSystem;
using namespace NFile;
using namespace NCommandLineParser;

extern CStdOutStream *g_StdStream;
extern CStdOutStream *g_ErrStream;

extern unsigned g_NumCodecs;
extern const CCodecInfo *g_Codecs[];

#ifndef Z7_NO_HASHCON
extern unsigned g_NumHashers;
extern const CHasherInfo *g_Hashers[];
#endif

DECLARE_AND_SET_CLIENT_VERSION_VAR

#if defined(Z7_PROG_VARIANT_A)
  #define PROG_NAME "7za"
  #define PROG_BLURB "aggregated for DOS"
#elif defined(Z7_PROG_VARIANT_M)
  #define PROG_NAME "7zm"
  #define PROG_BLURB "mini for DOS"
#elif defined(Z7_PROG_VARIANT_R)
  #define PROG_NAME "7zr"
  #define PROG_BLURB "reduced for DOS"
#else
  #error "The wrong Z7_PROG_VARIANT_ is defined for DOS."
#endif

static const char * const kBanner =
  "7-Zip " MY_VERSION_NUMBERS " " PROG_BLURB
  " : " COMMIT_HASH_STRING " @ " MY_DATE;

static const char * const kVersion =
  #if defined(__WATCOMC__)
  "Built " __DATE__ " " __TIME__ " using Open Watcom v2.\n"
  "\n"
  #endif
  PROG_NAME " is " MY_COPYRIGHT_CR ".\n"
  "\n"
  "  https://7-Zip.org/\n"
  "\n"
  "Retro7zip is a backport of 7-Zip for DOS and Win32c.\n"
  "\n"
  "  https://github.com/dajhorn/retro7zip/\n"
  "\n"
  "This software is released into the public domain.\n"
  "SPDX-License-Identifier: CC0-1.0\n"
  "\n"
  "  https://creativecommons.org/publicdomain/zero/1.0/\n"
  #if defined(USE_COPYRIGHT_CR)
  "\n"
  "This build contains non-free and/or licensed components.\n"
  #endif
  ;

static const char * const kShortHelp =
  "Usage:\n"
  "  " PROG_NAME " <command> [switches...] <archive> [file...] [@file...]\n"
  "  " PROG_NAME " {--help,--version}  Print longer usage or copyright information\n"
  "\n"
  "Commands:\n"
  "  a  Add files to archive; the filename suffix implies the archive type\n"
  "  x  Extract files from archive\n"
  "  t  Test files in archive\n"
  "\n"
  "Switches:\n"
  "  -mx<N>         Compression presets, -mx1 is faster .. -mx9 is better\n"
  "  -md<N>{b,k,m}  Dictionary size of N bytes, kilobytes, or megabytes\n"
  "  -o<path>       Output directory\n"
  "\n"
  "Examples:\n"
  "  " PROG_NAME " a -md4m My.7z DOCS\\  Creates an LZMA2 archive with a 4MB dictionary\n"
  "  " PROG_NAME " a -mx9 My.zip *.txt  Creates a ZIP archive with maximal compression\n"
  "  " PROG_NAME " x -dA:\\ My.tgz       Extracts a tarball to the first floppy drive\n"
  ;

static const char * const kLongHelp =
  "Usage:\n"
  "\n"
  "  " PROG_NAME " <command> [switches...] <archive> [file...] [@file...]\n"
  "\n"
  "Commands:\n"
  "\n"
  "  a   Add files to archive\n"
  "  d   Delete files from archive\n"
  "  e   Extract files from archive while discarding pathnames\n"
  #ifndef Z7_NO_HASHCON
  "  h   Calculate hash values for files\n"
  #endif
  "  i   Show information about supported containers and codecs\n"
  "  l   List contents of archive\n"
  "  rn  Rename files in archive\n"
  "  t   Test integrity of archive\n"
  "  u   Update files in archive\n"
  "  x   eXtract files from archive while preserving pathnames\n"
  "\n"
  "Archive Update Switches (for the u command):\n"
  "\n"
  "  -up<N>  File exists in archive but is not matched by update pattern\n"
  "  -uq<N>  File exists only in archive\n"
  "  -ur<N>  File exists only in update\n"
  "  -uw<N>  File in archive has different size than file in update\n"
  "  -ux<N>  File in archive is newer than file in update\n"
  "  -uy<N>  File in archive is older than file in update\n"
  "      0   Ignore file in update\n"
  "      1   Keep file already in archive\n"
  "      2   Add file in update\n"
  "      3   Mark file in archive as deleted (7z only)\n"
  "\n"
  "Archive Selection Switches (for the e, l, t, or x command with the -an switch):\n"
  "\n"
  "  -an         Specify command inputs with -ai instead of <archive>\n"
  "  -ai!<glob>  Include archives matching pattern   (eg: -ai!*.7z        )\n"
  "  -ai@<file>  Read -ai! patterns from listfile    (eg: -ai@include.txt )\n"
  "  -ax!<glob>  Exclude archives matching pattern   (eg: -ax!*.zip       )\n"
  "  -ax@<file>  Read -ax! patterns from listfile    (eg: -ax@exclude.txt )\n"
  "\n"
  "File Selection Switches:\n"
  "\n"
  "  -i!<glob>  Include files matching pattern   (eg: -i!*.bin       )\n"
  "  -i@<file>  Read -i! patterns from listfile  (eg: -i@include.txt )\n"
  "  -x!<glob>  Exclude files matching pattern   (eg: -x!tmp???.*    )\n"
  "  -x@<file>  REad -x! patterns from listfile  (eg: -x@exclude.txt )\n"
  "\n"
  "Conflict Handling (for the e and x commands):\n"
  "\n"
  "  -aoa  Overwrite existing files\n"
  "  -aos  Skip extracted files\n"
  "  -aot  Rename extracted files\n"
  "  -aou  Rename existing files\n"
  "\n"
  "Informational Switches:\n"
  "\n"
  "  -bb{0,1,2,3}  Verbosity level 0=silent 1=normal 2=extra 3=debug\n"
  "  -bd           Disable progress indicator\n"
  "  -bso{0,1,2}   Send info messages to 0=null 1=stdout 2=stderr\n"
  "  -bse{0,1,2}   Send error messages to 0=null 1=stdout 2=stderr\n"
  "  -bsp{0,1,2}   Send progress messages to 0=null 1=stdout 2=stderr\n"
  "\n"
  "Main Switches:\n"
  "\n"
  "  --           Stop processing switches; all subsequent arguments are files\n"
  "  --help       Print this message\n"
  "  --version    Print copyright information\n"
  "  -m<options>  Codec parameters\n"
  "  -mx[N]       Compression presets 0=store 1=faster .. 9=better\n"
  "  -o<path>     Extract files to this output directory\n"
  #ifndef Z7_NO_CRYPTO
  "  -p[key]      Encrypt or decrypt using password\n"
  #endif
  "  -r           Enable recursive file search\n"
  "  -r0          Enable resursive file search only for patterns\n"
  "  -r-          Disable recursive file search\n"
  "  -saa         Always append the type suffix to the archive name\n"
  "  -sae         Force the specified archive name\n"
  "  -sas         Append the type suffix to the archive name if missing\n"
  #ifndef Z7_NO_HASHCON
  "  -scrc<name>  Use this hash function in the x, e, or h commands\n"
  #endif
  "  -sdel        Delete input files after archive output\n"
  "  -slt         Show technical information in archive lists\n"
  "  -spe         Eliminate duplication of root folder for extract command\n"
  "  -spf         Use absolute pathnames including the drive letter\n"
  "  -spf2        Use absolute pathnames excluding the drive letter\n"
  "  -sse         Abort on any input file error\n"
  "  -stl         Set the archive timestamp from the most recently modified file\n"
  "  -stx<type>   Disable the handler for this container type\n"
  "  -t<type>     Create an archive of this container type\n"
  "  -v<N>[b,k,m] Create archive volumes of N-size bytes, kilobytes, or megabytes\n"
  "  -w           Put temporary files in %TEMP%\n"
  "  -w<path>     Override %TEMP% using this directory\n"
  "  -y           Assume yes for all prompts; force overwrites and deletions\n"
  "\n"
  "These commands and switches are unimplemented for DOS:\n"
  "\n"
  "  b       Benchmark command\n"
  #ifdef Z7_NO_HASHCON
  "  h       Calculate hash values for files\n"
  #endif
  "  -bt     Show runtime statistics\n"
  "  -mt     Enable multithreading\n"
  #ifdef Z7_NO_CRYPTO
  "  -p[key] Encrypt or decrypt using password\n"
  #endif
  "  -scc    Use this character set in the console\n"
  #ifdef Z7_NO_HASHCON
  "  -scrc   Use this hash function in the x, e, or h commands\n"
  #endif
  "  -scs    Use this character set in file listings\n"
  "  -seml   Send archive by email\n"
  "  -sfx    Create an SFX archive\n"
  "  -si     Read data from stdin\n"
  "  -slp    Enable large memory pages\n"
  "  -snh    Store hard links as metadata\n"
  "  -snl    Store soft links as \metadata\n"
  "  -sni    Store access control lists\n"
  "  -sns    Store alternate file streams\n"
  "  -so     Write data to stdout\n"
  "  -spd    Disable globbing and pattern matching\n"
  "  -ssc    Enable case sensitivity\n"
  "  -ssp    Preserve the last access times of input files\n"
  "  -ssw    Open shared files\n"
  "  -stm    Set the CPU thread affinity mask\n"
  ;


// ---------------------------
// exception messages

static const char * const kEverythingIsOk = "Everything is Ok";
static const char * const kUserErrorMessage = "Incorrect command line";
static const char * const kNoFormats = "7-Zip cannot find the code that works with archives.";
static const char * const kUnsupportedArcTypeMessage = "Unsupported archive type";
// static const char * const kUnsupportedUpdateArcType = "Can't create archive for that type";

#ifndef Z7_EXTRACT_ONLY
#define kDefaultSfxModule "7zCon.sfx"
#endif

Z7_ATTR_NORETURN
static void ShowMessageAndThrowException(LPCSTR message, NExitCode::EEnum code)
{
  if (g_ErrStream)
    *g_ErrStream << endl << "ERROR: " << message << endl;
  throw code;
}


static void PrintStringRight(CStdOutStream &so, const char *s, unsigned size)
{
  unsigned len = MyStringLen(s);
  for (unsigned i = len; i < size; i++)
    so << ' ';
  so << s;
}

static void PrintUInt32(CStdOutStream &so, UInt32 val, unsigned size)
{
  char s[16];
  ConvertUInt32ToString(val, s);
  PrintStringRight(so, s, size);
}

static void PrintLibIndex(CStdOutStream &so, int libIndex)
{
  if (libIndex >= 0)
    PrintUInt32(so, (UInt32)libIndex, 2);
  else
    so << "  ";
  so << ' ';
}

static void PrintString(CStdOutStream &so, const UString &s, unsigned size)
{
  unsigned len = s.Len();
  so << s;
  for (unsigned i = len; i < size; i++)
    so << ' ';
}

static void PrintWarningsPaths(const CErrorPathCodes &pc, CStdOutStream &so)
{
  FOR_VECTOR(i, pc.Paths)
  {
    so.NormalizePrint_UString_Path(fs2us(pc.Paths[i]));
    so << " : ";
    so << NError::MyFormatMessage(pc.Codes[i]) << endl;
  }
  so << "----------------" << endl;
}

static int WarningsCheck(HRESULT result, const CCallbackConsoleBase &callback,
    const CUpdateErrorInfo &errorInfo,
    CStdOutStream *so,
    CStdOutStream *se,
    bool showHeaders)
{
  int exitCode = NExitCode::kSuccess;
  
  if (callback.ScanErrors.Paths.Size() != 0)
  {
    if (se)
    {
      *se << endl;
      *se << "Scan WARNINGS for files and folders:" << endl << endl;
      PrintWarningsPaths(callback.ScanErrors, *se);
      *se << "Scan WARNINGS: " << callback.ScanErrors.Paths.Size();
      *se << endl;
    }
    exitCode = NExitCode::kWarning;
  }
  
  if (result != S_OK || errorInfo.ThereIsError())
  {
    if (se)
    {
      UString message;
      if (!errorInfo.Message.IsEmpty())
      {
        message += errorInfo.Message.Ptr();
        message.Add_LF();
      }
      {
        FOR_VECTOR(i, errorInfo.FileNames)
        {
          message += fs2us(errorInfo.FileNames[i]);
          message.Add_LF();
        }
      }
      if (errorInfo.SystemError != 0)
      {
        message += NError::MyFormatMessage(errorInfo.SystemError);
        message.Add_LF();
      }
      if (!message.IsEmpty())
        *se << L"\nError:\n" << message;
    }

    // we will work with (result) later
    // throw CSystemException(result);
    return NExitCode::kFatalError;
  }

  unsigned numErrors = callback.FailedFiles.Paths.Size();
  if (numErrors == 0)
  {
    if (showHeaders)
      if (callback.ScanErrors.Paths.Size() == 0)
        if (so)
        {
          if (se)
            se->Flush();
          *so << kEverythingIsOk << endl;
        }
  }
  else
  {
    if (se)
    {
      *se << endl;
      *se << "WARNINGS for files:" << endl << endl;
      PrintWarningsPaths(callback.FailedFiles, *se);
      *se << "WARNING: Cannot open " << numErrors << " file";
      if (numErrors > 1)
        *se << 's';
      *se << endl;
    }
    exitCode = NExitCode::kWarning;
  }
  
  return exitCode;
}

static void ThrowException_if_Error(HRESULT res)
{
  if (res != S_OK)
    throw CSystemException(res);
}

static void PrintNum(UInt64 val, unsigned numDigits, char c = ' ')
{
  char temp[64];
  char *p = temp + 32;
  ConvertUInt64ToString(val, p);
  unsigned len = MyStringLen(p);
  for (; len < numDigits; len++)
    *--p = c;
  *g_StdStream << p;
}

static UInt64 Get_timeofday_us()
{
  return (UInt64)time(NULL) * 1000000;
}

static void PrintTime(const char *s, UInt64 val, UInt64 total_us, UInt64 kFreq)
{
  *g_StdStream << endl << s << " Time =";

  {
    UInt64 sec, ms;
    
    if (kFreq == 0)
    {
      sec = val / 1000000;
      ms  = val % 1000000 / 1000;
    }
    else
    {
      sec = val / kFreq;
      ms = (UInt32)((val - (sec * kFreq)) * 1000 / kFreq);
    }
    
    PrintNum(sec, 6);
    *g_StdStream << '.';
    PrintNum(ms, 3, '0');
  }
  
  if (total_us == 0)
    return;

  UInt64 percent = 0;
  if (kFreq == 0)
    percent = val * 100 / total_us;
  else
  {
    const UInt64 kMaxVal = (UInt64)(Int64)-1;
    UInt32 m = 100000000;
    for (;;)
    {
      if (m == 0 || kFreq == 0)
        break;
      if (kMaxVal / m > val &&
        kMaxVal / kFreq > total_us)
        break;
      if (val > m)
        val >>= 1;
      else
        m >>= 1;
      if (kFreq > total_us)
        kFreq >>= 1;
      else
        total_us >>= 1;
    }
    const UInt64 total = kFreq * total_us;
    if (total != 0)
      percent = val * m / total;
  }
  *g_StdStream << " =";
  PrintNum(percent, 5);
  *g_StdStream << '%';
}

static void PrintStat(const UInt64 startTime)
{
  *g_StdStream << "@FIXME: PrintStat for DOS" << endl;
#if 0
  tms t;
  /* clock_t res = */ times(&t);
  const UInt64 totalTime = Get_timeofday_us() - startTime;
  const UInt64 kFreq = (UInt64)sysconf(_SC_CLK_TCK);
  PrintTime("Kernel ", (UInt64)t.tms_stime, totalTime, kFreq);
  PrintTime("User   ", (UInt64)t.tms_utime, totalTime, kFreq);
  PrintTime("Process", (UInt64)t.tms_utime + (UInt64)t.tms_stime, totalTime, kFreq);
  PrintTime("Global ", totalTime, totalTime, 0);
  *g_StdStream << endl;
#endif
}

static void PrintHexId(CStdOutStream &so, UInt64 id)
{
  char s[32];
  ConvertUInt64ToHex(id, s);
  PrintStringRight(so, s, 8);
}


#if !defined(__DOS__)
void Set_ModuleDirPrefix_From_ProgArg0(const char *s);
#endif // !defined(__DOS__)

int Main2(int numArgs, char *args[]);

int Main2(int numArgs, char *args[])
{
  #if defined(MY_CPU_SIZEOF_POINTER)
    { unsigned k = sizeof(void *); if (k != MY_CPU_SIZEOF_POINTER) throw "incorrect MY_CPU_PTR_SIZE"; }
  #endif

  #ifdef ENV_HAVE_LOCALE
  // printf("\nBefore SetLocale() : %s\n", IsNativeUtf8() ? "NATIVE UTF-8" : "IS NOT NATIVE UTF-8");
  MY_SetLocale();
  // printf("\nAfter  SetLocale() : %s\n", IsNativeUtf8() ? "NATIVE UTF-8" : "IS NOT NATIVE UTF-8");
  #endif

  const UInt64 startTime = Get_timeofday_us();


  UStringVector commandStrings;
  
  {
#if !defined(__DOS__)
    if (numArgs > 0)
      Set_ModuleDirPrefix_From_ProgArg0(args[0]);
#endif // !defined(__DOS__0

    for (int i = 0; i < numArgs; i++)
    {
      AString a (args[i]);
#if 0
      printf("\n%d %s :", i, a.Ptr());
      for (unsigned k = 0; k < a.Len(); k++)
        printf(" %2x", (unsigned)(Byte)a[k]);
#endif
      const UString s = MultiByteToUnicodeString(a);
      commandStrings.Add(s);
    }
    // printf("\n");
  }

  if (commandStrings.Size() > 0)
    commandStrings.Delete(0);

  if (commandStrings.Size() == 0)
  {
    unsigned int m = 0;
    *g_StdStream
      << endl << kBanner
      << " : LFN=" << LongFileNames()
      << " SWITCHES=-md" << MaximumDictionarySize(m)
      << endl << endl 
      << kShortHelp;
    return 0;
  }

  CArcCmdLineOptions options;

  CArcCmdLineParser parser;

  parser.Parse1(commandStrings, options);

  g_StdOut.IsTerminalMode = options.IsStdOutTerminal;
  g_StdErr.IsTerminalMode = options.IsStdErrTerminal;

  if (options.Number_for_Out != k_OutStream_stdout)
    g_StdStream = (options.Number_for_Out == k_OutStream_stderr ? &g_StdErr : NULL);

  if (options.Number_for_Errors != k_OutStream_stderr)
    g_ErrStream = (options.Number_for_Errors == k_OutStream_stdout ? &g_StdOut : NULL);

  CStdOutStream *percentsStream = NULL;
  if (options.Number_for_Percents != k_OutStream_disabled)
    percentsStream = (options.Number_for_Percents == k_OutStream_stderr) ? &g_StdErr : &g_StdOut;
  
  if (options.HelpMode && g_StdStream)
  {
    unsigned int m = 0;
    *g_StdStream
      << endl << kBanner
      << " : LFN=" << LongFileNames()
      << " SWITCHES=-md" << MaximumDictionarySize(m)
      << endl << endl
      << kLongHelp;
    return 0;
  }

  if (options.VersionMode && g_StdStream)
  {
    unsigned int m = 0;
    *g_StdStream
      << endl << kBanner
      << " : LFN=" << LongFileNames()
      << " SWITCHES=-md" << MaximumDictionarySize(m)
      << endl << endl
      << kVersion;
    return 0;
  }

  if (options.EnableHeaders && g_StdStream)
  {
    *g_StdStream << kBanner << endl;
    if (!parser.Parse1Log.IsEmpty())
      *g_StdStream << parser.Parse1Log;
  }

  parser.Parse2(options);

  {
    int cp = options.ConsoleCodePage;
    
    int stdout_cp = cp;
    int stderr_cp = cp;
    int stdin_cp = cp;
    
    if (stdout_cp != -1) g_StdOut.CodePage = stdout_cp;
    if (stderr_cp != -1) g_StdErr.CodePage = stderr_cp;
    if (stdin_cp != -1) g_StdIn.CodePage = stdin_cp;
  }
  g_StdOut.ListPathSeparatorSlash = options.ListPathSeparatorSlash;
  g_StdErr.ListPathSeparatorSlash = options.ListPathSeparatorSlash;

  unsigned percentsNameLevel = 1;
  if (options.LogLevel == 0 || options.Number_for_Percents != options.Number_for_Out)
    percentsNameLevel = 2;

  unsigned consoleWidth = 80;

  CREATE_CODECS_OBJECT

  codecs->CaseSensitive_Change = options.CaseSensitive_Change;
  codecs->CaseSensitive = options.CaseSensitive;
  ThrowException_if_Error(codecs->Load());

  #ifndef Z7_NO_HASHCON
  Codecs_AddHashArcHandler(codecs);
  #endif
  const bool isExtractGroupCommand = options.Command.IsFromExtractGroup();

  if (codecs->Formats.Size() == 0 &&
        (isExtractGroupCommand
        || options.Command.CommandType == NCommandType::kList
        || options.Command.IsFromUpdateGroup()))
  {
    throw kNoFormats;
  }

  CObjectVector<COpenType> types;
  if (!ParseOpenTypes(*codecs, options.ArcType, types))
  {
    throw kUnsupportedArcTypeMessage;
  }

  CIntVector excludedFormats;
  FOR_VECTOR (k, options.ExcludedArcTypes)
  {
    CIntVector tempIndices;
    if (!codecs->FindFormatForArchiveType(options.ExcludedArcTypes[k], tempIndices)
        || tempIndices.Size() != 1)
      throw kUnsupportedArcTypeMessage;
    
    
    
    excludedFormats.AddToUniqueSorted(tempIndices[0]);
    // excludedFormats.Sort();
  }

  int retCode = NExitCode::kSuccess;
  HRESULT hresultMain = S_OK;
  
  if (options.Command.CommandType == NCommandType::kInfo)
  {
    CStdOutStream &so = (g_StdStream ? *g_StdStream : g_StdOut);
    unsigned i;

    so << endl << "Formats:" << endl;
    
    const char * const kArcFlags = "KSNFMGOPBELHXCc+a+m+r+";
    const char * const kArcTimeFlags = "wudn";
    const unsigned kNumArcFlags = (unsigned)strlen(kArcFlags);
    const unsigned kNumArcTimeFlags = (unsigned)strlen(kArcTimeFlags);

    for (i = 0; i < codecs->Formats.Size(); i++)
    {
      const CArcInfoEx &arc = codecs->Formats[i];
      so << "   ";

      so << (char)(arc.UpdateEnabled ? 'C' : ' ');
      
      {
        unsigned b;
        for (b = 0; b < kNumArcFlags; b++)
          so << (char)((arc.Flags & ((UInt32)1 << b)) != 0 ? kArcFlags[b] : '.');
        so << ' ';
      }

      if (arc.TimeFlags != 0)
      {
        unsigned b;
        for (b = 0; b < kNumArcTimeFlags; b++)
          so << (char)((arc.TimeFlags & ((UInt32)1 << b)) != 0 ? kArcTimeFlags[b] : '.');
        so << arc.Get_DefaultTimePrec();
        so << ' ';
      }
      
      so << ' ';
      PrintString(so, arc.Name, 8);
      so << ' ';
      UString s;
      
      FOR_VECTOR (t, arc.Exts)
      {
        if (t != 0)
          s.Add_Space();
        const CArcExtInfo &ext = arc.Exts[t];
        s += ext.Ext;
        if (!ext.AddExt.IsEmpty())
        {
          s += " (";
          s += ext.AddExt;
          s.Add_Char(')');
        }
      }
      
      PrintString(so, s, 13);
      so << ' ';
      
      if (arc.SignatureOffset != 0)
        so << "offset=" << arc.SignatureOffset << ' ';

      // so << "numSignatures = " << arc.Signatures.Size() << " ";

      FOR_VECTOR(si, arc.Signatures)
      {
        if (si != 0)
          so << "  ||  ";

        const CByteBuffer &sig = arc.Signatures[si];
        
        for (size_t j = 0; j < sig.Size(); j++)
        {
          if (j != 0)
            so << ' ';
          const unsigned b = sig.ConstData()[j];
          if (b > 0x20 && b < 0x80)
          {
            so << (char)b;
          }
          else
          {
            so << GET_HEX_CHAR_UPPER(b >> 4);
            so << GET_HEX_CHAR_UPPER(b & 15);
          }
        }
      }
      so << endl;
    }

    so << endl << "Codecs:" << endl; //  << "Lib          ID Name" << endl;

    for (i = 0; i < g_NumCodecs; i++)
    {
      const CCodecInfo &cod = *g_Codecs[i];

      PrintLibIndex(so, -1);

      if (cod.NumStreams == 1)
        so << ' ';
      else
        so << cod.NumStreams;
      
      so << (char)(cod.CreateEncoder ? 'E' : ' ');
      so << (char)(cod.CreateDecoder ? 'D' : ' ');
      so << (char)(cod.IsFilter      ? 'F' : ' ');

      so << ' ';
      PrintHexId(so, cod.Id);
      so << ' ' << cod.Name << endl;
    }
    #ifndef Z7_NO_HASHCON
    so << endl << "Hashers:" << endl; //  << " L Size       ID Name" << endl;

    for (i = 0; i < g_NumHashers; i++)
    {
      const CHasherInfo &codec = *g_Hashers[i];
      PrintLibIndex(so, -1);
      PrintUInt32(so, codec.DigestSize, 4);
      so << ' ';
      PrintHexId(so, codec.Id);
      so << ' ' << codec.Name << endl;
    }
    #endif
  }
  else if (options.Command.CommandType == NCommandType::kBenchmark)
  {
    throw CArcCmdLineException("Benchmarking is not implemented for DOS.");
  }
  else if (isExtractGroupCommand || options.Command.CommandType == NCommandType::kList)
  {
    UStringVector ArchivePathsSorted;
    UStringVector ArchivePathsFullSorted;

    if (options.StdInMode)
    {
      ArchivePathsSorted.Add(options.ArcName_for_StdInMode);
      ArchivePathsFullSorted.Add(options.ArcName_for_StdInMode);
    }
    else
    {
      CExtractScanConsole scan;
      
      scan.Init(options.EnableHeaders ? g_StdStream : NULL,
          g_ErrStream, percentsStream,
          options.DisablePercents);
      scan.SetWindowWidth(consoleWidth);

      if (g_StdStream && options.EnableHeaders)
        *g_StdStream << "Scanning the drive for archives:" << endl;

      CDirItemsStat st;

      scan.StartScanning();

      hresultMain = EnumerateDirItemsAndSort(
          options.arcCensor,
          NWildcard::k_RelatPath,
          UString(), // addPathPrefix
          ArchivePathsSorted,
          ArchivePathsFullSorted,
          st,
          &scan);

      scan.CloseScanning();

      if (hresultMain == S_OK)
      {
        if (options.EnableHeaders)
          scan.PrintStat(st);
      }
      else
      {
        /*
        if (res != E_ABORT)
        {
          throw CSystemException(res);
          // errorInfo.Message = "Scanning error";
        }
        return res;
        */
      }
    }

    if (hresultMain == S_OK) {
    if (isExtractGroupCommand)
    {
      CExtractCallbackConsole *ecs = new CExtractCallbackConsole;
      CMyComPtr<IFolderArchiveExtractCallback> extractCallback = ecs;

      #ifndef Z7_NO_CRYPTO
      ecs->PasswordIsDefined = options.PasswordEnabled;
      ecs->Password = options.Password;
      #endif

      ecs->Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents);
      ecs->MultiArcMode = (ArchivePathsSorted.Size() > 1);

      ecs->LogLevel = options.LogLevel;
      ecs->PercentsNameLevel = percentsNameLevel;
      
      if (percentsStream)
        ecs->SetWindowWidth(consoleWidth);

      /*
      COpenCallbackConsole openCallback;
      openCallback.Init(g_StdStream, g_ErrStream);

      #ifndef Z7_NO_CRYPTO
      openCallback.PasswordIsDefined = options.PasswordEnabled;
      openCallback.Password = options.Password;
      #endif
      */

      CExtractOptions eo;
      (CExtractOptionsBase &)eo = options.ExtractOptions;
      
      eo.StdInMode = options.StdInMode;
      eo.StdOutMode = options.StdOutMode;
      eo.YesToAll = options.YesToAll;
      eo.TestMode = options.Command.IsTestCommand();
      
      #ifndef Z7_SFX
      eo.Properties = options.Properties;
      #endif

      UString errorMessage;
      CDecompressStat stat;

      #ifndef Z7_NO_HASHCON
      CHashBundle hb;
      IHashCalc *hashCalc = NULL;

      if (!options.HashMethods.IsEmpty())
      {
        hashCalc = &hb;
        ThrowException_if_Error(hb.SetMethods(EXTERNAL_CODECS_VARS_L options.HashMethods));
        // hb.Init();
      }
      #endif // Z7_NO_HASHCON
      
      hresultMain = Extract(
          // EXTERNAL_CODECS_VARS_L
          codecs,
          types,
          excludedFormats,
          ArchivePathsSorted,
          ArchivePathsFullSorted,
          options.Censor.Pairs.Front().Head,
          eo,
          ecs, ecs, ecs,
          #ifndef Z7_NO_HASHCON
            hashCalc,
          #else
            NULL,
          #endif
          errorMessage,
          stat);
      
      ecs->ClosePercents();

      if (!errorMessage.IsEmpty())
      {
        if (g_ErrStream)
          *g_ErrStream << endl << "ERROR:" << endl << errorMessage << endl;
        if (hresultMain == S_OK)
          hresultMain = E_FAIL;
      }

      CStdOutStream *so = g_StdStream;

      bool isError = false;

      if (so)
      {
        *so << endl;
        
        if (ecs->NumTryArcs > 1)
        {
          *so << "Archives: " << ecs->NumTryArcs << endl;
          *so << "OK archives: " << ecs->NumOkArcs << endl;
        }
      }

      if (ecs->NumCantOpenArcs != 0)
      {
        isError = true;
        if (so)
          *so << "Can't open as archive: " << ecs->NumCantOpenArcs << endl;
      }
      
      if (ecs->NumArcsWithError != 0)
      {
        isError = true;
        if (so)
          *so << "Archives with Errors: " << ecs->NumArcsWithError << endl;
      }
      
      if (so)
      {
        if (ecs->NumArcsWithWarnings != 0)
          *so << "Archives with Warnings: " << ecs->NumArcsWithWarnings << endl;
        
        if (ecs->NumOpenArcWarnings != 0)
        {
          *so << endl;
          if (ecs->NumOpenArcWarnings != 0)
            *so << "Warnings: " << ecs->NumOpenArcWarnings << endl;
        }
      }
      
      if (ecs->NumOpenArcErrors != 0)
      {
        isError = true;
        if (so)
        {
          *so << endl;
          if (ecs->NumOpenArcErrors != 0)
            *so << "Open Errors: " << ecs->NumOpenArcErrors << endl;
        }
      }

      if (isError)
        retCode = NExitCode::kFatalError;
      
      if (so) {
      if (ecs->NumArcsWithError != 0 || ecs->NumFileErrors != 0)
      {
        // if (ecs->NumArchives > 1)
        {
          *so << endl;
          if (ecs->NumFileErrors != 0)
            *so << "Sub items Errors: " << ecs->NumFileErrors << endl;
        }
      }
      else if (hresultMain == S_OK)
      {
        if (stat.NumFolders != 0)
          *so << "Folders: " << stat.NumFolders << endl;
        if (stat.NumFiles != 1 || stat.NumFolders != 0 || stat.NumAltStreams != 0)
          *so << "Files: " << stat.NumFiles << endl;
        if (stat.NumAltStreams != 0)
        {
          *so << "Alternate Streams: " << stat.NumAltStreams << endl;
          *so << "Alternate Streams Size: " << stat.AltStreams_UnpackSize << endl;
        }
        
        *so
          << "Size:       " << stat.UnpackSize << endl
          << "Compressed: " << stat.PackSize << endl;
        #ifndef Z7_NO_HASHCON
        if (hashCalc)
        {
          *so << endl;
          PrintHashStat(*so, hb);
        }
        #endif
      }
      } // if (so)
    }
    else // if_(!isExtractGroupCommand)
    {
      UInt64 numErrors = 0;
      UInt64 numWarnings = 0;
      
      // options.ExtractNtOptions.StoreAltStreams = true, if -sns[-] is not definmed

      CListOptions lo;
      lo.ExcludeDirItems = options.Censor.ExcludeDirItems;
      lo.ExcludeFileItems = options.Censor.ExcludeFileItems;
      lo.DisablePercents = options.DisablePercents;

      hresultMain = ListArchives(
          lo,
          codecs,
          types,
          excludedFormats,
          options.StdInMode,
          ArchivePathsSorted,
          ArchivePathsFullSorted,
          options.ExtractOptions.NtOptions.AltStreams.Val,
          options.AltStreams.Val, // we don't want to show AltStreams by default
          options.Censor.Pairs.Front().Head,
          options.EnableHeaders,
          options.TechMode,
          #ifndef Z7_NO_CRYPTO
          options.PasswordEnabled,
          options.Password,
          #endif
          &options.Properties,
          numErrors, numWarnings);

      if (options.EnableHeaders)
        if (numWarnings > 0)
          g_StdOut << endl << "Warnings: " << numWarnings << endl;
      
      if (numErrors > 0)
      {
        if (options.EnableHeaders)
          g_StdOut << endl << "Errors: " << numErrors << endl;
        retCode = NExitCode::kFatalError;
      }
    } // if_(isExtractGroupCommand)
    } // if_(hresultMain == S_OK)
  }
  else if (options.Command.IsFromUpdateGroup())
  {
   #ifdef Z7_EXTRACT_ONLY
    throw "update commands are not implemented";
   #else
    CUpdateOptions &uo = options.UpdateOptions;
    if (uo.SfxMode && uo.SfxModule.IsEmpty())
      uo.SfxModule = kDefaultSfxModule;

    COpenCallbackConsole openCallback;
    openCallback.Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents);

    #ifndef Z7_NO_CRYPTO
    bool passwordIsDefined =
        (options.PasswordEnabled && !options.Password.IsEmpty());
    openCallback.PasswordIsDefined = passwordIsDefined;
    openCallback.Password = options.Password;
    #endif

    CUpdateCallbackConsole callback;
    callback.LogLevel = options.LogLevel;
    callback.PercentsNameLevel = percentsNameLevel;

    if (percentsStream)
      callback.SetWindowWidth(consoleWidth);

    #ifndef Z7_NO_CRYPTO
    callback.PasswordIsDefined = passwordIsDefined;
    callback.AskPassword = (options.PasswordEnabled && options.Password.IsEmpty());
    callback.Password = options.Password;
    #endif

    callback.StdOutMode = uo.StdOutMode;
    callback.Init(
      // NULL,
      g_StdStream, g_ErrStream, percentsStream, options.DisablePercents);

    CUpdateErrorInfo errorInfo;

    /*
    if (!uo.Init(codecs, types, options.ArchiveName))
      throw kUnsupportedUpdateArcType;
    */
    hresultMain = UpdateArchive(codecs,
        types,
        options.ArchiveName,
        options.Censor,
        uo,
        errorInfo, &openCallback, &callback, true);

    callback.ClosePercents2();

    CStdOutStream *se = g_StdStream;
    if (!se)
      se = g_ErrStream;

    retCode = WarningsCheck(hresultMain, callback, errorInfo,
        g_StdStream, se,
        true // options.EnableHeaders
        );
   #endif
  }
  #ifdef Z7_NO_HASHCON
  else if (options.Command.CommandType == NCommandType::kHash)
  {
      throw CArcCmdLineException("The hash command is not in this 7-Zip variant.");
  }
  #else
  else if (options.Command.CommandType == NCommandType::kHash)
  {
    const CHashOptions &uo = options.HashOptions;

    CHashCallbackConsole callback;
    if (percentsStream)
      callback.SetWindowWidth(consoleWidth);
  
    callback.Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents);
    callback.PrintHeaders = options.EnableHeaders;
    callback.PrintFields = options.ListFields;

    AString errorInfoString;
    hresultMain = HashCalc(EXTERNAL_CODECS_VARS_L
        options.Censor, uo,
        errorInfoString, &callback);
    CUpdateErrorInfo errorInfo;
    errorInfo.Message = errorInfoString;
    CStdOutStream *se = g_StdStream;
    if (!se)
      se = g_ErrStream;
    retCode = WarningsCheck(hresultMain, callback, errorInfo, g_StdStream, se, options.EnableHeaders);
  }
  #endif // Z7_NO_HASHCON
  else
    ShowMessageAndThrowException(kUserErrorMessage, NExitCode::kUserError);

  if (options.ShowTime && g_StdStream)
    PrintStat(startTime);

  ThrowException_if_Error(hresultMain);

  return retCode;
}
