5 #include "../../../C/CpuArch.h"
\r
7 #include "Common/Buffer.h"
\r
8 #include "Common/ComTry.h"
\r
9 #include "Common/StringConvert.h"
\r
11 #include "Windows/PropVariant.h"
\r
12 #include "Windows/Time.h"
\r
14 #include "../ICoder.h"
\r
16 #include "../Common/LimitedStreams.h"
\r
17 #include "../Common/ProgressUtils.h"
\r
18 #include "../Common/RegisterArc.h"
\r
19 #include "../Common/StreamUtils.h"
\r
21 #include "../Compress/CopyCoder.h"
\r
22 #include "../Compress/LzhDecoder.h"
\r
24 #include "IArchive.h"
\r
26 #include "Common/ItemNameUtils.h"
\r
28 using namespace NWindows;
\r
29 using namespace NTime;
\r
31 #define Get16(p) GetUi16(p)
\r
32 #define Get32(p) GetUi32(p)
\r
34 namespace NArchive {
\r
37 const int kMethodIdSize = 5;
\r
39 const Byte kExtIdFileName = 0x01;
\r
40 const Byte kExtIdDirName = 0x02;
\r
41 const Byte kExtIdUnixTime = 0x54;
\r
47 AString GetString() const
\r
50 for (size_t i = 0; i < Data.GetCapacity(); i++)
\r
52 char c = (char)Data[i];
\r
64 Byte Method[kMethodIdSize];
\r
70 UInt32 ModifiedTime;
\r
72 CObjectVector<CExtension> Extensions;
\r
74 bool IsValidMethod() const { return (Method[0] == '-' && Method[1] == 'l' && Method[4] == '-'); }
\r
75 bool IsLhMethod() const {return (IsValidMethod() && Method[2] == 'h'); }
\r
76 bool IsDir() const {return (IsLhMethod() && Method[3] == 'd'); }
\r
78 bool IsCopyMethod() const
\r
80 return (IsLhMethod() && Method[3] == '0') ||
\r
81 (IsValidMethod() && Method[2] == 'z' && Method[3] == '4');
\r
84 bool IsLh1GroupMethod() const
\r
96 bool IsLh4GroupMethod() const
\r
111 int GetNumDictBits() const
\r
117 case '1': return 12;
\r
118 case '2': return 13;
\r
119 case '3': return 13;
\r
120 case '4': return 12;
\r
121 case '5': return 13;
\r
122 case '6': return 15;
\r
123 case '7': return 16;
\r
128 int FindExt(Byte type) const
\r
130 for (int i = 0; i < Extensions.Size(); i++)
\r
131 if (Extensions[i].Type == type)
\r
135 bool GetUnixTime(UInt32 &value) const
\r
137 int index = FindExt(kExtIdUnixTime);
\r
142 value = ModifiedTime;
\r
147 const Byte *data = (const Byte *)(Extensions[index].Data);
\r
148 value = GetUi32(data);
\r
152 AString GetDirName() const
\r
154 int index = FindExt(kExtIdDirName);
\r
157 return Extensions[index].GetString();
\r
160 AString GetFileName() const
\r
162 int index = FindExt(kExtIdFileName);
\r
165 return Extensions[index].GetString();
\r
168 AString GetName() const
\r
170 AString dirName = GetDirName();
\r
171 const char kDirSeparator = '\\';
\r
172 // check kDirSeparator in Linux
\r
173 dirName.Replace((char)(unsigned char)0xFF, kDirSeparator);
\r
174 if (!dirName.IsEmpty() && dirName.Back() != kDirSeparator)
\r
175 dirName += kDirSeparator;
\r
176 return dirName + GetFileName();
\r
180 struct CItemEx: public CItem
\r
182 UInt64 DataPosition;
\r
187 CMyComPtr<IInStream> m_Stream;
\r
190 HRESULT ReadBytes(void *data, UInt32 size, UInt32 &processedSize);
\r
191 HRESULT CheckReadBytes(void *data, UInt32 size);
\r
193 HRESULT Open(IInStream *inStream);
\r
194 HRESULT GetNextItem(bool &filled, CItemEx &itemInfo);
\r
195 HRESULT Skip(UInt64 numBytes);
\r
198 HRESULT CInArchive::ReadBytes(void *data, UInt32 size, UInt32 &processedSize)
\r
200 size_t realProcessedSize = size;
\r
201 RINOK(ReadStream(m_Stream, data, &realProcessedSize));
\r
202 processedSize = (UInt32)realProcessedSize;
\r
203 m_Position += processedSize;
\r
207 HRESULT CInArchive::CheckReadBytes(void *data, UInt32 size)
\r
209 UInt32 processedSize;
\r
210 RINOK(ReadBytes(data, size, processedSize));
\r
211 return (processedSize == size) ? S_OK: S_FALSE;
\r
214 HRESULT CInArchive::Open(IInStream *inStream)
\r
216 RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &m_Position));
\r
217 m_Stream = inStream;
\r
221 static const Byte *ReadUInt16(const Byte *p, UInt16 &v)
\r
227 static const Byte *ReadString(const Byte *p, size_t size, AString &s)
\r
230 for (size_t i = 0; i < size; i++)
\r
240 static Byte CalcSum(const Byte *data, size_t size)
\r
243 for (size_t i = 0; i < size; i++)
\r
244 sum = (Byte)(sum + data[i]);
\r
248 HRESULT CInArchive::GetNextItem(bool &filled, CItemEx &item)
\r
252 UInt32 processedSize;
\r
253 Byte startHeader[2];
\r
254 RINOK(ReadBytes(startHeader, 2, processedSize))
\r
255 if (processedSize == 0)
\r
257 if (processedSize == 1)
\r
258 return (startHeader[0] == 0) ? S_OK: S_FALSE;
\r
259 if (startHeader[0] == 0 && startHeader[1] == 0)
\r
263 const UInt32 kBasicPartSize = 22;
\r
264 RINOK(ReadBytes(header, kBasicPartSize, processedSize));
\r
265 if (processedSize != kBasicPartSize)
\r
266 return (startHeader[0] == 0) ? S_OK: S_FALSE;
\r
268 const Byte *p = header;
\r
269 memcpy(item.Method, p, kMethodIdSize);
\r
270 if (!item.IsValidMethod())
\r
272 p += kMethodIdSize;
\r
273 item.PackSize = Get32(p);
\r
274 item.Size = Get32(p + 4);
\r
275 item.ModifiedTime = Get32(p + 8);
\r
276 item.Attributes = p[12];
\r
277 item.Level = p[13];
\r
279 if (item.Level > 2)
\r
282 if (item.Level < 2)
\r
284 headerSize = startHeader[0];
\r
285 if (headerSize < kBasicPartSize)
\r
287 UInt32 remain = headerSize - kBasicPartSize;
\r
288 RINOK(CheckReadBytes(header + kBasicPartSize, remain));
\r
289 if (startHeader[1] != CalcSum(header, headerSize))
\r
291 size_t nameLength = *p++;
\r
292 if ((p - header) + nameLength + 2 > headerSize)
\r
294 p = ReadString(p, nameLength, item.Name);
\r
297 headerSize = startHeader[0] | ((UInt32)startHeader[1] << 8);
\r
298 p = ReadUInt16(p, item.CRC);
\r
299 if (item.Level != 0)
\r
301 if (item.Level == 2)
\r
303 RINOK(CheckReadBytes(header + kBasicPartSize, 2));
\r
305 if ((size_t)(p - header) + 3 > headerSize)
\r
309 p = ReadUInt16(p, nextSize);
\r
310 while (nextSize != 0)
\r
314 if (item.Level == 1)
\r
316 if (item.PackSize < nextSize)
\r
318 item.PackSize -= nextSize;
\r
321 RINOK(CheckReadBytes(&ext.Type, 1))
\r
323 ext.Data.SetCapacity(nextSize);
\r
324 RINOK(CheckReadBytes((Byte *)ext.Data, nextSize))
\r
325 item.Extensions.Add(ext);
\r
327 RINOK(CheckReadBytes(hdr2, 2));
\r
328 ReadUInt16(hdr2, nextSize);
\r
331 item.DataPosition = m_Position;
\r
336 HRESULT CInArchive::Skip(UInt64 numBytes)
\r
339 RINOK(m_Stream->Seek(numBytes, STREAM_SEEK_CUR, &newPostion));
\r
340 m_Position += numBytes;
\r
341 if (m_Position != newPostion)
\r
352 static COsPair g_OsPairs[] =
\r
366 { 'T', "TownsOS" },
\r
368 { 'w', "Windows 95" },
\r
369 { 'W', "Windows NT" },
\r
373 static const char *kUnknownOS = "Unknown";
\r
375 static const char *GetOS(Byte osId)
\r
377 for (int i = 0; i < sizeof(g_OsPairs) / sizeof(g_OsPairs[0]); i++)
\r
378 if (g_OsPairs[i].Id == osId)
\r
379 return g_OsPairs[i].Name;
\r
383 static STATPROPSTG kProps[] =
\r
385 { NULL, kpidPath, VT_BSTR},
\r
386 { NULL, kpidIsDir, VT_BOOL},
\r
387 { NULL, kpidSize, VT_UI8},
\r
388 { NULL, kpidPackSize, VT_UI8},
\r
389 { NULL, kpidMTime, VT_FILETIME},
\r
390 // { NULL, kpidAttrib, VT_UI4},
\r
391 { NULL, kpidCRC, VT_UI4},
\r
392 { NULL, kpidMethod, VT_BSTR},
\r
393 { NULL, kpidHostOS, VT_BSTR}
\r
400 static UInt16 Table[256];
\r
401 static void InitTable();
\r
403 CCRC(): _value(0) {}
\r
404 void Init() { _value = 0; }
\r
405 void Update(const void *data, size_t size);
\r
406 UInt16 GetDigest() const { return _value; }
\r
409 static const UInt16 kCRCPoly = 0xA001;
\r
411 UInt16 CCRC::Table[256];
\r
413 void CCRC::InitTable()
\r
415 for (UInt32 i = 0; i < 256; i++)
\r
418 for (int j = 0; j < 8; j++)
\r
420 r = (r >> 1) ^ kCRCPoly;
\r
423 CCRC::Table[i] = (UInt16)r;
\r
427 class CCRCTableInit
\r
430 CCRCTableInit() { CCRC::InitTable(); }
\r
433 void CCRC::Update(const void *data, size_t size)
\r
436 const Byte *p = (const Byte *)data;
\r
437 for (; size > 0; size--, p++)
\r
438 v = (UInt16)(Table[((Byte)(v)) ^ *p] ^ (v >> 8));
\r
443 class COutStreamWithCRC:
\r
444 public ISequentialOutStream,
\r
445 public CMyUnknownImp
\r
450 STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
\r
453 CMyComPtr<ISequentialOutStream> _stream;
\r
455 void Init(ISequentialOutStream *stream)
\r
460 void ReleaseStream() { _stream.Release(); }
\r
461 UInt32 GetCRC() const { return _crc.GetDigest(); }
\r
462 void InitCRC() { _crc.Init(); }
\r
465 STDMETHODIMP COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize)
\r
467 UInt32 realProcessedSize;
\r
471 realProcessedSize = size;
\r
475 result = _stream->Write(data, size, &realProcessedSize);
\r
476 _crc.Update(data, realProcessedSize);
\r
477 if (processedSize != NULL)
\r
478 *processedSize = realProcessedSize;
\r
484 public CMyUnknownImp
\r
486 CObjectVector<CItemEx> _items;
\r
487 CMyComPtr<IInStream> _stream;
\r
489 MY_UNKNOWN_IMP1(IInArchive)
\r
490 INTERFACE_IInArchive(;)
\r
494 IMP_IInArchive_Props
\r
495 IMP_IInArchive_ArcProps_NO
\r
497 CHandler::CHandler() {}
\r
499 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
\r
501 *numItems = _items.Size();
\r
505 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
\r
508 NWindows::NCOM::CPropVariant prop;
\r
509 const CItemEx &item = _items[index];
\r
514 UString s = NItemName::WinNameToOSName(MultiByteToUnicodeString(item.GetName(), CP_OEMCP));
\r
517 if (s[s.Length() - 1] == WCHAR_PATH_SEPARATOR)
\r
518 s.Delete(s.Length() - 1);
\r
523 case kpidIsDir: prop = item.IsDir(); break;
\r
524 case kpidSize: prop = item.Size; break;
\r
525 case kpidPackSize: prop = item.PackSize; break;
\r
526 case kpidCRC: prop = (UInt32)item.CRC; break;
\r
527 case kpidHostOS: prop = GetOS(item.OsId); break;
\r
532 if (item.GetUnixTime(unixTime))
\r
533 NTime::UnixTimeToFileTime(unixTime, utc);
\r
536 FILETIME localFileTime;
\r
537 if (DosTimeToFileTime(item.ModifiedTime, localFileTime))
\r
539 if (!LocalFileTimeToFileTime(&localFileTime, &utc))
\r
540 utc.dwHighDateTime = utc.dwLowDateTime = 0;
\r
543 utc.dwHighDateTime = utc.dwLowDateTime = 0;
\r
548 // case kpidAttrib: prop = (UInt32)item.Attributes; break;
\r
551 char method2[kMethodIdSize + 1];
\r
552 method2[kMethodIdSize] = 0;
\r
553 memcpy(method2, item.Method, kMethodIdSize);
\r
558 prop.Detach(value);
\r
563 STDMETHODIMP CHandler::Open(IInStream *stream,
\r
564 const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback)
\r
570 CInArchive archive;
\r
573 bool needSetTotal = true;
\r
575 if (callback != NULL)
\r
577 RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos));
\r
578 RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL));
\r
581 RINOK(archive.Open(stream));
\r
586 HRESULT result = archive.GetNextItem(filled, item);
\r
587 if (result == S_FALSE)
\r
589 if (result != S_OK)
\r
594 archive.Skip(item.PackSize);
\r
595 if (callback != NULL)
\r
599 RINOK(callback->SetTotal(NULL, &endPos));
\r
600 needSetTotal = false;
\r
602 if (_items.Size() % 100 == 0)
\r
604 UInt64 numFiles = _items.Size();
\r
605 UInt64 numBytes = item.DataPosition;
\r
606 RINOK(callback->SetCompleted(&numFiles, &numBytes));
\r
610 if (_items.IsEmpty())
\r
623 STDMETHODIMP CHandler::Close()
\r
630 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
\r
631 Int32 testModeSpec, IArchiveExtractCallback *extractCallback)
\r
634 bool testMode = (testModeSpec != 0);
\r
635 UInt64 totalUnPacked = 0, totalPacked = 0;
\r
636 bool allFilesMode = (numItems == (UInt32)-1);
\r
638 numItems = _items.Size();
\r
642 for (i = 0; i < numItems; i++)
\r
644 const CItemEx &item = _items[allFilesMode ? i : indices[i]];
\r
645 totalUnPacked += item.Size;
\r
646 totalPacked += item.PackSize;
\r
648 RINOK(extractCallback->SetTotal(totalUnPacked));
\r
650 UInt64 currentTotalUnPacked = 0, currentTotalPacked = 0;
\r
651 UInt64 currentItemUnPacked, currentItemPacked;
\r
653 NCompress::NLzh::NDecoder::CCoder *lzhDecoderSpec = 0;
\r
654 CMyComPtr<ICompressCoder> lzhDecoder;
\r
655 CMyComPtr<ICompressCoder> lzh1Decoder;
\r
656 CMyComPtr<ICompressCoder> arj2Decoder;
\r
658 NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
\r
659 CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
\r
661 CLocalProgress *lps = new CLocalProgress;
\r
662 CMyComPtr<ICompressProgressInfo> progress = lps;
\r
663 lps->Init(extractCallback, false);
\r
665 CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
\r
666 CMyComPtr<ISequentialInStream> inStream(streamSpec);
\r
667 streamSpec->SetStream(_stream);
\r
669 for (i = 0; i < numItems; i++, currentTotalUnPacked += currentItemUnPacked,
\r
670 currentTotalPacked += currentItemPacked)
\r
672 currentItemUnPacked = 0;
\r
673 currentItemPacked = 0;
\r
675 lps->InSize = currentTotalPacked;
\r
676 lps->OutSize = currentTotalUnPacked;
\r
677 RINOK(lps->SetCur());
\r
679 CMyComPtr<ISequentialOutStream> realOutStream;
\r
681 askMode = testMode ? NExtract::NAskMode::kTest :
\r
682 NExtract::NAskMode::kExtract;
\r
683 Int32 index = allFilesMode ? i : indices[i];
\r
684 const CItemEx &item = _items[index];
\r
685 RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
\r
691 RINOK(extractCallback->PrepareOperation(askMode));
\r
692 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
\r
697 if (!testMode && !realOutStream)
\r
700 RINOK(extractCallback->PrepareOperation(askMode));
\r
701 currentItemUnPacked = item.Size;
\r
702 currentItemPacked = item.PackSize;
\r
705 COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC;
\r
706 CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
\r
707 outStreamSpec->Init(realOutStream);
\r
708 realOutStream.Release();
\r
711 _stream->Seek(item.DataPosition, STREAM_SEEK_SET, &pos);
\r
713 streamSpec->Init(item.PackSize);
\r
715 HRESULT result = S_OK;
\r
716 Int32 opRes = NExtract::NOperationResult::kOK;
\r
718 if (item.IsCopyMethod())
\r
720 result = copyCoder->Code(inStream, outStream, NULL, NULL, progress);
\r
721 if (result == S_OK && copyCoderSpec->TotalSize != item.PackSize)
\r
724 else if (item.IsLh4GroupMethod())
\r
728 lzhDecoderSpec = new NCompress::NLzh::NDecoder::CCoder;
\r
729 lzhDecoder = lzhDecoderSpec;
\r
731 lzhDecoderSpec->SetDictionary(item.GetNumDictBits());
\r
732 result = lzhDecoder->Code(inStream, outStream, NULL, ¤tItemUnPacked, progress);
\r
735 else if (item.IsLh1GroupMethod())
\r
739 lzh1DecoderSpec = new NCompress::NLzh1::NDecoder::CCoder;
\r
740 lzh1Decoder = lzh1DecoderSpec;
\r
742 lzh1DecoderSpec->SetDictionary(item.GetNumDictBits());
\r
743 result = lzh1Decoder->Code(inStream, outStream, NULL, ¤tItemUnPacked, progress);
\r
747 opRes = NExtract::NOperationResult::kUnSupportedMethod;
\r
749 if (opRes == NExtract::NOperationResult::kOK)
\r
751 if (result == S_FALSE)
\r
752 opRes = NExtract::NOperationResult::kDataError;
\r
756 if (outStreamSpec->GetCRC() != item.CRC)
\r
757 opRes = NExtract::NOperationResult::kCRCError;
\r
760 outStream.Release();
\r
761 RINOK(extractCallback->SetOperationResult(opRes));
\r
768 static IInArchive *CreateArc() { return new CHandler; }
\r
770 static CArcInfo g_ArcInfo =
\r
771 { L"Lzh", L"lzh lha", 0, 6, { '-', 'l' }, 2, false, CreateArc, 0 };
\r