1 // LzhHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/MyBuffer.h"
9 #include "../../Common/StringConvert.h"
10 
11 #include "../../Windows/PropVariant.h"
12 #include "../../Windows/PropVariantUtils.h"
13 #include "../../Windows/TimeUtils.h"
14 
15 #include "../ICoder.h"
16 
17 #include "../Common/LimitedStreams.h"
18 #include "../Common/ProgressUtils.h"
19 #include "../Common/RegisterArc.h"
20 #include "../Common/StreamUtils.h"
21 
22 #include "../Compress/CopyCoder.h"
23 #include "../Compress/LzhDecoder.h"
24 
25 #include "IArchive.h"
26 
27 #include "Common/ItemNameUtils.h"
28 
29 using namespace NWindows;
30 using namespace NTime;
31 
32 #define Get16(p) GetUi16(p)
33 #define Get32(p) GetUi32(p)
34 
35 
36 // CRC-16 (-IBM, -ANSI). The poly is 0x8005 (x^16 + x^15 + x^2 + 1)
37 
38 static const UInt16 kCrc16Poly = 0xA001;
39 
40 static UInt16 g_LzhCrc16Table[256];
41 
42 #define CRC16_UPDATE_BYTE(crc, b) (g_LzhCrc16Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8))
43 
44 UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size);
LzhCrc16Update(UInt32 crc,const void * data,size_t size)45 UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size)
46 {
47   const Byte *p = (const Byte *)data;
48   const Byte *pEnd = p + size;
49   for (; p != pEnd; p++)
50     crc = CRC16_UPDATE_BYTE(crc, *p);
51   return crc;
52 }
53 
54 static class CLzhCrc16TableInit
55 {
56 public:
CLzhCrc16TableInit()57   CLzhCrc16TableInit()
58   {
59     for (UInt32 i = 0; i < 256; i++)
60     {
61       UInt32 r = i;
62       for (unsigned j = 0; j < 8; j++)
63         r = (r >> 1) ^ (kCrc16Poly & ((UInt32)0 - (r & 1)));
64       g_LzhCrc16Table[i] = (UInt16)r;
65     }
66   }
67 } g_LzhCrc16TableInit;
68 
69 
70 namespace NArchive {
71 namespace NLzh{
72 
73 const unsigned kMethodIdSize = 5;
74 
75 const Byte kExtIdFileName = 0x01;
76 const Byte kExtIdDirName  = 0x02;
77 const Byte kExtIdUnixTime = 0x54;
78 
79 struct CExtension
80 {
81   Byte Type;
82   CByteBuffer Data;
83 
GetStringNArchive::NLzh::CExtension84   AString GetString() const
85   {
86     AString s;
87     s.SetFrom_CalcLen((const char *)(const Byte *)Data, (unsigned)Data.Size());
88     return s;
89   }
90 };
91 
92 const UInt32 kBasicPartSize = 22;
93 
IsArc_Lzh(const Byte * p,size_t size)94 API_FUNC_static_IsArc IsArc_Lzh(const Byte *p, size_t size)
95 {
96   if (size < 2 + kBasicPartSize)
97     return k_IsArc_Res_NEED_MORE;
98   if (p[2] != '-' || p[3] != 'l'  || p[4] != 'h' || p[6] != '-')
99     return k_IsArc_Res_NO;
100   Byte n = p[5];
101   if (n != 'd')
102     if (n < '0' || n > '7')
103       return k_IsArc_Res_NO;
104   return k_IsArc_Res_YES;
105 }
106 }
107 
108 struct CItem
109 {
110   AString Name;
111   Byte Method[kMethodIdSize];
112   Byte Attributes;
113   Byte Level;
114   Byte OsId;
115   UInt32 PackSize;
116   UInt32 Size;
117   UInt32 ModifiedTime;
118   UInt16 CRC;
119   CObjectVector<CExtension> Extensions;
120 
IsValidMethodNArchive::CItem121   bool IsValidMethod() const  { return (Method[0] == '-' && Method[1] == 'l' && Method[4] == '-'); }
IsLhMethodNArchive::CItem122   bool IsLhMethod() const  {return (IsValidMethod() && Method[2] == 'h'); }
IsDirNArchive::CItem123   bool IsDir() const {return (IsLhMethod() && Method[3] == 'd'); }
124 
IsCopyMethodNArchive::CItem125   bool IsCopyMethod() const
126   {
127     return (IsLhMethod() && Method[3] == '0') ||
128       (IsValidMethod() && Method[2] == 'z' && Method[3] == '4');
129   }
130 
IsLh1GroupMethodNArchive::CItem131   bool IsLh1GroupMethod() const
132   {
133     if (!IsLhMethod())
134       return false;
135     switch (Method[3])
136     {
137       case '1':
138         return true;
139     }
140     return false;
141   }
142 
IsLh4GroupMethodNArchive::CItem143   bool IsLh4GroupMethod() const
144   {
145     if (!IsLhMethod())
146       return false;
147     switch (Method[3])
148     {
149       case '4':
150       case '5':
151       case '6':
152       case '7':
153         return true;
154     }
155     return false;
156   }
157 
GetNumDictBitsNArchive::CItem158   unsigned GetNumDictBits() const
159   {
160     if (!IsLhMethod())
161       return 0;
162     switch (Method[3])
163     {
164       case '1': return 12;
165       case '2': return 13;
166       case '3': return 13;
167       case '4': return 12;
168       case '5': return 13;
169       case '6': return 15;
170       case '7': return 16;
171     }
172     return 0;
173   }
174 
FindExtNArchive::CItem175   int FindExt(Byte type) const
176   {
177     FOR_VECTOR (i, Extensions)
178       if (Extensions[i].Type == type)
179         return i;
180     return -1;
181   }
182 
GetUnixTimeNArchive::CItem183   bool GetUnixTime(UInt32 &value) const
184   {
185     value = 0;
186     int index = FindExt(kExtIdUnixTime);
187     if (index < 0
188         || Extensions[index].Data.Size() < 4)
189     {
190       if (Level == 2)
191       {
192         value = ModifiedTime;
193         return true;
194       }
195       return false;
196     }
197     const Byte *data = (const Byte *)(Extensions[index].Data);
198     value = GetUi32(data);
199     return true;
200   }
201 
GetDirNameNArchive::CItem202   AString GetDirName() const
203   {
204     int index = FindExt(kExtIdDirName);
205     if (index < 0)
206       return AString();
207     return Extensions[index].GetString();
208   }
209 
GetFileNameNArchive::CItem210   AString GetFileName() const
211   {
212     int index = FindExt(kExtIdFileName);
213     if (index < 0)
214       return Name;
215     return Extensions[index].GetString();
216   }
217 
GetNameNArchive::CItem218   AString GetName() const
219   {
220     AString s (GetDirName());
221     const char kDirSeparator = '\\';
222     // check kDirSeparator in Linux
223     s.Replace((char)(unsigned char)0xFF, kDirSeparator);
224     if (!s.IsEmpty() && s.Back() != kDirSeparator)
225       s += kDirSeparator;
226     s += GetFileName();
227     return s;
228   }
229 };
230 
ReadUInt16(const Byte * p,UInt16 & v)231 static const Byte *ReadUInt16(const Byte *p, UInt16 &v)
232 {
233   v = Get16(p);
234   return p + 2;
235 }
236 
ReadString(const Byte * p,size_t size,AString & s)237 static const Byte *ReadString(const Byte *p, size_t size, AString &s)
238 {
239   s.Empty();
240   for (size_t i = 0; i < size; i++)
241   {
242     char c = p[i];
243     if (c == 0)
244       break;
245     s += c;
246   }
247   return p + size;
248 }
249 
CalcSum(const Byte * data,size_t size)250 static Byte CalcSum(const Byte *data, size_t size)
251 {
252   Byte sum = 0;
253   for (size_t i = 0; i < size; i++)
254     sum = (Byte)(sum + data[i]);
255   return sum;
256 }
257 
GetNextItem(ISequentialInStream * stream,bool & filled,CItem & item)258 static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &item)
259 {
260   filled = false;
261 
262   size_t processedSize = 2;
263   Byte startHeader[2];
264   RINOK(ReadStream(stream, startHeader, &processedSize))
265   if (processedSize == 0)
266     return S_OK;
267   if (processedSize == 1)
268     return (startHeader[0] == 0) ? S_OK: S_FALSE;
269   if (startHeader[0] == 0 && startHeader[1] == 0)
270     return S_OK;
271 
272   Byte header[256];
273   processedSize = kBasicPartSize;
274   RINOK(ReadStream(stream, header, &processedSize));
275   if (processedSize != kBasicPartSize)
276     return (startHeader[0] == 0) ? S_OK: S_FALSE;
277 
278   const Byte *p = header;
279   memcpy(item.Method, p, kMethodIdSize);
280   if (!item.IsValidMethod())
281     return S_OK;
282   p += kMethodIdSize;
283   item.PackSize = Get32(p);
284   item.Size = Get32(p + 4);
285   item.ModifiedTime = Get32(p + 8);
286   item.Attributes = p[12];
287   item.Level = p[13];
288   p += 14;
289   if (item.Level > 2)
290     return S_FALSE;
291   UInt32 headerSize;
292   if (item.Level < 2)
293   {
294     headerSize = startHeader[0];
295     if (headerSize < kBasicPartSize)
296       return S_FALSE;
297     RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, headerSize - kBasicPartSize));
298     if (startHeader[1] != CalcSum(header, headerSize))
299       return S_FALSE;
300     size_t nameLength = *p++;
301     if ((p - header) + nameLength + 2 > headerSize)
302       return S_FALSE;
303     p = ReadString(p, nameLength, item.Name);
304   }
305   else
306     headerSize = startHeader[0] | ((UInt32)startHeader[1] << 8);
307   p = ReadUInt16(p, item.CRC);
308   if (item.Level != 0)
309   {
310     if (item.Level == 2)
311     {
312       RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, 2));
313     }
314     if ((size_t)(p - header) + 3 > headerSize)
315       return S_FALSE;
316     item.OsId = *p++;
317     UInt16 nextSize;
318     p = ReadUInt16(p, nextSize);
319     while (nextSize != 0)
320     {
321       if (nextSize < 3)
322         return S_FALSE;
323       if (item.Level == 1)
324       {
325         if (item.PackSize < nextSize)
326           return S_FALSE;
327         item.PackSize -= nextSize;
328       }
329       if (item.Extensions.Size() >= (1 << 8))
330         return S_FALSE;
331       CExtension ext;
332       RINOK(ReadStream_FALSE(stream, &ext.Type, 1))
333       nextSize = (UInt16)(nextSize - 3);
334       ext.Data.Alloc(nextSize);
335       RINOK(ReadStream_FALSE(stream, (Byte *)ext.Data, nextSize))
336       item.Extensions.Add(ext);
337       Byte hdr2[2];
338       RINOK(ReadStream_FALSE(stream, hdr2, 2));
339       ReadUInt16(hdr2, nextSize);
340     }
341   }
342   filled = true;
343   return S_OK;
344 }
345 
346 
347 static const CUInt32PCharPair g_OsPairs[] =
348 {
349   {   0, "MS-DOS" },
350   { 'M', "MS-DOS" },
351   { '2', "OS/2" },
352   { '9', "OS9" },
353   { 'K', "OS/68K" },
354   { '3', "OS/386" },
355   { 'H', "HUMAN" },
356   { 'U', "UNIX" },
357   { 'C', "CP/M" },
358   { 'F', "FLEX" },
359   { 'm', "Mac" },
360   { 'R', "Runser" },
361   { 'T', "TownsOS" },
362   { 'X', "XOSK" },
363   { 'w', "Windows 95" },
364   { 'W', "Windows NT" },
365   { 'J', "Java VM" }
366 };
367 
368 
369 static const Byte kProps[] =
370 {
371   kpidPath,
372   kpidIsDir,
373   kpidSize,
374   kpidPackSize,
375   kpidMTime,
376   // kpidAttrib,
377   kpidCRC,
378   kpidMethod,
379   kpidHostOS
380 };
381 
382 
383 class COutStreamWithCRC:
384   public ISequentialOutStream,
385   public CMyUnknownImp
386 {
387 public:
388   MY_UNKNOWN_IMP
389 
390   STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
391 private:
392   UInt32 _crc;
393   CMyComPtr<ISequentialOutStream> _stream;
394 public:
Init(ISequentialOutStream * stream)395   void Init(ISequentialOutStream *stream)
396   {
397     _stream = stream;
398     _crc = 0;
399   }
ReleaseStream()400   void ReleaseStream() { _stream.Release(); }
GetCRC() const401   UInt32 GetCRC() const { return _crc; }
402 };
403 
Write(const void * data,UInt32 size,UInt32 * processedSize)404 STDMETHODIMP COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize)
405 {
406   HRESULT res = S_OK;
407   if (_stream)
408     res = _stream->Write(data, size, &size);
409   _crc = LzhCrc16Update(_crc, data, size);
410   if (processedSize)
411     *processedSize = size;
412   return res;
413 }
414 
415 
416 struct CItemEx: public CItem
417 {
418   UInt64 DataPosition;
419 };
420 
421 
422 class CHandler:
423   public IInArchive,
424   public CMyUnknownImp
425 {
426   CObjectVector<CItemEx> _items;
427   CMyComPtr<IInStream> _stream;
428   UInt64 _phySize;
429   UInt32 _errorFlags;
430   bool _isArc;
431 public:
432   MY_UNKNOWN_IMP1(IInArchive)
433   INTERFACE_IInArchive(;)
434   CHandler();
435 };
436 
437 IMP_IInArchive_Props
438 IMP_IInArchive_ArcProps_NO_Table
439 
CHandler()440 CHandler::CHandler() {}
441 
GetNumberOfItems(UInt32 * numItems)442 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
443 {
444   *numItems = _items.Size();
445   return S_OK;
446 }
447 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)448 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
449 {
450   NCOM::CPropVariant prop;
451   switch (propID)
452   {
453     case kpidPhySize: prop = _phySize; break;
454 
455     case kpidErrorFlags:
456       UInt32 v = _errorFlags;
457       if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;
458       prop = v;
459       break;
460   }
461   prop.Detach(value);
462   return S_OK;
463 }
464 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)465 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
466 {
467   COM_TRY_BEGIN
468   NCOM::CPropVariant prop;
469   const CItemEx &item = _items[index];
470   switch (propID)
471   {
472     case kpidPath:
473     {
474       UString s = NItemName::WinPathToOsPath(MultiByteToUnicodeString(item.GetName(), CP_OEMCP));
475       if (!s.IsEmpty())
476       {
477         if (s.Back() == WCHAR_PATH_SEPARATOR)
478           s.DeleteBack();
479         prop = s;
480       }
481       break;
482     }
483     case kpidIsDir:  prop = item.IsDir(); break;
484     case kpidSize:   prop = item.Size; break;
485     case kpidPackSize:  prop = item.PackSize; break;
486     case kpidCRC:  prop = (UInt32)item.CRC; break;
487     case kpidHostOS:  PAIR_TO_PROP(g_OsPairs, item.OsId, prop); break;
488     case kpidMTime:
489     {
490       FILETIME utc;
491       UInt32 unixTime;
492       if (item.GetUnixTime(unixTime))
493         NTime::UnixTimeToFileTime(unixTime, utc);
494       else
495       {
496         FILETIME localFileTime;
497         if (DosTimeToFileTime(item.ModifiedTime, localFileTime))
498         {
499           if (!LocalFileTimeToFileTime(&localFileTime, &utc))
500             utc.dwHighDateTime = utc.dwLowDateTime = 0;
501         }
502         else
503           utc.dwHighDateTime = utc.dwLowDateTime = 0;
504       }
505       prop = utc;
506       break;
507     }
508     // case kpidAttrib:  prop = (UInt32)item.Attributes; break;
509     case kpidMethod:
510     {
511       char method2[kMethodIdSize + 1];
512       method2[kMethodIdSize] = 0;
513       memcpy(method2, item.Method, kMethodIdSize);
514       prop = method2;
515       break;
516     }
517   }
518   prop.Detach(value);
519   return S_OK;
520   COM_TRY_END
521 }
522 
Open(IInStream * stream,const UInt64 *,IArchiveOpenCallback * callback)523 STDMETHODIMP CHandler::Open(IInStream *stream,
524     const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback)
525 {
526   COM_TRY_BEGIN
527   Close();
528   try
529   {
530     _items.Clear();
531 
532     UInt64 endPos = 0;
533     bool needSetTotal = true;
534 
535     RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos));
536     RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
537 
538     for (;;)
539     {
540       CItemEx item;
541       bool filled;
542       HRESULT res = GetNextItem(stream, filled, item);
543       RINOK(stream->Seek(0, STREAM_SEEK_CUR, &item.DataPosition));
544       if (res == S_FALSE)
545       {
546         _errorFlags = kpv_ErrorFlags_HeadersError;
547         break;
548       }
549 
550       if (res != S_OK)
551         return S_FALSE;
552       _phySize = item.DataPosition;
553       if (!filled)
554         break;
555       _items.Add(item);
556 
557       _isArc = true;
558 
559       UInt64 newPostion;
560       RINOK(stream->Seek(item.PackSize, STREAM_SEEK_CUR, &newPostion));
561       if (newPostion > endPos)
562       {
563         _phySize = endPos;
564         _errorFlags = kpv_ErrorFlags_UnexpectedEnd;
565         break;
566       }
567       _phySize = newPostion;
568       if (callback)
569       {
570         if (needSetTotal)
571         {
572           RINOK(callback->SetTotal(NULL, &endPos));
573           needSetTotal = false;
574         }
575         if (_items.Size() % 100 == 0)
576         {
577           UInt64 numFiles = _items.Size();
578           UInt64 numBytes = item.DataPosition;
579           RINOK(callback->SetCompleted(&numFiles, &numBytes));
580         }
581       }
582     }
583     if (_items.IsEmpty())
584       return S_FALSE;
585 
586     _stream = stream;
587   }
588   catch(...)
589   {
590     return S_FALSE;
591   }
592   COM_TRY_END
593   return S_OK;
594 }
595 
Close()596 STDMETHODIMP CHandler::Close()
597 {
598   _isArc = false;
599   _phySize = 0;
600   _errorFlags = 0;
601   _items.Clear();
602   _stream.Release();
603   return S_OK;
604 }
605 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testModeSpec,IArchiveExtractCallback * extractCallback)606 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
607     Int32 testModeSpec, IArchiveExtractCallback *extractCallback)
608 {
609   COM_TRY_BEGIN
610   bool testMode = (testModeSpec != 0);
611   UInt64 totalUnPacked = 0, totalPacked = 0;
612   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
613   if (allFilesMode)
614     numItems = _items.Size();
615   if (numItems == 0)
616     return S_OK;
617   UInt32 i;
618   for (i = 0; i < numItems; i++)
619   {
620     const CItemEx &item = _items[allFilesMode ? i : indices[i]];
621     totalUnPacked += item.Size;
622     totalPacked += item.PackSize;
623   }
624   RINOK(extractCallback->SetTotal(totalUnPacked));
625 
626   UInt64 currentTotalUnPacked = 0, currentTotalPacked = 0;
627   UInt64 currentItemUnPacked, currentItemPacked;
628 
629   NCompress::NLzh::NDecoder::CCoder *lzhDecoderSpec = 0;
630   CMyComPtr<ICompressCoder> lzhDecoder;
631   // CMyComPtr<ICompressCoder> lzh1Decoder;
632   // CMyComPtr<ICompressCoder> arj2Decoder;
633 
634   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
635   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
636 
637   CLocalProgress *lps = new CLocalProgress;
638   CMyComPtr<ICompressProgressInfo> progress = lps;
639   lps->Init(extractCallback, false);
640 
641   CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
642   CMyComPtr<ISequentialInStream> inStream(streamSpec);
643   streamSpec->SetStream(_stream);
644 
645   for (i = 0; i < numItems; i++, currentTotalUnPacked += currentItemUnPacked,
646       currentTotalPacked += currentItemPacked)
647   {
648     currentItemUnPacked = 0;
649     currentItemPacked = 0;
650 
651     lps->InSize = currentTotalPacked;
652     lps->OutSize = currentTotalUnPacked;
653     RINOK(lps->SetCur());
654 
655     CMyComPtr<ISequentialOutStream> realOutStream;
656     Int32 askMode;
657     askMode = testMode ? NExtract::NAskMode::kTest :
658         NExtract::NAskMode::kExtract;
659     Int32 index = allFilesMode ? i : indices[i];
660     const CItemEx &item = _items[index];
661     RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
662 
663     if (item.IsDir())
664     {
665       // if (!testMode)
666       {
667         RINOK(extractCallback->PrepareOperation(askMode));
668         RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
669       }
670       continue;
671     }
672 
673     if (!testMode && !realOutStream)
674       continue;
675 
676     RINOK(extractCallback->PrepareOperation(askMode));
677     currentItemUnPacked = item.Size;
678     currentItemPacked = item.PackSize;
679 
680     {
681       COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC;
682       CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
683       outStreamSpec->Init(realOutStream);
684       realOutStream.Release();
685 
686       UInt64 pos;
687       _stream->Seek(item.DataPosition, STREAM_SEEK_SET, &pos);
688 
689       streamSpec->Init(item.PackSize);
690 
691       HRESULT res = S_OK;
692       Int32 opRes = NExtract::NOperationResult::kOK;
693 
694       if (item.IsCopyMethod())
695       {
696         res = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
697         if (res == S_OK && copyCoderSpec->TotalSize != item.PackSize)
698           res = S_FALSE;
699       }
700       else if (item.IsLh4GroupMethod())
701       {
702         if (!lzhDecoder)
703         {
704           lzhDecoderSpec = new NCompress::NLzh::NDecoder::CCoder;
705           lzhDecoder = lzhDecoderSpec;
706         }
707         lzhDecoderSpec->FinishMode = true;
708         lzhDecoderSpec->SetDictSize(1 << item.GetNumDictBits());
709         res = lzhDecoder->Code(inStream, outStream, NULL, &currentItemUnPacked, progress);
710         if (res == S_OK && lzhDecoderSpec->GetInputProcessedSize() != item.PackSize)
711           res = S_FALSE;
712       }
713       /*
714       else if (item.IsLh1GroupMethod())
715       {
716         if (!lzh1Decoder)
717         {
718           lzh1DecoderSpec = new NCompress::NLzh1::NDecoder::CCoder;
719           lzh1Decoder = lzh1DecoderSpec;
720         }
721         lzh1DecoderSpec->SetDictionary(item.GetNumDictBits());
722         res = lzh1Decoder->Code(inStream, outStream, NULL, &currentItemUnPacked, progress);
723       }
724       */
725       else
726         opRes = NExtract::NOperationResult::kUnsupportedMethod;
727 
728       if (opRes == NExtract::NOperationResult::kOK)
729       {
730         if (res == S_FALSE)
731           opRes = NExtract::NOperationResult::kDataError;
732         else
733         {
734           RINOK(res);
735           if (outStreamSpec->GetCRC() != item.CRC)
736             opRes = NExtract::NOperationResult::kCRCError;
737         }
738       }
739       outStream.Release();
740       RINOK(extractCallback->SetOperationResult(opRes));
741     }
742   }
743   return S_OK;
744   COM_TRY_END
745 }
746 
747 static const Byte k_Signature[] = { '-', 'l', 'h' };
748 
749 REGISTER_ARC_I(
750   "Lzh", "lzh lha", 0, 6,
751   k_Signature,
752   2,
753   0,
754   IsArc_Lzh)
755 
756 }}
757