1 // ChmHandler.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../../../Common/ComTry.h"
6 #include "../../../Common/StringConvert.h"
7 #include "../../../Common/UTFConvert.h"
8 
9 #include "../../../Windows/PropVariant.h"
10 #include "../../../Windows/TimeUtils.h"
11 
12 #include "../../Common/LimitedStreams.h"
13 #include "../../Common/ProgressUtils.h"
14 #include "../../Common/StreamUtils.h"
15 #include "../../Common/RegisterArc.h"
16 
17 #include "../../Compress/CopyCoder.h"
18 #include "../../Compress/LzxDecoder.h"
19 
20 #include "../Common/ItemNameUtils.h"
21 
22 #include "ChmHandler.h"
23 
24 using namespace NWindows;
25 using namespace NTime;
26 
27 namespace NArchive {
28 namespace NChm {
29 
30 // #define _CHM_DETAILS
31 
32 #ifdef _CHM_DETAILS
33 
34 enum
35 {
36   kpidSection = kpidUserDefined
37 };
38 
39 #endif
40 
41 static const Byte kProps[] =
42 {
43   kpidPath,
44   kpidSize,
45   kpidMethod,
46   kpidBlock
47 
48   #ifdef _CHM_DETAILS
49   ,
50   L"Section", kpidSection,
51   kpidOffset
52   #endif
53 };
54 
55 /*
56 static const Byte kArcProps[] =
57 {
58   // kpidNumBlocks,
59 };
60 */
61 
62 IMP_IInArchive_Props
63 
64 IMP_IInArchive_ArcProps_NO_Table
65 
GetArchiveProperty(PROPID propID,PROPVARIANT * value)66 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
67 {
68   // COM_TRY_BEGIN
69   NCOM::CPropVariant prop;
70   switch (propID)
71   {
72     /*
73     case kpidNumBlocks:
74     {
75       UInt64 numBlocks = 0;
76       FOR_VECTOR(i, m_Database.Sections)
77       {
78         const CSectionInfo &s = m_Database.Sections[i];
79         FOR_VECTOR(j, s.Methods)
80         {
81           const CMethodInfo &m = s.Methods[j];
82           if (m.IsLzx())
83             numBlocks += m.LzxInfo.ResetTable.GetNumBlocks();
84         }
85       }
86       prop = numBlocks;
87       break;
88     }
89     */
90     case kpidOffset: prop = m_Database.StartPosition; break;
91     case kpidPhySize: prop = m_Database.PhySize; break;
92 
93     case kpidErrorFlags: prop = m_ErrorFlags; break;
94   }
95   prop.Detach(value);
96   return S_OK;
97   // COM_TRY_END
98 }
99 
GetProperty(UInt32 index,PROPID propID,PROPVARIANT * value)100 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
101 {
102   COM_TRY_BEGIN
103   NCOM::CPropVariant prop;
104 
105   if (m_Database.NewFormat)
106   {
107     switch (propID)
108     {
109       case kpidSize:
110         prop = (UInt64)m_Database.NewFormatString.Len();
111       break;
112     }
113     prop.Detach(value);
114     return S_OK;
115   }
116 
117   unsigned entryIndex;
118   if (m_Database.LowLevel)
119     entryIndex = index;
120   else
121     entryIndex = m_Database.Indices[index];
122 
123   const CItem &item = m_Database.Items[entryIndex];
124 
125   switch (propID)
126   {
127     case kpidPath:
128     {
129       UString us;
130       // if (
131       ConvertUTF8ToUnicode(item.Name, us);
132       {
133         if (!m_Database.LowLevel)
134         {
135           if (us.Len() > 1 && us[0] == L'/')
136             us.Delete(0);
137         }
138         NItemName::ReplaceToOsSlashes_Remove_TailSlash(us);
139         prop = us;
140       }
141       break;
142     }
143     case kpidIsDir:  prop = item.IsDir(); break;
144     case kpidSize:  prop = item.Size; break;
145     case kpidMethod:
146     {
147       if (!item.IsDir())
148       {
149         if (item.Section == 0)
150           prop = "Copy";
151         else if (item.Section < m_Database.Sections.Size())
152           prop = m_Database.Sections[(unsigned)item.Section].GetMethodName();
153       }
154       break;
155     }
156     case kpidBlock:
157       if (m_Database.LowLevel)
158         prop = item.Section;
159       else if (item.Section != 0 && item.Section < m_Database.Sections.Size())
160         prop = m_Database.GetFolder(index);
161       break;
162 
163     #ifdef _CHM_DETAILS
164 
165     case kpidSection:  prop = (UInt32)item.Section; break;
166     case kpidOffset:  prop = (UInt32)item.Offset; break;
167 
168     #endif
169   }
170 
171   prop.Detach(value);
172   return S_OK;
173   COM_TRY_END
174 }
175 
176 /*
177 class CProgressImp: public CProgressVirt
178 {
179   CMyComPtr<IArchiveOpenCallback> _callback;
180 public:
181   STDMETHOD(SetTotal)(const UInt64 *numFiles);
182   STDMETHOD(SetCompleted)(const UInt64 *numFiles);
183   CProgressImp(IArchiveOpenCallback *callback): _callback(callback) {};
184 };
185 
186 STDMETHODIMP CProgressImp::SetTotal(const UInt64 *numFiles)
187 {
188   if (_callback)
189     return _callback->SetCompleted(numFiles, NULL);
190   return S_OK;
191 }
192 
193 STDMETHODIMP CProgressImp::SetCompleted(const UInt64 *numFiles)
194 {
195   if (_callback)
196     return _callback->SetCompleted(numFiles, NULL);
197   return S_OK;
198 }
199 */
200 
Open(IInStream * inStream,const UInt64 * maxCheckStartPosition,IArchiveOpenCallback *)201 STDMETHODIMP CHandler::Open(IInStream *inStream,
202     const UInt64 *maxCheckStartPosition,
203     IArchiveOpenCallback * /* openArchiveCallback */)
204 {
205   COM_TRY_BEGIN
206   Close();
207   try
208   {
209     CInArchive archive(_help2);
210     // CProgressImp progressImp(openArchiveCallback);
211     HRESULT res = archive.Open(inStream, maxCheckStartPosition, m_Database);
212     if (!archive.IsArc) m_ErrorFlags |= kpv_ErrorFlags_IsNotArc;
213     if (archive.HeadersError) m_ErrorFlags |= kpv_ErrorFlags_HeadersError;
214     if (archive.UnexpectedEnd)  m_ErrorFlags |= kpv_ErrorFlags_UnexpectedEnd;
215     if (archive.UnsupportedFeature)  m_ErrorFlags |= kpv_ErrorFlags_UnsupportedFeature;
216 
217     RINOK(res);
218     /*
219     if (m_Database.LowLevel)
220       return S_FALSE;
221     */
222     m_Stream = inStream;
223   }
224   catch(...)
225   {
226     return S_FALSE;
227   }
228   return S_OK;
229   COM_TRY_END
230 }
231 
Close()232 STDMETHODIMP CHandler::Close()
233 {
234   m_ErrorFlags = 0;
235   m_Database.Clear();
236   m_Stream.Release();
237   return S_OK;
238 }
239 
240 class CChmFolderOutStream:
241   public ISequentialOutStream,
242   public CMyUnknownImp
243 {
244 public:
245   MY_UNKNOWN_IMP
246 
247   HRESULT Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK);
248   STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
249 
250   UInt64 m_FolderSize;
251   UInt64 m_PosInFolder;
252   UInt64 m_PosInSection;
253   const CRecordVector<bool> *m_ExtractStatuses;
254   unsigned m_StartIndex;
255   unsigned m_CurrentIndex;
256   unsigned m_NumFiles;
257 
258 private:
259   const CFilesDatabase *m_Database;
260   CMyComPtr<IArchiveExtractCallback> m_ExtractCallback;
261   bool m_TestMode;
262 
263   bool m_IsOk;
264   bool m_FileIsOpen;
265   UInt64 m_RemainFileSize;
266   CMyComPtr<ISequentialOutStream> m_RealOutStream;
267 
268   HRESULT OpenFile();
269   HRESULT WriteEmptyFiles();
270 public:
271   void Init(
272     const CFilesDatabase *database,
273     IArchiveExtractCallback *extractCallback,
274     bool testMode);
275   HRESULT FlushCorrupted(UInt64 maxSize);
276 };
277 
Init(const CFilesDatabase * database,IArchiveExtractCallback * extractCallback,bool testMode)278 void CChmFolderOutStream::Init(
279     const CFilesDatabase *database,
280     IArchiveExtractCallback *extractCallback,
281     bool testMode)
282 {
283   m_Database = database;
284   m_ExtractCallback = extractCallback;
285   m_TestMode = testMode;
286 
287   m_CurrentIndex = 0;
288   m_FileIsOpen = false;
289 }
290 
OpenFile()291 HRESULT CChmFolderOutStream::OpenFile()
292 {
293   Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? (m_TestMode ?
294       NExtract::NAskMode::kTest :
295       NExtract::NAskMode::kExtract) :
296       NExtract::NAskMode::kSkip;
297   m_RealOutStream.Release();
298   RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode));
299   if (!m_RealOutStream && !m_TestMode)
300     askMode = NExtract::NAskMode::kSkip;
301   return m_ExtractCallback->PrepareOperation(askMode);
302 }
303 
WriteEmptyFiles()304 HRESULT CChmFolderOutStream::WriteEmptyFiles()
305 {
306   if (m_FileIsOpen)
307     return S_OK;
308   for (; m_CurrentIndex < m_NumFiles; m_CurrentIndex++)
309   {
310     UInt64 fileSize = m_Database->GetFileSize(m_StartIndex + m_CurrentIndex);
311     if (fileSize != 0)
312       return S_OK;
313     HRESULT result = OpenFile();
314     m_RealOutStream.Release();
315     RINOK(result);
316     RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
317   }
318   return S_OK;
319 }
320 
321 // This is WritePart function
Write2(const void * data,UInt32 size,UInt32 * processedSize,bool isOK)322 HRESULT CChmFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK)
323 {
324   UInt32 realProcessed = 0;
325   if (processedSize)
326    *processedSize = 0;
327 
328   while (size != 0)
329   {
330     if (m_FileIsOpen)
331     {
332       UInt32 numBytesToWrite = (UInt32)MyMin(m_RemainFileSize, (UInt64)(size));
333       HRESULT res = S_OK;
334       if (numBytesToWrite > 0)
335       {
336         if (!isOK)
337           m_IsOk = false;
338         if (m_RealOutStream)
339         {
340           UInt32 processedSizeLocal = 0;
341           res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal);
342           numBytesToWrite = processedSizeLocal;
343         }
344       }
345       realProcessed += numBytesToWrite;
346       if (processedSize)
347         *processedSize = realProcessed;
348       data = (const void *)((const Byte *)data + numBytesToWrite);
349       size -= numBytesToWrite;
350       m_RemainFileSize -= numBytesToWrite;
351       m_PosInSection += numBytesToWrite;
352       m_PosInFolder += numBytesToWrite;
353       if (res != S_OK)
354         return res;
355       if (m_RemainFileSize == 0)
356       {
357         m_RealOutStream.Release();
358         RINOK(m_ExtractCallback->SetOperationResult(
359           m_IsOk ?
360             NExtract::NOperationResult::kOK:
361             NExtract::NOperationResult::kDataError));
362         m_FileIsOpen = false;
363       }
364       if (realProcessed > 0)
365         break; // with this break this function works as write part
366     }
367     else
368     {
369       if (m_CurrentIndex >= m_NumFiles)
370       {
371         realProcessed += size;
372         if (processedSize)
373           *processedSize = realProcessed;
374         return S_OK;
375         // return E_FAIL;
376       }
377 
378       unsigned fullIndex = m_StartIndex + m_CurrentIndex;
379       m_RemainFileSize = m_Database->GetFileSize(fullIndex);
380       UInt64 fileOffset = m_Database->GetFileOffset(fullIndex);
381       if (fileOffset < m_PosInSection)
382         return E_FAIL;
383 
384       if (fileOffset > m_PosInSection)
385       {
386         UInt32 numBytesToWrite = (UInt32)MyMin(fileOffset - m_PosInSection, UInt64(size));
387         realProcessed += numBytesToWrite;
388         if (processedSize)
389           *processedSize = realProcessed;
390         data = (const void *)((const Byte *)data + numBytesToWrite);
391         size -= numBytesToWrite;
392         m_PosInSection += numBytesToWrite;
393         m_PosInFolder += numBytesToWrite;
394       }
395 
396       if (fileOffset == m_PosInSection)
397       {
398         RINOK(OpenFile());
399         m_FileIsOpen = true;
400         m_CurrentIndex++;
401         m_IsOk = true;
402       }
403     }
404   }
405 
406   return WriteEmptyFiles();
407 }
408 
Write(const void * data,UInt32 size,UInt32 * processedSize)409 STDMETHODIMP CChmFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
410 {
411   return Write2(data, size, processedSize, true);
412 }
413 
FlushCorrupted(UInt64 maxSize)414 HRESULT CChmFolderOutStream::FlushCorrupted(UInt64 maxSize)
415 {
416   const UInt32 kBufferSize = (1 << 10);
417   Byte buffer[kBufferSize];
418   for (unsigned i = 0; i < kBufferSize; i++)
419     buffer[i] = 0;
420   if (maxSize > m_FolderSize)
421     maxSize = m_FolderSize;
422   while (m_PosInFolder < maxSize)
423   {
424     UInt32 size = (UInt32)MyMin(maxSize - m_PosInFolder, (UInt64)kBufferSize);
425     UInt32 processedSizeLocal = 0;
426     RINOK(Write2(buffer, size, &processedSizeLocal, false));
427     if (processedSizeLocal == 0)
428       return S_OK;
429   }
430   return S_OK;
431 }
432 
433 
Extract(const UInt32 * indices,UInt32 numItems,Int32 testModeSpec,IArchiveExtractCallback * extractCallback)434 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
435     Int32 testModeSpec, IArchiveExtractCallback *extractCallback)
436 {
437   COM_TRY_BEGIN
438   bool allFilesMode = (numItems == (UInt32)(Int32)-1);
439 
440   if (allFilesMode)
441     numItems = m_Database.NewFormat ? 1:
442       (m_Database.LowLevel ?
443       m_Database.Items.Size():
444       m_Database.Indices.Size());
445   if (numItems == 0)
446     return S_OK;
447   bool testMode = (testModeSpec != 0);
448 
449   UInt64 currentTotalSize = 0;
450 
451   NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder;
452   CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
453   UInt32 i;
454 
455   CLocalProgress *lps = new CLocalProgress;
456   CMyComPtr<ICompressProgressInfo> progress = lps;
457   lps->Init(extractCallback, false);
458 
459   CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
460   CMyComPtr<ISequentialInStream> inStream(streamSpec);
461   streamSpec->SetStream(m_Stream);
462 
463   if (m_Database.LowLevel)
464   {
465     UInt64 currentItemSize = 0;
466     UInt64 totalSize = 0;
467 
468     if (m_Database.NewFormat)
469       totalSize = m_Database.NewFormatString.Len();
470     else
471       for (i = 0; i < numItems; i++)
472         totalSize += m_Database.Items[allFilesMode ? i : indices[i]].Size;
473 
474     extractCallback->SetTotal(totalSize);
475 
476     for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize)
477     {
478       currentItemSize = 0;
479       lps->InSize = currentTotalSize; // Change it
480       lps->OutSize = currentTotalSize;
481 
482       RINOK(lps->SetCur());
483       CMyComPtr<ISequentialOutStream> realOutStream;
484       Int32 askMode= testMode ?
485           NExtract::NAskMode::kTest :
486           NExtract::NAskMode::kExtract;
487       Int32 index = allFilesMode ? i : indices[i];
488       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
489 
490       if (m_Database.NewFormat)
491       {
492         if (index != 0)
493           return E_FAIL;
494         if (!testMode && !realOutStream)
495           continue;
496         if (!testMode)
497         {
498           UInt32 size = m_Database.NewFormatString.Len();
499           RINOK(WriteStream(realOutStream, (const char *)m_Database.NewFormatString, size));
500         }
501         RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
502         continue;
503       }
504 
505       const CItem &item = m_Database.Items[index];
506 
507       currentItemSize = item.Size;
508 
509       if (!testMode && !realOutStream)
510         continue;
511       RINOK(extractCallback->PrepareOperation(askMode));
512       if (item.Section != 0)
513       {
514         RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
515         continue;
516       }
517 
518       if (testMode)
519       {
520         RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
521         continue;
522       }
523 
524       RINOK(m_Stream->Seek(m_Database.ContentOffset + item.Offset, STREAM_SEEK_SET, NULL));
525       streamSpec->Init(item.Size);
526 
527       RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress));
528       realOutStream.Release();
529       RINOK(extractCallback->SetOperationResult((copyCoderSpec->TotalSize == item.Size) ?
530           NExtract::NOperationResult::kOK:
531           NExtract::NOperationResult::kDataError));
532     }
533     return S_OK;
534   }
535 
536   UInt64 lastFolderIndex = ((UInt64)0 - 1);
537 
538   for (i = 0; i < numItems; i++)
539   {
540     UInt32 index = allFilesMode ? i : indices[i];
541     const CItem &item = m_Database.Items[m_Database.Indices[index]];
542     const UInt64 sectionIndex = item.Section;
543     if (item.IsDir() || item.Size == 0)
544       continue;
545     if (sectionIndex == 0)
546     {
547       currentTotalSize += item.Size;
548       continue;
549     }
550 
551     if (sectionIndex >= m_Database.Sections.Size())
552       continue;
553 
554     const CSectionInfo &section = m_Database.Sections[(unsigned)sectionIndex];
555     if (section.IsLzx())
556     {
557       const CLzxInfo &lzxInfo = section.Methods[0].LzxInfo;
558       UInt64 folderIndex = m_Database.GetFolder(index);
559       if (lastFolderIndex == folderIndex)
560         folderIndex++;
561       lastFolderIndex = m_Database.GetLastFolder(index);
562       for (; folderIndex <= lastFolderIndex; folderIndex++)
563         currentTotalSize += lzxInfo.GetFolderSize();
564     }
565   }
566 
567   RINOK(extractCallback->SetTotal(currentTotalSize));
568 
569   NCompress::NLzx::CDecoder *lzxDecoderSpec = NULL;
570   CMyComPtr<IUnknown> lzxDecoder;
571   CChmFolderOutStream *chmFolderOutStream = 0;
572   CMyComPtr<ISequentialOutStream> outStream;
573 
574   currentTotalSize = 0;
575 
576   CRecordVector<bool> extractStatuses;
577 
578   CByteBuffer packBuf;
579 
580   for (i = 0;;)
581   {
582     RINOK(extractCallback->SetCompleted(&currentTotalSize));
583 
584     if (i >= numItems)
585       break;
586 
587     UInt32 index = allFilesMode ? i : indices[i];
588     i++;
589     const CItem &item = m_Database.Items[m_Database.Indices[index]];
590     const UInt64 sectionIndex = item.Section;
591     Int32 askMode= testMode ?
592         NExtract::NAskMode::kTest :
593         NExtract::NAskMode::kExtract;
594 
595     if (item.IsDir())
596     {
597       CMyComPtr<ISequentialOutStream> realOutStream;
598       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
599       RINOK(extractCallback->PrepareOperation(askMode));
600       realOutStream.Release();
601       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
602       continue;
603     }
604 
605     lps->InSize = currentTotalSize; // Change it
606     lps->OutSize = currentTotalSize;
607 
608     if (item.Size == 0 || sectionIndex == 0)
609     {
610       CMyComPtr<ISequentialOutStream> realOutStream;
611       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
612       if (!testMode && !realOutStream)
613         continue;
614       RINOK(extractCallback->PrepareOperation(askMode));
615       Int32 opRes = NExtract::NOperationResult::kOK;
616       if (!testMode && item.Size != 0)
617       {
618         RINOK(m_Stream->Seek(m_Database.ContentOffset + item.Offset, STREAM_SEEK_SET, NULL));
619         streamSpec->Init(item.Size);
620         RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress));
621         if (copyCoderSpec->TotalSize != item.Size)
622           opRes = NExtract::NOperationResult::kDataError;
623       }
624       realOutStream.Release();
625       RINOK(extractCallback->SetOperationResult(opRes));
626       currentTotalSize += item.Size;
627       continue;
628     }
629 
630     if (sectionIndex >= m_Database.Sections.Size())
631     {
632       // we must report error here;
633       CMyComPtr<ISequentialOutStream> realOutStream;
634       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
635       if (!testMode && !realOutStream)
636         continue;
637       RINOK(extractCallback->PrepareOperation(askMode));
638       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kHeadersError));
639       continue;
640     }
641 
642     const CSectionInfo &section = m_Database.Sections[(unsigned)sectionIndex];
643 
644     if (!section.IsLzx())
645     {
646       CMyComPtr<ISequentialOutStream> realOutStream;
647       RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
648       if (!testMode && !realOutStream)
649         continue;
650       RINOK(extractCallback->PrepareOperation(askMode));
651       RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod));
652       continue;
653     }
654 
655     const CLzxInfo &lzxInfo = section.Methods[0].LzxInfo;
656 
657     if (!chmFolderOutStream)
658     {
659       chmFolderOutStream = new CChmFolderOutStream;
660       outStream = chmFolderOutStream;
661     }
662 
663     chmFolderOutStream->Init(&m_Database, extractCallback, testMode);
664 
665     if (!lzxDecoderSpec)
666     {
667       lzxDecoderSpec = new NCompress::NLzx::CDecoder;
668       lzxDecoder = lzxDecoderSpec;
669     }
670 
671     UInt64 folderIndex = m_Database.GetFolder(index);
672 
673     const UInt64 compressedPos = m_Database.ContentOffset + section.Offset;
674     RINOK(lzxDecoderSpec->SetParams_and_Alloc(lzxInfo.GetNumDictBits()));
675 
676     const CItem *lastItem = &item;
677     extractStatuses.Clear();
678     extractStatuses.Add(true);
679 
680     for (;; folderIndex++)
681     {
682       RINOK(extractCallback->SetCompleted(&currentTotalSize));
683 
684       UInt64 startPos = lzxInfo.GetFolderPos(folderIndex);
685       UInt64 finishPos = lastItem->Offset + lastItem->Size;
686       UInt64 limitFolderIndex = lzxInfo.GetFolder(finishPos);
687 
688       lastFolderIndex = m_Database.GetLastFolder(index);
689       UInt64 folderSize = lzxInfo.GetFolderSize();
690       UInt64 unPackSize = folderSize;
691 
692       if (extractStatuses.IsEmpty())
693         chmFolderOutStream->m_StartIndex = index + 1;
694       else
695         chmFolderOutStream->m_StartIndex = index;
696 
697       if (limitFolderIndex == folderIndex)
698       {
699         for (; i < numItems; i++)
700         {
701           const UInt32 nextIndex = allFilesMode ? i : indices[i];
702           const CItem &nextItem = m_Database.Items[m_Database.Indices[nextIndex]];
703           if (nextItem.Section != sectionIndex)
704             break;
705           UInt64 nextFolderIndex = m_Database.GetFolder(nextIndex);
706           if (nextFolderIndex != folderIndex)
707             break;
708           for (index++; index < nextIndex; index++)
709             extractStatuses.Add(false);
710           extractStatuses.Add(true);
711           index = nextIndex;
712           lastItem = &nextItem;
713           if (nextItem.Size != 0)
714             finishPos = nextItem.Offset + nextItem.Size;
715           lastFolderIndex = m_Database.GetLastFolder(index);
716         }
717       }
718 
719       unPackSize = MyMin(finishPos - startPos, unPackSize);
720 
721       chmFolderOutStream->m_FolderSize = folderSize;
722       chmFolderOutStream->m_PosInFolder = 0;
723       chmFolderOutStream->m_PosInSection = startPos;
724       chmFolderOutStream->m_ExtractStatuses = &extractStatuses;
725       chmFolderOutStream->m_NumFiles = extractStatuses.Size();
726       chmFolderOutStream->m_CurrentIndex = 0;
727 
728       try
729       {
730         UInt64 startBlock = lzxInfo.GetBlockIndexFromFolderIndex(folderIndex);
731         const CResetTable &rt = lzxInfo.ResetTable;
732         UInt32 numBlocks = (UInt32)rt.GetNumBlocks(unPackSize);
733 
734         for (UInt32 b = 0; b < numBlocks; b++)
735         {
736           UInt64 completedSize = currentTotalSize + chmFolderOutStream->m_PosInSection - startPos;
737           RINOK(extractCallback->SetCompleted(&completedSize));
738           UInt64 bCur = startBlock + b;
739           if (bCur >= rt.ResetOffsets.Size())
740             return E_FAIL;
741           UInt64 offset = rt.ResetOffsets[(unsigned)bCur];
742           UInt64 compressedSize;
743           rt.GetCompressedSizeOfBlock(bCur, compressedSize);
744 
745           // chm writes full blocks. So we don't need to use reduced size for last block
746 
747           RINOK(m_Stream->Seek(compressedPos + offset, STREAM_SEEK_SET, NULL));
748           streamSpec->SetStream(m_Stream);
749           streamSpec->Init(compressedSize);
750 
751           lzxDecoderSpec->SetKeepHistory(b > 0);
752 
753           size_t compressedSizeT = (size_t)compressedSize;
754           if (compressedSizeT != compressedSize)
755             throw 2;
756           packBuf.AllocAtLeast(compressedSizeT);
757 
758           HRESULT res = ReadStream_FALSE(inStream, packBuf, compressedSizeT);
759 
760           if (res == S_OK)
761           {
762             lzxDecoderSpec->KeepHistoryForNext = true;
763             res = lzxDecoderSpec->Code(packBuf, compressedSizeT, kBlockSize); // rt.BlockSize;
764             if (res == S_OK)
765               res = WriteStream(chmFolderOutStream,
766                   lzxDecoderSpec->GetUnpackData(),
767                   lzxDecoderSpec->GetUnpackSize());
768           }
769 
770           if (res != S_OK)
771           {
772             if (res != S_FALSE)
773               return res;
774             throw 1;
775           }
776         }
777       }
778       catch(...)
779       {
780         RINOK(chmFolderOutStream->FlushCorrupted(unPackSize));
781       }
782 
783       currentTotalSize += folderSize;
784       if (folderIndex == lastFolderIndex)
785         break;
786       extractStatuses.Clear();
787     }
788   }
789   return S_OK;
790   COM_TRY_END
791 }
792 
GetNumberOfItems(UInt32 * numItems)793 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
794 {
795     *numItems = m_Database.NewFormat ? 1:
796       (m_Database.LowLevel ?
797       m_Database.Items.Size():
798       m_Database.Indices.Size());
799   return S_OK;
800 }
801 
802 namespace NChm {
803 
804 static const Byte k_Signature[] = { 'I', 'T', 'S', 'F', 3, 0, 0, 0, 0x60, 0,  0, 0 };
805 
806 REGISTER_ARC_I_CLS(
807   CHandler(false),
808   "Chm", "chm chi chq chw", 0, 0xE9,
809   k_Signature,
810   0,
811   0,
812   NULL)
813 
814 }
815 
816 namespace NHxs {
817 
818 static const Byte k_Signature[] = { 'I', 'T', 'O', 'L', 'I', 'T', 'L', 'S', 1, 0, 0, 0, 0x28, 0, 0, 0 };
819 
820 REGISTER_ARC_I_CLS(
821   CHandler(true),
822   "Hxs", "hxs hxi hxr hxq hxw lit", 0, 0xCE,
823   k_Signature,
824   0,
825   NArcInfoFlags::kFindSignature,
826   NULL)
827 
828 }
829 
830 }}
831