1 // MslzHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../C/CpuArch.h"
6 
7 #include "../../Common/ComTry.h"
8 #include "../../Common/MyString.h"
9 
10 #include "../../Windows/PropVariant.h"
11 
12 #include "../Common/InBuffer.h"
13 #include "../Common/ProgressUtils.h"
14 #include "../Common/RegisterArc.h"
15 #include "../Common/StreamUtils.h"
16 
17 #include "Common/DummyOutStream.h"
18 
19 namespace NArchive {
20 namespace NMslz {
21 
22 static const UInt32 kUnpackSizeMax = 0xFFFFFFE0;
23 
24 class CHandler:
25   public IInArchive,
26   public IArchiveOpenSeq,
27   public CMyUnknownImp
28 {
29   CMyComPtr<IInStream> _inStream;
30   CMyComPtr<ISequentialInStream> _seqStream;
31 
32   bool _isArc;
33   bool _needSeekToStart;
34   bool _dataAfterEnd;
35   bool _needMoreInput;
36 
37   bool _packSize_Defined;
38   bool _unpackSize_Defined;
39 
40   UInt32 _unpackSize;
41   UInt64 _packSize;
42   UInt64 _originalFileSize;
43   UString _name;
44 
45   void ParseName(Byte replaceByte, IArchiveOpenCallback *callback);
46 public:
47   MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq)
48   INTERFACE_IInArchive(;)
49   STDMETHOD(OpenSeq)(ISequentialInStream *stream);
50 };
51 
52 static const Byte kProps[] =
53 {
54   kpidPath,
55   kpidSize,
56   kpidPackSize,
57 };
58 
59 IMP_IInArchive_Props
60 IMP_IInArchive_ArcProps_NO_Table
61 
GetNumberOfItems(UInt32 * numItems)62 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
63 {
64   *numItems = 1;
65   return S_OK;
66 }
67 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)68 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
69 {
70   COM_TRY_BEGIN
71   NWindows::NCOM::CPropVariant prop;
72   switch (propID)
73   {
74     case kpidExtension: prop = "mslz"; break;
75     case kpidIsNotArcType: prop = true; break;
76     case kpidPhySize: if (_packSize_Defined) prop = _packSize; break;
77     case kpidErrorFlags:
78     {
79       UInt32 v = 0;
80       if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;;
81       if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd;
82       if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd;
83       prop = v;
84     }
85   }
86   prop.Detach(value);
87   return S_OK;
88   COM_TRY_END
89 }
90 
91 
GetProperty(UInt32,PROPID propID,PROPVARIANT * value)92 STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)
93 {
94   COM_TRY_BEGIN
95   NWindows::NCOM::CPropVariant prop;
96   switch (propID)
97   {
98     case kpidPath: if (!_name.IsEmpty()) prop = _name; break;
99     case kpidSize: if (_unpackSize_Defined || _inStream) prop = _unpackSize; break;
100     case kpidPackSize: if (_packSize_Defined || _inStream) prop = _packSize; break;
101   }
102   prop.Detach(value);
103   return S_OK;
104   COM_TRY_END
105 }
106 
107 static const unsigned kSignatureSize = 9;
108 static const unsigned kHeaderSize = kSignatureSize + 1 + 4;
109 #define MSLZ_SIGNATURE { 0x53, 0x5A, 0x44, 0x44, 0x88, 0xF0, 0x27, 0x33, 0x41 }
110 // old signature: 53 5A 20 88 F0 27 33
111 static const Byte kSignature[kSignatureSize] = MSLZ_SIGNATURE;
112 
113 // we support only 3 chars strings here
114 static const char * const g_Exts[] =
115 {
116     "bin"
117   , "dll"
118   , "exe"
119   , "kmd"
120   , "pdf"
121   , "sys"
122 };
123 
ParseName(Byte replaceByte,IArchiveOpenCallback * callback)124 void CHandler::ParseName(Byte replaceByte, IArchiveOpenCallback *callback)
125 {
126   if (!callback)
127     return;
128   CMyComPtr<IArchiveOpenVolumeCallback> volumeCallback;
129   callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volumeCallback);
130   if (!volumeCallback)
131     return;
132 
133   NWindows::NCOM::CPropVariant prop;
134   if (volumeCallback->GetProperty(kpidName, &prop) != S_OK || prop.vt != VT_BSTR)
135     return;
136 
137   UString s = prop.bstrVal;
138   if (s.IsEmpty() ||
139       s.Back() != L'_')
140     return;
141 
142   s.DeleteBack();
143   _name = s;
144 
145   if (replaceByte == 0)
146   {
147     if (s.Len() < 3 || s[s.Len() - 3] != '.')
148       return;
149     for (unsigned i = 0; i < ARRAY_SIZE(g_Exts); i++)
150     {
151       const char *ext = g_Exts[i];
152       if (s[s.Len() - 2] == (Byte)ext[0] &&
153           s[s.Len() - 1] == (Byte)ext[1])
154       {
155         replaceByte = ext[2];
156         break;
157       }
158     }
159   }
160 
161   if (replaceByte >= 0x20 && replaceByte < 0x80)
162     _name += (char)replaceByte;
163 }
164 
Open(IInStream * stream,const UInt64 *,IArchiveOpenCallback * callback)165 STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */,
166     IArchiveOpenCallback *callback)
167 {
168   COM_TRY_BEGIN
169   {
170     Close();
171     _needSeekToStart = true;
172     Byte buffer[kHeaderSize];
173     RINOK(ReadStream_FALSE(stream, buffer, kHeaderSize));
174     if (memcmp(buffer, kSignature, kSignatureSize) != 0)
175       return S_FALSE;
176     _unpackSize = GetUi32(buffer + 10);
177     if (_unpackSize > kUnpackSizeMax)
178       return S_FALSE;
179     RINOK(stream->Seek(0, STREAM_SEEK_END, &_originalFileSize));
180     _packSize = _originalFileSize;
181 
182     ParseName(buffer[kSignatureSize], callback);
183 
184     _isArc = true;
185     _unpackSize_Defined = true;
186     _inStream = stream;
187     _seqStream = stream;
188   }
189   return S_OK;
190   COM_TRY_END
191 }
192 
Close()193 STDMETHODIMP CHandler::Close()
194 {
195   _originalFileSize = 0;
196   _packSize = 0;
197   _unpackSize = 0;
198 
199   _isArc = false;
200   _needSeekToStart = false;
201   _dataAfterEnd = false;
202   _needMoreInput = false;
203 
204   _packSize_Defined = false;
205   _unpackSize_Defined =  false;
206 
207   _seqStream.Release();
208   _inStream.Release();
209   _name.Empty();
210   return S_OK;
211 }
212 
213 // MslzDec is modified LZSS algorithm of Haruhiko Okumura:
214 //   maxLen = 18; Okumura
215 //   maxLen = 16; MS
216 
217 #define PROGRESS_AND_WRITE \
218   if ((dest & kMask) == 0) { if (outStream) RINOK(WriteStream(outStream, buf, kBufSize)); \
219     if ((dest & ((1 << 20) - 1)) == 0) \
220     if (progress) \
221       { UInt64 inSize = inStream.GetProcessedSize(); UInt64 outSize = dest; \
222         RINOK(progress->SetRatioInfo(&inSize, &outSize)); }}
223 
MslzDec(CInBuffer & inStream,ISequentialOutStream * outStream,UInt32 unpackSize,bool & needMoreData,ICompressProgressInfo * progress)224 static HRESULT MslzDec(CInBuffer &inStream, ISequentialOutStream *outStream, UInt32 unpackSize, bool &needMoreData, ICompressProgressInfo *progress)
225 {
226   const unsigned kBufSize = (1 << 12);
227   const unsigned kMask = kBufSize - 1;
228   Byte buf[kBufSize];
229   UInt32 dest = 0;
230   memset(buf, ' ', kBufSize);
231 
232   while (dest < unpackSize)
233   {
234     Byte b;
235     if (!inStream.ReadByte(b))
236     {
237       needMoreData = true;
238       return S_FALSE;
239     }
240 
241     for (unsigned mask = (unsigned)b | 0x100; mask > 1 && dest < unpackSize; mask >>= 1)
242     {
243       if (!inStream.ReadByte(b))
244       {
245         needMoreData = true;
246         return S_FALSE;
247       }
248 
249       if (mask & 1)
250       {
251         buf[dest++ & kMask] = b;
252         PROGRESS_AND_WRITE
253       }
254       else
255       {
256         Byte b1;
257         if (!inStream.ReadByte(b1))
258         {
259           needMoreData = true;
260           return S_FALSE;
261         }
262         const unsigned kMaxLen = 16; // 18 in Okumura's code.
263         unsigned src = (((((unsigned)b1 & 0xF0) << 4) | b) + kMaxLen) & kMask;
264         unsigned len = (b1 & 0xF) + 3;
265         if (len > kMaxLen || dest + len > unpackSize)
266           return S_FALSE;
267 
268         do
269         {
270           buf[dest++ & kMask] = buf[src++ & kMask];
271           PROGRESS_AND_WRITE
272         }
273         while (--len != 0);
274       }
275     }
276   }
277 
278   if (outStream)
279     RINOK(WriteStream(outStream, buf, dest & kMask));
280   return S_OK;
281 }
282 
OpenSeq(ISequentialInStream * stream)283 STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream)
284 {
285   COM_TRY_BEGIN
286   Close();
287   _isArc = true;
288   _seqStream = stream;
289   return S_OK;
290   COM_TRY_END
291 }
292 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testMode,IArchiveExtractCallback * extractCallback)293 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
294     Int32 testMode, IArchiveExtractCallback *extractCallback)
295 {
296   COM_TRY_BEGIN
297   if (numItems == 0)
298     return S_OK;
299   if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0))
300     return E_INVALIDARG;
301 
302   // extractCallback->SetTotal(_unpackSize);
303 
304   CMyComPtr<ISequentialOutStream> realOutStream;
305   Int32 askMode = testMode ?
306       NExtract::NAskMode::kTest :
307       NExtract::NAskMode::kExtract;
308   RINOK(extractCallback->GetStream(0, &realOutStream, askMode));
309   if (!testMode && !realOutStream)
310     return S_OK;
311 
312   extractCallback->PrepareOperation(askMode);
313 
314   CDummyOutStream *outStreamSpec = new CDummyOutStream;
315   CMyComPtr<ISequentialOutStream> outStream(outStreamSpec);
316   outStreamSpec->SetStream(realOutStream);
317   outStreamSpec->Init();
318   realOutStream.Release();
319 
320   CLocalProgress *lps = new CLocalProgress;
321   CMyComPtr<ICompressProgressInfo> progress = lps;
322   lps->Init(extractCallback, false);
323 
324   if (_needSeekToStart)
325   {
326     if (!_inStream)
327       return E_FAIL;
328     RINOK(_inStream->Seek(0, STREAM_SEEK_SET, NULL));
329   }
330   else
331     _needSeekToStart = true;
332 
333   Int32 opRes = NExtract::NOperationResult::kDataError;
334 
335   bool isArc = false;
336   bool needMoreInput = false;
337   try
338   {
339     CInBuffer s;
340     if (!s.Create(1 << 20))
341       return E_OUTOFMEMORY;
342     s.SetStream(_seqStream);
343     s.Init();
344 
345     Byte buffer[kHeaderSize];
346     if (s.ReadBytes(buffer, kHeaderSize) == kHeaderSize)
347     {
348       UInt32 unpackSize;
349       if (memcmp(buffer, kSignature, kSignatureSize) == 0)
350       {
351         unpackSize = GetUi32(buffer + 10);
352         if (unpackSize <= kUnpackSizeMax)
353         {
354           HRESULT result = MslzDec(s, outStream, unpackSize, needMoreInput, progress);
355           if (result == S_OK)
356             opRes = NExtract::NOperationResult::kOK;
357           else if (result != S_FALSE)
358             return result;
359           _unpackSize = unpackSize;
360           _unpackSize_Defined = true;
361 
362           _packSize = s.GetProcessedSize();
363           _packSize_Defined = true;
364 
365           if (_inStream && _packSize < _originalFileSize)
366             _dataAfterEnd = true;
367 
368           isArc = true;
369         }
370       }
371     }
372   }
373   catch (CInBufferException &e) { return e.ErrorCode; }
374 
375   _isArc = isArc;
376   if (isArc)
377     _needMoreInput = needMoreInput;
378   if (!_isArc)
379     opRes = NExtract::NOperationResult::kIsNotArc;
380   else if (_needMoreInput)
381     opRes = NExtract::NOperationResult::kUnexpectedEnd;
382   else if (_dataAfterEnd)
383     opRes = NExtract::NOperationResult::kDataAfterEnd;
384 
385   outStream.Release();
386   return extractCallback->SetOperationResult(opRes);
387   COM_TRY_END
388 }
389 
390 REGISTER_ARC_I(
391   "MsLZ", "mslz", 0, 0xD5,
392   kSignature,
393   0,
394   0,
395   NULL)
396 
397 }}
398