5 #include "Common/ComTry.h"
\r
6 #include "Common/Defs.h"
\r
7 #include "Common/StringConvert.h"
\r
8 #include "Common/UTFConvert.h"
\r
10 #include "Windows/PropVariant.h"
\r
11 #include "Windows/Time.h"
\r
13 #include "../../Common/LimitedStreams.h"
\r
14 #include "../../Common/ProgressUtils.h"
\r
15 #include "../../Common/StreamUtils.h"
\r
17 #include "../../Compress/CopyCoder.h"
\r
18 #include "../../Compress/LzxDecoder.h"
\r
20 #include "../Common/ItemNameUtils.h"
\r
22 #include "ChmHandler.h"
\r
24 using namespace NWindows;
\r
25 using namespace NTime;
\r
27 namespace NArchive {
\r
30 // #define _CHM_DETAILS
\r
36 kpidSection = kpidUserDefined
\r
41 STATPROPSTG kProps[] =
\r
43 { NULL, kpidPath, VT_BSTR},
\r
44 { NULL, kpidSize, VT_UI8},
\r
45 { NULL, kpidMethod, VT_BSTR},
\r
46 { NULL, kpidBlock, VT_UI4}
\r
50 { L"Section", kpidSection, VT_UI4},
\r
51 { NULL, kpidOffset, VT_UI4}
\r
55 STATPROPSTG kArcProps[] =
\r
57 { NULL, kpidNumBlocks, VT_UI8}
\r
60 IMP_IInArchive_Props
\r
62 IMP_IInArchive_ArcProps_NO
\r
64 IMP_IInArchive_ArcProps
\r
66 STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)
\r
69 NWindows::NCOM::CPropVariant prop;
\r
74 UInt64 numBlocks = 0;
\r
75 for (int i = 0; i < m_Database.Sections.Size(); i++)
\r
77 const CSectionInfo &s = m_Database.Sections[i];
\r
78 for (int j = 0; j < s.Methods.Size(); j++)
\r
80 const CMethodInfo &m = s.Methods[j];
\r
82 numBlocks += m.LzxInfo.ResetTable.GetNumBlocks();
\r
95 STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)
\r
98 NWindows::NCOM::CPropVariant prop;
\r
99 if (m_Database.NewFormat)
\r
104 prop = (UInt64)m_Database.NewFormatString.Length();
\r
107 prop.Detach(value);
\r
111 if (m_Database.LowLevel)
\r
112 entryIndex = index;
\r
114 entryIndex = m_Database.Indices[index];
\r
115 const CItem &item = m_Database.Items[entryIndex];
\r
121 if (ConvertUTF8ToUnicode(item.Name, us))
\r
123 if (!m_Database.LowLevel)
\r
125 if (us.Length() > 1)
\r
129 prop = NItemName::GetOSName2(us);
\r
133 case kpidIsDir: prop = item.IsDir(); break;
\r
134 case kpidSize: prop = item.Size; break;
\r
138 if (item.Section == 0)
\r
140 else if (item.Section < m_Database.Sections.Size())
\r
141 prop = m_Database.Sections[(int)item.Section].GetMethodName();
\r
145 if (m_Database.LowLevel)
\r
146 prop = item.Section;
\r
147 else if (item.Section != 0)
\r
148 prop = m_Database.GetFolder(index);
\r
151 #ifdef _CHM_DETAILS
\r
153 case kpidSection: prop = (UInt32)item.Section; break;
\r
154 case kpidOffset: prop = (UInt32)item.Offset; break;
\r
158 prop.Detach(value);
\r
163 class CProgressImp: public CProgressVirt
\r
165 CMyComPtr<IArchiveOpenCallback> _callback;
\r
167 STDMETHOD(SetTotal)(const UInt64 *numFiles);
\r
168 STDMETHOD(SetCompleted)(const UInt64 *numFiles);
\r
169 CProgressImp(IArchiveOpenCallback *callback): _callback(callback) {};
\r
172 STDMETHODIMP CProgressImp::SetTotal(const UInt64 *numFiles)
\r
175 return _callback->SetCompleted(numFiles, NULL);
\r
179 STDMETHODIMP CProgressImp::SetCompleted(const UInt64 *numFiles)
\r
182 return _callback->SetCompleted(numFiles, NULL);
\r
186 STDMETHODIMP CHandler::Open(IInStream *inStream,
\r
187 const UInt64 *maxCheckStartPosition,
\r
188 IArchiveOpenCallback * /* openArchiveCallback */)
\r
191 m_Stream.Release();
\r
194 CInArchive archive;
\r
195 // CProgressImp progressImp(openArchiveCallback);
\r
196 RINOK(archive.Open(inStream, maxCheckStartPosition, m_Database));
\r
198 if (m_Database.LowLevel)
\r
201 m_Stream = inStream;
\r
211 STDMETHODIMP CHandler::Close()
\r
213 m_Database.Clear();
\r
214 m_Stream.Release();
\r
218 class CChmFolderOutStream:
\r
219 public ISequentialOutStream,
\r
220 public CMyUnknownImp
\r
225 HRESULT Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK);
\r
226 STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize);
\r
228 UInt64 m_FolderSize;
\r
229 UInt64 m_PosInFolder;
\r
230 UInt64 m_PosInSection;
\r
231 const CRecordVector<bool> *m_ExtractStatuses;
\r
233 int m_CurrentIndex;
\r
237 const CFilesDatabase *m_Database;
\r
238 CMyComPtr<IArchiveExtractCallback> m_ExtractCallback;
\r
243 UInt64 m_RemainFileSize;
\r
244 CMyComPtr<ISequentialOutStream> m_RealOutStream;
\r
246 HRESULT OpenFile();
\r
247 HRESULT WriteEmptyFiles();
\r
250 const CFilesDatabase *database,
\r
251 IArchiveExtractCallback *extractCallback,
\r
253 HRESULT FlushCorrupted(UInt64 maxSize);
\r
256 void CChmFolderOutStream::Init(
\r
257 const CFilesDatabase *database,
\r
258 IArchiveExtractCallback *extractCallback,
\r
261 m_Database = database;
\r
262 m_ExtractCallback = extractCallback;
\r
263 m_TestMode = testMode;
\r
265 m_CurrentIndex = 0;
\r
266 m_FileIsOpen = false;
\r
269 HRESULT CChmFolderOutStream::OpenFile()
\r
271 Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? (m_TestMode ?
\r
272 NExtract::NAskMode::kTest :
\r
273 NExtract::NAskMode::kExtract) :
\r
274 NExtract::NAskMode::kSkip;
\r
275 m_RealOutStream.Release();
\r
276 RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode));
\r
277 if (!m_RealOutStream && !m_TestMode)
\r
278 askMode = NExtract::NAskMode::kSkip;
\r
279 return m_ExtractCallback->PrepareOperation(askMode);
\r
282 HRESULT CChmFolderOutStream::WriteEmptyFiles()
\r
286 for (;m_CurrentIndex < m_NumFiles; m_CurrentIndex++)
\r
288 UInt64 fileSize = m_Database->GetFileSize(m_StartIndex + m_CurrentIndex);
\r
291 HRESULT result = OpenFile();
\r
292 m_RealOutStream.Release();
\r
294 RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
\r
299 // This is WritePart function
\r
300 HRESULT CChmFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK)
\r
302 UInt32 realProcessed = 0;
\r
303 if (processedSize != NULL)
\r
304 *processedSize = 0;
\r
309 UInt32 numBytesToWrite = (UInt32)MyMin(m_RemainFileSize, (UInt64)(size));
\r
310 HRESULT res = S_OK;
\r
311 if (numBytesToWrite > 0)
\r
315 if (m_RealOutStream)
\r
317 UInt32 processedSizeLocal = 0;
\r
318 res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal);
\r
319 numBytesToWrite = processedSizeLocal;
\r
322 realProcessed += numBytesToWrite;
\r
323 if (processedSize != NULL)
\r
324 *processedSize = realProcessed;
\r
325 data = (const void *)((const Byte *)data + numBytesToWrite);
\r
326 size -= numBytesToWrite;
\r
327 m_RemainFileSize -= numBytesToWrite;
\r
328 m_PosInSection += numBytesToWrite;
\r
329 m_PosInFolder += numBytesToWrite;
\r
332 if (m_RemainFileSize == 0)
\r
334 m_RealOutStream.Release();
\r
335 RINOK(m_ExtractCallback->SetOperationResult(
\r
337 NExtract::NOperationResult::kOK:
\r
338 NExtract::NOperationResult::kDataError));
\r
339 m_FileIsOpen = false;
\r
341 if (realProcessed > 0)
\r
342 break; // with this break this function works as write part
\r
346 if (m_CurrentIndex >= m_NumFiles)
\r
348 int fullIndex = m_StartIndex + m_CurrentIndex;
\r
349 m_RemainFileSize = m_Database->GetFileSize(fullIndex);
\r
350 UInt64 fileOffset = m_Database->GetFileOffset(fullIndex);
\r
351 if (fileOffset < m_PosInSection)
\r
353 if (fileOffset > m_PosInSection)
\r
355 UInt32 numBytesToWrite = (UInt32)MyMin(fileOffset - m_PosInSection, UInt64(size));
\r
356 realProcessed += numBytesToWrite;
\r
357 if (processedSize != NULL)
\r
358 *processedSize = realProcessed;
\r
359 data = (const void *)((const Byte *)data + numBytesToWrite);
\r
360 size -= numBytesToWrite;
\r
361 m_PosInSection += numBytesToWrite;
\r
362 m_PosInFolder += numBytesToWrite;
\r
364 if (fileOffset == m_PosInSection)
\r
367 m_FileIsOpen = true;
\r
373 return WriteEmptyFiles();
\r
376 STDMETHODIMP CChmFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)
\r
378 return Write2(data, size, processedSize, true);
\r
381 HRESULT CChmFolderOutStream::FlushCorrupted(UInt64 maxSize)
\r
383 const UInt32 kBufferSize = (1 << 10);
\r
384 Byte buffer[kBufferSize];
\r
385 for (int i = 0; i < kBufferSize; i++)
\r
387 if (maxSize > m_FolderSize)
\r
388 maxSize = m_FolderSize;
\r
389 while (m_PosInFolder < maxSize)
\r
391 UInt32 size = (UInt32)MyMin(maxSize - m_PosInFolder, (UInt64)kBufferSize);
\r
392 UInt32 processedSizeLocal = 0;
\r
393 RINOK(Write2(buffer, size, &processedSizeLocal, false));
\r
394 if (processedSizeLocal == 0)
\r
401 STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems,
\r
402 Int32 testModeSpec, IArchiveExtractCallback *extractCallback)
\r
405 bool allFilesMode = (numItems == (UInt32)-1);
\r
408 numItems = m_Database.NewFormat ? 1:
\r
409 (m_Database.LowLevel ?
\r
410 m_Database.Items.Size():
\r
411 m_Database.Indices.Size());
\r
414 bool testMode = (testModeSpec != 0);
\r
416 UInt64 currentTotalSize = 0;
\r
418 NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder();
\r
419 CMyComPtr<ICompressCoder> copyCoder = copyCoderSpec;
\r
422 CLocalProgress *lps = new CLocalProgress;
\r
423 CMyComPtr<ICompressProgressInfo> progress = lps;
\r
424 lps->Init(extractCallback, false);
\r
426 CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream;
\r
427 CMyComPtr<ISequentialInStream> inStream(streamSpec);
\r
428 streamSpec->SetStream(m_Stream);
\r
430 if (m_Database.LowLevel)
\r
432 UInt64 currentItemSize = 0;
\r
433 UInt64 totalSize = 0;
\r
434 if (m_Database.NewFormat)
\r
435 totalSize = m_Database.NewFormatString.Length();
\r
437 for (i = 0; i < numItems; i++)
\r
438 totalSize += m_Database.Items[allFilesMode ? i : indices[i]].Size;
\r
439 extractCallback->SetTotal(totalSize);
\r
441 for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize)
\r
443 currentItemSize = 0;
\r
444 lps->InSize = currentTotalSize; // Change it
\r
445 lps->OutSize = currentTotalSize;
\r
447 RINOK(lps->SetCur());
\r
448 CMyComPtr<ISequentialOutStream> realOutStream;
\r
449 Int32 askMode= testMode ?
\r
450 NExtract::NAskMode::kTest :
\r
451 NExtract::NAskMode::kExtract;
\r
452 Int32 index = allFilesMode ? i : indices[i];
\r
453 RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
\r
455 if (m_Database.NewFormat)
\r
459 if (!testMode && !realOutStream)
\r
463 UInt32 size = m_Database.NewFormatString.Length();
\r
464 RINOK(WriteStream(realOutStream, (const char *)m_Database.NewFormatString, size));
\r
466 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
\r
469 const CItem &item = m_Database.Items[index];
\r
471 currentItemSize = item.Size;
\r
473 if (!testMode && !realOutStream)
\r
475 RINOK(extractCallback->PrepareOperation(askMode));
\r
476 if (item.Section != 0)
\r
478 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnSupportedMethod));
\r
484 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
\r
488 RINOK(m_Stream->Seek(m_Database.ContentOffset + item.Offset, STREAM_SEEK_SET, NULL));
\r
489 streamSpec->Init(item.Size);
\r
491 RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress));
\r
492 realOutStream.Release();
\r
493 RINOK(extractCallback->SetOperationResult((copyCoderSpec->TotalSize == item.Size) ?
\r
494 NExtract::NOperationResult::kOK:
\r
495 NExtract::NOperationResult::kDataError));
\r
500 UInt64 lastFolderIndex = ((UInt64)0 - 1);
\r
501 for (i = 0; i < numItems; i++)
\r
503 UInt32 index = allFilesMode ? i : indices[i];
\r
504 int entryIndex = m_Database.Indices[index];
\r
505 const CItem &item = m_Database.Items[entryIndex];
\r
506 UInt64 sectionIndex = item.Section;
\r
507 if (item.IsDir() || item.Size == 0)
\r
509 if (sectionIndex == 0)
\r
511 currentTotalSize += item.Size;
\r
514 const CSectionInfo §ion = m_Database.Sections[(int)item.Section];
\r
515 if (section.IsLzx())
\r
517 const CLzxInfo &lzxInfo = section.Methods[0].LzxInfo;
\r
518 UInt64 folderIndex = m_Database.GetFolder(index);
\r
519 if (lastFolderIndex == folderIndex)
\r
521 lastFolderIndex = m_Database.GetLastFolder(index);
\r
522 for (; folderIndex <= lastFolderIndex; folderIndex++)
\r
523 currentTotalSize += lzxInfo.GetFolderSize();
\r
527 RINOK(extractCallback->SetTotal(currentTotalSize));
\r
529 NCompress::NLzx::CDecoder *lzxDecoderSpec = 0;
\r
530 CMyComPtr<ICompressCoder> lzxDecoder;
\r
531 CChmFolderOutStream *chmFolderOutStream = 0;
\r
532 CMyComPtr<ISequentialOutStream> outStream;
\r
534 currentTotalSize = 0;
\r
536 CRecordVector<bool> extractStatuses;
\r
537 for (i = 0; i < numItems;)
\r
539 RINOK(extractCallback->SetCompleted(¤tTotalSize));
\r
540 UInt32 index = allFilesMode ? i : indices[i];
\r
542 int entryIndex = m_Database.Indices[index];
\r
543 const CItem &item = m_Database.Items[entryIndex];
\r
544 UInt64 sectionIndex = item.Section;
\r
545 Int32 askMode= testMode ?
\r
546 NExtract::NAskMode::kTest :
\r
547 NExtract::NAskMode::kExtract;
\r
550 CMyComPtr<ISequentialOutStream> realOutStream;
\r
551 RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
\r
552 RINOK(extractCallback->PrepareOperation(askMode));
\r
553 realOutStream.Release();
\r
554 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK));
\r
558 lps->InSize = currentTotalSize; // Change it
\r
559 lps->OutSize = currentTotalSize;
\r
561 if (item.Size == 0 || sectionIndex == 0)
\r
563 CMyComPtr<ISequentialOutStream> realOutStream;
\r
564 RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
\r
565 if (!testMode && !realOutStream)
\r
567 RINOK(extractCallback->PrepareOperation(askMode));
\r
568 Int32 opRes = NExtract::NOperationResult::kOK;
\r
569 if (!testMode && item.Size != 0)
\r
571 RINOK(m_Stream->Seek(m_Database.ContentOffset + item.Offset, STREAM_SEEK_SET, NULL));
\r
572 streamSpec->Init(item.Size);
\r
573 RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress));
\r
574 if (copyCoderSpec->TotalSize != item.Size)
\r
575 opRes = NExtract::NOperationResult::kDataError;
\r
577 realOutStream.Release();
\r
578 RINOK(extractCallback->SetOperationResult(opRes));
\r
579 currentTotalSize += item.Size;
\r
583 const CSectionInfo §ion = m_Database.Sections[(int)sectionIndex];
\r
585 if (!section.IsLzx())
\r
587 CMyComPtr<ISequentialOutStream> realOutStream;
\r
588 RINOK(extractCallback->GetStream(index, &realOutStream, askMode));
\r
589 if (!testMode && !realOutStream)
\r
591 RINOK(extractCallback->PrepareOperation(askMode));
\r
592 RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnSupportedMethod));
\r
596 const CLzxInfo &lzxInfo = section.Methods[0].LzxInfo;
\r
598 if (chmFolderOutStream == 0)
\r
600 chmFolderOutStream = new CChmFolderOutStream;
\r
601 outStream = chmFolderOutStream;
\r
604 chmFolderOutStream->Init(&m_Database, extractCallback, testMode);
\r
606 if (lzxDecoderSpec == NULL)
\r
608 lzxDecoderSpec = new NCompress::NLzx::CDecoder;
\r
609 lzxDecoder = lzxDecoderSpec;
\r
612 UInt64 folderIndex = m_Database.GetFolder(index);
\r
614 UInt64 compressedPos = m_Database.ContentOffset + section.Offset;
\r
615 UInt32 numDictBits = lzxInfo.GetNumDictBits();
\r
616 RINOK(lzxDecoderSpec->SetParams(numDictBits));
\r
618 const CItem *lastItem = &item;
\r
619 extractStatuses.Clear();
\r
620 extractStatuses.Add(true);
\r
622 for (;; folderIndex++)
\r
624 RINOK(extractCallback->SetCompleted(¤tTotalSize));
\r
626 UInt64 startPos = lzxInfo.GetFolderPos(folderIndex);
\r
627 UInt64 finishPos = lastItem->Offset + lastItem->Size;
\r
628 UInt64 limitFolderIndex = lzxInfo.GetFolder(finishPos);
\r
630 lastFolderIndex = m_Database.GetLastFolder(index);
\r
631 UInt64 folderSize = lzxInfo.GetFolderSize();
\r
632 UInt64 unPackSize = folderSize;
\r
633 if (extractStatuses.IsEmpty())
\r
634 chmFolderOutStream->m_StartIndex = index + 1;
\r
636 chmFolderOutStream->m_StartIndex = index;
\r
637 if (limitFolderIndex == folderIndex)
\r
639 for (; i < numItems; i++)
\r
641 UInt32 nextIndex = allFilesMode ? i : indices[i];
\r
642 int entryIndex = m_Database.Indices[nextIndex];
\r
643 const CItem &nextItem = m_Database.Items[entryIndex];
\r
644 if (nextItem.Section != sectionIndex)
\r
646 UInt64 nextFolderIndex = m_Database.GetFolder(nextIndex);
\r
647 if (nextFolderIndex != folderIndex)
\r
649 for (index++; index < nextIndex; index++)
\r
650 extractStatuses.Add(false);
\r
651 extractStatuses.Add(true);
\r
653 lastItem = &nextItem;
\r
654 if (nextItem.Size != 0)
\r
655 finishPos = nextItem.Offset + nextItem.Size;
\r
656 lastFolderIndex = m_Database.GetLastFolder(index);
\r
659 unPackSize = MyMin(finishPos - startPos, unPackSize);
\r
661 chmFolderOutStream->m_FolderSize = folderSize;
\r
662 chmFolderOutStream->m_PosInFolder = 0;
\r
663 chmFolderOutStream->m_PosInSection = startPos;
\r
664 chmFolderOutStream->m_ExtractStatuses = &extractStatuses;
\r
665 chmFolderOutStream->m_NumFiles = extractStatuses.Size();
\r
666 chmFolderOutStream->m_CurrentIndex = 0;
\r
669 UInt64 startBlock = lzxInfo.GetBlockIndexFromFolderIndex(folderIndex);
\r
670 const CResetTable &rt = lzxInfo.ResetTable;
\r
671 UInt32 numBlocks = (UInt32)rt.GetNumBlocks(unPackSize);
\r
672 for (UInt32 b = 0; b < numBlocks; b++)
\r
674 UInt64 completedSize = currentTotalSize + chmFolderOutStream->m_PosInSection - startPos;
\r
675 RINOK(extractCallback->SetCompleted(&completedSize));
\r
676 UInt64 bCur = startBlock + b;
\r
677 if (bCur >= rt.ResetOffsets.Size())
\r
679 UInt64 offset = rt.ResetOffsets[(int)bCur];
\r
680 UInt64 compressedSize;
\r
681 rt.GetCompressedSizeOfBlock(bCur, compressedSize);
\r
682 UInt64 rem = finishPos - chmFolderOutStream->m_PosInSection;
\r
683 if (rem > rt.BlockSize)
\r
684 rem = rt.BlockSize;
\r
685 RINOK(m_Stream->Seek(compressedPos + offset, STREAM_SEEK_SET, NULL));
\r
686 streamSpec->SetStream(m_Stream);
\r
687 streamSpec->Init(compressedSize);
\r
688 lzxDecoderSpec->SetKeepHistory(b > 0);
\r
689 HRESULT res = lzxDecoder->Code(inStream, outStream, NULL, &rem, NULL);
\r
692 if (res != S_FALSE)
\r
700 RINOK(chmFolderOutStream->FlushCorrupted(unPackSize));
\r
702 currentTotalSize += folderSize;
\r
703 if (folderIndex == lastFolderIndex)
\r
705 extractStatuses.Clear();
\r
712 STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems)
\r
714 *numItems = m_Database.NewFormat ? 1:
\r
715 (m_Database.LowLevel ?
\r
716 m_Database.Items.Size():
\r
717 m_Database.Indices.Size());
\r