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, ¤tItemUnPacked, 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, ¤tItemUnPacked, 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