// OpenArchive.cpp #include "StdAfx.h" #include "Common/Wildcard.h" #include "Windows/FileDir.h" #include "Windows/PropVariant.h" #include "../../Common/FileStreams.h" #include "../../Common/StreamUtils.h" #include "DefaultName.h" #include "OpenArchive.h" using namespace NWindows; // Static-SFX (for Linux) can be big. const UInt64 kMaxCheckStartPosition = 1 << 22; HRESULT GetArchiveItemBoolProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result) { NCOM::CPropVariant prop; result = false; RINOK(archive->GetProperty(index, propID, &prop)); if (prop.vt == VT_BOOL) result = VARIANT_BOOLToBool(prop.boolVal); else if (prop.vt != VT_EMPTY) return E_FAIL; return S_OK; } HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &result) { return GetArchiveItemBoolProp(archive, index, kpidIsDir, result); } HRESULT CArc::GetItemPath(UInt32 index, UString &result) const { { NCOM::CPropVariant prop; RINOK(Archive->GetProperty(index, kpidPath, &prop)); if (prop.vt == VT_BSTR) result = prop.bstrVal; else if (prop.vt == VT_EMPTY) result.Empty(); else return E_FAIL; } if (result.IsEmpty()) { result = DefaultName; NCOM::CPropVariant prop; RINOK(Archive->GetProperty(index, kpidExtension, &prop)); if (prop.vt == VT_BSTR) { result += L'.'; result += prop.bstrVal; } else if (prop.vt != VT_EMPTY) return E_FAIL; } return S_OK; } HRESULT CArc::GetItemMTime(UInt32 index, FILETIME &ft, bool &defined) const { NCOM::CPropVariant prop; defined = false; ft.dwHighDateTime = ft.dwLowDateTime = 0; RINOK(Archive->GetProperty(index, kpidMTime, &prop)); if (prop.vt == VT_FILETIME) { ft = prop.filetime; defined = true; } else if (prop.vt != VT_EMPTY) return E_FAIL; else if (MTimeDefined) { ft = MTime; defined = true; } return S_OK; } #ifndef _SFX static inline bool TestSignature(const Byte *p1, const Byte *p2, size_t size) { for (size_t i = 0; i < size; i++) if (p1[i] != p2[i]) return false; return true; } #endif #ifdef UNDER_CE static const int kNumHashBytes = 1; #define HASH_VAL(buf, pos) ((buf)[pos]) #else static const int kNumHashBytes = 2; #define HASH_VAL(buf, pos) ((buf)[pos] | ((UInt32)(buf)[pos + 1] << 8)) #endif HRESULT CArc::OpenStream( CCodecs *codecs, int formatIndex, IInStream *stream, ISequentialInStream *seqStream, IArchiveOpenCallback *callback) { Archive.Release(); ErrorMessage.Empty(); const UString fileName = ExtractFileNameFromPath(Path); UString extension; { int dotPos = fileName.ReverseFind(L'.'); if (dotPos >= 0) extension = fileName.Mid(dotPos + 1); } CIntVector orderIndices; if (formatIndex >= 0) orderIndices.Add(formatIndex); else { int i; int numFinded = 0; for (i = 0; i < codecs->Formats.Size(); i++) if (codecs->Formats[i].FindExtension(extension) >= 0) orderIndices.Insert(numFinded++, i); else orderIndices.Add(i); if (!stream) { if (numFinded != 1) return E_NOTIMPL; orderIndices.DeleteFrom(1); } #ifndef _SFX if (orderIndices.Size() >= 2 && (numFinded == 0 || extension.CompareNoCase(L"exe") == 0)) { CIntVector orderIndices2; CByteBuffer byteBuffer; const size_t kBufferSize = (1 << 21); byteBuffer.SetCapacity(kBufferSize); RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); size_t processedSize = kBufferSize; RINOK(ReadStream(stream, byteBuffer, &processedSize)); if (processedSize == 0) return S_FALSE; const Byte *buf = byteBuffer; CByteBuffer hashBuffer; const UInt32 kNumVals = 1 << (kNumHashBytes * 8); hashBuffer.SetCapacity(kNumVals); Byte *hash = hashBuffer; memset(hash, 0xFF, kNumVals); Byte prevs[256]; if (orderIndices.Size() >= 256) return S_FALSE; int i; for (i = 0; i < orderIndices.Size(); i++) { const CArcInfoEx &ai = codecs->Formats[orderIndices[i]]; const CByteBuffer &sig = ai.StartSignature; if (sig.GetCapacity() < (unsigned int)kNumHashBytes) // PQR for MinGW-w64: Signed < Unsigned comparison. continue; UInt32 v = HASH_VAL(sig, 0); prevs[i] = hash[v]; hash[v] = (Byte)i; } processedSize -= (kNumHashBytes - 1); for (UInt32 pos = 0; pos < processedSize; pos++) { for (; pos < processedSize && hash[HASH_VAL(buf, pos)] == 0xFF; pos++); if (pos == processedSize) break; UInt32 v = HASH_VAL(buf, pos); Byte *ptr = &hash[v]; int i = *ptr; do { int index = orderIndices[i]; const CArcInfoEx &ai = codecs->Formats[index]; const CByteBuffer &sig = ai.StartSignature; if (sig.GetCapacity() != 0 && pos + sig.GetCapacity() <= processedSize + (kNumHashBytes - 1) && TestSignature(buf + pos, sig, sig.GetCapacity())) { orderIndices2.Add(index); orderIndices[i] = 0xFF; *ptr = prevs[i]; } else ptr = &prevs[i]; i = *ptr; } while (i != 0xFF); } for (i = 0; i < orderIndices.Size(); i++) { int val = orderIndices[i]; if (val != 0xFF) orderIndices2.Add(val); } orderIndices = orderIndices2; } else if (extension == L"000" || extension == L"001") { CByteBuffer byteBuffer; const size_t kBufferSize = (1 << 10); byteBuffer.SetCapacity(kBufferSize); Byte *buffer = byteBuffer; RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); size_t processedSize = kBufferSize; RINOK(ReadStream(stream, buffer, &processedSize)); if (processedSize >= 16) { Byte kRarHeader[] = {0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00}; if (TestSignature(buffer, kRarHeader, 7) && buffer[9] == 0x73 && (buffer[10] & 1) != 0) { for (int i = 0; i < orderIndices.Size(); i++) { int index = orderIndices[i]; const CArcInfoEx &ai = codecs->Formats[index]; if (ai.Name.CompareNoCase(L"rar") != 0) continue; orderIndices.Delete(i--); orderIndices.Insert(0, index); break; } } } } if (orderIndices.Size() >= 2) { int isoIndex = codecs->FindFormatForArchiveType(L"iso"); int udfIndex = codecs->FindFormatForArchiveType(L"udf"); int iIso = -1; int iUdf = -1; for (int i = 0; i < orderIndices.Size(); i++) { if (orderIndices[i] == isoIndex) iIso = i; if (orderIndices[i] == udfIndex) iUdf = i; } if (iUdf > iIso && iIso >= 0) { orderIndices[iUdf] = isoIndex; orderIndices[iIso] = udfIndex; } } #endif } for (int i = 0; i < orderIndices.Size(); i++) { if (stream) { RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); } CMyComPtr archive; FormatIndex = orderIndices[i]; RINOK(codecs->CreateInArchive(FormatIndex, archive)); if (!archive) continue; #ifdef EXTERNAL_CODECS { CMyComPtr setCompressCodecsInfo; archive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); if (setCompressCodecsInfo) { RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs)); } } #endif // OutputDebugStringW(codecs->Formats[FormatIndex].Name); HRESULT result; if (stream) result = archive->Open(stream, &kMaxCheckStartPosition, callback); else { CMyComPtr openSeq; archive.QueryInterface(IID_IArchiveOpenSeq, (void **)&openSeq); if (!openSeq) return E_NOTIMPL; result = openSeq->OpenSeq(seqStream); } if (result == S_FALSE) continue; RINOK(result); { NCOM::CPropVariant prop; archive->GetArchiveProperty(kpidError, &prop); if (prop.vt != VT_EMPTY) ErrorMessage = (prop.vt == VT_BSTR) ? prop.bstrVal : L"Unknown error"; } Archive = archive; const CArcInfoEx &format = codecs->Formats[FormatIndex]; if (format.Exts.Size() == 0) DefaultName = GetDefaultName2(fileName, L"", L""); else { int subExtIndex = format.FindExtension(extension); if (subExtIndex < 0) subExtIndex = 0; const CArcExtInfo &extInfo = format.Exts[subExtIndex]; DefaultName = GetDefaultName2(fileName, extInfo.Ext, extInfo.AddExt); } return S_OK; } return S_FALSE; } HRESULT CArc::OpenStreamOrFile( CCodecs *codecs, int formatIndex, bool stdInMode, IInStream *stream, IArchiveOpenCallback *callback) { CMyComPtr fileStream; CMyComPtr seqStream; if (stdInMode) seqStream = new CStdInFileStream; else if (!stream) { CInFileStream *fileStreamSpec = new CInFileStream; fileStream = fileStreamSpec; if (!fileStreamSpec->Open(Path)) return GetLastError(); stream = fileStream; } /* if (callback) { UInt64 fileSize; RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize)); RINOK(callback->SetTotal(NULL, &fileSize)) } */ return OpenStream(codecs, formatIndex, stream, seqStream, callback); } HRESULT CArchiveLink::Close() { for (int i = Arcs.Size() - 1; i >= 0; i--) { RINOK(Arcs[i].Archive->Close()); } IsOpen = false; return S_OK; } void CArchiveLink::Release() { while (!Arcs.IsEmpty()) Arcs.DeleteBack(); } HRESULT CArchiveLink::Open( CCodecs *codecs, const CIntVector &formatIndices, bool stdInMode, IInStream *stream, const UString &filePath, IArchiveOpenCallback *callback) { Release(); if (formatIndices.Size() >= 32) return E_NOTIMPL; HRESULT resSpec; for (;;) { resSpec = S_OK; int formatIndex = -1; if (formatIndices.Size() >= 1) { if (Arcs.Size() >= formatIndices.Size()) break; formatIndex = formatIndices[formatIndices.Size() - Arcs.Size() - 1]; } else if (Arcs.Size() >= 32) break; if (Arcs.IsEmpty()) { CArc arc; arc.Path = filePath; arc.SubfileIndex = (UInt32)(Int32)-1; RINOK(arc.OpenStreamOrFile(codecs, formatIndex, stdInMode, stream, callback)); Arcs.Add(arc); continue; } const CArc &arc = Arcs.Back(); resSpec = (formatIndices.Size() == 0 ? S_OK : E_NOTIMPL); UInt32 mainSubfile; { NCOM::CPropVariant prop; RINOK(arc.Archive->GetArchiveProperty(kpidMainSubfile, &prop)); if (prop.vt == VT_UI4) mainSubfile = prop.ulVal; else break; UInt32 numItems; RINOK(arc.Archive->GetNumberOfItems(&numItems)); if (mainSubfile >= numItems) break; } CMyComPtr getStream; if (arc.Archive->QueryInterface(IID_IInArchiveGetStream, (void **)&getStream) != S_OK || !getStream) break; CMyComPtr subSeqStream; if (getStream->GetStream(mainSubfile, &subSeqStream) != S_OK || !subSeqStream) break; CMyComPtr subStream; if (subSeqStream.QueryInterface(IID_IInStream, &subStream) != S_OK || !subStream) break; CArc arc2; RINOK(arc.GetItemPath(mainSubfile, arc2.Path)); CMyComPtr setSubArchiveName; callback->QueryInterface(IID_IArchiveOpenSetSubArchiveName, (void **)&setSubArchiveName); if (setSubArchiveName) setSubArchiveName->SetSubArchiveName(arc2.Path); arc2.SubfileIndex = mainSubfile; HRESULT result = arc2.OpenStream(codecs, formatIndex, subStream, NULL, callback); resSpec = (formatIndices.Size() == 0 ? S_OK : S_FALSE); if (result == S_FALSE) break; RINOK(result); RINOK(arc.GetItemMTime(mainSubfile, arc2.MTime, arc2.MTimeDefined)); Arcs.Add(arc2); } IsOpen = !Arcs.IsEmpty(); return S_OK; } static void SetCallback(const UString &filePath, IOpenCallbackUI *callbackUI, IArchiveOpenCallback *reOpenCallback, CMyComPtr &callback) { COpenCallbackImp *openCallbackSpec = new COpenCallbackImp; callback = openCallbackSpec; openCallbackSpec->Callback = callbackUI; openCallbackSpec->ReOpenCallback = reOpenCallback; UString fullName; int fileNamePartStartIndex; NFile::NDirectory::MyGetFullPathName(filePath, fullName, fileNamePartStartIndex); openCallbackSpec->Init( fullName.Left(fileNamePartStartIndex), fullName.Mid(fileNamePartStartIndex)); } HRESULT CArchiveLink::Open2(CCodecs *codecs, const CIntVector &formatIndices, bool stdInMode, IInStream *stream, const UString &filePath, IOpenCallbackUI *callbackUI) { VolumesSize = 0; COpenCallbackImp *openCallbackSpec = new COpenCallbackImp; CMyComPtr callback = openCallbackSpec; openCallbackSpec->Callback = callbackUI; UString fullName, prefix, name; if (!stream && !stdInMode) { int fileNamePartStartIndex; if (!NFile::NDirectory::MyGetFullPathName(filePath, fullName, fileNamePartStartIndex)) return GetLastError(); prefix = fullName.Left(fileNamePartStartIndex); name = fullName.Mid(fileNamePartStartIndex); openCallbackSpec->Init(prefix, name); } else { openCallbackSpec->SetSubArchiveName(filePath); } RINOK(Open(codecs, formatIndices, stdInMode, stream, filePath, callback)); VolumePaths.Add(prefix + name); for (int i = 0; i < openCallbackSpec->FileNames.Size(); i++) VolumePaths.Add(prefix + openCallbackSpec->FileNames[i]); VolumesSize = openCallbackSpec->TotalSize; return S_OK; } HRESULT CArchiveLink::ReOpen(CCodecs *codecs, const UString &filePath, IArchiveOpenCallback *callback) { if (Arcs.Size() > 1) return E_NOTIMPL; if (Arcs.Size() == 0) return Open2(codecs, CIntVector(), false, NULL, filePath, 0); CMyComPtr openCallbackNew; SetCallback(filePath, NULL, callback, openCallbackNew); CInFileStream *fileStreamSpec = new CInFileStream; CMyComPtr stream(fileStreamSpec); if (!fileStreamSpec->Open(filePath)) return GetLastError(); HRESULT res = GetArchive()->Open(stream, &kMaxCheckStartPosition, callback); IsOpen = (res == S_OK); return res; }