// NSisHandler.cpp #include "StdAfx.h" #include "../../../../C/CpuArch.h" #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" #include "../../../Windows/PropVariant.h" #include "../../Common/ProgressUtils.h" #include "../../Common/StreamUtils.h" #include "../Common/ItemNameUtils.h" #include "NsisHandler.h" #define Get32(p) GetUi32(p) using namespace NWindows; namespace NArchive { namespace NNsis { static const char *kBcjMethod = "BCJ"; static const char *kUnknownMethod = "Unknown"; static const char * const kMethods[] = { "Copy" , "Deflate" , "BZip2" , "LZMA" }; static const Byte kProps[] = { kpidPath, kpidSize, kpidPackSize, kpidMTime, kpidAttrib, kpidMethod, kpidSolid, kpidOffset }; static const Byte kArcProps[] = { kpidMethod, kpidSolid, kpidHeadersSize, kpidEmbeddedStubSize, kpidSubType // kpidCodePage }; IMP_IInArchive_Props IMP_IInArchive_ArcProps static AString UInt32ToString(UInt32 val) { char s[16]; ConvertUInt32ToString(val, s); return s; } static AString GetStringForSizeValue(UInt32 val) { for (int i = 31; i >= 0; i--) if (((UInt32)1 << i) == val) return UInt32ToString(i); char c = 'b'; if ((val & ((1 << 20) - 1)) == 0) { val >>= 20; c = 'm'; } else if ((val & ((1 << 10) - 1)) == 0) { val >>= 10; c = 'k'; } return UInt32ToString(val) + c; } static AString GetMethod(bool useFilter, NMethodType::EEnum method, UInt32 dict) { AString s; if (useFilter) { s += kBcjMethod; s.Add_Space(); } s += ((unsigned)method < ARRAY_SIZE(kMethods)) ? kMethods[(unsigned)method] : kUnknownMethod; if (method == NMethodType::kLZMA) { s += ':'; s += GetStringForSizeValue(dict); } return s; } /* AString CHandler::GetMethod(NMethodType::EEnum method, bool useItemFilter, UInt32 dictionary) const { AString s; if (_archive.IsSolid && _archive.UseFilter || !_archive.IsSolid && useItemFilter) { s += kBcjMethod; s.Add_Space(); } s += (method < ARRAY_SIZE(kMethods)) ? kMethods[method] : kUnknownMethod; if (method == NMethodType::kLZMA) { s += ':'; s += GetStringForSizeValue(_archive.IsSolid ? _archive.DictionarySize: dictionary); } return s; } */ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { // case kpidCodePage: if (_archive.IsUnicode) prop = "UTF-16"; break; case kpidSubType: { AString s = _archive.GetFormatDescription(); if (!_archive.IsInstaller) { s.Add_Space_if_NotEmpty(); s += "(Uninstall)"; } if (!s.IsEmpty()) prop = s; break; } case kpidMethod: prop = _methodString; break; case kpidSolid: prop = _archive.IsSolid; break; case kpidOffset: prop = _archive.StartOffset; break; case kpidPhySize: prop = (UInt64)((UInt64)_archive.ExeStub.Size() + _archive.FirstHeader.ArcSize); break; case kpidEmbeddedStubSize: prop = (UInt64)_archive.ExeStub.Size(); break; case kpidHeadersSize: prop = _archive.FirstHeader.HeaderSize; break; case kpidErrorFlags: { UInt32 v = 0; if (!_archive.IsArc) v |= kpv_ErrorFlags_IsNotArc; if (_archive.IsTruncated()) v |= kpv_ErrorFlags_UnexpectedEnd; prop = v; break; } case kpidName: { AString s; #ifdef NSIS_SCRIPT if (!_archive.Name.IsEmpty()) s = _archive.Name; if (!_archive.IsInstaller) { if (!s.IsEmpty()) s += '.'; s += "Uninstall"; } #endif if (s.IsEmpty()) s = _archive.IsInstaller ? "Install" : "Uninstall"; s += (_archive.ExeStub.Size() == 0) ? ".nsis" : ".exe"; prop = _archive.ConvertToUnicode(s); break; } #ifdef NSIS_SCRIPT case kpidShortComment: { if (!_archive.BrandingText.IsEmpty()) prop = _archive.ConvertToUnicode(_archive.BrandingText); break; } #endif } prop.Detach(value); return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback * /* openArchiveCallback */) { COM_TRY_BEGIN Close(); { if (_archive.Open(stream, maxCheckStartPosition) != S_OK) return S_FALSE; { UInt32 dict = _archive.DictionarySize; if (!_archive.IsSolid) { FOR_VECTOR (i, _archive.Items) { const CItem &item = _archive.Items[i]; if (item.DictionarySize > dict) dict = item.DictionarySize; } } _methodString = GetMethod(_archive.UseFilter, _archive.Method, dict); } } return S_OK; COM_TRY_END } STDMETHODIMP CHandler::Close() { _archive.Clear(); _archive.Release(); return S_OK; } STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) { *numItems = _archive.Items.Size() #ifdef NSIS_SCRIPT + 1 + _archive.LicenseFiles.Size(); #endif ; return S_OK; } bool CHandler::GetUncompressedSize(unsigned index, UInt32 &size) const { size = 0; const CItem &item = _archive.Items[index]; if (item.Size_Defined) size = item.Size; else if (_archive.IsSolid && item.EstimatedSize_Defined) size = item.EstimatedSize; else return false; return true; } bool CHandler::GetCompressedSize(unsigned index, UInt32 &size) const { size = 0; const CItem &item = _archive.Items[index]; if (item.CompressedSize_Defined) size = item.CompressedSize; else { if (_archive.IsSolid) { if (index == 0) size = _archive.FirstHeader.GetDataSize(); else return false; } else { if (!item.IsCompressed) size = item.Size; else return false; } } return true; } STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN NCOM::CPropVariant prop; #ifdef NSIS_SCRIPT if (index >= (UInt32)_archive.Items.Size()) { if (index == (UInt32)_archive.Items.Size()) { switch (propID) { case kpidPath: prop = "[NSIS].nsi"; break; case kpidSize: case kpidPackSize: prop = (UInt64)_archive.Script.Len(); break; case kpidSolid: prop = false; break; } } else { const CLicenseFile &lic = _archive.LicenseFiles[index - (_archive.Items.Size() + 1)]; switch (propID) { case kpidPath: prop = lic.Name; break; case kpidSize: case kpidPackSize: prop = (UInt64)lic.Size; break; case kpidSolid: prop = false; break; } } } else #endif { const CItem &item = _archive.Items[index]; switch (propID) { case kpidOffset: prop = item.Pos; break; case kpidPath: { UString s = NItemName::WinNameToOSName(_archive.GetReducedName(index)); if (!s.IsEmpty()) prop = (const wchar_t *)s; break; } case kpidSize: { UInt32 size; if (GetUncompressedSize(index, size)) prop = (UInt64)size; break; } case kpidPackSize: { UInt32 size; if (GetCompressedSize(index, size)) prop = (UInt64)size; break; } case kpidMTime: { if (item.MTime.dwHighDateTime > 0x01000000 && item.MTime.dwHighDateTime < 0xFF000000) prop = item.MTime; break; } case kpidAttrib: { if (item.Attrib_Defined) prop = item.Attrib; break; } case kpidMethod: if (_archive.IsSolid) prop = _methodString; else prop = GetMethod(_archive.UseFilter, item.IsCompressed ? _archive.Method : NMethodType::kCopy, item.DictionarySize); break; case kpidSolid: prop = _archive.IsSolid; break; } } prop.Detach(value); return S_OK; COM_TRY_END } static bool UninstallerPatch(const Byte *p, size_t size, CByteBuffer &dest) { for (;;) { if (size < 4) return false; UInt32 len = Get32(p); if (len == 0) return size == 4; if (size < 8) return false; UInt32 offs = Get32(p + 4); p += 8; size -= 8; if (size < len || offs > dest.Size() || len > dest.Size() - offs) return false; memcpy(dest + offs, p, len); p += len; size -= len; } } STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) { COM_TRY_BEGIN bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) GetNumberOfItems(&numItems); if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt64 solidPosMax = 0; UInt32 i; for (i = 0; i < numItems; i++) { UInt32 index = (allFilesMode ? i : indices[i]); #ifdef NSIS_SCRIPT if (index >= _archive.Items.Size()) { if (index == _archive.Items.Size()) totalSize += _archive.Script.Len(); else totalSize += _archive.LicenseFiles[index - (_archive.Items.Size() + 1)].Size; } else #endif { UInt32 size; if (_archive.IsSolid) { GetUncompressedSize(index, size); UInt64 pos = (UInt64)_archive.GetPosOfSolidItem(index) + size; if (solidPosMax < pos) solidPosMax = pos; } else { GetCompressedSize(index, size); totalSize += size; } } } extractCallback->SetTotal(totalSize + solidPosMax); CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(extractCallback, !_archive.IsSolid); if (_archive.IsSolid) { RINOK(_archive.SeekTo_DataStreamOffset()); RINOK(_archive.InitDecoder()); _archive.Decoder.StreamPos = 0; } /* We use tempBuf for solid archives, if there is duplicate item. We don't know uncompressed size for non-solid archives, so we can't allocate exact buffer. We use tempBuf also for first part (EXE stub) of unistall.exe and tempBuf2 is used for second part (NSIS script). */ CByteBuffer tempBuf; CByteBuffer tempBuf2; /* tempPos is pos in uncompressed stream of previous item for solid archive, that was written to tempBuf */ UInt64 tempPos = (UInt64)(Int64)-1; /* prevPos is pos in uncompressed stream of previous item for solid archive. It's used for test mode (where we don't need to test same file second time */ UInt64 prevPos = (UInt64)(Int64)-1; // if there is error in solid archive, we show error for all subsequent files bool solidDataError = false; UInt64 curTotalPacked = 0, curTotalUnpacked = 0; UInt32 curPacked = 0; UInt64 curUnpacked = 0; for (i = 0; i < numItems; i++, curTotalPacked += curPacked, curTotalUnpacked += curUnpacked) { lps->InSize = curTotalPacked; lps->OutSize = curTotalUnpacked; if (_archive.IsSolid) lps->OutSize += _archive.Decoder.StreamPos; curPacked = 0; curUnpacked = 0; RINOK(lps->SetCur()); // RINOK(extractCallback->SetCompleted(¤tTotalSize)); CMyComPtr realOutStream; Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; UInt32 index = allFilesMode ? i : indices[i]; RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); bool dataError = false; #ifdef NSIS_SCRIPT if (index >= (UInt32)_archive.Items.Size()) { const void *data; size_t size; if (index == (UInt32)_archive.Items.Size()) { data = (const Byte *)_archive.Script; size = _archive.Script.Len(); } else { CLicenseFile &lic = _archive.LicenseFiles[index - (_archive.Items.Size() + 1)]; if (lic.Text.Size() != 0) data = lic.Text; else data = _archive._data + lic.Offset; size = lic.Size; } curUnpacked = size; if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); if (realOutStream) RINOK(WriteStream(realOutStream, data, size)); } else #endif { const CItem &item = _archive.Items[index]; if (!_archive.IsSolid) GetCompressedSize(index, curPacked); if (!testMode && !realOutStream) continue; RINOK(extractCallback->PrepareOperation(askMode)); dataError = solidDataError; bool needDecompress = !solidDataError; if (needDecompress) { if (testMode && _archive.IsSolid && _archive.GetPosOfSolidItem(index) == prevPos) needDecompress = false; } if (needDecompress) { bool writeToTemp = false; bool readFromTemp = false; if (!_archive.IsSolid) { RINOK(_archive.SeekToNonSolidItem(index)); } else { UInt64 pos = _archive.GetPosOfSolidItem(index); if (pos < _archive.Decoder.StreamPos) { if (pos != tempPos) solidDataError = dataError = true; readFromTemp = true; } else { HRESULT res = _archive.Decoder.SetToPos(pos, progress); if (res != S_OK) { if (res != S_FALSE) return res; solidDataError = dataError = true; } else if (!testMode && i + 1 < numItems) { UInt32 next = allFilesMode ? i + 1 : indices[i + 1]; if (next < _archive.Items.Size()) { UInt64 nextPos = _archive.GetPosOfSolidItem(next); if (nextPos == pos) { writeToTemp = true; tempPos = pos; } } } } prevPos = pos; } if (!dataError) { // UInt32 unpackSize = 0; // bool unpackSize_Defined = false; bool writeToTemp1 = writeToTemp; if (item.IsUninstaller) { // unpackSize = item.PatchSize; // unpackSize_Defined = true; if (!readFromTemp) writeToTemp = true; writeToTemp1 = writeToTemp; if (_archive.ExeStub.Size() == 0) { if (writeToTemp1 && !readFromTemp) tempBuf.Free(); writeToTemp1 = false; } } if (readFromTemp) { if (realOutStream && !item.IsUninstaller) RINOK(WriteStream(realOutStream, tempBuf, tempBuf.Size())); } else { UInt32 curUnpacked32 = 0; HRESULT res = _archive.Decoder.Decode( writeToTemp1 ? &tempBuf : NULL, item.IsUninstaller, item.PatchSize, item.IsUninstaller ? NULL : (ISequentialOutStream *)realOutStream, progress, curPacked, curUnpacked32); curUnpacked = curUnpacked32; if (_archive.IsSolid) curUnpacked = 0; if (res != S_OK) { if (res != S_FALSE) return res; dataError = true; if (_archive.IsSolid) solidDataError = true; } } } if (!dataError && item.IsUninstaller) { if (_archive.ExeStub.Size() != 0) { CByteBuffer destBuf = _archive.ExeStub; dataError = !UninstallerPatch(tempBuf, tempBuf.Size(), destBuf); if (realOutStream) RINOK(WriteStream(realOutStream, destBuf, destBuf.Size())); } if (readFromTemp) { if (realOutStream) RINOK(WriteStream(realOutStream, tempBuf2, tempBuf2.Size())); } else { UInt32 curPacked2 = 0; UInt32 curUnpacked2 = 0; HRESULT res = _archive.Decoder.Decode( writeToTemp ? &tempBuf2 : NULL, false, 0, realOutStream, progress, curPacked2, curUnpacked2); curPacked += curPacked2; if (!_archive.IsSolid) curUnpacked += curUnpacked2; if (res != S_OK) { if (res != S_FALSE) return res; dataError = true; if (_archive.IsSolid) solidDataError = true; } } } } } realOutStream.Release(); RINOK(extractCallback->SetOperationResult(dataError ? NExtract::NOperationResult::kDataError : NExtract::NOperationResult::kOK)); } return S_OK; COM_TRY_END } }}