diff options
Diffstat (limited to 'libs/archive/src')
| -rw-r--r-- | libs/archive/src/CMakeLists.txt | 101 | ||||
| -rw-r--r-- | libs/archive/src/archive.cpp | 609 | ||||
| -rw-r--r-- | libs/archive/src/compat.h | 87 | ||||
| -rw-r--r-- | libs/archive/src/extractcallback.cpp | 364 | ||||
| -rw-r--r-- | libs/archive/src/extractcallback.h | 133 | ||||
| -rw-r--r-- | libs/archive/src/fileio.cpp | 449 | ||||
| -rw-r--r-- | libs/archive/src/fileio.h | 343 | ||||
| -rw-r--r-- | libs/archive/src/formatter.h | 121 | ||||
| -rw-r--r-- | libs/archive/src/inputstream.cpp | 70 | ||||
| -rw-r--r-- | libs/archive/src/inputstream.h | 54 | ||||
| -rw-r--r-- | libs/archive/src/instrument.h | 111 | ||||
| -rw-r--r-- | libs/archive/src/interfaceguids.cpp | 37 | ||||
| -rw-r--r-- | libs/archive/src/library.h | 164 | ||||
| -rw-r--r-- | libs/archive/src/multioutputstream.cpp | 142 | ||||
| -rw-r--r-- | libs/archive/src/multioutputstream.h | 106 | ||||
| -rw-r--r-- | libs/archive/src/opencallback.cpp | 168 | ||||
| -rw-r--r-- | libs/archive/src/opencallback.h | 75 | ||||
| -rw-r--r-- | libs/archive/src/propertyvariant.cpp | 214 | ||||
| -rw-r--r-- | libs/archive/src/propertyvariant.h | 56 | ||||
| -rw-r--r-- | libs/archive/src/unknown_impl.h | 191 | ||||
| -rw-r--r-- | libs/archive/src/version.rc | 28 |
21 files changed, 3623 insertions, 0 deletions
diff --git a/libs/archive/src/CMakeLists.txt b/libs/archive/src/CMakeLists.txt new file mode 100644 index 0000000..75e9b53 --- /dev/null +++ b/libs/archive/src/CMakeLists.txt @@ -0,0 +1,101 @@ +cmake_minimum_required(VERSION 3.16) + +if(WIN32) + find_package(7zip CONFIG REQUIRED) +endif() + +add_library(archive) + +set_target_properties(archive PROPERTIES CXX_STANDARD 20) + +# On Linux, we use the bundled 7zip SDK headers and dlopen the 7z.so at runtime +# On Windows, we use the vcpkg 7zip package +if(WIN32) + target_link_libraries(archive PRIVATE 7zip::7zip) +else() + # Add 7zip SDK headers from thirdparty + target_include_directories(archive PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../thirdparty/CPP + ) + # We need to compile MyWindows.cpp for BSTR/Variant support on Linux + target_sources(archive PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/../thirdparty/CPP/Common/MyWindows.cpp + ) + target_link_libraries(archive PRIVATE dl) + target_compile_definitions(archive PRIVATE -DARCHIVE_LINUX_PORT) +endif() + +set(ARCHIVE_SOURCES + archive.cpp + compat.h + extractcallback.cpp + extractcallback.h + fileio.cpp + fileio.h + formatter.h + inputstream.cpp + inputstream.h + instrument.h + interfaceguids.cpp + library.h + multioutputstream.cpp + multioutputstream.h + opencallback.cpp + opencallback.h + propertyvariant.cpp + propertyvariant.h + unknown_impl.h +) + +if(WIN32) + list(APPEND ARCHIVE_SOURCES version.rc) +endif() + +target_sources(archive + PRIVATE + ${ARCHIVE_SOURCES} + PUBLIC + FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR}/../include + FILES ${CMAKE_CURRENT_LIST_DIR}/../include/archive/archive.h +) + +target_include_directories(archive PRIVATE ${CMAKE_CURRENT_LIST_DIR}/../include/archive) + +if (MSVC) + target_compile_options(archive + PRIVATE + "/MP" + "/W4" + "/external:anglebrackets" + "/external:W0" + ) + target_link_options(archive + PRIVATE + $<$<CONFIG:RelWithDebInfo>:/LTCG /INCREMENTAL:NO /OPT:REF /OPT:ICF> + ) + + set_target_properties(archive PROPERTIES VS_STARTUP_PROJECT archive) +endif() + +if(NOT MSVC) + target_compile_options(archive PRIVATE -Wall -Wextra -Wno-unused-parameter) +endif() + +if (BUILD_STATIC) + target_compile_definitions(archive PUBLIC -DMO2_ARCHIVE_BUILD_STATIC) +else() + target_compile_definitions(archive PRIVATE -DMO2_ARCHIVE_BUILD_EXPORT) +endif() + +add_library(mo2::archive ALIAS archive) + +install(TARGETS archive EXPORT archiveTargets FILE_SET HEADERS) +if (NOT BUILD_STATIC AND WIN32) + install(FILES $<TARGET_PDB_FILE:archive> DESTINATION pdb OPTIONAL) +endif() +install(EXPORT archiveTargets + FILE mo2-archive-targets.cmake + NAMESPACE mo2:: + DESTINATION lib/cmake/mo2-archive +) diff --git a/libs/archive/src/archive.cpp b/libs/archive/src/archive.cpp new file mode 100644 index 0000000..6d35366 --- /dev/null +++ b/libs/archive/src/archive.cpp @@ -0,0 +1,609 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "archive.h" +#include "compat.h" + +#include "extractcallback.h" +#include "inputstream.h" +#include "library.h" +#include "opencallback.h" +#include "propertyvariant.h" + +#include <algorithm> +#include <map> +#include <sstream> +#include <stddef.h> +#include <string> +#include <unordered_map> +#include <vector> + +namespace PropID = NArchive::NHandlerPropID; + +class FileDataImpl : public FileData +{ + friend class Archive; + +public: + FileDataImpl(std::wstring const& fileName, UInt64 size, UInt64 crc, bool isDirectory) + : m_FileName(fileName), m_Size(size), m_CRC(crc), m_IsDirectory(isDirectory) + {} + + virtual std::wstring getArchiveFilePath() const override { return m_FileName; } + virtual uint64_t getSize() const override { return m_Size; } + + virtual void addOutputFilePath(std::wstring const& fileName) override + { + m_OutputFilePaths.push_back(fileName); + } + virtual const std::vector<std::wstring>& getOutputFilePaths() const override + { + return m_OutputFilePaths; + } + + virtual void clearOutputFilePaths() override { m_OutputFilePaths.clear(); } + + bool isEmpty() const { return m_OutputFilePaths.empty(); } + virtual bool isDirectory() const override { return m_IsDirectory; } + virtual uint64_t getCRC() const override { return m_CRC; } + +private: + std::wstring m_FileName; + UInt64 m_Size; + UInt64 m_CRC; + std::vector<std::wstring> m_OutputFilePaths; + bool m_IsDirectory; +}; + +/// represents the connection to one archive and provides common functionality +class ArchiveImpl : public Archive +{ + + // Callback that does nothing but avoid having to check if the callback is present + // everytime. + static LogCallback DefaultLogCallback; + +public: + ArchiveImpl(); + virtual ~ArchiveImpl(); + + virtual bool isValid() const { return m_Valid; } + + virtual Error getLastError() const { return m_LastError; } + virtual void setLogCallback(LogCallback logCallback) override + { + // Wrap the callback so that we do not have to check if it is set everywhere: + m_LogCallback = logCallback ? logCallback : DefaultLogCallback; + } + + virtual bool open(std::wstring const& archiveName, + PasswordCallback passwordCallback) override; + virtual void close() override; + const std::vector<FileData*>& getFileList() const override { return m_FileList; } + virtual bool extract(std::wstring const& outputDirectory, + ProgressCallback progressCallback, + FileChangeCallback fileChangeCallback, + ErrorCallback errorCallback) override; + + virtual void cancel() override; + +private: + void clearFileList(); + void resetFileList(); + + HRESULT loadFormats(); + +private: + typedef UINT32(WINAPI* CreateObjectFunc)(const GUID* clsID, const GUID* interfaceID, + void** outObject); + CreateObjectFunc m_CreateObjectFunc; + + // A note: In 7zip source code this not is what this typedef is called, the old + // GetHandlerPropertyFunc appears to be deprecated. + typedef UInt32(WINAPI* GetPropertyFunc)(UInt32 index, PROPID propID, + PROPVARIANT* value); + GetPropertyFunc m_GetHandlerPropertyFunc; + + template <typename T> + T readHandlerProperty(UInt32 index, PROPID propID) const; + + template <typename T> + T readProperty(UInt32 index, PROPID propID) const; + + bool m_Valid; + Error m_LastError; + + ALibrary m_Library; + std::wstring m_ArchiveName; // TBH I don't think this is required + CComPtr<IInArchive> m_ArchivePtr; + CArchiveExtractCallback* m_ExtractCallback; + + LogCallback m_LogCallback; + PasswordCallback m_PasswordCallback; + + std::vector<FileData*> m_FileList; + + std::wstring m_Password; + + struct ArchiveFormatInfo + { + CLSID m_ClassID; + std::wstring m_Name; + std::vector<std::string> m_Signatures; + std::wstring m_Extensions; + std::wstring m_AdditionalExtensions; + UInt32 m_SignatureOffset; + }; + + typedef std::vector<ArchiveFormatInfo> Formats; + Formats m_Formats; + + typedef std::unordered_map<std::wstring, Formats> FormatMap; + FormatMap m_FormatMap; + + // I don't think one signature could possibly describe two formats. + typedef std::map<std::string, ArchiveFormatInfo> SignatureMap; + SignatureMap m_SignatureMap; + + std::size_t m_MaxSignatureLen = 0; +}; + +Archive::LogCallback ArchiveImpl::DefaultLogCallback([](LogLevel, std::wstring const&) { +}); + +template <typename T> +T ArchiveImpl::readHandlerProperty(UInt32 index, PROPID propID) const +{ + PropertyVariant prop; + if (m_GetHandlerPropertyFunc(index, propID, &prop) != S_OK) { + throw std::runtime_error("Failed to read property"); + } + return static_cast<T>(prop); +} + +template <typename T> +T ArchiveImpl::readProperty(UInt32 index, PROPID propID) const +{ + PropertyVariant prop; + if (m_ArchivePtr->GetProperty(index, propID, &prop) != S_OK) { + throw std::runtime_error("Failed to read property"); + } + return static_cast<T>(prop); +} + +// Seriously, there is one format returned in the list that has no registered +// extension and no signature. WTF? +HRESULT ArchiveImpl::loadFormats() +{ + typedef UInt32(WINAPI * GetNumberOfFormatsFunc)(UInt32 * numFormats); + GetNumberOfFormatsFunc getNumberOfFormats = + m_Library.resolve<GetNumberOfFormatsFunc>("GetNumberOfFormats"); + if (getNumberOfFormats == nullptr) { + return E_FAIL; + } + + UInt32 numFormats; + RINOK(getNumberOfFormats(&numFormats)); + + for (UInt32 i = 0; i < numFormats; ++i) { + ArchiveFormatInfo item; + + item.m_Name = readHandlerProperty<std::wstring>(i, PropID::kName); + + item.m_ClassID = readHandlerProperty<GUID>(i, PropID::kClassID); + + // Should split up the extensions and map extension to type, and see what we get + // from that for preference then try all extensions anyway... + item.m_Extensions = readHandlerProperty<std::wstring>(i, PropID::kExtension); + + // This is unnecessary currently for our purposes. Basically, for each + // extension, there's an 'addext' which, if set (to other than *) means that + // theres a double encoding going on. For instance, the bzip format is like this + // addext = "* * .tar .tar" + // ext = "bz2 bzip2 tbz2 tbz" + // which means that tbz2 and tbz should uncompress to a tar file which can be + // further processed as if it were a tar file. Having said which, we don't + // need to support this at all, so I'm storing it but ignoring it. + item.m_AdditionalExtensions = + readHandlerProperty<std::wstring>(i, PropID::kAddExtension); + + std::string signature = readHandlerProperty<std::string>(i, PropID::kSignature); + if (!signature.empty()) { + item.m_Signatures.push_back(signature); + if (m_MaxSignatureLen < signature.size()) { + m_MaxSignatureLen = signature.size(); + } + m_SignatureMap[signature] = item; + } + + std::string multiSig = readHandlerProperty<std::string>(i, PropID::kMultiSignature); + const char* multiSigBytes = multiSig.c_str(); + std::size_t size = multiSig.length(); + while (size > 0) { + unsigned len = *multiSigBytes++; + size--; + if (len > size) + break; + std::string sig(multiSigBytes, multiSigBytes + len); + multiSigBytes = multiSigBytes + len; + size -= len; + item.m_Signatures.push_back(sig); + if (m_MaxSignatureLen < sig.size()) { + m_MaxSignatureLen = sig.size(); + } + m_SignatureMap[sig] = item; + } + + UInt32 offset = readHandlerProperty<UInt32>(i, PropID::kSignatureOffset); + item.m_SignatureOffset = offset; + + // Now split the extension up from the space separated string and create + // a map from each extension to the possible formats + // We could make these pointers but it's not a massive overhead and nobody + // should be changing this + std::wistringstream s(item.m_Extensions); + std::wstring t; + while (s >> t) { + m_FormatMap[t].push_back(item); + } + m_Formats.push_back(item); + } + return S_OK; +} + +ArchiveImpl::ArchiveImpl() + : m_Valid(false), m_LastError(Error::ERROR_NONE), m_Library("dlls/7zip.dll"), + m_PasswordCallback{} +{ + // Reset the log callback: + setLogCallback({}); + + if (!m_Library) { + m_LastError = Error::ERROR_LIBRARY_NOT_FOUND; + return; + } + + m_CreateObjectFunc = m_Library.resolve<CreateObjectFunc>("CreateObject"); + if (m_CreateObjectFunc == nullptr) { + m_LastError = Error::ERROR_LIBRARY_INVALID; + return; + } + + m_GetHandlerPropertyFunc = m_Library.resolve<GetPropertyFunc>("GetHandlerProperty2"); + if (m_GetHandlerPropertyFunc == nullptr) { + m_LastError = Error::ERROR_LIBRARY_INVALID; + return; + } + + try { + if (loadFormats() != S_OK) { + m_LastError = Error::ERROR_LIBRARY_INVALID; + return; + } + + m_Valid = true; + return; + } catch (std::exception const& e) { + m_LogCallback(LogLevel::Error, std::format(L"Caught exception {}.", e)); + m_LastError = Error::ERROR_LIBRARY_INVALID; + } +} + +ArchiveImpl::~ArchiveImpl() +{ + close(); +} + +bool ArchiveImpl::open(std::wstring const& archiveName, + PasswordCallback passwordCallback) +{ + m_ArchiveName = archiveName; // Just for debugging, not actually used... + + Formats formatList = m_Formats; + + // Convert to long path if it's not already: + std::filesystem::path filepath = IO::make_path(archiveName); + + // If it doesn't exist or is a directory, error + if (!exists(filepath) || is_directory(filepath)) { + m_LastError = Error::ERROR_ARCHIVE_NOT_FOUND; + return false; + } + + // in rars the password seems to be requested during extraction, not on open, so we + // need to hold on to the callback for now + m_PasswordCallback = passwordCallback; + + CComPtr<InputStream> file(new InputStream); + + if (!file->Open(filepath)) { + m_LastError = Error::ERROR_FAILED_TO_OPEN_ARCHIVE; + return false; + } + + CComPtr<CArchiveOpenCallback> openCallbackPtr; + try { + openCallbackPtr = + new CArchiveOpenCallback(passwordCallback, m_LogCallback, filepath); + } catch (std::runtime_error const&) { + m_LastError = Error::ERROR_FAILED_TO_OPEN_ARCHIVE; + return false; + } + + // Try to open the archive + + bool sigMismatch = false; + + { + // Get the first iterator that is strictly > the signature we're looking for. + for (auto signatureInfo : m_SignatureMap) { + // Read the signature of the file and look that up. + std::vector<char> buff; + buff.reserve(m_MaxSignatureLen); + UInt32 act; + file->Seek(0, STREAM_SEEK_SET, nullptr); + file->Read(buff.data(), static_cast<UInt32>(m_MaxSignatureLen), &act); + file->Seek(0, STREAM_SEEK_SET, nullptr); + std::string signature = std::string(buff.data(), act); + if (signatureInfo.first == std::string(buff.data(), signatureInfo.first.size())) { + if (m_CreateObjectFunc(&signatureInfo.second.m_ClassID, &IID_IInArchive, + (void**)&m_ArchivePtr) != S_OK) { + m_LastError = Error::ERROR_LIBRARY_ERROR; + return false; + } + + if (m_ArchivePtr->Open(file, 0, openCallbackPtr) != S_OK) { + m_LogCallback(LogLevel::Debug, + std::format(L"Failed to open {} using {} (from signature).", + archiveName, signatureInfo.second.m_Name)); + m_ArchivePtr.Release(); + } else { + m_LogCallback(LogLevel::Debug, + std::format(L"Opened {} using {} (from signature).", + archiveName, signatureInfo.second.m_Name)); + + // Retrieve the extension (warning: .extension() contains the dot): + std::wstring ext = + ArchiveStrings::towlower(filepath.extension().wstring().substr(1)); + std::wistringstream s(signatureInfo.second.m_Extensions); + std::wstring t; + bool found = false; + while (s >> t) { + if (t == ext) { + found = true; + break; + } + } + if (!found) { + m_LogCallback(LogLevel::Warning, + L"The extension of this file did not match the expected " + L"extensions for this format."); + sigMismatch = true; + } + } + // Arguably we should give up here if it's not OK if 7zip can't even start + // to decode even though we've found the format from the signature. + // Sadly, the 7zip API documentation is pretty well non-existant. + break; + } + std::vector<ArchiveFormatInfo>::iterator iter = std::find_if( + formatList.begin(), formatList.end(), [=](ArchiveFormatInfo a) -> bool { + return a.m_Name == signatureInfo.second.m_Name; + }); + if (iter != formatList.end()) + formatList.erase(iter); + } + } + + { + // determine archive type based on extension + Formats const* formats = nullptr; + std::wstring ext = + ArchiveStrings::towlower(filepath.extension().wstring().substr(1)); + FormatMap::const_iterator map_iter = m_FormatMap.find(ext); + if (map_iter != m_FormatMap.end()) { + formats = &map_iter->second; + if (formats != nullptr) { + if (m_ArchivePtr == nullptr) { + // OK, we have some potential formats. If there is only one, try it now. If + // there are multiple formats, we'll try by signature lookup first. + for (ArchiveFormatInfo format : *formats) { + if (m_CreateObjectFunc(&format.m_ClassID, &IID_IInArchive, + (void**)&m_ArchivePtr) != S_OK) { + m_LastError = Error::ERROR_LIBRARY_ERROR; + return false; + } + + if (m_ArchivePtr->Open(file, 0, openCallbackPtr) != S_OK) { + m_LogCallback(LogLevel::Debug, + std::format(L"Failed to open {} using {} (from signature).", + archiveName, format.m_Name)); + m_ArchivePtr.Release(); + } else { + m_LogCallback(LogLevel::Debug, + std::format(L"Opened {} using {} (from signature).", + archiveName, format.m_Name)); + break; + } + + std::vector<ArchiveFormatInfo>::iterator iter = std::find_if( + formatList.begin(), formatList.end(), [=](ArchiveFormatInfo a) -> bool { + return a.m_Name == format.m_Name; + }); + if (iter != formatList.end()) + formatList.erase(iter); + } + } else if (sigMismatch) { + std::vector<std::wstring> vformats; + for (ArchiveFormatInfo format : *formats) { + vformats.push_back(format.m_Name); + } + m_LogCallback( + LogLevel::Warning, + std::format(L"The format(s) expected for this extension are: {}.", + ArchiveStrings::join(vformats, L", "))); + } + } + } + } + + if (m_ArchivePtr == nullptr) { + m_LogCallback(LogLevel::Warning, L"Trying to open an archive but could not " + L"recognize the extension or signature."); + m_LogCallback( + LogLevel::Debug, + L"Attempting to open the file with the remaining formats as a fallback..."); + for (auto format : formatList) { + if (m_CreateObjectFunc(&format.m_ClassID, &IID_IInArchive, + (void**)&m_ArchivePtr) != S_OK) { + m_LastError = Error::ERROR_LIBRARY_ERROR; + return false; + } + if (m_ArchivePtr->Open(file, 0, openCallbackPtr) == S_OK) { + m_LogCallback(LogLevel::Debug, + std::format(L"Opened {} using {} (from signature).", archiveName, + format.m_Name)); + m_LogCallback(LogLevel::Warning, + L"This archive likely has an incorrect extension."); + break; + } else + m_ArchivePtr.Release(); + } + } + + if (m_ArchivePtr == nullptr) { + m_LastError = Error::ERROR_INVALID_ARCHIVE_FORMAT; + return false; + } + + m_Password = openCallbackPtr->GetPassword(); + /* + UInt32 subFile = ULONG_MAX; + { + NCOM::CPropVariant prop; + if (m_ArchivePtr->GetArchiveProperty(kpidMainSubfile, &prop) != S_OK) { + throw std::runtime_error("failed to get property kpidMainSubfile"); + } + + if (prop.vt == VT_UI4) { + subFile = prop.ulVal; + } + } + + if (subFile != ULONG_MAX) { + std::wstring subPath = GetArchiveItemPath(m_ArchivePtr, subFile); + + CMyComPtr<IArchiveOpenSetSubArchiveName> setSubArchiveName; + openCallbackPtr.QueryInterface(IID_IArchiveOpenSetSubArchiveName, (void + **)&setSubArchiveName); if (setSubArchiveName) { + setSubArchiveName->SetSubArchiveName(subPath.c_str()); + } + }*/ + + m_LastError = Error::ERROR_NONE; + + resetFileList(); + return true; +} + +void ArchiveImpl::close() +{ + if (m_ArchivePtr != nullptr) { + m_ArchivePtr->Close(); + } + clearFileList(); + m_ArchivePtr.Release(); + m_PasswordCallback = {}; +} + +void ArchiveImpl::clearFileList() +{ + for (std::vector<FileData*>::iterator iter = m_FileList.begin(); + iter != m_FileList.end(); ++iter) { + delete *iter; + } + m_FileList.clear(); +} + +void ArchiveImpl::resetFileList() +{ + UInt32 numItems = 0; + clearFileList(); + + m_ArchivePtr->GetNumberOfItems(&numItems); + + for (UInt32 i = 0; i < numItems; ++i) { + m_FileList.push_back(new FileDataImpl( + readProperty<std::wstring>(i, kpidPath), readProperty<UInt64>(i, kpidSize), + readProperty<UInt64>(i, kpidCRC), readProperty<bool>(i, kpidIsDir))); + } +} + +bool ArchiveImpl::extract(std::wstring const& outputDirectory, + ProgressCallback progressCallback, + FileChangeCallback fileChangeCallback, + ErrorCallback errorCallback) + +{ + // Retrieve the list of indices we want to extract: + std::vector<UInt32> indices; + UInt64 totalSize = 0; + for (std::size_t i = 0; i < m_FileList.size(); ++i) { + FileDataImpl* fileData = static_cast<FileDataImpl*>(m_FileList[i]); + if (!fileData->isEmpty()) { + indices.push_back(static_cast<UInt32>(i)); + totalSize += fileData->getSize(); + } + } + + m_ExtractCallback = new CArchiveExtractCallback( + progressCallback, fileChangeCallback, errorCallback, m_PasswordCallback, + m_LogCallback, m_ArchivePtr, outputDirectory, &m_FileList[0], m_FileList.size(), + totalSize, &m_Password); + HRESULT result = m_ArchivePtr->Extract( + indices.data(), static_cast<UInt32>(indices.size()), false, m_ExtractCallback); + // Note: m_ExtractCallBack is deleted by Extract + switch (result) { + case S_OK: { + // nop + } break; + case E_ABORT: { + m_LastError = Error::ERROR_EXTRACT_CANCELLED; + } break; + case E_OUTOFMEMORY: { + m_LastError = Error::ERROR_OUT_OF_MEMORY; + } break; + default: { + m_LastError = Error::ERROR_LIBRARY_ERROR; + } break; + } + + return result == S_OK; +} + +void ArchiveImpl::cancel() +{ + m_ExtractCallback->SetCanceled(true); +} + +std::unique_ptr<Archive> CreateArchive() +{ + return std::make_unique<ArchiveImpl>(); +} diff --git a/libs/archive/src/compat.h b/libs/archive/src/compat.h new file mode 100644 index 0000000..65c2ec5 --- /dev/null +++ b/libs/archive/src/compat.h @@ -0,0 +1,87 @@ +/* +Mod Organizer archive handling - Linux compatibility header + +Copyright (C) 2024 MO2 Team. All rights reserved. + +This header provides Windows COM compatibility types for Linux builds. +On Windows, the real Windows headers are used instead. +*/ + +#ifndef ARCHIVE_COMPAT_H +#define ARCHIVE_COMPAT_H + +#ifdef _WIN32 + +#include <Unknwn.h> +#include <atlbase.h> +#include <initguid.h> +#include <guiddef.h> +#include <PropIdl.h> + +#else // Linux + +#include <Common/MyWindows.h> +#include <cstring> + +// PropVariantInit / PropVariantClear - map to VariantClear from 7zip SDK +inline HRESULT PropVariantInit(PROPVARIANT* pvar) { + pvar->vt = VT_EMPTY; + return S_OK; +} + +inline HRESULT PropVariantClear(PROPVARIANT* pvar) { + return VariantClear(pvar); +} + +// A minimal CComPtr replacement for Linux +template <class T> +class CComPtr +{ +public: + CComPtr() : p(nullptr) {} + CComPtr(T* lp) : p(lp) { if (p) p->AddRef(); } + CComPtr(const CComPtr<T>& other) : p(other.p) { if (p) p->AddRef(); } + ~CComPtr() { Release(); } + + CComPtr& operator=(T* lp) { + if (lp) lp->AddRef(); + Release(); + p = lp; + return *this; + } + + CComPtr& operator=(const CComPtr<T>& other) { + if (this != &other) { + if (other.p) other.p->AddRef(); + Release(); + p = other.p; + } + return *this; + } + + void Release() { + if (p) { + p->Release(); + p = nullptr; + } + } + + T* Detach() { + T* pt = p; + p = nullptr; + return pt; + } + + operator T*() const { return p; } + T* operator->() const { return p; } + T** operator&() { return &p; } + bool operator!() const { return p == nullptr; } + bool operator==(std::nullptr_t) const { return p == nullptr; } + bool operator!=(std::nullptr_t) const { return p != nullptr; } + + T* p; +}; + +#endif // _WIN32 + +#endif // ARCHIVE_COMPAT_H diff --git a/libs/archive/src/extractcallback.cpp b/libs/archive/src/extractcallback.cpp new file mode 100644 index 0000000..7629298 --- /dev/null +++ b/libs/archive/src/extractcallback.cpp @@ -0,0 +1,364 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "compat.h" + +#include <algorithm> +#include <filesystem> +#include <format> +#include <stdexcept> +#include <string> + +#include "archive.h" +#include "extractcallback.h" +#include "propertyvariant.h" + +std::wstring operationResultToString(Int32 operationResult) +{ + namespace R = NArchive::NExtract::NOperationResult; + + switch (operationResult) { + case R::kOK: + return {}; + + case R::kUnsupportedMethod: + return L"Encoding method unsupported"; + + case R::kDataError: + return L"Data error"; + + case R::kCRCError: + return L"CRC error"; + + case R::kUnavailable: + return L"Unavailable"; + + case R::kUnexpectedEnd: + return L"Unexpected end of archive"; + + case R::kDataAfterEnd: + return L"Data after end of archive"; + + case R::kIsNotArc: + return L"Not an ARC"; + + case R::kHeadersError: + return L"Bad headers"; + + case R::kWrongPassword: + return L"Wrong password"; + + default: + return std::format(L"Unknown error {}", operationResult); + } +} + +CArchiveExtractCallback::CArchiveExtractCallback( + Archive::ProgressCallback progressCallback, + Archive::FileChangeCallback fileChangeCallback, + Archive::ErrorCallback errorCallback, Archive::PasswordCallback passwordCallback, + Archive::LogCallback logCallback, IInArchive* archiveHandler, + std::wstring const& directoryPath, FileData* const* fileData, std::size_t nbFiles, + UInt64 totalFileSize, std::wstring* password) + : m_ArchiveHandler(archiveHandler), m_Total(0), m_DirectoryPath(), + m_Extracting(false), m_Canceled(false), m_Timers{}, m_ProcessedFileInfo{}, + m_OutputFileStream{}, m_OutFileStreamCom{}, m_FileData(fileData), + m_NbFiles(nbFiles), m_TotalFileSize(totalFileSize), m_LastCallbackFileSize(0), + m_ExtractedFileSize(0), m_ProgressCallback(progressCallback), + m_FileChangeCallback(fileChangeCallback), m_ErrorCallback(errorCallback), + m_PasswordCallback(passwordCallback), m_LogCallback(logCallback), + m_Password(password) +{ + m_DirectoryPath = IO::make_path(directoryPath); +} + +CArchiveExtractCallback::~CArchiveExtractCallback() +{ +#ifdef INSTRUMENT_ARCHIVE + m_LogCallback(Archive::LogLevel::Debug, m_Timers.GetStream.toString(L"GetStream")); + m_LogCallback(Archive::LogLevel::Debug, m_Timers.SetOperationResult.SetMTime.toString( + L"SetOperationResult.SetMTime")); + m_LogCallback(Archive::LogLevel::Debug, m_Timers.SetOperationResult.Close.toString( + L"SetOperationResult.Close")); + m_LogCallback(Archive::LogLevel::Debug, m_Timers.SetOperationResult.Release.toString( + L"SetOperationResult.Release")); + m_LogCallback(Archive::LogLevel::Debug, + m_Timers.SetOperationResult.SetFileAttributesW.toString( + L"SetOperationResult.SetFileAttributesW")); +#endif +} + +STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 size) throw() +{ + m_Total = size; + return S_OK; +} + +STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64* completed) throw() +{ + if (m_ProgressCallback) { + m_ProgressCallback(Archive::ProgressType::ARCHIVE, *completed, m_Total); + } + return m_Canceled ? E_ABORT : S_OK; +} + +template <typename T> +bool CArchiveExtractCallback::getOptionalProperty(UInt32 index, int property, + T* result) const +{ + PropertyVariant prop; + if (m_ArchiveHandler->GetProperty(index, property, &prop) != S_OK) { + m_LogCallback(Archive::LogLevel::Error, + std::format(L"Error getting property {}.", property)); + return false; + } + if (prop.is_empty()) { + return false; + } + *result = static_cast<T>(prop); + return true; +} + +template <typename T> +bool CArchiveExtractCallback::getProperty(UInt32 index, int property, T* result) const +{ + PropertyVariant prop; + if (m_ArchiveHandler->GetProperty(index, property, &prop) != S_OK) { + m_LogCallback(Archive::LogLevel::Error, + std::format(L"Error getting property {}.", property)); + return false; + } + + *result = static_cast<T>(prop); + return true; +} + +STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, + ISequentialOutStream** outStream, + Int32 askExtractMode) throw() +{ + [[maybe_unused]] auto guard = m_Timers.GetStream.instrument(); + namespace fs = std::filesystem; + + *outStream = nullptr; + m_OutFileStreamCom.Release(); + + m_FullProcessedPaths.clear(); + m_Extracting = false; + + if (askExtractMode != NArchive::NExtract::NAskMode::kExtract) { + return S_OK; + } + + std::vector<std::wstring> filenames = m_FileData[index]->getOutputFilePaths(); + m_FileData[index]->clearOutputFilePaths(); + if (filenames.empty()) { + return S_OK; + } + +#ifndef _WIN32 + // Archives from Windows contain backslash path separators which are valid + // filename characters on Linux - convert them to forward slashes. + for (auto& fn : filenames) { + std::replace(fn.begin(), fn.end(), L'\\', L'/'); + } +#endif + + try { + m_ProcessedFileInfo.AttribDefined = + getOptionalProperty(index, kpidAttrib, &m_ProcessedFileInfo.Attrib); + + if (!getProperty(index, kpidIsDir, &m_ProcessedFileInfo.isDir)) { + return E_ABORT; + } + + // Why do we do this? And if we are doing this, shouldn't we copy the created + // and accessed times (kpidATime, kpidCTime) as well? + m_ProcessedFileInfo.MTimeDefined = + getOptionalProperty(index, kpidMTime, &m_ProcessedFileInfo.MTime); + + if (m_ProcessedFileInfo.isDir) { + for (auto const& filename : filenames) { + auto fullpath = m_DirectoryPath / fs::path(filename).make_preferred(); + std::error_code ec; + std::filesystem::create_directories(fullpath, ec); + if (ec) { + reportError(L"cannot created directory '{}': {}", fullpath, ec); + return E_ABORT; + } + m_FullProcessedPaths.push_back(fullpath); + } + } else { + for (auto const& filename : filenames) { + auto fullProcessedPath = m_DirectoryPath / fs::path(filename).make_preferred(); + // If the filename contains a '/' we want to make the directory + auto directoryPath = fullProcessedPath.parent_path(); + if (!fs::exists(directoryPath)) { + // Make the containing directory + std::error_code ec; + std::filesystem::create_directories(directoryPath, ec); + if (ec) { + reportError(L"cannot created directory '{}': {}", directoryPath, ec); + return E_ABORT; + } + // m_DirectoryPath.mkpath(filename.left(slashPos)); + } + // If the file already exists, delete it + if (fs::exists(fullProcessedPath)) { + std::error_code ec; + if (!fs::remove(fullProcessedPath, ec)) { + reportError(L"cannot delete output file '{}': {}", fullProcessedPath, ec); + return E_ABORT; + } + } + m_FullProcessedPaths.push_back(fullProcessedPath); + } + + m_OutputFileStream = new MultiOutputStream([this](UInt32 size, UInt64) { + m_ExtractedFileSize += size; + if (m_ProgressCallback) { + m_ProgressCallback(Archive::ProgressType::EXTRACTION, m_ExtractedFileSize, + m_TotalFileSize); + } + }); + CComPtr<MultiOutputStream> outStreamCom(m_OutputFileStream); + + if (!m_OutputFileStream->Open(m_FullProcessedPaths)) { + reportError(L"cannot open output file '{}': {}", m_FullProcessedPaths[0], + ::GetLastError()); + return E_ABORT; + } + + UInt64 fileSize; + auto fileSizeFound = getOptionalProperty(index, kpidSize, &fileSize); + if (fileSizeFound && m_OutputFileStream->SetSize(fileSize) != S_OK) { + m_LogCallback(Archive::LogLevel::Error, + std::format(L"SetSize() failed on {}.", m_FullProcessedPaths[0])); + } + + // This is messy but I can't find another way of doing it. A simple + // assignment of m_outFileStream to *outStream doesn't increase the + // reference count. + m_OutFileStreamCom = outStreamCom; + *outStream = outStreamCom.Detach(); + } + + if (m_FileChangeCallback) { + m_FileChangeCallback(Archive::FileChangeType::EXTRACTION_START, filenames[0]); + } + + return S_OK; + } catch (std::exception const& e) { + m_LogCallback(Archive::LogLevel::Error, + std::format(L"Caught exception {} in GetStream.", e)); + } + return E_FAIL; +} + +STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) throw() +{ + if (m_Canceled) { + return E_ABORT; + } + m_Extracting = askExtractMode == NArchive::NExtract::NAskMode::kExtract; + return S_OK; +} + +STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) throw() +{ + if (operationResult != NArchive::NExtract::NOperationResult::kOK) { + reportError(operationResultToString(operationResult)); + } + + if (m_OutFileStreamCom) { + if (m_ProcessedFileInfo.MTimeDefined) { + [[maybe_unused]] auto guard = m_Timers.SetOperationResult.SetMTime.instrument(); + m_OutputFileStream->SetMTime(&m_ProcessedFileInfo.MTime); + } + [[maybe_unused]] auto guard = m_Timers.SetOperationResult.Close.instrument(); + RINOK(m_OutputFileStream->Close()) + } + + { + [[maybe_unused]] auto guard = m_Timers.SetOperationResult.Release.instrument(); + m_OutFileStreamCom.Release(); + } + + [[maybe_unused]] auto guard2 = m_Timers.SetOperationResult.SetFileAttributesW.instrument(); + if (m_Extracting && m_ProcessedFileInfo.AttribDefined) { + // this is moderately annoying. I can't do this on the file handle because if + // the file in question is a directory there isn't a file handle. + // Also I'd like to convert the attributes to QT attributes but I'm not sure + // if that's possible. Hence the conversions and strange string. + for (auto& path : m_FullProcessedPaths) { +#ifdef _WIN32 + std::wstring const fn = L"\\\\?\\" + path.native(); + // If the attributes are POSIX-based, fix that + if (m_ProcessedFileInfo.Attrib & 0xF0000000) + m_ProcessedFileInfo.Attrib &= 0x7FFF; + + // Should probably log any errors here somehow + ::SetFileAttributesW(fn.c_str(), m_ProcessedFileInfo.Attrib); +#else + // On Linux, we could set file permissions based on the attributes, + // but Windows file attributes don't map well to POSIX permissions. + // For now, we only handle the read-only attribute and only for files. + // Applying read-only to directories can break extraction when later + // files need to be created inside those directories. + if (!m_ProcessedFileInfo.isDir && + (m_ProcessedFileInfo.Attrib & FILE_ATTRIBUTE_READONLY)) { + std::filesystem::permissions(path, + std::filesystem::perms::owner_write, + std::filesystem::perm_options::remove); + } else if (m_ProcessedFileInfo.isDir) { + // Keep extracted directories writable for the owner. + std::filesystem::permissions(path, + std::filesystem::perms::owner_write, + std::filesystem::perm_options::add); + } +#endif + } + } + + return S_OK; +} + +STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR* passwordOut) throw() +{ + // if we've already got a password, don't ask again (and again...) + if (m_Password->empty() && m_PasswordCallback) { + *m_Password = m_PasswordCallback(); + } + + *passwordOut = ::SysAllocString(m_Password->c_str()); + return *passwordOut != 0 ? S_OK : E_OUTOFMEMORY; +} + +void CArchiveExtractCallback::SetCanceled(bool aCanceled) +{ + m_Canceled = aCanceled; +} + +void CArchiveExtractCallback::reportError(std::wstring const& message) +{ + if (m_ErrorCallback) { + m_ErrorCallback(message); + } +} diff --git a/libs/archive/src/extractcallback.h b/libs/archive/src/extractcallback.h new file mode 100644 index 0000000..f95fc86 --- /dev/null +++ b/libs/archive/src/extractcallback.h @@ -0,0 +1,133 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef EXTRACTCALLBACK_H +#define EXTRACTCALLBACK_H + +#include <atomic> +#include <chrono> +#include <filesystem> +#include <format> + +#include <7zip/Archive/IArchive.h> +#include <7zip/IPassword.h> + +#include "compat.h" + +#include "archive.h" +#include "formatter.h" +#include "instrument.h" +#include "multioutputstream.h" +#include "unknown_impl.h" + +class FileData; + +class CArchiveExtractCallback : public IArchiveExtractCallback, + public ICryptoGetTextPassword +{ + + // A note: It appears that the IArchiveExtractCallback interface includes the + // IProgress interface, swo we need to respond to it + UNKNOWN_3_INTERFACE(IArchiveExtractCallback, ICryptoGetTextPassword, IProgress); + +public: + CArchiveExtractCallback(Archive::ProgressCallback progressCallback, + Archive::FileChangeCallback fileChangeCallback, + Archive::ErrorCallback errorCallback, + Archive::PasswordCallback passwordCallback, + Archive::LogCallback logCallback, IInArchive* archiveHandler, + std::wstring const& directoryPath, FileData* const* fileData, + std::size_t nbFiles, UInt64 totalFileSize, + std::wstring* password); + + virtual ~CArchiveExtractCallback(); + + void SetCanceled(bool aCanceled); + + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveExtractCallback) + + // ICryptoGetTextPassword + STDMETHOD(CryptoGetTextPassword)(BSTR* aPassword) throw(); + +private: + void reportError(const std::wstring& message); + + template <class... Args> + void reportError(std::wformat_string<Args...> format, Args&&... args) + { + reportError(std::format(format, std::forward<Args>(args)...)); + } + + template <typename T> + bool getOptionalProperty(UInt32 index, int property, T* result) const; + template <typename T> + bool getProperty(UInt32 index, int property, T* result) const; + +private: + CComPtr<IInArchive> m_ArchiveHandler; + + UInt64 m_Total; + + std::filesystem::path m_DirectoryPath; + bool m_Extracting; + std::atomic<bool> m_Canceled; + + struct + { + ArchiveTimers::Timer GetStream; + struct + { + ArchiveTimers::Timer SetMTime; + ArchiveTimers::Timer Close; + ArchiveTimers::Timer Release; + ArchiveTimers::Timer SetFileAttributesW; + } SetOperationResult; + } m_Timers; + + struct CProcessedFileInfo + { + FILETIME MTime; + UInt32 Attrib; + bool isDir; + bool AttribDefined; + bool MTimeDefined; + } m_ProcessedFileInfo; + + MultiOutputStream* m_OutputFileStream; + CComPtr<MultiOutputStream> m_OutFileStreamCom; + + std::vector<std::filesystem::path> m_FullProcessedPaths; + + FileData* const* m_FileData; + std::size_t m_NbFiles; + UInt64 m_TotalFileSize; + UInt64 m_LastCallbackFileSize; + UInt64 m_ExtractedFileSize; + + Archive::ProgressCallback m_ProgressCallback; + Archive::FileChangeCallback m_FileChangeCallback; + Archive::ErrorCallback m_ErrorCallback; + Archive::PasswordCallback m_PasswordCallback; + Archive::LogCallback m_LogCallback; + std::wstring* m_Password; +}; + +#endif // EXTRACTCALLBACK_H diff --git a/libs/archive/src/fileio.cpp b/libs/archive/src/fileio.cpp new file mode 100644 index 0000000..83d0665 --- /dev/null +++ b/libs/archive/src/fileio.cpp @@ -0,0 +1,449 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "fileio.h" + +#ifdef _WIN32 + +inline bool BOOLToBool(BOOL v) +{ + return (v != FALSE); +} + +namespace IO +{ + +// FileBase + +bool FileBase::Close() noexcept +{ + if (m_Handle == INVALID_HANDLE_VALUE) + return true; + if (!::CloseHandle(m_Handle)) + return false; + m_Handle = INVALID_HANDLE_VALUE; + return true; +} + +bool FileBase::GetPosition(UInt64& position) noexcept +{ + return Seek(0, FILE_CURRENT, position); +} + +bool FileBase::GetLength(UInt64& length) const noexcept +{ + DWORD sizeHigh; + DWORD sizeLow = ::GetFileSize(m_Handle, &sizeHigh); + if (sizeLow == 0xFFFFFFFF) + if (::GetLastError() != NO_ERROR) + return false; + length = (((UInt64)sizeHigh) << 32) + sizeLow; + return true; +} + +bool FileBase::Seek(Int64 distanceToMove, DWORD moveMethod, + UInt64& newPosition) noexcept +{ + LONG high = (LONG)(distanceToMove >> 32); + DWORD low = ::SetFilePointer(m_Handle, (LONG)(distanceToMove & 0xFFFFFFFF), &high, + moveMethod); + if (low == 0xFFFFFFFF) + if (::GetLastError() != NO_ERROR) + return false; + newPosition = (((UInt64)(UInt32)high) << 32) + low; + return true; +} +bool FileBase::Seek(UInt64 position, UInt64& newPosition) noexcept +{ + return Seek(position, FILE_BEGIN, newPosition); +} + +bool FileBase::SeekToBegin() noexcept +{ + UInt64 newPosition; + return Seek(0, newPosition); +} + +bool FileBase::SeekToEnd(UInt64& newPosition) noexcept +{ + return Seek(0, FILE_END, newPosition); +} + +bool FileBase::Create(std::filesystem::path const& path, DWORD desiredAccess, + DWORD shareMode, DWORD creationDisposition, + DWORD flagsAndAttributes) noexcept +{ + if (!Close()) { + return false; + } + + m_Handle = + ::CreateFileW(path.c_str(), desiredAccess, shareMode, (LPSECURITY_ATTRIBUTES)NULL, + creationDisposition, flagsAndAttributes, (HANDLE)NULL); + + return m_Handle != INVALID_HANDLE_VALUE; +} + +bool FileBase::GetFileInformation(std::filesystem::path const& path, + FileInfo* info) noexcept +{ + // Use FileBase to open/close the file: + FileBase file; + if (!file.Create(path, 0, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS)) + return false; + + BY_HANDLE_FILE_INFORMATION finfo; + if (!BOOLToBool(GetFileInformationByHandle(file.m_Handle, &finfo))) { + return false; + } + + *info = FileInfo(path, finfo); + return true; +} + +// FileIn + +bool FileIn::Open(std::filesystem::path const& filepath, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept +{ + bool res = Create(filepath.c_str(), GENERIC_READ, shareMode, creationDisposition, + flagsAndAttributes); + return res; +} +bool FileIn::OpenShared(std::filesystem::path const& filepath, + bool shareForWrite) noexcept +{ + return Open(filepath, FILE_SHARE_READ | (shareForWrite ? FILE_SHARE_WRITE : 0), + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); +} +bool FileIn::Open(std::filesystem::path const& filepath) noexcept +{ + return OpenShared(filepath, false); +} +bool FileIn::Read(void* data, UInt32 size, UInt32& processedSize) noexcept +{ + processedSize = 0; + do { + UInt32 processedLoc = 0; + bool res = ReadPart(data, size, processedLoc); + processedSize += processedLoc; + if (!res) + return false; + if (processedLoc == 0) + return true; + data = (void*)((unsigned char*)data + processedLoc); + size -= processedLoc; + } while (size > 0); + return true; +} +bool FileIn::Read1(void* data, UInt32 size, UInt32& processedSize) noexcept +{ + DWORD processedLoc = 0; + bool res = BOOLToBool(::ReadFile(m_Handle, data, size, &processedLoc, NULL)); + processedSize = (UInt32)processedLoc; + return res; +} +bool FileIn::ReadPart(void* data, UInt32 size, UInt32& processedSize) noexcept +{ + if (size > kChunkSizeMax) + size = kChunkSizeMax; + return Read1(data, size, processedSize); +} + +// FileOut + +bool FileOut::Open(std::filesystem::path const& fileName, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept +{ + return Create(fileName, GENERIC_WRITE, shareMode, creationDisposition, + flagsAndAttributes); +} + +bool FileOut::Open(std::filesystem::path const& fileName) noexcept +{ + return Open(fileName, FILE_SHARE_READ, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL); +} + +bool FileOut::SetTime(const FILETIME* cTime, const FILETIME* aTime, + const FILETIME* mTime) noexcept +{ + return BOOLToBool(::SetFileTime(m_Handle, cTime, aTime, mTime)); +} +bool FileOut::SetMTime(const FILETIME* mTime) noexcept +{ + return SetTime(NULL, NULL, mTime); +} +bool FileOut::Write(const void* data, UInt32 size, UInt32& processedSize) noexcept +{ + processedSize = 0; + do { + UInt32 processedLoc = 0; + bool res = WritePart(data, size, processedLoc); + processedSize += processedLoc; + if (!res) + return false; + if (processedLoc == 0) + return true; + data = (const void*)((const unsigned char*)data + processedLoc); + size -= processedLoc; + } while (size > 0); + return true; +} + +bool FileOut::SetLength(UInt64 length) noexcept +{ + UInt64 newPosition; + if (!Seek(length, newPosition)) + return false; + if (newPosition != length) + return false; + return SetEndOfFile(); +} +bool FileOut::SetEndOfFile() noexcept +{ + return BOOLToBool(::SetEndOfFile(m_Handle)); +} + +bool FileOut::WritePart(const void* data, UInt32 size, UInt32& processedSize) noexcept +{ + if (size > kChunkSizeMax) + size = kChunkSizeMax; + DWORD processedLoc = 0; + bool res = BOOLToBool(::WriteFile(m_Handle, data, size, &processedLoc, NULL)); + processedSize = (UInt32)processedLoc; + return res; +} + +} // namespace IO + +#else // Linux + +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <cstring> +#include <cerrno> + +namespace IO +{ + +// FileBase + +bool FileBase::Close() noexcept +{ + if (m_Fd == -1) + return true; + if (::close(m_Fd) != 0) + return false; + m_Fd = -1; + return true; +} + +bool FileBase::GetPosition(UInt64& position) noexcept +{ + return Seek(0, SEEK_CUR, position); +} + +bool FileBase::GetLength(UInt64& length) const noexcept +{ + struct stat st; + if (fstat(m_Fd, &st) != 0) + return false; + length = (UInt64)st.st_size; + return true; +} + +bool FileBase::Seek(Int64 distanceToMove, int whence, UInt64& newPosition) noexcept +{ + off_t result = ::lseek(m_Fd, (off_t)distanceToMove, whence); + if (result == (off_t)-1) + return false; + newPosition = (UInt64)result; + return true; +} + +bool FileBase::Seek(UInt64 position, UInt64& newPosition) noexcept +{ + return Seek((Int64)position, SEEK_SET, newPosition); +} + +bool FileBase::SeekToBegin() noexcept +{ + UInt64 newPosition; + return Seek(0, newPosition); +} + +bool FileBase::SeekToEnd(UInt64& newPosition) noexcept +{ + return Seek(0, SEEK_END, newPosition); +} + +bool FileBase::Create(std::filesystem::path const& path, int flags, int mode) noexcept +{ + if (!Close()) { + return false; + } + + m_Fd = ::open(path.c_str(), flags, mode); + return m_Fd != -1; +} + +bool FileBase::GetFileInformation(std::filesystem::path const& path, + FileInfo* info) noexcept +{ + struct stat st; + if (::stat(path.c_str(), &st) != 0) { + return false; + } + + *info = FileInfo(path, st); + return true; +} + +// FileIn + +bool FileIn::Open(std::filesystem::path const& filepath) noexcept +{ + return Create(filepath, O_RDONLY); +} + +bool FileIn::Read(void* data, UInt32 size, UInt32& processedSize) noexcept +{ + processedSize = 0; + do { + UInt32 processedLoc = 0; + bool res = ReadPart(data, size, processedLoc); + processedSize += processedLoc; + if (!res) + return false; + if (processedLoc == 0) + return true; + data = (void*)((unsigned char*)data + processedLoc); + size -= processedLoc; + } while (size > 0); + return true; +} + +bool FileIn::Read1(void* data, UInt32 size, UInt32& processedSize) noexcept +{ + ssize_t result = ::read(m_Fd, data, size); + if (result < 0) { + processedSize = 0; + return false; + } + processedSize = (UInt32)result; + return true; +} + +bool FileIn::ReadPart(void* data, UInt32 size, UInt32& processedSize) noexcept +{ + if (size > kChunkSizeMax) + size = kChunkSizeMax; + return Read1(data, size, processedSize); +} + +// FileOut + +bool FileOut::Open(std::filesystem::path const& fileName) noexcept +{ + return Create(fileName, O_WRONLY | O_CREAT | O_TRUNC, 0644); +} + +bool FileOut::SetTime(const FILETIME* /*cTime*/, const FILETIME* /*aTime*/, + const FILETIME* mTime) noexcept +{ + return SetMTime(mTime); +} + +bool FileOut::SetMTime(const FILETIME* mTime) noexcept +{ + if (!mTime) return true; + + // Convert FILETIME to timespec + // FILETIME is 100ns intervals since 1601-01-01 + // Unix time is seconds since 1970-01-01 + constexpr UInt64 EPOCH_DIFF = 116444736000000000ULL; + UInt64 ticks = ((UInt64)mTime->dwHighDateTime << 32) | mTime->dwLowDateTime; + if (ticks < EPOCH_DIFF) return false; + ticks -= EPOCH_DIFF; + + struct timespec times[2]; + // atime - keep current + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + // mtime + times[1].tv_sec = (time_t)(ticks / 10000000ULL); + times[1].tv_nsec = (long)((ticks % 10000000ULL) * 100); + + return futimens(m_Fd, times) == 0; +} + +bool FileOut::Write(const void* data, UInt32 size, UInt32& processedSize) noexcept +{ + processedSize = 0; + do { + UInt32 processedLoc = 0; + bool res = WritePart(data, size, processedLoc); + processedSize += processedLoc; + if (!res) + return false; + if (processedLoc == 0) + return true; + data = (const void*)((const unsigned char*)data + processedLoc); + size -= processedLoc; + } while (size > 0); + return true; +} + +bool FileOut::SetLength(UInt64 length) noexcept +{ + UInt64 newPosition; + if (!Seek(length, newPosition)) + return false; + if (newPosition != length) + return false; + return SetEndOfFile(); +} + +bool FileOut::SetEndOfFile() noexcept +{ + UInt64 pos; + if (!GetPosition(pos)) + return false; + return ftruncate(m_Fd, (off_t)pos) == 0; +} + +bool FileOut::WritePart(const void* data, UInt32 size, UInt32& processedSize) noexcept +{ + if (size > kChunkSizeMax) + size = kChunkSizeMax; + ssize_t result = ::write(m_Fd, data, size); + if (result < 0) { + processedSize = 0; + return false; + } + processedSize = (UInt32)result; + return true; +} + +} // namespace IO + +#endif diff --git a/libs/archive/src/fileio.h b/libs/archive/src/fileio.h new file mode 100644 index 0000000..72b71c7 --- /dev/null +++ b/libs/archive/src/fileio.h @@ -0,0 +1,343 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARCHIVE_FILEIO_H +#define ARCHIVE_FILEIO_H + +// This code is adapted from 7z client code. + +#ifdef _WIN32 +#include <Windows.h> +#include "7zip//Archive/IArchive.h" +#else +#include <Common/MyWindows.h> +#include <7zip/Archive/IArchive.h> +#endif + +#include <filesystem> +#include <string> + +#ifndef _WIN32 +#include <sys/stat.h> +#endif + +namespace IO +{ + +#ifdef _WIN32 + +/** + * Small class that wraps windows BY_HANDLE_FILE_INFORMATION and returns + * type matching 7z types. + */ +class FileInfo +{ +public: + FileInfo() : m_Valid{false} {}; + FileInfo(std::filesystem::path const& path, BY_HANDLE_FILE_INFORMATION fileInfo) + : m_Valid{true}, m_Path(path), m_FileInfo{fileInfo} + {} + + bool isValid() const { return m_Valid; } + + const std::filesystem::path& path() const { return m_Path; } + + UInt32 fileAttributes() const { return m_FileInfo.dwFileAttributes; } + FILETIME creationTime() const { return m_FileInfo.ftCreationTime; } + FILETIME lastAccessTime() const { return m_FileInfo.ftLastAccessTime; } + FILETIME lastWriteTime() const { return m_FileInfo.ftLastWriteTime; } + UInt32 volumeSerialNumber() const { return m_FileInfo.dwVolumeSerialNumber; } + UInt64 fileSize() const + { + return ((UInt64)m_FileInfo.nFileSizeHigh) << 32 | m_FileInfo.nFileSizeLow; + } + UInt32 numberOfLinks() const { return m_FileInfo.nNumberOfLinks; } + UInt64 fileInfex() const + { + return ((UInt64)m_FileInfo.nFileIndexHigh) << 32 | m_FileInfo.nFileIndexLow; + } + + bool isArchived() const { return MatchesMask(FILE_ATTRIBUTE_ARCHIVE); } + bool isCompressed() const { return MatchesMask(FILE_ATTRIBUTE_COMPRESSED); } + bool isDir() const { return MatchesMask(FILE_ATTRIBUTE_DIRECTORY); } + bool isEncrypted() const { return MatchesMask(FILE_ATTRIBUTE_ENCRYPTED); } + bool isHidden() const { return MatchesMask(FILE_ATTRIBUTE_HIDDEN); } + bool isNormal() const { return MatchesMask(FILE_ATTRIBUTE_NORMAL); } + bool isOffline() const { return MatchesMask(FILE_ATTRIBUTE_OFFLINE); } + bool isReadOnly() const { return MatchesMask(FILE_ATTRIBUTE_READONLY); } + bool iasReparsePoint() const { return MatchesMask(FILE_ATTRIBUTE_REPARSE_POINT); } + bool isSparse() const { return MatchesMask(FILE_ATTRIBUTE_SPARSE_FILE); } + bool isSystem() const { return MatchesMask(FILE_ATTRIBUTE_SYSTEM); } + bool isTemporary() const { return MatchesMask(FILE_ATTRIBUTE_TEMPORARY); } + +private: + bool MatchesMask(UINT32 mask) const + { + return ((m_FileInfo.dwFileAttributes & mask) != 0); + } + + bool m_Valid; + std::filesystem::path m_Path; + BY_HANDLE_FILE_INFORMATION m_FileInfo; +}; + +#else // Linux + +/** + * FileInfo class for Linux - uses stat() to get file information. + * Returns 7zip-compatible types. + */ +class FileInfo +{ +public: + FileInfo() : m_Valid{false}, m_Stat{} {}; + FileInfo(std::filesystem::path const& path, struct stat const& st) + : m_Valid{true}, m_Path(path), m_Stat{st} + {} + + bool isValid() const { return m_Valid; } + + const std::filesystem::path& path() const { return m_Path; } + + UInt32 fileAttributes() const { + UInt32 attr = 0; + if (S_ISDIR(m_Stat.st_mode)) attr |= FILE_ATTRIBUTE_DIRECTORY; + if (!(m_Stat.st_mode & S_IWUSR)) attr |= FILE_ATTRIBUTE_READONLY; + if (attr == 0) attr = FILE_ATTRIBUTE_NORMAL; + return attr; + } + + // Convert timespec to FILETIME (100ns intervals since 1601-01-01) + static FILETIME timespecToFiletime(struct timespec const& ts) { + // Offset between 1601-01-01 and 1970-01-01 in 100ns intervals + constexpr UInt64 EPOCH_DIFF = 116444736000000000ULL; + UInt64 ticks = (UInt64)ts.tv_sec * 10000000ULL + (UInt64)ts.tv_nsec / 100ULL + EPOCH_DIFF; + FILETIME ft; + ft.dwLowDateTime = (DWORD)(ticks & 0xFFFFFFFF); + ft.dwHighDateTime = (DWORD)(ticks >> 32); + return ft; + } + + FILETIME creationTime() const { + // Linux doesn't have creation time in all filesystems, use ctime (status change) + return timespecToFiletime(m_Stat.st_ctim); + } + FILETIME lastAccessTime() const { + return timespecToFiletime(m_Stat.st_atim); + } + FILETIME lastWriteTime() const { + return timespecToFiletime(m_Stat.st_mtim); + } + UInt32 volumeSerialNumber() const { return (UInt32)m_Stat.st_dev; } + UInt64 fileSize() const { return (UInt64)m_Stat.st_size; } + UInt32 numberOfLinks() const { return (UInt32)m_Stat.st_nlink; } + UInt64 fileInfex() const { return (UInt64)m_Stat.st_ino; } + + bool isArchived() const { return false; } + bool isCompressed() const { return false; } + bool isDir() const { return S_ISDIR(m_Stat.st_mode); } + bool isEncrypted() const { return false; } + bool isHidden() const { return false; } + bool isNormal() const { return S_ISREG(m_Stat.st_mode); } + bool isOffline() const { return false; } + bool isReadOnly() const { return !(m_Stat.st_mode & S_IWUSR); } + bool iasReparsePoint() const { return S_ISLNK(m_Stat.st_mode); } + bool isSparse() const { return false; } + bool isSystem() const { return false; } + bool isTemporary() const { return false; } + +private: + bool m_Valid; + std::filesystem::path m_Path; + struct stat m_Stat; +}; + +#endif // _WIN32 + +class FileBase +{ +public: // Constructors, destructor, assignment. +#ifdef _WIN32 + FileBase() noexcept : m_Handle{INVALID_HANDLE_VALUE} {} + + FileBase(FileBase&& other) noexcept : m_Handle{other.m_Handle} + { + other.m_Handle = INVALID_HANDLE_VALUE; + } +#else + FileBase() noexcept : m_Fd{-1} {} + + FileBase(FileBase&& other) noexcept : m_Fd{other.m_Fd} + { + other.m_Fd = -1; + } +#endif + + ~FileBase() noexcept { Close(); } + + FileBase(FileBase const&) = delete; + FileBase& operator=(FileBase const&) = delete; + FileBase& operator=(FileBase&&) = delete; + +public: // Operations + bool Close() noexcept; + + bool GetPosition(UInt64& position) noexcept; + bool GetLength(UInt64& length) const noexcept; + +#ifdef _WIN32 + bool Seek(Int64 distanceToMove, DWORD moveMethod, UInt64& newPosition) noexcept; +#else + bool Seek(Int64 distanceToMove, int whence, UInt64& newPosition) noexcept; +#endif + bool Seek(UInt64 position, UInt64& newPosition) noexcept; + bool SeekToBegin() noexcept; + bool SeekToEnd(UInt64& newPosition) noexcept; + + // Note: Only the static version (unlike in 7z) because I want FileInfo to hold the + // path to the file, and the non-static version is never used (except by the static + // version). + static bool GetFileInformation(std::filesystem::path const& path, + FileInfo* info) noexcept; + +protected: +#ifdef _WIN32 + bool Create(std::filesystem::path const& path, DWORD desiredAccess, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; +#else + bool Create(std::filesystem::path const& path, int flags, int mode = 0644) noexcept; +#endif + +protected: + static constexpr UInt32 kChunkSizeMax = (1 << 22); + +#ifdef _WIN32 + HANDLE m_Handle; +#else + int m_Fd; +#endif +}; + +class FileIn : public FileBase +{ +public: + using FileBase::FileBase; + +public: // Operations +#ifdef _WIN32 + bool Open(std::filesystem::path const& filepath, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; + bool OpenShared(std::filesystem::path const& filepath, bool shareForWrite) noexcept; +#endif + bool Open(std::filesystem::path const& filepath) noexcept; + + bool Read(void* data, UInt32 size, UInt32& processedSize) noexcept; + +protected: + bool Read1(void* data, UInt32 size, UInt32& processedSize) noexcept; + bool ReadPart(void* data, UInt32 size, UInt32& processedSize) noexcept; +}; + +class FileOut : public FileBase +{ +public: + using FileBase::FileBase; + +public: // Operations: +#ifdef _WIN32 + bool Open(std::filesystem::path const& fileName, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; +#endif + bool Open(std::filesystem::path const& fileName) noexcept; + + bool SetTime(const FILETIME* cTime, const FILETIME* aTime, + const FILETIME* mTime) noexcept; + bool SetMTime(const FILETIME* mTime) noexcept; + bool Write(const void* data, UInt32 size, UInt32& processedSize) noexcept; + + bool SetLength(UInt64 length) noexcept; + bool SetEndOfFile() noexcept; + +protected: // Protected Operations: + bool WritePart(const void* data, UInt32 size, UInt32& processedSize) noexcept; +}; + +/** + * @brief Convert the given wide-string to a path object. + * + * On Windows: adds the long-path prefix if not present. + * On Linux: simply converts wstring to path (no long-path prefix needed). + * + * @param path The string containing the path. + * + * @return the created path. + */ +inline std::filesystem::path make_path(std::wstring const& pathstr) +{ + namespace fs = std::filesystem; + +#ifdef _WIN32 + constexpr const wchar_t* lprefix = L"\\\\?\\"; + constexpr const wchar_t* unc_prefix = L"\\\\"; + constexpr const wchar_t* unc_lprefix = L"\\\\?\\UNC\\"; + + // If path is already a long path, just return it: + if (pathstr.starts_with(lprefix)) { + return fs::path{pathstr}.make_preferred(); + } + + fs::path path{pathstr}; + + // Convert to an absolute path: + if (!path.is_absolute()) { + path = fs::absolute(path); + } + + // backslashes + path = path.make_preferred(); + + // Get rid of duplicate separators and relative moves + path = path.lexically_normal(); + + const std::wstring pathstr_fixed = path.native(); + + // If this is a UNC, the prefix is different + if (pathstr_fixed.starts_with(unc_prefix)) { + return fs::path{unc_lprefix + pathstr_fixed.substr(2)}; + } + + // Add the long-path prefix + return fs::path{lprefix + pathstr_fixed}; +#else + // On Linux, simply convert wstring to path (no long-path prefix needed) + fs::path path{pathstr}; + + if (!path.is_absolute()) { + path = fs::absolute(path); + } + + path = path.lexically_normal(); + return path; +#endif +} + +} // namespace IO + +#endif diff --git a/libs/archive/src/formatter.h b/libs/archive/src/formatter.h new file mode 100644 index 0000000..0776bee --- /dev/null +++ b/libs/archive/src/formatter.h @@ -0,0 +1,121 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARCHIVE_FORMAT_H +#define ARCHIVE_FORMAT_H + +// This header specialize formatter() for some useful types. It should not be +// exposed outside of the library. It also contains some useful methods for +// string manipulation. + +#include <format> + +#include <cwctype> +#include <filesystem> +#include <stdexcept> +#include <string> + +template <> +struct std::formatter<std::string, wchar_t> : std::formatter<std::wstring, wchar_t> +{ + template <typename FormatContext> + auto format(std::string const& s, FormatContext& ctx) const + { + return std::formatter<std::wstring, wchar_t>::format( + std::wstring(s.begin(), s.end()), ctx); + } +}; + +template <> +struct std::formatter<std::exception, wchar_t> : std::formatter<std::string, wchar_t> +{ + template <typename FormatContext> + auto format(std::exception const& ex, FormatContext& ctx) const + { + return std::formatter<std::string, wchar_t>::format(ex.what(), ctx); + } +}; + +template <> +struct std::formatter<std::error_code, wchar_t> : std::formatter<std::string, wchar_t> +{ + template <typename FormatContext> + auto format(std::error_code const& ec, FormatContext& ctx) const + { + return std::formatter<std::string, wchar_t>::format(ec.message(), ctx); + } +}; + +template <> +struct std::formatter<std::filesystem::path, wchar_t> + : std::formatter<std::wstring, wchar_t> +{ + template <typename FormatContext> + auto format(std::filesystem::path const& path, FormatContext& ctx) const + { + return std::formatter<std::wstring, wchar_t>::format(path.wstring(), ctx); + } +}; + +namespace ArchiveStrings +{ + +/** + * @brief Join the element of the given container using the given separator. + * + * @param c The container. Must be satisfy standard container requirements. + * @param sep The separator. + * + * @return a string containing the element joint, or an empty string if c + * is empty. + */ +template <class C> +std::wstring join(C const& c, std::wstring const& sep) +{ + auto begin = std::begin(c), end = std::end(c); + + if (begin == end) { + return {}; + } + std::wstring r = *begin++; + for (; begin != end; ++begin) { + r += *begin + sep; + } + + return r; +} + +/** + * @brief Conver the given string to lowercase. + * + * @param s The string to convert. + * + * @return the converted string. + */ +inline std::wstring towlower(std::wstring s) +{ + std::transform(std::begin(s), std::end(s), std::begin(s), [](wchar_t c) { + return static_cast<wchar_t>(::towlower(c)); + }); + return s; +} +} // namespace ArchiveStrings + +#endif diff --git a/libs/archive/src/inputstream.cpp b/libs/archive/src/inputstream.cpp new file mode 100644 index 0000000..b44986a --- /dev/null +++ b/libs/archive/src/inputstream.cpp @@ -0,0 +1,70 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "inputstream.h" +#include "compat.h" + +static inline HRESULT ConvertBoolToHRESULT(bool result) +{ + if (result) { + return S_OK; + } + DWORD lastError = ::GetLastError(); + if (lastError == 0) { + return E_FAIL; + } + return HRESULT_FROM_WIN32(lastError); +} + +InputStream::InputStream() {} + +InputStream::~InputStream() {} + +bool InputStream::Open(std::filesystem::path const& filename) +{ + return m_File.Open(filename); +} + +STDMETHODIMP InputStream::Read(void* data, UInt32 size, UInt32* processedSize) throw() +{ + UInt32 realProcessedSize; + bool result = m_File.Read(data, size, realProcessedSize); + + if (processedSize != nullptr) { + *processedSize = realProcessedSize; + } + + if (result) { + return S_OK; + } + + return HRESULT_FROM_WIN32(::GetLastError()); +} + +STDMETHODIMP InputStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) throw() +{ + UInt64 realNewPosition = offset; + bool result = m_File.Seek(offset, seekOrigin, realNewPosition); + + if (newPosition) { + *newPosition = realNewPosition; + } + return ConvertBoolToHRESULT(result); +} diff --git a/libs/archive/src/inputstream.h b/libs/archive/src/inputstream.h new file mode 100644 index 0000000..b305137 --- /dev/null +++ b/libs/archive/src/inputstream.h @@ -0,0 +1,54 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef INPUTSTREAM_H +#define INPUTSTREAM_H + +#include <7zip/IStream.h> + +#include <filesystem> + +#include "fileio.h" +#include "unknown_impl.h" + +/** This class implements an input stream for opening archive files + * + * Note that the handling on errors could be better. + */ +class InputStream : public IInStream +{ + + UNKNOWN_1_INTERFACE(IInStream); + +public: + InputStream(); + + virtual ~InputStream(); + + bool Open(std::filesystem::path const& filename); + + STDMETHOD(Read)(void* data, UInt32 size, UInt32* processedSize) throw(); + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) throw(); + +private: + IO::FileIn m_File; +}; + +#endif // INPUTSTREAM_H diff --git a/libs/archive/src/instrument.h b/libs/archive/src/instrument.h new file mode 100644 index 0000000..82dc3f7 --- /dev/null +++ b/libs/archive/src/instrument.h @@ -0,0 +1,111 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARCHIVE_INSTRUMENT_H +#define ARCHIVE_INSTRUMENT_H + +#include <chrono> +#include <format> + +namespace ArchiveTimers +{ + +/** + * If INSTRUMENT_ARCHIVE is not defined, we define a Timer() class that is empty and + * does nothing, and thus should be optimized out by the compiler. + */ +#ifdef INSTRUMENT_ARCHIVE + +/** + * Small class that can be used to instrument portion of code using a guard. + * + */ +class Timer +{ +public: + using clock_t = std::chrono::system_clock; + + struct TimerGuard + { + + TimerGuard(TimerGuard const&) = delete; + TimerGuard(TimerGuard&&) = delete; + TimerGuard& operator=(TimerGuard const&) = delete; + TimerGuard& operator=(TimerGuard&&) = delete; + + ~TimerGuard() + { + m_Timer.ncalls++; + m_Timer.time += clock_t::now() - m_Start; + } + + private: + TimerGuard(Timer& timer) : m_Timer{timer}, m_Start{clock_t::now()} {} + + Timer& m_Timer; + clock_t::time_point m_Start; + + friend class Timer; + }; + + /** + * + */ + Timer() = default; + + /** + * @brief Instrument a portion of code. + * + * Instrumenting is done by calling `instrument()` and storing the result in a local + * variable. The scope of the local guard variable is the scope of the + * instrumentation. + * + * @return a guard to instrument the code. + */ + TimerGuard instrument() { return {*this}; } + + std::wstring toString(std::wstring const& name) const + { + auto ms = [](auto&& t) { + return std::chrono::duration<double, std::milli>(t); + }; + return std::format( + L"Instrument '{}': {} calls, total of {}ms, {:.3f}ms per call on average.", + name, ncalls, ms(time).count(), ms(time).count() / ncalls); + } + +private: + std::size_t ncalls{0}; + clock_t::duration time{0}; +}; + +#else + +struct Timer +{ + // This is the only method needed: + Timer& instrument() { return *this; } +}; + +#endif + +} // namespace ArchiveTimers + +#endif diff --git a/libs/archive/src/interfaceguids.cpp b/libs/archive/src/interfaceguids.cpp new file mode 100644 index 0000000..ae6659b --- /dev/null +++ b/libs/archive/src/interfaceguids.cpp @@ -0,0 +1,37 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// This file instantiates the GUIDs needed for linking with the 7zip code +#ifdef _WIN32 +#include <initguid.h> +#else +#define INITGUID +#include <Common/MyWindows.h> +#endif + +// extractcallback, opencallback +#include <7zip/IPassword.h> + +// archive, opencallback +// Note: In a sense, this is unnecessary as IArchive.h includes it +#include <7zip/IStream.h> + +// archive, opencallback +#include <7zip/Archive/IArchive.h> diff --git a/libs/archive/src/library.h b/libs/archive/src/library.h new file mode 100644 index 0000000..9fec56d --- /dev/null +++ b/libs/archive/src/library.h @@ -0,0 +1,164 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef ARCHIVE_LIBRARY_H +#define ARCHIVE_LIBRARY_H + +#ifdef _WIN32 +#include <Windows.h> +#else +#include <Common/MyWindows.h> +#include <cstdlib> +#include <dlfcn.h> +#include <string> +#include <unistd.h> +#include <vector> +#endif + +/** + * Very small wrapper around shared libraries (DLL on Windows, .so on Linux). + */ +class ALibrary +{ +public: +#ifdef _WIN32 + ALibrary(const char* path) : m_Module{nullptr}, m_LastError{ERROR_SUCCESS} + { + m_Module = LoadLibraryA(path); + if (m_Module == nullptr) { + updateLastError(); + } + } + + ~ALibrary() + { + if (m_Module) { + FreeLibrary(m_Module); + } + } + + template <class T> + T resolve(const char* procName) + { + if (!m_Module) { + return nullptr; + } + auto proc = GetProcAddress(m_Module, procName); + if (!proc) { + updateLastError(); + return nullptr; + } + return reinterpret_cast<T>(proc); + } + + DWORD getLastError() const { return m_LastError; } + bool isOpen() const { return m_Module != nullptr; } + operator bool() const { return isOpen(); } + +private: + void updateLastError() { m_LastError = ::GetLastError(); } + + HMODULE m_Module; + DWORD m_LastError; + +#else // Linux + + ALibrary(const char* path) : m_Handle{nullptr}, m_LastError{0} + { + // Find the directory containing our own executable + std::string exeDir; + char selfPath[4096]; + ssize_t len = readlink("/proc/self/exe", selfPath, sizeof(selfPath) - 1); + if (len > 0) { + selfPath[len] = '\0'; + exeDir = std::string(selfPath); + auto slash = exeDir.rfind('/'); + if (slash != std::string::npos) + exeDir = exeDir.substr(0, slash); + } + + // AppImage: check MO2_DLLS_DIR env var first (writable dir next to AppImage) + std::string envDlls; + const char* envVal = std::getenv("MO2_DLLS_DIR"); + if (envVal && envVal[0] != '\0') { + envDlls = envVal; + } + + // Try bundled 7z.so locations first (env override, exe dir, dlls/ subdir) + std::vector<std::string> tryPaths; + if (!envDlls.empty()) { + tryPaths.push_back(envDlls + "/7z.so"); + } + tryPaths.push_back(exeDir + "/7z.so"); + tryPaths.push_back(exeDir + "/dlls/7z.so"); + tryPaths.push_back("7z.so"); + tryPaths.push_back("dlls/7z.so"); + tryPaths.push_back("/usr/lib/p7zip/7z.so"); + tryPaths.push_back("/usr/lib64/p7zip/7z.so"); + tryPaths.push_back("/usr/libexec/p7zip/7z.so"); + + for (const auto& tryPath : tryPaths) { + m_Handle = dlopen(tryPath.c_str(), RTLD_LAZY); + if (m_Handle) break; + } + + if (!m_Handle) { + // Last resort: try the original path as-is + m_Handle = dlopen(path, RTLD_LAZY); + } + + if (!m_Handle) { + m_LastError = 1; + } + } + + ~ALibrary() + { + if (m_Handle) { + dlclose(m_Handle); + } + } + + template <class T> + T resolve(const char* procName) + { + if (!m_Handle) { + return nullptr; + } + void* sym = dlsym(m_Handle, procName); + if (!sym) { + m_LastError = 1; + return nullptr; + } + return reinterpret_cast<T>(sym); + } + + DWORD getLastError() const { return m_LastError; } + bool isOpen() const { return m_Handle != nullptr; } + operator bool() const { return isOpen(); } + +private: + void* m_Handle; + DWORD m_LastError; + +#endif +}; + +#endif diff --git a/libs/archive/src/multioutputstream.cpp b/libs/archive/src/multioutputstream.cpp new file mode 100644 index 0000000..a2eca06 --- /dev/null +++ b/libs/archive/src/multioutputstream.cpp @@ -0,0 +1,142 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "multioutputstream.h" +#include "compat.h" + +#ifdef _WIN32 +#include <fcntl.h> +#include <io.h> +#endif + +static inline HRESULT ConvertBoolToHRESULT(bool result) +{ + if (result) { + return S_OK; + } + DWORD lastError = ::GetLastError(); + if (lastError == 0) { + return E_FAIL; + } + return HRESULT_FROM_WIN32(lastError); +} + +////////////////////////// +// MultiOutputStream + +MultiOutputStream::MultiOutputStream(WriteCallback callback) : m_WriteCallback(callback) +{} + +MultiOutputStream::~MultiOutputStream() {} + +HRESULT MultiOutputStream::Close() +{ + for (auto& file : m_Files) { + file.Close(); + } + return S_OK; +} + +bool MultiOutputStream::Open(std::vector<std::filesystem::path> const& filepaths) +{ + m_ProcessedSize = 0; + bool ok = true; + m_Files.clear(); + for (auto& path : filepaths) { + m_Files.emplace_back(); + if (!m_Files.back().Open(path)) { + ok = false; + } + } + return ok; +} + +STDMETHODIMP MultiOutputStream::Write(const void* data, UInt32 size, + UInt32* processedSize) throw() +{ + bool update_processed(true); + for (auto& file : m_Files) { + UInt32 realProcessedSize; + if (!file.Write(data, size, realProcessedSize)) { + return ConvertBoolToHRESULT(false); + } + if (update_processed) { + m_ProcessedSize += realProcessedSize; + if (m_WriteCallback) { + m_WriteCallback(realProcessedSize, m_ProcessedSize); + } + update_processed = false; + } + if (processedSize != nullptr) { + *processedSize = realProcessedSize; + } + } + return S_OK; +} + +STDMETHODIMP MultiOutputStream::Seek(Int64 offset, UInt32 seekOrigin, + UInt64* newPosition) throw() +{ + if (seekOrigin >= 3) + return STG_E_INVALIDFUNCTION; + + bool result = true; + for (auto& file : m_Files) { + UInt64 realNewPosition; + result = file.Seek(offset, seekOrigin, realNewPosition); + if (newPosition) + *newPosition = realNewPosition; + } + return ConvertBoolToHRESULT(result); +} + +STDMETHODIMP MultiOutputStream::SetSize(UInt64 newSize) throw() +{ + bool result = true; + for (auto& file : m_Files) { + UInt64 currentPos; +#ifdef _WIN32 + if (!file.Seek(0, FILE_CURRENT, currentPos)) +#else + if (!file.Seek(0, SEEK_CUR, currentPos)) +#endif + return E_FAIL; + bool cresult = file.SetLength(newSize); + UInt64 currentPos2; + result = result && cresult && file.Seek(currentPos, currentPos2); + } + return result ? S_OK : E_FAIL; +} + +HRESULT MultiOutputStream::GetSize(UInt64* size) +{ + if (m_Files.empty()) { + return ConvertBoolToHRESULT(false); + } + return ConvertBoolToHRESULT(m_Files[0].GetLength(*size)); +} + +bool MultiOutputStream::SetMTime(FILETIME const* mTime) +{ + for (auto& file : m_Files) { + file.SetMTime(mTime); + } + return true; +} diff --git a/libs/archive/src/multioutputstream.h b/libs/archive/src/multioutputstream.h new file mode 100644 index 0000000..624ab5b --- /dev/null +++ b/libs/archive/src/multioutputstream.h @@ -0,0 +1,106 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef MULTIOUTPUTSTREAM_H +#define MULTIOUTPUTSTREAM_H + +#include <filesystem> +#include <functional> +#include <memory> +#include <vector> + +#include <7zip/IStream.h> + +#include "fileio.h" +#include "unknown_impl.h" + +/** This class allows you to open and output to multiple file handles at a time. + * It implements the ISequentalOutputStream interface and has some extra functions + * which are used by the CArchiveExtractCallback class to basically open and + * set the timestamp on all the files. + * + * Note that the handling on errors could be better. + */ +class MultiOutputStream : public IOutStream +{ + + UNKNOWN_1_INTERFACE(IOutStream); + +public: + // Callback for write. Args are: 1) number of bytes that have been + // written for this call, 2) number of bytes that have been written + // in total. + using WriteCallback = std::function<void(UInt32, UInt64)>; + + MultiOutputStream(WriteCallback callback = {}); + + virtual ~MultiOutputStream(); + + /** Opens the supplied files. + * + * @returns true if all went OK, false if any file failed to open + */ + bool Open(std::vector<std::filesystem::path> const& fileNames); + + /** Closes all the files opened by the last open + * + * Note if there are any errors, the code will merely report the last one. + */ + HRESULT Close(); + + /** Sets the modification time on the open files + * + * @returns true if all files had the time set succesfully, false otherwise + * note that this will give up as soon as it gets an error + */ + bool SetMTime(FILETIME const* mTime); + + // ISequentialOutStream interface + + /** Write data to all the streams + * + * The processedSize returned will be the same as size, unless + * there was an error, in which case it might or might not be different. + * @warn If an error happens, the code will not attempt any further writing, + * so some files might not get written to at all + */ + STDMETHOD(Write)(const void* data, UInt32 size, UInt32* processedSize) throw() override; + + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) throw() override; + STDMETHOD(SetSize)(UInt64 newSize) throw() override; + HRESULT GetSize(UInt64* size); + +private: + WriteCallback m_WriteCallback; + + /** This is the amount of data written to *any one* file. + * + * If there are errors writing to one of the files, this might or might + * not match what was actually written to another of the files + */ + UInt64 m_ProcessedSize; + + /** All the files opened for this 'stream' + * + */ + std::vector<IO::FileOut> m_Files; +}; + +#endif // MULTIOUTPUTSTREAM_H diff --git a/libs/archive/src/opencallback.cpp b/libs/archive/src/opencallback.cpp new file mode 100644 index 0000000..9dd0e24 --- /dev/null +++ b/libs/archive/src/opencallback.cpp @@ -0,0 +1,168 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "opencallback.h" +#include "compat.h" + +#include "inputstream.h" +#include "propertyvariant.h" + +#include <format> +#include <memory> +#include <stdexcept> +#include <string> +#include <vector> + +#include "fileio.h" + +#define UNUSED(x) + +CArchiveOpenCallback::CArchiveOpenCallback(Archive::PasswordCallback passwordCallback, + Archive::LogCallback logCallback, + std::filesystem::path const& filepath) + : m_PasswordCallback(passwordCallback), m_LogCallback(logCallback), + m_Path(filepath), m_SubArchiveMode(false) +{ + if (!exists(filepath)) { + throw std::runtime_error("invalid archive path"); + } + + if (!IO::FileBase::GetFileInformation(filepath, &m_FileInfo)) { + throw std::runtime_error("failed to retrieve file information"); + } +} + +/* -------------------- IArchiveOpenCallback -------------------- */ +STDMETHODIMP CArchiveOpenCallback::SetTotal(const UInt64* UNUSED(files), + const UInt64* UNUSED(bytes)) throw() +{ + return S_OK; +} + +STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64* UNUSED(files), + const UInt64* UNUSED(bytes)) throw() +{ + return S_OK; +} + +/* -------------------- ICryptoGetTextPassword -------------------- */ +/* This apparently implements ICryptoGetTextPassword but even with a passworded + * archive it doesn't seem to be called. There is also another API which isn't + * implemented. + */ +STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR* passwordOut) throw() +{ + if (!m_PasswordCallback) { + return E_ABORT; + } + m_Password = m_PasswordCallback(); + *passwordOut = ::SysAllocString(m_Password.c_str()); + return *passwordOut != 0 ? S_OK : E_OUTOFMEMORY; +} + +/* -------------------- IArchiveOpenSetSubArchiveName -------------------- */ +/* I don't know what this does or how you call it. */ +STDMETHODIMP CArchiveOpenCallback::SetSubArchiveName(const wchar_t* name) throw() +{ + m_SubArchiveMode = true; + m_SubArchiveName = name; + return S_OK; +} + +/* -------------------- IArchiveOpenVolumeCallback -------------------- */ + +STDMETHODIMP CArchiveOpenCallback::GetProperty(PROPID propID, + PROPVARIANT* value) throw() +{ + // A scan of the source code indicates that the only things that ever call the + // IArchiveOpenVolumeCallback interface ask for the file name and size. + PropertyVariant& prop = *static_cast<PropertyVariant*>(value); + + switch (propID) { + case kpidName: { + if (m_SubArchiveMode) { + prop = m_SubArchiveName; + } else { + // Note: Need to call .native(), otherwize we get a link error because we try to + // assign a fs::path to the variant. + prop = m_Path.filename().wstring(); + } + } break; + + case kpidIsDir: + prop = m_FileInfo.isDir(); + break; + case kpidSize: + prop = m_FileInfo.fileSize(); + break; + case kpidAttrib: + prop = m_FileInfo.fileAttributes(); + break; + case kpidCTime: + prop = m_FileInfo.creationTime(); + break; + case kpidATime: + prop = m_FileInfo.lastAccessTime(); + break; + case kpidMTime: + prop = m_FileInfo.lastWriteTime(); + break; + + default: + m_LogCallback(Archive::LogLevel::Warning, + std::format(L"Unexpected property {}.", propID)); + } + return S_OK; +} + +STDMETHODIMP CArchiveOpenCallback::GetStream(const wchar_t* name, + IInStream** inStream) throw() +{ + *inStream = nullptr; + + // this function will be called repeatedly for split archives, `name` will + // have increasing numbers in the extension and S_FALSE must be returned + // when a filename doesn't exist so the search stops + + if (!name) { + return S_FALSE; + } + + // `name` is just the filename, so build a path from the directory that + // contained the last file + const auto path = m_Path.parent_path() / name; + + if (!exists(m_FileInfo.path()) || m_FileInfo.isDir()) { + return S_FALSE; + } + + if (!IO::FileBase::GetFileInformation(path, &m_FileInfo)) { + return S_FALSE; + } + + CComPtr<InputStream> inFile(new InputStream); + + if (!inFile->Open(m_FileInfo.path())) { + return ::GetLastError(); + } + + *inStream = inFile.Detach(); + return S_OK; +} diff --git a/libs/archive/src/opencallback.h b/libs/archive/src/opencallback.h new file mode 100644 index 0000000..44367be --- /dev/null +++ b/libs/archive/src/opencallback.h @@ -0,0 +1,75 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef OPENCALLBACK_H +#define OPENCALLBACK_H + +#include <filesystem> +#include <string> + +#include <7zip/Archive/IArchive.h> +#include <7zip/IPassword.h> + +#include "archive.h" +#include "fileio.h" +#include "unknown_impl.h" + +class CArchiveOpenCallback : public IArchiveOpenCallback, + public IArchiveOpenVolumeCallback, + public ICryptoGetTextPassword, + public IArchiveOpenSetSubArchiveName +{ + + UNKNOWN_4_INTERFACE(IArchiveOpenCallback, IArchiveOpenVolumeCallback, + ICryptoGetTextPassword, IArchiveOpenSetSubArchiveName); + +public: + CArchiveOpenCallback(Archive::PasswordCallback passwordCallback, + Archive::LogCallback logCallback, + std::filesystem::path const& filepath); + + virtual ~CArchiveOpenCallback() {} + + const std::wstring& GetPassword() const { return m_Password; } + + Z7_IFACE_COM7_IMP(IArchiveOpenCallback) + Z7_IFACE_COM7_IMP(IArchiveOpenVolumeCallback) + + // ICryptoGetTextPassword interface + STDMETHOD(CryptoGetTextPassword)(BSTR* password) throw(); + // Not implemented STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR + // *password); + + // IArchiveOpenSetSubArchiveName interface + STDMETHOD(SetSubArchiveName)(const wchar_t* name) throw(); + +private: + Archive::PasswordCallback m_PasswordCallback; + Archive::LogCallback m_LogCallback; + std::wstring m_Password; + + std::filesystem::path m_Path; + IO::FileInfo m_FileInfo; + + bool m_SubArchiveMode; + std::wstring m_SubArchiveName; +}; + +#endif // OPENCALLBACK_H diff --git a/libs/archive/src/propertyvariant.cpp b/libs/archive/src/propertyvariant.cpp new file mode 100644 index 0000000..fe882b2 --- /dev/null +++ b/libs/archive/src/propertyvariant.cpp @@ -0,0 +1,214 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "propertyvariant.h" +#include "compat.h" + +#ifdef _WIN32 +#include <guiddef.h> +#endif + +#include <stdexcept> +#include <stdint.h> +#include <string> + +PropertyVariant::PropertyVariant() +{ + PropVariantInit(this); +} + +PropertyVariant::~PropertyVariant() +{ + clear(); +} + +void PropertyVariant::clear() +{ + PropVariantClear(this); +} + +// Arguably the behviours for empty here are wrong. +template <> +PropertyVariant::operator bool() const +{ + switch (vt) { + case VT_EMPTY: + return false; + + case VT_BOOL: + return boolVal != VARIANT_FALSE; + + default: + throw std::runtime_error("Property is not a bool"); + } +} + +template <> +PropertyVariant::operator uint64_t() const +{ + switch (vt) { + case VT_EMPTY: + return 0; + + case VT_UI1: + return bVal; + + case VT_UI2: + return uiVal; + + case VT_UI4: + return ulVal; + + case VT_UI8: + return static_cast<uint64_t>(uhVal.QuadPart); + + default: + throw std::runtime_error("Property is not an unsigned integer"); + } +} + +template <> +PropertyVariant::operator uint32_t() const +{ + switch (vt) { + case VT_EMPTY: + return 0; + + case VT_UI1: + return bVal; + + case VT_UI2: + return uiVal; + + case VT_UI4: + return ulVal; + + default: + throw std::runtime_error("Property is not an unsigned integer"); + } +} + +template <> +PropertyVariant::operator std::wstring() const +{ + switch (vt) { + case VT_EMPTY: + return L""; + + case VT_BSTR: + return std::wstring(bstrVal, ::SysStringLen(bstrVal)); + + default: + throw std::runtime_error("Property is not a string"); + } +} + +// This is what he does, though it looks rather a strange use of the property +template <> +PropertyVariant::operator std::string() const +{ + switch (vt) { + case VT_EMPTY: + return ""; + + case VT_BSTR: + // If he can do a memcpy, I can do a reinterpret case + return std::string(reinterpret_cast<char const*>(bstrVal), + ::SysStringByteLen(bstrVal)); + + default: + throw std::runtime_error("Property is not a string"); + } +} + +// This is what he does, though it looks rather a strange use of the property +template <> +PropertyVariant::operator GUID() const +{ + switch (vt) { + case VT_BSTR: + // He did a cast too! + return *reinterpret_cast<const GUID*>(bstrVal); + + default: + throw std::runtime_error("Property is not a GUID (string)"); + } +} + +template <> +PropertyVariant::operator FILETIME() const +{ + switch (vt) { + case VT_FILETIME: + return filetime; + + default: + throw std::runtime_error("Property is not a file time"); + } +} + +// Assignments +template <> +PropertyVariant& PropertyVariant::operator=(std::wstring const& str) +{ + clear(); + vt = VT_BSTR; + bstrVal = ::SysAllocString(str.c_str()); + if (bstrVal == NULL) { + throw std::bad_alloc(); + } + return *this; +} + +template <> +PropertyVariant& PropertyVariant::operator=(bool const& n) +{ + clear(); + vt = VT_BOOL; + boolVal = n ? VARIANT_TRUE : VARIANT_FALSE; + return *this; +} + +template <> +PropertyVariant& PropertyVariant::operator=(FILETIME const& n) +{ + clear(); + vt = VT_FILETIME; + filetime = n; + return *this; +} + +template <> +PropertyVariant& PropertyVariant::operator=(uint32_t const& n) +{ + clear(); + vt = VT_UI4; + ulVal = n; + return *this; +} + +template <> +PropertyVariant& PropertyVariant::operator=(uint64_t const& n) +{ + clear(); + vt = VT_UI8; + uhVal.QuadPart = n; + return *this; +} diff --git a/libs/archive/src/propertyvariant.h b/libs/archive/src/propertyvariant.h new file mode 100644 index 0000000..0748d94 --- /dev/null +++ b/libs/archive/src/propertyvariant.h @@ -0,0 +1,56 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef PROPERTYVARIANT_H +#define PROPERTYVARIANT_H + +#ifdef _WIN32 +#include <PropIdl.h> +#else +#include <Common/MyWindows.h> +#endif + +/** This class implements a wrapper round the PROPVARIANT structure which + * makes it a little friendler to use and a little more C++ish + * + * Sadly the inheritance needs to be public. Once you have a pointer to the + * base class you can do pretty much anything to it anyway. + */ +class PropertyVariant : public tagPROPVARIANT +{ +public: + PropertyVariant(); + ~PropertyVariant(); + + // clears the property should you wish to overwrite it with another one. + void clear(); + + bool is_empty() { return vt == VT_EMPTY; } + + // It's too much like hard work listing all the valid ones. If it doesn't + // link, then you need to implement it... + template <typename T> + explicit operator T() const; + + template <typename T> + PropertyVariant& operator=(T const&); +}; + +#endif // PROPVARIANT_H diff --git a/libs/archive/src/unknown_impl.h b/libs/archive/src/unknown_impl.h new file mode 100644 index 0000000..78e9f4a --- /dev/null +++ b/libs/archive/src/unknown_impl.h @@ -0,0 +1,191 @@ +/* +Mod Organizer archive handling + +Copyright (C) 2012 Sebastian Herbord, 2020 MO2 Team. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef UNKNOWN_IMPL_H +#define UNKNOWN_IMPL_H + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#else +#include <Common/MyWindows.h> +#include <atomic> +#endif + +/* This implements a common way of creating classes which implement one or more + * COM interfaces. + * + * Your implementaton should be something like this + * + * class MyImplementation : + * public interface1, + * public interface2 //etc to taste + * { + * UNKNOWN_1_INTERFACE(interface1); + * or UNKNOWN_2_INTERFACE(interface1, interface2); etc + * + * It would probably be possible to do something very magic with macros + * that allowed you to do + * class My_Implmentation : UNKNOWN_IMPL_n(interface1, ...) + * + * that did all the above for you, but I can't see how to maintain the braces + * in order to make the class declaration look nice. + * + * You might ask why I have e-implemented this from the 7-zip stuff (sort of). + * + * Well, + * + * Firstly, the 7zip macros are slightly buggy (they don't null the outpointer + * if the interface isn't found), and they aren't thread safe. + * + * Secondly, although 7-zip has a requirement to run on platforms other than + * windows, we don't. So I changed this to use the Com functionality supplied + * by windows, which (potentially) has better debugging facilities + * + * Thirdly, this removes a number of dependencies on 7-zip source which aren't + * really part of the API + */ + +#ifdef _WIN32 + +/* Do not use these macros, use the ones at the bottom */ +#define UNKNOWN__INTERFACE_BEGIN \ +public: \ + STDMETHOD_(ULONG, AddRef)() \ + { \ + return ::InterlockedIncrement(&m_RefCount__); \ + } \ + \ + STDMETHOD_(ULONG, Release)() \ + { \ + ULONG res = ::InterlockedDecrement(&m_RefCount__); \ + if (res == 0) { \ + delete this; \ + } \ + return res; \ + } \ + \ + STDMETHOD(QueryInterface)(REFGUID iid, void** outObject) \ + { \ + if (iid == IID_IUnknown) { + +#define UNKNOWN__INTERFACE_UNKNOWN(interface) \ + *outObject = static_cast<IUnknown*>(static_cast<interface*>(this)); \ + } + +#define UNKNOWN__INTERFACE_NAME(interface) \ + else if (iid == IID_##interface) \ + { \ + *outObject = static_cast<interface*>(this); \ + } + +#define UNKNOWN__INTERFACE_END \ + else \ + { \ + *outObject = nullptr; \ + return E_NOINTERFACE; \ + } \ + AddRef(); \ + return S_OK; \ + } \ + \ +private: \ + ULONG m_RefCount__ = 0 + +#else // Linux + +/* Do not use these macros, use the ones at the bottom */ +#define UNKNOWN__INTERFACE_BEGIN \ +public: \ + STDMETHOD_(ULONG, AddRef)() \ + { \ + return ++m_RefCount__; \ + } \ + \ + STDMETHOD_(ULONG, Release)() \ + { \ + ULONG res = --m_RefCount__; \ + if (res == 0) { \ + delete this; \ + } \ + return res; \ + } \ + \ + STDMETHOD(QueryInterface)(REFGUID iid, void** outObject) \ + { \ + if (iid == IID_IUnknown) { + +#define UNKNOWN__INTERFACE_UNKNOWN(interface) \ + *outObject = static_cast<IUnknown*>(static_cast<interface*>(this)); \ + } + +#define UNKNOWN__INTERFACE_NAME(interface) \ + else if (iid == IID_##interface) \ + { \ + *outObject = static_cast<interface*>(this); \ + } + +#define UNKNOWN__INTERFACE_END \ + else \ + { \ + *outObject = nullptr; \ + return E_NOINTERFACE; \ + } \ + AddRef(); \ + return S_OK; \ + } \ + \ +private: \ + std::atomic<ULONG> m_RefCount__{0} + +#endif // _WIN32 + +/* These are the macros you should be using */ +#define UNKNOWN_1_INTERFACE(interface) \ + UNKNOWN__INTERFACE_BEGIN \ + UNKNOWN__INTERFACE_UNKNOWN(interface) \ + UNKNOWN__INTERFACE_NAME(interface) \ + UNKNOWN__INTERFACE_END + +#define UNKNOWN_2_INTERFACE(interface1, interface2) \ + UNKNOWN__INTERFACE_BEGIN \ + UNKNOWN__INTERFACE_UNKNOWN(interface1) \ + UNKNOWN__INTERFACE_NAME(interface1) \ + UNKNOWN__INTERFACE_NAME(interface2) \ + UNKNOWN__INTERFACE_END + +#define UNKNOWN_3_INTERFACE(interface1, interface2, interface3) \ + UNKNOWN__INTERFACE_BEGIN \ + UNKNOWN__INTERFACE_UNKNOWN(interface1) \ + UNKNOWN__INTERFACE_NAME(interface1) \ + UNKNOWN__INTERFACE_NAME(interface2) \ + UNKNOWN__INTERFACE_NAME(interface3) \ + UNKNOWN__INTERFACE_END + +#define UNKNOWN_4_INTERFACE(interface1, interface2, interface3, interface4) \ + UNKNOWN__INTERFACE_BEGIN \ + UNKNOWN__INTERFACE_UNKNOWN(interface1) \ + UNKNOWN__INTERFACE_NAME(interface1) \ + UNKNOWN__INTERFACE_NAME(interface2) \ + UNKNOWN__INTERFACE_NAME(interface3) \ + UNKNOWN__INTERFACE_NAME(interface4) \ + UNKNOWN__INTERFACE_END + +#endif // UNKNOWN_IMPL_H diff --git a/libs/archive/src/version.rc b/libs/archive/src/version.rc new file mode 100644 index 0000000..0fb4cf0 --- /dev/null +++ b/libs/archive/src/version.rc @@ -0,0 +1,28 @@ +#include "Winver.h" + +#define VER_FILEVERSION 2,0,0 +#define VER_FILEVERSION_STR 2,0,0 + +VS_VERSION_INFO VERSIONINFO +FILEVERSION VER_FILEVERSION +PRODUCTVERSION VER_FILEVERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +FILEFLAGS (0) +FILEOS VOS__WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "Mod Organizer 2 Team" + VALUE "FileVersion", VER_FILEVERSION_STR + END +END + +BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 +END +END |
