1// LzmaAlone.cpp
2
3#include "StdAfx.h"
4
5#include <stdio.h>
6
7#if (defined(_WIN32) || defined(OS2) || defined(MSDOS)) && !defined(UNDER_CE)
8#include <fcntl.h>
9#include <io.h>
10#define MY_SET_BINARY_MODE(file) _setmode(_fileno(file), O_BINARY)
11#else
12#define MY_SET_BINARY_MODE(file)
13#endif
14
15// #include "../../../Common/MyWindows.h"
16#include "../../../Common/MyInitGuid.h"
17
18#include "../../../../C/7zVersion.h"
19#include "../../../../C/Alloc.h"
20#include "../../../../C/Lzma86.h"
21
22#include "../../../Windows/NtCheck.h"
23
24#ifndef _7ZIP_ST
25#include "../../../Windows/System.h"
26#endif
27
28#include "../../../Common/CommandLineParser.h"
29#include "../../../Common/StringConvert.h"
30#include "../../../Common/StringToInt.h"
31
32#include "../../Common/FileStreams.h"
33#include "../../Common/StreamUtils.h"
34
35#include "../../Compress/LzmaDecoder.h"
36#include "../../Compress/LzmaEncoder.h"
37
38#include "../../UI/Console/BenchCon.h"
39
40
41using namespace NCommandLineParser;
42
43static const char *kCantAllocate = "Can not allocate memory";
44static const char *kReadError = "Read error";
45static const char *kWriteError = "Write error";
46
47namespace NKey {
48enum Enum
49{
50  kHelp1 = 0,
51  kHelp2,
52  kMethod,
53  kLevel,
54  kAlgo,
55  kDict,
56  kFb,
57  kMc,
58  kLc,
59  kLp,
60  kPb,
61  kMatchFinder,
62  kMultiThread,
63  kEOS,
64  kStdIn,
65  kStdOut,
66  kFilter86
67};
68}
69
70static const CSwitchForm kSwitchForms[] =
71{
72  { "?",  NSwitchType::kSimple, false },
73  { "H",  NSwitchType::kSimple, false },
74  { "MM", NSwitchType::kString, false, 1 },
75  { "X", NSwitchType::kString, false, 1 },
76  { "A", NSwitchType::kString, false, 1 },
77  { "D", NSwitchType::kString, false, 1 },
78  { "FB", NSwitchType::kString, false, 1 },
79  { "MC", NSwitchType::kString, false, 1 },
80  { "LC", NSwitchType::kString, false, 1 },
81  { "LP", NSwitchType::kString, false, 1 },
82  { "PB", NSwitchType::kString, false, 1 },
83  { "MF", NSwitchType::kString, false, 1 },
84  { "MT", NSwitchType::kString, false, 0 },
85  { "EOS", NSwitchType::kSimple, false },
86  { "SI",  NSwitchType::kSimple, false },
87  { "SO",  NSwitchType::kSimple, false },
88  { "F86",  NSwitchType::kChar, false, 0, "+" }
89};
90
91static void PrintMessage(const char *s)
92{
93  fputs(s, stderr);
94}
95
96static void PrintHelp()
97{
98  PrintMessage("\nUsage:  LZMA <e|d> inputFile outputFile [<switches>...]\n"
99             "  e: encode file\n"
100             "  d: decode file\n"
101             "  b: Benchmark\n"
102    "<Switches>\n"
103    "  -a{N}:  set compression mode - [0, 1], default: 1 (max)\n"
104    "  -d{N}:  set dictionary size - [12, 30], default: 23 (8MB)\n"
105    "  -fb{N}: set number of fast bytes - [5, 273], default: 128\n"
106    "  -mc{N}: set number of cycles for match finder\n"
107    "  -lc{N}: set number of literal context bits - [0, 8], default: 3\n"
108    "  -lp{N}: set number of literal pos bits - [0, 4], default: 0\n"
109    "  -pb{N}: set number of pos bits - [0, 4], default: 2\n"
110    "  -mf{MF_ID}: set Match Finder: [bt2, bt3, bt4, hc4], default: bt4\n"
111    "  -mt{N}: set number of CPU threads\n"
112    "  -eos:   write End Of Stream marker\n"
113    "  -si:    read data from stdin\n"
114    "  -so:    write data to stdout\n"
115    );
116}
117
118static void PrintHelpAndExit(const char *s)
119{
120  fprintf(stderr, "\nError: %s\n\n", s);
121  PrintHelp();
122  throw -1;
123}
124
125static void IncorrectCommand()
126{
127  PrintHelpAndExit("Incorrect command");
128}
129
130static void WriteArgumentsToStringList(int numArgs, const char *args[], UStringVector &strings)
131{
132  for (int i = 1; i < numArgs; i++)
133    strings.Add(MultiByteToUnicodeString(args[i]));
134}
135
136static bool GetNumber(const wchar_t *s, UInt32 &value)
137{
138  value = 0;
139  if (*s == 0)
140    return false;
141  const wchar_t *end;
142  value = ConvertStringToUInt32(s, &end);
143  return *end == 0;
144}
145
146static void ParseUInt32(const CParser &parser, unsigned index, UInt32 &res)
147{
148  if (parser[index].ThereIs)
149    if (!GetNumber(parser[index].PostStrings[0], res))
150      IncorrectCommand();
151}
152
153#define NT_CHECK_FAIL_ACTION PrintMessage("Unsupported Windows version"); return 1;
154
155int main2(int numArgs, const char *args[])
156{
157  NT_CHECK
158
159  PrintMessage("\nLZMA " MY_VERSION_COPYRIGHT_DATE "\n");
160
161  if (numArgs == 1)
162  {
163    PrintHelp();
164    return 0;
165  }
166
167  bool unsupportedTypes = (sizeof(Byte) != 1 || sizeof(UInt32) < 4 || sizeof(UInt64) < 4);
168  if (unsupportedTypes)
169  {
170    PrintMessage("Unsupported base types. Edit Common/Types.h and recompile");
171    return 1;
172  }
173
174  UStringVector commandStrings;
175  WriteArgumentsToStringList(numArgs, args, commandStrings);
176
177  CParser parser(ARRAY_SIZE(kSwitchForms));
178  try
179  {
180    parser.ParseStrings(kSwitchForms, commandStrings);
181  }
182  catch(...)
183  {
184    IncorrectCommand();
185  }
186
187  if (parser[NKey::kHelp1].ThereIs || parser[NKey::kHelp2].ThereIs)
188  {
189    PrintHelp();
190    return 0;
191  }
192  const UStringVector &nonSwitchStrings = parser.NonSwitchStrings;
193
194  unsigned paramIndex = 0;
195  if (paramIndex >= nonSwitchStrings.Size())
196    IncorrectCommand();
197  const UString &command = nonSwitchStrings[paramIndex++];
198
199  CObjectVector<CProperty> props;
200  bool dictDefined = false;
201  UInt32 dict = (UInt32)(Int32)-1;
202  if (parser[NKey::kDict].ThereIs)
203  {
204    UInt32 dicLog;
205    const UString &s = parser[NKey::kDict].PostStrings[0];
206    if (!GetNumber(s, dicLog))
207      IncorrectCommand();
208    dict = 1 << dicLog;
209    dictDefined = true;
210    CProperty prop;
211    prop.Name = L"d";
212    prop.Value = s;
213    props.Add(prop);
214  }
215  if (parser[NKey::kLevel].ThereIs)
216  {
217    UInt32 level = 5;
218    const UString &s = parser[NKey::kLevel].PostStrings[0];
219    if (!GetNumber(s, level))
220      IncorrectCommand();
221    CProperty prop;
222    prop.Name = L"x";
223    prop.Value = s;
224    props.Add(prop);
225  }
226  UString mf = L"BT4";
227  if (parser[NKey::kMatchFinder].ThereIs)
228    mf = parser[NKey::kMatchFinder].PostStrings[0];
229
230  UInt32 numThreads = (UInt32)(Int32)-1;
231
232  #ifndef _7ZIP_ST
233  if (parser[NKey::kMultiThread].ThereIs)
234  {
235    UInt32 numCPUs = NWindows::NSystem::GetNumberOfProcessors();
236    const UString &s = parser[NKey::kMultiThread].PostStrings[0];
237    if (s.IsEmpty())
238      numThreads = numCPUs;
239    else
240      if (!GetNumber(s, numThreads))
241        IncorrectCommand();
242    CProperty prop;
243    prop.Name = L"mt";
244    prop.Value = s;
245    props.Add(prop);
246  }
247  #endif
248
249  if (parser[NKey::kMethod].ThereIs)
250  {
251    UString s = parser[NKey::kMethod].PostStrings[0];
252    if (s.IsEmpty() || s[0] != '=')
253      IncorrectCommand();
254    CProperty prop;
255    prop.Name = L"m";
256    prop.Value = s.Ptr(1);
257    props.Add(prop);
258  }
259
260  if (MyStringCompareNoCase(command, L"b") == 0)
261  {
262    const UInt32 kNumDefaultItereations = 1;
263    UInt32 numIterations = kNumDefaultItereations;
264    {
265      if (paramIndex < nonSwitchStrings.Size())
266        if (!GetNumber(nonSwitchStrings[paramIndex++], numIterations))
267          numIterations = kNumDefaultItereations;
268    }
269    HRESULT res = BenchCon(props, numIterations, stderr);
270    if (res != S_OK)
271    {
272      if (res != E_ABORT)
273      {
274        PrintMessage("Benchmark Error");
275        return 1;
276      }
277    }
278    return 0;
279  }
280
281  if (numThreads == (UInt32)(Int32)-1)
282    numThreads = 1;
283
284  bool encodeMode = false;
285  if (MyStringCompareNoCase(command, L"e") == 0)
286    encodeMode = true;
287  else if (MyStringCompareNoCase(command, L"d") == 0)
288    encodeMode = false;
289  else
290    IncorrectCommand();
291
292  bool stdInMode = parser[NKey::kStdIn].ThereIs;
293  bool stdOutMode = parser[NKey::kStdOut].ThereIs;
294
295  CMyComPtr<ISequentialInStream> inStream;
296  CInFileStream *inStreamSpec = 0;
297  if (stdInMode)
298  {
299    inStream = new CStdInFileStream;
300    MY_SET_BINARY_MODE(stdin);
301  }
302  else
303  {
304    if (paramIndex >= nonSwitchStrings.Size())
305      IncorrectCommand();
306    const UString &inputName = nonSwitchStrings[paramIndex++];
307    inStreamSpec = new CInFileStream;
308    inStream = inStreamSpec;
309    if (!inStreamSpec->Open(us2fs(inputName)))
310    {
311      fprintf(stderr, "\nError: can not open input file %s\n",
312          (const char *)GetOemString(inputName));
313      return 1;
314    }
315  }
316
317  CMyComPtr<ISequentialOutStream> outStream;
318  COutFileStream *outStreamSpec = NULL;
319  if (stdOutMode)
320  {
321    outStream = new CStdOutFileStream;
322    MY_SET_BINARY_MODE(stdout);
323  }
324  else
325  {
326    if (paramIndex >= nonSwitchStrings.Size())
327      IncorrectCommand();
328    const UString &outputName = nonSwitchStrings[paramIndex++];
329    outStreamSpec = new COutFileStream;
330    outStream = outStreamSpec;
331    if (!outStreamSpec->Create(us2fs(outputName), true))
332    {
333      fprintf(stderr, "\nError: can not open output file %s\n",
334        (const char *)GetOemString(outputName));
335      return 1;
336    }
337  }
338
339  if (parser[NKey::kFilter86].ThereIs)
340  {
341    // -f86 switch is for x86 filtered mode: BCJ + LZMA.
342    if (parser[NKey::kEOS].ThereIs || stdInMode)
343      throw "Can not use stdin in this mode";
344    UInt64 fileSize;
345    inStreamSpec->File.GetLength(fileSize);
346    if (fileSize > 0xF0000000)
347      throw "File is too big";
348    size_t inSize = (size_t)fileSize;
349    Byte *inBuffer = 0;
350    if (inSize != 0)
351    {
352      inBuffer = (Byte *)MyAlloc((size_t)inSize);
353      if (inBuffer == 0)
354        throw kCantAllocate;
355    }
356
357    if (ReadStream_FAIL(inStream, inBuffer, inSize) != S_OK)
358      throw "Can not read";
359
360    Byte *outBuffer = 0;
361    size_t outSize;
362    if (encodeMode)
363    {
364      // we allocate 105% of original size for output buffer
365      outSize = (size_t)fileSize / 20 * 21 + (1 << 16);
366      if (outSize != 0)
367      {
368        outBuffer = (Byte *)MyAlloc((size_t)outSize);
369        if (outBuffer == 0)
370          throw kCantAllocate;
371      }
372      if (!dictDefined)
373        dict = 1 << 23;
374      int res = Lzma86_Encode(outBuffer, &outSize, inBuffer, inSize,
375          5, dict, parser[NKey::kFilter86].PostCharIndex == 0 ? SZ_FILTER_YES : SZ_FILTER_AUTO);
376      if (res != 0)
377      {
378        fprintf(stderr, "\nEncoder error = %d\n", (int)res);
379        return 1;
380      }
381    }
382    else
383    {
384      UInt64 outSize64;
385      if (Lzma86_GetUnpackSize(inBuffer, inSize, &outSize64) != 0)
386        throw "data error";
387      outSize = (size_t)outSize64;
388      if (outSize != outSize64)
389        throw "too big";
390      if (outSize != 0)
391      {
392        outBuffer = (Byte *)MyAlloc(outSize);
393        if (outBuffer == 0)
394          throw kCantAllocate;
395      }
396      int res = Lzma86_Decode(outBuffer, &outSize, inBuffer, &inSize);
397      if (inSize != (size_t)fileSize)
398        throw "incorrect processed size";
399      if (res != 0)
400        throw "LzmaDecoder error";
401    }
402    if (WriteStream(outStream, outBuffer, outSize) != S_OK)
403      throw kWriteError;
404    MyFree(outBuffer);
405    MyFree(inBuffer);
406    return 0;
407  }
408
409
410  UInt64 fileSize;
411  if (encodeMode)
412  {
413    NCompress::NLzma::CEncoder *encoderSpec = new NCompress::NLzma::CEncoder;
414    CMyComPtr<ICompressCoder> encoder = encoderSpec;
415
416    if (!dictDefined)
417      dict = 1 << 23;
418
419    UInt32 pb = 2;
420    UInt32 lc = 3; // = 0; for 32-bit data
421    UInt32 lp = 0; // = 2; for 32-bit data
422    UInt32 algo = 1;
423    UInt32 fb = 128;
424    UInt32 mc = 16 + fb / 2;
425    bool mcDefined = false;
426
427    bool eos = parser[NKey::kEOS].ThereIs || stdInMode;
428
429    ParseUInt32(parser, NKey::kAlgo, algo);
430    ParseUInt32(parser, NKey::kFb, fb);
431    ParseUInt32(parser, NKey::kLc, lc);
432    ParseUInt32(parser, NKey::kLp, lp);
433    ParseUInt32(parser, NKey::kPb, pb);
434
435    mcDefined = parser[NKey::kMc].ThereIs;
436    if (mcDefined)
437      if (!GetNumber(parser[NKey::kMc].PostStrings[0], mc))
438        IncorrectCommand();
439
440    const PROPID propIDs[] =
441    {
442      NCoderPropID::kDictionarySize,
443      NCoderPropID::kPosStateBits,
444      NCoderPropID::kLitContextBits,
445      NCoderPropID::kLitPosBits,
446      NCoderPropID::kAlgorithm,
447      NCoderPropID::kNumFastBytes,
448      NCoderPropID::kMatchFinder,
449      NCoderPropID::kEndMarker,
450      NCoderPropID::kNumThreads,
451      NCoderPropID::kMatchFinderCycles,
452    };
453    const unsigned kNumPropsMax = ARRAY_SIZE(propIDs);
454
455    PROPVARIANT props[kNumPropsMax];
456    for (int p = 0; p < 6; p++)
457      props[p].vt = VT_UI4;
458
459    props[0].ulVal = (UInt32)dict;
460    props[1].ulVal = (UInt32)pb;
461    props[2].ulVal = (UInt32)lc;
462    props[3].ulVal = (UInt32)lp;
463    props[4].ulVal = (UInt32)algo;
464    props[5].ulVal = (UInt32)fb;
465
466    props[6].vt = VT_BSTR;
467    props[6].bstrVal = const_cast<BSTR>((const wchar_t *)mf);
468
469    props[7].vt = VT_BOOL;
470    props[7].boolVal = eos ? VARIANT_TRUE : VARIANT_FALSE;
471
472    props[8].vt = VT_UI4;
473    props[8].ulVal = (UInt32)numThreads;
474
475    // it must be last in property list
476    props[9].vt = VT_UI4;
477    props[9].ulVal = (UInt32)mc;
478
479    unsigned numProps = kNumPropsMax;
480    if (!mcDefined)
481      numProps--;
482
483    if (encoderSpec->SetCoderProperties(propIDs, props, numProps) != S_OK)
484      IncorrectCommand();
485    encoderSpec->WriteCoderProperties(outStream);
486
487    if (eos || stdInMode)
488      fileSize = (UInt64)(Int64)-1;
489    else
490      inStreamSpec->File.GetLength(fileSize);
491
492    for (int i = 0; i < 8; i++)
493    {
494      Byte b = Byte(fileSize >> (8 * i));
495      if (outStream->Write(&b, 1, 0) != S_OK)
496      {
497        PrintMessage(kWriteError);
498        return 1;
499      }
500    }
501    HRESULT result = encoder->Code(inStream, outStream, 0, 0, 0);
502    if (result == E_OUTOFMEMORY)
503    {
504      PrintMessage("\nError: Can not allocate memory\n");
505      return 1;
506    }
507    else if (result != S_OK)
508    {
509      fprintf(stderr, "\nEncoder error = %X\n", (unsigned)result);
510      return 1;
511    }
512  }
513  else
514  {
515    NCompress::NLzma::CDecoder *decoderSpec = new NCompress::NLzma::CDecoder;
516    CMyComPtr<ICompressCoder> decoder = decoderSpec;
517    decoderSpec->FinishStream = true;
518    const UInt32 kPropertiesSize = 5;
519    Byte header[kPropertiesSize + 8];
520    if (ReadStream_FALSE(inStream, header, kPropertiesSize + 8) != S_OK)
521    {
522      PrintMessage(kReadError);
523      return 1;
524    }
525    if (decoderSpec->SetDecoderProperties2(header, kPropertiesSize) != S_OK)
526    {
527      PrintMessage("SetDecoderProperties error");
528      return 1;
529    }
530    fileSize = 0;
531    for (int i = 0; i < 8; i++)
532      fileSize |= ((UInt64)header[kPropertiesSize + i]) << (8 * i);
533
534    bool isSizeDefined = (fileSize != (UInt64)(Int64)-1);
535    HRESULT res = decoder->Code(inStream, outStream, 0, isSizeDefined ? &fileSize : NULL, 0) != S_OK;
536    if (res != S_OK)
537    {
538      PrintMessage("Decoder error");
539      return 1;
540    }
541    if (isSizeDefined && decoderSpec->GetOutputProcessedSize() != fileSize)
542    {
543      PrintMessage("Error: incorrect uncompressed size in header");
544      return 1;
545    }
546  }
547  if (outStreamSpec != NULL)
548  {
549    if (outStreamSpec->Close() != S_OK)
550    {
551      PrintMessage("File closing error");
552      return 1;
553    }
554  }
555  return 0;
556}
557
558int MY_CDECL main(int numArgs, const char *args[])
559{
560  try { return main2(numArgs, args); }
561  catch (const char *s)
562  {
563    fprintf(stderr, "\nError: %s\n", s);
564    return 1;
565  }
566  catch(...)
567  {
568    PrintMessage("\nError\n");
569    return 1;
570  }
571}
572