From bc77d2a270cc24fd69a8b46ccad4cca2b334a19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 18 May 2024 19:11:29 +0200 Subject: [PATCH 1/4] Apply clang-format. --- .clang-format | 41 ++ .gitattributes | 7 + src/archive.cpp | 1193 +++++++++++++++++++------------------ src/archive.h | 526 ++++++++-------- src/extractcallback.cpp | 682 +++++++++++---------- src/extractcallback.h | 278 +++++---- src/fileio.cpp | 366 +++++++----- src/fileio.h | 339 ++++++----- src/formatter.h | 161 ++--- src/inputstream.cpp | 16 +- src/inputstream.h | 12 +- src/instrument.h | 119 ++-- src/interfaceguids.cpp | 10 +- src/library.h | 29 +- src/multioutputstream.cpp | 270 ++++----- src/multioutputstream.h | 216 ++++--- src/opencallback.cpp | 327 +++++----- src/opencallback.h | 153 +++-- src/propertyvariant.cpp | 181 +++--- src/propertyvariant.h | 19 +- src/unknown_impl.h | 121 ++-- 21 files changed, 2604 insertions(+), 2462 deletions(-) create mode 100644 .clang-format create mode 100644 .gitattributes diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6098e1f --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +--- +# We'll use defaults from the LLVM style, but with 4 columns indentation. +BasedOnStyle: LLVM +IndentWidth: 2 +--- +Language: Cpp +DeriveLineEnding: false +UseCRLF: true +DerivePointerAlignment: false +PointerAlignment: Left +AlignConsecutiveAssignments: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AlwaysBreakTemplateDeclarations: Yes +AccessModifierOffset: -2 +AlignTrailingComments: true +SpacesBeforeTrailingComments: 2 +NamespaceIndentation: Inner +MaxEmptyLinesToKeep: 1 +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: true +ColumnLimit: 88 +ForEachMacros: ['Q_FOREACH', 'foreach'] diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f869712 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.cpp text eol=crlf +*.h text eol=crlf diff --git a/src/archive.cpp b/src/archive.cpp index 9ea84c4..9583fad 100644 --- a/src/archive.cpp +++ b/src/archive.cpp @@ -1,584 +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 -#include "archive.h" - -#include "extractcallback.h" -#include "inputstream.h" -#include "opencallback.h" -#include "propertyvariant.h" -#include "library.h" - -#include -#include -#include -#include -#include -#include -#include - -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& 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 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& 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 T readHandlerProperty(UInt32 index, PROPID propID) const; - - template 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 m_ArchivePtr; - CArchiveExtractCallback *m_ExtractCallback; - - LogCallback m_LogCallback; - PasswordCallback m_PasswordCallback; - - std::vector m_FileList; - - std::wstring m_Password; - - struct ArchiveFormatInfo - { - CLSID m_ClassID; - std::wstring m_Name; - std::vector m_Signatures; - std::wstring m_Extensions; - std::wstring m_AdditionalExtensions; - UInt32 m_SignatureOffset; - }; - - typedef std::vector Formats; - Formats m_Formats; - - typedef std::unordered_map FormatMap; - FormatMap m_FormatMap; - - //I don't think one signature could possibly describe two formats. - typedef std::map SignatureMap; - SignatureMap m_SignatureMap; - - std::size_t m_MaxSignatureLen = 0; -}; - -Archive::LogCallback ArchiveImpl::DefaultLogCallback([](LogLevel, std::wstring const&) {}); - -template 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(prop); -} - -template 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(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("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(i, PropID::kName); - - item.m_ClassID = readHandlerProperty(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(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(i, PropID::kAddExtension); - - std::string signature = readHandlerProperty(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(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(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/7z") - , 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, fmt::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 file(new InputStream); - - if (!file->Open(filepath)) { - m_LastError = Error::ERROR_FAILED_TO_OPEN_ARCHIVE; - return false; - } - - CComPtr openCallbackPtr; - try { - openCallbackPtr = new CArchiveOpenCallback(passwordCallback, m_LogCallback, filepath); - } - catch (std::runtime_error const& ex) { - 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 buff; - buff.reserve(m_MaxSignatureLen); - UInt32 act; - file->Seek(0, STREAM_SEEK_SET, nullptr); - file->Read(buff.data(), static_cast(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, fmt::format(L"Failed to open {} using {} (from signature).", - archiveName, signatureInfo.second.m_Name)); - m_ArchivePtr.Release(); - } - else { - m_LogCallback(LogLevel::Debug, fmt::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().native().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 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::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().native().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, fmt::format(L"Failed to open {} using {} (from signature).", - archiveName, format.m_Name)); - m_ArchivePtr.Release(); - } - else { - m_LogCallback(LogLevel::Debug, fmt::format(L"Opened {} using {} (from signature).", - archiveName, format.m_Name)); - break; - } - - std::vector::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 vformats; - for (ArchiveFormatInfo format : *formats) { - vformats.push_back(format.m_Name); - } - m_LogCallback(LogLevel::Warning, fmt::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 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, fmt::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 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::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(i, kpidPath), - readProperty(i, kpidSize), - readProperty(i, kpidCRC), - readProperty(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 indices; - UInt64 totalSize = 0; - for (std::size_t i = 0; i < m_FileList.size(); ++i) { - FileDataImpl* fileData = static_cast(m_FileList[i]); - if (!fileData->isEmpty()) { - indices.push_back(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(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 CreateArchive() -{ - return std::make_unique(); -} +/* +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 + +#include "extractcallback.h" +#include "inputstream.h" +#include "library.h" +#include "opencallback.h" +#include "propertyvariant.h" + +#include +#include +#include +#include +#include +#include +#include + +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& 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 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& 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 + T readHandlerProperty(UInt32 index, PROPID propID) const; + + template + 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 m_ArchivePtr; + CArchiveExtractCallback* m_ExtractCallback; + + LogCallback m_LogCallback; + PasswordCallback m_PasswordCallback; + + std::vector m_FileList; + + std::wstring m_Password; + + struct ArchiveFormatInfo + { + CLSID m_ClassID; + std::wstring m_Name; + std::vector m_Signatures; + std::wstring m_Extensions; + std::wstring m_AdditionalExtensions; + UInt32 m_SignatureOffset; + }; + + typedef std::vector Formats; + Formats m_Formats; + + typedef std::unordered_map FormatMap; + FormatMap m_FormatMap; + + // I don't think one signature could possibly describe two formats. + typedef std::map SignatureMap; + SignatureMap m_SignatureMap; + + std::size_t m_MaxSignatureLen = 0; +}; + +Archive::LogCallback ArchiveImpl::DefaultLogCallback([](LogLevel, std::wstring const&) { +}); + +template +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(prop); +} + +template +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(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("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(i, PropID::kName); + + item.m_ClassID = readHandlerProperty(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(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(i, PropID::kAddExtension); + + std::string signature = readHandlerProperty(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(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(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/7z"), + m_PasswordCallback{} +{ + // Reset the log callback: + setLogCallback({}); + + if (!m_Library) { + m_LastError = Error::ERROR_LIBRARY_NOT_FOUND; + return; + } + + m_CreateObjectFunc = m_Library.resolve("CreateObject"); + if (m_CreateObjectFunc == nullptr) { + m_LastError = Error::ERROR_LIBRARY_INVALID; + return; + } + + m_GetHandlerPropertyFunc = m_Library.resolve("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, fmt::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 file(new InputStream); + + if (!file->Open(filepath)) { + m_LastError = Error::ERROR_FAILED_TO_OPEN_ARCHIVE; + return false; + } + + CComPtr openCallbackPtr; + try { + openCallbackPtr = + new CArchiveOpenCallback(passwordCallback, m_LogCallback, filepath); + } catch (std::runtime_error const& ex) { + 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 buff; + buff.reserve(m_MaxSignatureLen); + UInt32 act; + file->Seek(0, STREAM_SEEK_SET, nullptr); + file->Read(buff.data(), static_cast(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, + fmt::format(L"Failed to open {} using {} (from signature).", + archiveName, signatureInfo.second.m_Name)); + m_ArchivePtr.Release(); + } else { + m_LogCallback(LogLevel::Debug, + fmt::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().native().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::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().native().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, + fmt::format(L"Failed to open {} using {} (from signature).", + archiveName, format.m_Name)); + m_ArchivePtr.Release(); + } else { + m_LogCallback(LogLevel::Debug, + fmt::format(L"Opened {} using {} (from signature).", + archiveName, format.m_Name)); + break; + } + + std::vector::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 vformats; + for (ArchiveFormatInfo format : *formats) { + vformats.push_back(format.m_Name); + } + m_LogCallback( + LogLevel::Warning, + fmt::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, + fmt::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 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::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(i, kpidPath), readProperty(i, kpidSize), + readProperty(i, kpidCRC), readProperty(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 indices; + UInt64 totalSize = 0; + for (std::size_t i = 0; i < m_FileList.size(); ++i) { + FileDataImpl* fileData = static_cast(m_FileList[i]); + if (!fileData->isEmpty()) { + indices.push_back(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(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 CreateArchive() +{ + return std::make_unique(); +} diff --git a/src/archive.h b/src/archive.h index 51b8a1e..7128092 100644 --- a/src/archive.h +++ b/src/archive.h @@ -1,256 +1,270 @@ -/* -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 ARCHIVE_H -#define ARCHIVE_H - -#include -#include -#include -#include - -#ifndef DLLEXPORT - #ifdef MODORGANIZER_ARCHIVE_BUILDING - #define DLLEXPORT _declspec(dllexport) - #else - #define DLLEXPORT _declspec(dllimport) - #endif -#endif - -class FileData { -public: - - /** - * @return the path of this entry in the archive (usually relative, unless the archive - * contains absolute path). - */ - virtual std::wstring getArchiveFilePath() const = 0; - - /** - * @return the size of this entry in bytes (uncompressed). - */ - virtual uint64_t getSize() const = 0; - - /** - * @brief Add the given filepath to the list of files to create from this - * entry when extracting. - * - * @param filepath The filepath to add, relative to the output folder. - */ - virtual void addOutputFilePath(std::wstring const& filepath) = 0; - - /** - * @brief Retrieve the list of filepaths to extract this entry to. - * - * @return the list of paths this entry should be extracted to, relative to the - * output folder. - */ - virtual const std::vector& getOutputFilePaths() const = 0; - - /** - * @brief Clear the list of output file paths for this entry. - */ - virtual void clearOutputFilePaths() = 0; - - /** - * @return the CRC of this file. - */ - virtual uint64_t getCRC() const = 0; - - /** - * @return true if this entry is a directory, false otherwize. - */ - virtual bool isDirectory() const = 0; -}; - - -class Archive { -public: // Declarations - - enum class LogLevel { - Debug, - Info, - Warning, - Error - }; - - enum class ProgressType { - - // Indicates the 7z progression in the archive (related to reading the archive. When extracting - // a lot of files, this may reach 100% way before the extraction is complete since most of the - // time will be spend writing data and not reading it (use EXTRACTION in this case). When - // extracting few small files, this may be useful for solid archives since most of the time - // will be spent in reading and decompressing the archive rather than in writing the actual files. - ARCHIVE, - - // Progress about extraction. If this reach 100%, it means that the extraction of all files is - // complete. The EXTRACTION progress may not start immediately, and might be kind of chaotic when - // extracting few files from an archive, but is much more representative of the actual progress - // than ARCHIVE. - EXTRACTION - }; - - enum class FileChangeType { - EXTRACTION_START, - EXTRACTION_END - }; - - static constexpr int MAX_PASSWORD_LENGTH = 256; - - /** - * List of callbacks: - */ - using LogCallback = std::function; - using ProgressCallback = std::function; - using PasswordCallback = std::function; - using FileChangeCallback = std::function; - using ErrorCallback = std::function; - - /** - * - */ - enum class Error { - ERROR_NONE, - ERROR_EXTRACT_CANCELLED, - ERROR_LIBRARY_NOT_FOUND, - ERROR_LIBRARY_INVALID, - ERROR_ARCHIVE_NOT_FOUND, - ERROR_FAILED_TO_OPEN_ARCHIVE, - ERROR_INVALID_ARCHIVE_FORMAT, - ERROR_LIBRARY_ERROR, - ERROR_ARCHIVE_INVALID, - ERROR_OUT_OF_MEMORY - }; - -public: // Special member functions: - - virtual ~Archive() {} - -public: - - /** - * @brief Check if this Archive wrapper is in a valid state. - * - * A non-valid Archive instance usually means that the 7z DLLs could not be loaded properly. Failures - * to open or extract archives do not invalidate the Archive, so this should only be used to check - * if the Archive object has been initialized properly. - * - * @return true if this instance is valid, false otherwise. - */ - virtual bool isValid() const = 0; - - /** - * @return retrieve the error-code of the last error that occurred. - */ - virtual Error getLastError() const = 0; - - /** - * @brief Set the callback used to log messages. - * - * To remove the callback, you can pass a default-constructed LogCallback object. - * - * @param logCallback The new callback to use for logging message. - */ - virtual void setLogCallback(LogCallback logCallback) = 0; - - /** - * @brief Open the given archive. - * - * @param archivePath Path to the archive to open. - * @param passwordCallback Callback to use to ask user for password. This callback must remain - * valid until extraction is complete since some types of archives only requires password when - * extracting. - * - * @return true if the archive was open properly, false otherwise. - */ - virtual bool open(std::wstring const &archivePath, PasswordCallback passwordCallback) = 0; - - /** - * @brief Close the currently opened archive. - */ - virtual void close() = 0; - - /** - * @return the list of files in the currently opened archive. - */ - virtual const std::vector& getFileList() const = 0; - - /** - * @brief Extract the content of the archive. - * - * This function uses the filenames from FileData to obtain the extraction paths of file. All - * the callbacks are optional (you can specify default-constructed std::function). Overloads with - * one or two callbacks are also provided. - * - * @param outputDirectory Path to the directory where the archive should be extracted. If not empty, - * conflicting files will be replaced by the extracted ones. - * @param progressCallback Function called to notify extraction progress. - * @param fileChangeCallback Function called when the file currently being extracted changes. - * @param errorCallback Function called when an error occurs. - * - * @return true if the archive was extracted, false otherwise. - */ - virtual bool extract(std::wstring const &outputDirectory, - ProgressCallback progressCallback, - FileChangeCallback fileChangeCallback, - ErrorCallback errorCallback) = 0; - - /** - * @brief Cancel the current extraction process. - */ - virtual void cancel() = 0; - - // A bunch of useful overloads (with one or two callbacks): - bool extract(std::wstring const& outputDirectory, - ErrorCallback errorCallback) { - return extract(outputDirectory, {}, {}, errorCallback); - } - bool extract(std::wstring const& outputDirectory, - ProgressCallback progressCallback) { - return extract(outputDirectory, progressCallback, {}, {}); - } - bool extract(std::wstring const& outputDirectory, - FileChangeCallback fileChangeCallback) { - return extract(outputDirectory, {}, fileChangeCallback, {}); - } - bool extract(std::wstring const& outputDirectory, - ProgressCallback progressCallback, ErrorCallback errorCallback) { - return extract(outputDirectory, progressCallback, {}, errorCallback); - } - bool extract(std::wstring const& outputDirectory, - ProgressCallback progressCallback, FileChangeCallback fileChangeCallback) { - return extract(outputDirectory, progressCallback, fileChangeCallback, {}); - } - bool extract(std::wstring const& outputDirectory, - FileChangeCallback fileChangeCallback, ErrorCallback errorCallback) { - return extract(outputDirectory, {}, fileChangeCallback, errorCallback); - } - -}; - -/** - * @brief Factory function for archive-objects. - * - * @return a pointer to a new Archive object that can be used to manipulate archives. - */ -DLLEXPORT std::unique_ptr CreateArchive(); - - -#endif // ARCHIVE_H +/* +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 ARCHIVE_H +#define ARCHIVE_H + +#include +#include +#include +#include + +#ifndef DLLEXPORT +#ifdef MODORGANIZER_ARCHIVE_BUILDING +#define DLLEXPORT _declspec(dllexport) +#else +#define DLLEXPORT _declspec(dllimport) +#endif +#endif + +class FileData +{ +public: + /** + * @return the path of this entry in the archive (usually relative, unless the archive + * contains absolute path). + */ + virtual std::wstring getArchiveFilePath() const = 0; + + /** + * @return the size of this entry in bytes (uncompressed). + */ + virtual uint64_t getSize() const = 0; + + /** + * @brief Add the given filepath to the list of files to create from this + * entry when extracting. + * + * @param filepath The filepath to add, relative to the output folder. + */ + virtual void addOutputFilePath(std::wstring const& filepath) = 0; + + /** + * @brief Retrieve the list of filepaths to extract this entry to. + * + * @return the list of paths this entry should be extracted to, relative to the + * output folder. + */ + virtual const std::vector& getOutputFilePaths() const = 0; + + /** + * @brief Clear the list of output file paths for this entry. + */ + virtual void clearOutputFilePaths() = 0; + + /** + * @return the CRC of this file. + */ + virtual uint64_t getCRC() const = 0; + + /** + * @return true if this entry is a directory, false otherwize. + */ + virtual bool isDirectory() const = 0; +}; + +class Archive +{ +public: // Declarations + enum class LogLevel + { + Debug, + Info, + Warning, + Error + }; + + enum class ProgressType + { + + // Indicates the 7z progression in the archive (related to reading the archive. When + // extracting + // a lot of files, this may reach 100% way before the extraction is complete since + // most of the + // time will be spend writing data and not reading it (use EXTRACTION in this case). + // When + // extracting few small files, this may be useful for solid archives since most of + // the time + // will be spent in reading and decompressing the archive rather than in writing the + // actual files. + ARCHIVE, + + // Progress about extraction. If this reach 100%, it means that the extraction of + // all files is + // complete. The EXTRACTION progress may not start immediately, and might be kind of + // chaotic when + // extracting few files from an archive, but is much more representative of the + // actual progress + // than ARCHIVE. + EXTRACTION + }; + + enum class FileChangeType + { + EXTRACTION_START, + EXTRACTION_END + }; + + static constexpr int MAX_PASSWORD_LENGTH = 256; + + /** + * List of callbacks: + */ + using LogCallback = std::function; + using ProgressCallback = std::function; + using PasswordCallback = std::function; + using FileChangeCallback = std::function; + using ErrorCallback = std::function; + + /** + * + */ + enum class Error + { + ERROR_NONE, + ERROR_EXTRACT_CANCELLED, + ERROR_LIBRARY_NOT_FOUND, + ERROR_LIBRARY_INVALID, + ERROR_ARCHIVE_NOT_FOUND, + ERROR_FAILED_TO_OPEN_ARCHIVE, + ERROR_INVALID_ARCHIVE_FORMAT, + ERROR_LIBRARY_ERROR, + ERROR_ARCHIVE_INVALID, + ERROR_OUT_OF_MEMORY + }; + +public: // Special member functions: + virtual ~Archive() {} + +public: + /** + * @brief Check if this Archive wrapper is in a valid state. + * + * A non-valid Archive instance usually means that the 7z DLLs could not be loaded + * properly. Failures to open or extract archives do not invalidate the Archive, so + * this should only be used to check if the Archive object has been initialized + * properly. + * + * @return true if this instance is valid, false otherwise. + */ + virtual bool isValid() const = 0; + + /** + * @return retrieve the error-code of the last error that occurred. + */ + virtual Error getLastError() const = 0; + + /** + * @brief Set the callback used to log messages. + * + * To remove the callback, you can pass a default-constructed LogCallback object. + * + * @param logCallback The new callback to use for logging message. + */ + virtual void setLogCallback(LogCallback logCallback) = 0; + + /** + * @brief Open the given archive. + * + * @param archivePath Path to the archive to open. + * @param passwordCallback Callback to use to ask user for password. This callback + * must remain valid until extraction is complete since some types of archives only + * requires password when extracting. + * + * @return true if the archive was open properly, false otherwise. + */ + virtual bool open(std::wstring const& archivePath, + PasswordCallback passwordCallback) = 0; + + /** + * @brief Close the currently opened archive. + */ + virtual void close() = 0; + + /** + * @return the list of files in the currently opened archive. + */ + virtual const std::vector& getFileList() const = 0; + + /** + * @brief Extract the content of the archive. + * + * This function uses the filenames from FileData to obtain the extraction paths of + * file. All the callbacks are optional (you can specify default-constructed + * std::function). Overloads with one or two callbacks are also provided. + * + * @param outputDirectory Path to the directory where the archive should be extracted. + * If not empty, conflicting files will be replaced by the extracted ones. + * @param progressCallback Function called to notify extraction progress. + * @param fileChangeCallback Function called when the file currently being extracted + * changes. + * @param errorCallback Function called when an error occurs. + * + * @return true if the archive was extracted, false otherwise. + */ + virtual bool extract(std::wstring const& outputDirectory, + ProgressCallback progressCallback, + FileChangeCallback fileChangeCallback, + ErrorCallback errorCallback) = 0; + + /** + * @brief Cancel the current extraction process. + */ + virtual void cancel() = 0; + + // A bunch of useful overloads (with one or two callbacks): + bool extract(std::wstring const& outputDirectory, ErrorCallback errorCallback) + { + return extract(outputDirectory, {}, {}, errorCallback); + } + bool extract(std::wstring const& outputDirectory, ProgressCallback progressCallback) + { + return extract(outputDirectory, progressCallback, {}, {}); + } + bool extract(std::wstring const& outputDirectory, + FileChangeCallback fileChangeCallback) + { + return extract(outputDirectory, {}, fileChangeCallback, {}); + } + bool extract(std::wstring const& outputDirectory, ProgressCallback progressCallback, + ErrorCallback errorCallback) + { + return extract(outputDirectory, progressCallback, {}, errorCallback); + } + bool extract(std::wstring const& outputDirectory, ProgressCallback progressCallback, + FileChangeCallback fileChangeCallback) + { + return extract(outputDirectory, progressCallback, fileChangeCallback, {}); + } + bool extract(std::wstring const& outputDirectory, + FileChangeCallback fileChangeCallback, ErrorCallback errorCallback) + { + return extract(outputDirectory, {}, fileChangeCallback, errorCallback); + } +}; + +/** + * @brief Factory function for archive-objects. + * + * @return a pointer to a new Archive object that can be used to manipulate archives. + */ +DLLEXPORT std::unique_ptr CreateArchive(); + +#endif // ARCHIVE_H diff --git a/src/extractcallback.cpp b/src/extractcallback.cpp index 7603840..3ad0181 100644 --- a/src/extractcallback.cpp +++ b/src/extractcallback.cpp @@ -1,344 +1,338 @@ -/* -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 - -#include -#include - -#include "extractcallback.h" -#include "archive.h" -#include "propertyvariant.h" - -#include -#include -#include - -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 fmt::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_ExtractedFileSize(0) - , m_LastCallbackFileSize(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) -{ - m_Total = size; - return S_OK; -} - -STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 *completed) -{ - if (m_ProgressCallback) { - m_ProgressCallback(Archive::ProgressType::ARCHIVE, *completed, m_Total); - } - return m_Canceled ? E_ABORT : S_OK; -} - -template 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, fmt::format(L"Error getting property {}.", property)); - return false; - } - if (prop.is_empty()) { - return false; - } - *result = static_cast(prop); - return true; -} - -template 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, fmt::format(L"Error getting property {}.", property)); - return false; - } - - *result = static_cast(prop); - return true; -} - -STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode) -{ - 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 filenames = m_FileData[index]->getOutputFilePaths(); - m_FileData[index]->clearOutputFilePaths(); - if (filenames.empty()) { - return S_OK; - } - - 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 totalSize) { - m_ExtractedFileSize += size; - if (m_ProgressCallback) { - m_ProgressCallback(Archive::ProgressType::EXTRACTION, m_ExtractedFileSize, m_TotalFileSize); - } - }); - CComPtr 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, fmt::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, fmt::format(L"Caught exception {} in GetStream.", e)); - } - return E_FAIL; -} - -STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) -{ - if (m_Canceled) { - return E_ABORT; - } - m_Extracting = askExtractMode == NArchive::NExtract::NAskMode::kExtract; - return S_OK; -} - -STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) -{ - if (operationResult != NArchive::NExtract::NOperationResult::kOK) { - reportError(operationResultToString(operationResult)); - } - - if (m_OutFileStreamCom) { - if (m_ProcessedFileInfo.MTimeDefined) { - auto guard = m_Timers.SetOperationResult.SetMTime.instrument(); - m_OutputFileStream->SetMTime(&m_ProcessedFileInfo.MTime); - } - auto guard = m_Timers.SetOperationResult.Close.instrument(); - RINOK(m_OutputFileStream->Close()) - } - - { - auto guard = m_Timers.SetOperationResult.Release.instrument(); - m_OutFileStreamCom.Release(); - } - - auto guard = 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) { - 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); - } - } - - return S_OK; -} - - -STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *passwordOut) -{ - // 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); - } -} +/* +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 + +#include +#include + +#include "archive.h" +#include "extractcallback.h" +#include "propertyvariant.h" + +#include +#include +#include + +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 fmt::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_ExtractedFileSize(0), + m_LastCallbackFileSize(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) +{ + m_Total = size; + return S_OK; +} + +STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64* completed) +{ + if (m_ProgressCallback) { + m_ProgressCallback(Archive::ProgressType::ARCHIVE, *completed, m_Total); + } + return m_Canceled ? E_ABORT : S_OK; +} + +template +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, + fmt::format(L"Error getting property {}.", property)); + return false; + } + if (prop.is_empty()) { + return false; + } + *result = static_cast(prop); + return true; +} + +template +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, + fmt::format(L"Error getting property {}.", property)); + return false; + } + + *result = static_cast(prop); + return true; +} + +STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, + ISequentialOutStream** outStream, + Int32 askExtractMode) +{ + 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 filenames = m_FileData[index]->getOutputFilePaths(); + m_FileData[index]->clearOutputFilePaths(); + if (filenames.empty()) { + return S_OK; + } + + 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 totalSize) { + m_ExtractedFileSize += size; + if (m_ProgressCallback) { + m_ProgressCallback(Archive::ProgressType::EXTRACTION, m_ExtractedFileSize, + m_TotalFileSize); + } + }); + CComPtr 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, + fmt::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, + fmt::format(L"Caught exception {} in GetStream.", e)); + } + return E_FAIL; +} + +STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) +{ + if (m_Canceled) { + return E_ABORT; + } + m_Extracting = askExtractMode == NArchive::NExtract::NAskMode::kExtract; + return S_OK; +} + +STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) +{ + if (operationResult != NArchive::NExtract::NOperationResult::kOK) { + reportError(operationResultToString(operationResult)); + } + + if (m_OutFileStreamCom) { + if (m_ProcessedFileInfo.MTimeDefined) { + auto guard = m_Timers.SetOperationResult.SetMTime.instrument(); + m_OutputFileStream->SetMTime(&m_ProcessedFileInfo.MTime); + } + auto guard = m_Timers.SetOperationResult.Close.instrument(); + RINOK(m_OutputFileStream->Close()) + } + + { + auto guard = m_Timers.SetOperationResult.Release.instrument(); + m_OutFileStreamCom.Release(); + } + + auto guard = 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) { + 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); + } + } + + return S_OK; +} + +STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR* passwordOut) +{ + // 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/src/extractcallback.h b/src/extractcallback.h index 4a128c0..1b61dfa 100644 --- a/src/extractcallback.h +++ b/src/extractcallback.h @@ -1,143 +1,135 @@ -/* -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 -#include -#include - -#include "7zip/Archive/IArchive.h" -#include "7zip/IPassword.h" - -#include - -#include -#include - -#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); - -private: - - void reportError(const std::wstring& message); - - template - void reportError(const wchar_t* format, Args&& ...args) - { - reportError(fmt::format(format, std::forward(args)...)); - } - - template bool getOptionalProperty(UInt32 index, int property, T *result) const; - template bool getProperty(UInt32 index, int property, T *result) const; - -private: - - CComPtr m_ArchiveHandler; - - UInt64 m_Total; - - std::filesystem::path m_DirectoryPath; - bool m_Extracting; - std::atomic 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 m_OutFileStreamCom; - - std::vector 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 +/* +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 +#include +#include + +#include "7zip/Archive/IArchive.h" +#include "7zip/IPassword.h" + +#include + +#include +#include + +#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); + +private: + void reportError(const std::wstring& message); + + template + void reportError(const wchar_t* format, Args&&... args) + { + reportError(fmt::format(format, std::forward(args)...)); + } + + template + bool getOptionalProperty(UInt32 index, int property, T* result) const; + template + bool getProperty(UInt32 index, int property, T* result) const; + +private: + CComPtr m_ArchiveHandler; + + UInt64 m_Total; + + std::filesystem::path m_DirectoryPath; + bool m_Extracting; + std::atomic 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 m_OutFileStreamCom; + + std::vector 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/src/fileio.cpp b/src/fileio.cpp index 08ecae9..b2d078a 100644 --- a/src/fileio.cpp +++ b/src/fileio.cpp @@ -20,175 +20,213 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "fileio.h" -inline bool BOOLToBool(BOOL v) { return (v != FALSE); } +inline bool BOOLToBool(BOOL v) +{ + return (v != FALSE); +} -namespace IO { +namespace IO +{ - // FileBase +// 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; +bool FileBase::Close() noexcept +{ + if (m_Handle == INVALID_HANDLE_VALUE) 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()) { + 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; - } - - 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))) { + 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; - } - - *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)) + 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 (newPosition != length) + 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; - 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; - } - -} \ No newline at end of file + 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 diff --git a/src/fileio.h b/src/fileio.h index e10f0cd..59f3638 100644 --- a/src/fileio.h +++ b/src/fileio.h @@ -30,184 +30,193 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include #include -namespace IO { - - /** - * 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; - }; - - class FileBase { - public: // Constructors, destructor, assignment. - - FileBase() noexcept : m_Handle{ INVALID_HANDLE_VALUE } { } - - FileBase(FileBase&& other) noexcept : m_Handle{ other.m_Handle } { - other.m_Handle = INVALID_HANDLE_VALUE; - } - - ~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; - - bool Seek(Int64 distanceToMove, DWORD moveMethod, UInt64& newPosition) noexcept; - 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: - - bool Create(std::filesystem::path const& path, DWORD desiredAccess, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; - - protected: - - static constexpr UInt32 kChunkSizeMax = (1 << 22); - - HANDLE m_Handle; - - }; - - class FileIn : public FileBase { - public: - using FileBase::FileBase; - - public: // Operations - bool Open(std::filesystem::path const& filepath, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; - bool OpenShared(std::filesystem::path const& filepath, bool shareForWrite) noexcept; - 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: - - bool Open(std::filesystem::path const& fileName, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; - 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: +namespace IO +{ + +/** + * 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 WritePart(const void* data, UInt32 size, UInt32& processedSize) noexcept; - }; + 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); + } - /** - * @brief Convert the given wide-string to a path object, after adding (if not already present) - * the Windows long-path prefix. - * - * @param path The string containing the path. - * @param exists true if the path should exists, false otherwize. - * - * @return the created path. - */ - inline std::filesystem::path make_path(std::wstring const& pathstr) { - namespace fs = std::filesystem; - constexpr const wchar_t* lprefix = L"\\\\?\\"; - constexpr const wchar_t* unc_prefix = L"\\\\"; - constexpr const wchar_t* unc_lprefix = L"\\\\?\\UNC\\"; + bool m_Valid; + std::filesystem::path m_Path; + BY_HANDLE_FILE_INFORMATION m_FileInfo; +}; - // If path is already a long path, just return it: - if (pathstr.starts_with(lprefix)) { - return fs::path{ pathstr }.make_preferred(); - } +class FileBase +{ +public: // Constructors, destructor, assignment. + FileBase() noexcept : m_Handle{INVALID_HANDLE_VALUE} {} - fs::path path{ pathstr }; + FileBase(FileBase&& other) noexcept : m_Handle{other.m_Handle} + { + other.m_Handle = INVALID_HANDLE_VALUE; + } - // Convert to an absolute path: - if (!path.is_absolute()) { - path = fs::absolute(path); - } + ~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; + + bool Seek(Int64 distanceToMove, DWORD moveMethod, UInt64& newPosition) noexcept; + 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: + bool Create(std::filesystem::path const& path, DWORD desiredAccess, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; + +protected: + static constexpr UInt32 kChunkSizeMax = (1 << 22); + + HANDLE m_Handle; +}; + +class FileIn : public FileBase +{ +public: + using FileBase::FileBase; + +public: // Operations + bool Open(std::filesystem::path const& filepath, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; + bool OpenShared(std::filesystem::path const& filepath, bool shareForWrite) noexcept; + 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: + bool Open(std::filesystem::path const& fileName, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) noexcept; + 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, after adding (if not already + * present) the Windows long-path prefix. + * + * @param path The string containing the path. + * @param exists true if the path should exists, false otherwize. + * + * @return the created path. + */ +inline std::filesystem::path make_path(std::wstring const& pathstr) +{ + namespace fs = std::filesystem; + 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(); + } - // backslashes - path = path.make_preferred(); + fs::path path{pathstr}; - // Get rid of duplicate separators and relative moves - path = path.lexically_normal(); + // Convert to an absolute path: + if (!path.is_absolute()) { + path = fs::absolute(path); + } + // backslashes + path = path.make_preferred(); - const std::wstring pathstr_fixed = path.native(); + // Get rid of duplicate separators and relative moves + path = path.lexically_normal(); - // 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) }; - } + const std::wstring pathstr_fixed = path.native(); - // Add the long-path prefix (cannot concatenate string an path so need - // to call .native() to concatenate): - return fs::path{ lprefix + pathstr_fixed }; + // 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 (cannot concatenate string an path so need + // to call .native() to concatenate): + return fs::path{lprefix + pathstr_fixed}; } +} // namespace IO + #endif diff --git a/src/formatter.h b/src/formatter.h index a29b447..0a8cad7 100644 --- a/src/formatter.h +++ b/src/formatter.h @@ -32,88 +32,97 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include #include -// Specializing fmt::formatter works, but gives warning, for whatever reason... So putting -// everything in the namespace. -namespace fmt { - - // I don't know why fmt does not provide this... - template<> - struct formatter : formatter - { - template - auto format(std::string const& s, FormatContext& ctx) { - return formatter::format(std::wstring(s.begin(), s.end()), ctx); - } - }; - - template<> - struct formatter : formatter - { - template - auto format(std::exception const& ex, FormatContext& ctx) { - return formatter::format(ex.what(), ctx); - } - }; - - template<> - struct formatter : formatter - { - template - auto format(std::error_code const& ec, FormatContext& ctx) { - return formatter::format(ec.message(), ctx); - } - }; - - template<> - struct formatter : formatter +// Specializing fmt::formatter works, but gives warning, for whatever reason... So +// putting everything in the namespace. +namespace fmt +{ + +// I don't know why fmt does not provide this... +template <> +struct formatter : formatter +{ + template + auto format(std::string const& s, FormatContext& ctx) { - template - auto format(std::filesystem::path const& path, FormatContext& ctx) { - return formatter::format(path.native(), ctx); - } - }; + return formatter::format(std::wstring(s.begin(), s.end()), + ctx); + } +}; -} +template <> +struct formatter : formatter +{ + template + auto format(std::exception const& ex, FormatContext& ctx) + { + return formatter::format(ex.what(), 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 - 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; +template <> +struct formatter : formatter +{ + template + auto format(std::error_code const& ec, FormatContext& ctx) + { + return formatter::format(ec.message(), ctx); } +}; - /** - * @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(::towlower(c)); }); - return s; +template <> +struct formatter : formatter +{ + template + auto format(std::filesystem::path const& path, FormatContext& ctx) + { + return formatter::format(path.native(), ctx); + } +}; + +} // namespace fmt + +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 +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(::towlower(c)); + }); + return s; +} +} // namespace ArchiveStrings -#endif \ No newline at end of file +#endif diff --git a/src/inputstream.cpp b/src/inputstream.cpp index 049c41f..cecc931 100644 --- a/src/inputstream.cpp +++ b/src/inputstream.cpp @@ -18,8 +18,8 @@ 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 #include "inputstream.h" +#include static inline HRESULT ConvertBoolToHRESULT(bool result) { @@ -33,21 +33,19 @@ static inline HRESULT ConvertBoolToHRESULT(bool result) return HRESULT_FROM_WIN32(lastError); } -InputStream::InputStream() -{} - -InputStream::~InputStream() { } +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) +STDMETHODIMP InputStream::Read(void* data, UInt32 size, UInt32* processedSize) { UInt32 realProcessedSize; - bool result = m_File.Read(data, size, realProcessedSize); + bool result = m_File.Read(data, size, realProcessedSize); if (processedSize != nullptr) { *processedSize = realProcessedSize; @@ -60,10 +58,10 @@ STDMETHODIMP InputStream::Read(void *data, UInt32 size, UInt32 *processedSize) return HRESULT_FROM_WIN32(::GetLastError()); } -STDMETHODIMP InputStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +STDMETHODIMP InputStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) { UInt64 realNewPosition = offset; - bool result = m_File.Seek(offset, seekOrigin, realNewPosition); + bool result = m_File.Seek(offset, seekOrigin, realNewPosition); if (newPosition) { *newPosition = realNewPosition; diff --git a/src/inputstream.h b/src/inputstream.h index 41a7210..30b056a 100644 --- a/src/inputstream.h +++ b/src/inputstream.h @@ -21,7 +21,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #ifndef INPUTSTREAM_H #define INPUTSTREAM_H - #include "7zip/IStream.h" #include @@ -33,8 +32,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Note that the handling on errors could be better. */ -class InputStream : - public IInStream +class InputStream : public IInStream { UNKNOWN_1_INTERFACE(IInStream); @@ -44,13 +42,13 @@ class InputStream : virtual ~InputStream(); - bool Open(std::filesystem::path const &filename); + bool Open(std::filesystem::path const& filename); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); + STDMETHOD(Read)(void* data, UInt32 size, UInt32* processedSize); + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition); private: IO::FileIn m_File; }; -#endif // INPUTSTREAM_H +#endif // INPUTSTREAM_H diff --git a/src/instrument.h b/src/instrument.h index 3b3941e..8bbf85a 100644 --- a/src/instrument.h +++ b/src/instrument.h @@ -25,7 +25,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include -namespace ArchiveTimers { +namespace ArchiveTimers +{ /** * If INSTRUMENT_ARCHIVE is not defined, we define a Timer() class that is empty and @@ -33,75 +34,79 @@ namespace ArchiveTimers { */ #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; +/** + * 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; + } - struct TimerGuard { + private: + TimerGuard(Timer& timer) : m_Timer{timer}, m_Start{clock_t::now()} {} - TimerGuard(TimerGuard const&) = delete; - TimerGuard(TimerGuard&&) = delete; - TimerGuard& operator=(TimerGuard const&) = delete; - TimerGuard& operator=(TimerGuard&&) = delete; + Timer& m_Timer; + clock_t::time_point m_Start; - ~TimerGuard() { - m_Timer.ncalls++; - m_Timer.time += clock_t::now() - m_Start; - } + friend class Timer; + }; - private: - TimerGuard(Timer& timer) : m_Timer{ timer }, m_Start{ clock_t::now() } { } + /** + * + */ + Timer() = default; - Timer& m_Timer; - clock_t::time_point m_Start; + /** + * @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}; } - friend class Timer; + std::wstring toString(std::wstring const& name) const + { + auto ms = [](auto&& t) { + return std::chrono::duration(t); }; - - /** - * - */ - 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(t); }; - return fmt::format(L"Instrument '{}': {} calls, total of {}ms, {:.3f}ms per call on average.", + return fmt::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 }; - - }; +private: + std::size_t ncalls{0}; + clock_t::duration time{0}; +}; #else - struct Timer { - // This is the only method needed: - Timer& instrument() { return *this; } - }; +struct Timer +{ + // This is the only method needed: + Timer& instrument() { return *this; } +}; #endif -} +} // namespace ArchiveTimers -#endif \ No newline at end of file +#endif diff --git a/src/interfaceguids.cpp b/src/interfaceguids.cpp index b028f19..acb6253 100644 --- a/src/interfaceguids.cpp +++ b/src/interfaceguids.cpp @@ -18,15 +18,15 @@ 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 +// This file instantiates the GUIDs needed for linking with the 7zip code #include -//extractcallback, opencallback +// extractcallback, opencallback #include "7zip/IPassword.h" -//archive, opencallback -//Note: In a sense, this is unnecessary as IArchive.h includes it +// archive, opencallback +// Note: In a sense, this is unnecessary as IArchive.h includes it #include "7zip/IStream.h" -//archive, opencallback +// archive, opencallback #include "7zip/Archive/IArchive.h" diff --git a/src/library.h b/src/library.h index 71da7ba..a13452d 100644 --- a/src/library.h +++ b/src/library.h @@ -26,18 +26,19 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA /** * Very small wrapper around Windows DLLs functions. */ -class ALibrary { +class ALibrary +{ public: - - ALibrary(const char* path) : - m_Module{ nullptr }, m_LastError{ ERROR_SUCCESS } { + ALibrary(const char* path) : m_Module{nullptr}, m_LastError{ERROR_SUCCESS} + { m_Module = LoadLibraryA(path); if (m_Module == nullptr) { updateLastError(); } } - ~ALibrary() { + ~ALibrary() + { if (m_Module) { FreeLibrary(m_Module); } @@ -52,7 +53,8 @@ class ALibrary { * @note T must be a pointer type, and no type checking is performed. */ template - T resolve(const char* procName) { + T resolve(const char* procName) + { if (!m_Module) { return nullptr; } @@ -68,14 +70,13 @@ class ALibrary { * @return the last error that occurs with this object, or ERROR_SUCCESS if no * error occurred. */ - DWORD getLastError() const { - return m_LastError; - } + DWORD getLastError() const { return m_LastError; } /** * @return true if this library is open, false otherwize. */ - bool isOpen() const { + bool isOpen() const + { // Note: The library is always open, unless it fails during construction. return m_Module != nullptr; } @@ -83,14 +84,10 @@ class ALibrary { operator bool() const { return isOpen(); } private: - - void updateLastError() { - m_LastError = ::GetLastError(); - } + void updateLastError() { m_LastError = ::GetLastError(); } HMODULE m_Module; DWORD m_LastError; - }; -#endif \ No newline at end of file +#endif diff --git a/src/multioutputstream.cpp b/src/multioutputstream.cpp index fd81a26..506975d 100644 --- a/src/multioutputstream.cpp +++ b/src/multioutputstream.cpp @@ -1,134 +1,136 @@ -/* -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 -#include "multioutputstream.h" - -#include -#include - -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 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.native())) { - ok = false; - } - } - return ok; -} - -STDMETHODIMP MultiOutputStream::Write(const void *data, UInt32 size, UInt32 *processedSize) -{ - 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) -{ - if (seekOrigin >= 3) - return STG_E_INVALIDFUNCTION; - - bool result = true; - for (auto& file : m_Files) { - UInt64 realNewPosition; - bool result = file.Seek(offset, seekOrigin, realNewPosition); - if (newPosition) - *newPosition = realNewPosition; - } - return ConvertBoolToHRESULT(result); -} - -STDMETHODIMP MultiOutputStream::SetSize(UInt64 newSize) -{ - bool result = true; - for (auto& file : m_Files) { - UInt64 currentPos; - if (!file.Seek(0, FILE_CURRENT, currentPos)) - return E_FAIL; - bool result = file.SetLength(newSize); - UInt64 currentPos2; - result = result && 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; -} +/* +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 + +#include +#include + +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 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.native())) { + ok = false; + } + } + return ok; +} + +STDMETHODIMP MultiOutputStream::Write(const void* data, UInt32 size, + UInt32* processedSize) +{ + 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) +{ + if (seekOrigin >= 3) + return STG_E_INVALIDFUNCTION; + + bool result = true; + for (auto& file : m_Files) { + UInt64 realNewPosition; + bool result = file.Seek(offset, seekOrigin, realNewPosition); + if (newPosition) + *newPosition = realNewPosition; + } + return ConvertBoolToHRESULT(result); +} + +STDMETHODIMP MultiOutputStream::SetSize(UInt64 newSize) +{ + bool result = true; + for (auto& file : m_Files) { + UInt64 currentPos; + if (!file.Seek(0, FILE_CURRENT, currentPos)) + return E_FAIL; + bool result = file.SetLength(newSize); + UInt64 currentPos2; + result = result && 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/src/multioutputstream.h b/src/multioutputstream.h index 8509b58..3328f77 100644 --- a/src/multioutputstream.h +++ b/src/multioutputstream.h @@ -1,110 +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 -#include -#include -#include - -#include "7zip/IStream.h" - -#include "unknown_impl.h" -#include "fileio.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; - - 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 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) override; - - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) override; - STDMETHOD(SetSize)(UInt64 newSize) 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 m_Files; - -}; - -#endif // MULTIOUTPUTSTREAM_H +/* +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 +#include +#include +#include + +#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; + + 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 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) override; + + STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) override; + STDMETHOD(SetSize)(UInt64 newSize) 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 m_Files; +}; + +#endif // MULTIOUTPUTSTREAM_H diff --git a/src/opencallback.cpp b/src/opencallback.cpp index b64c5eb..95e3619 100644 --- a/src/opencallback.cpp +++ b/src/opencallback.cpp @@ -1,157 +1,170 @@ -/* -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 -#include "opencallback.h" - -#include "inputstream.h" -#include "propertyvariant.h" - -#include - -#include -#include -#include -#include - -#include "fileio.h" - -#include -#include - -#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)) -{ - return S_OK; -} - -STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64 *UNUSED(files), const UInt64 *UNUSED(bytes)) -{ - 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) -{ - 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) -{ - m_SubArchiveMode = true; - m_SubArchiveName = name; - return S_OK; -} - -/* -------------------- IArchiveOpenVolumeCallback -------------------- */ - -STDMETHODIMP CArchiveOpenCallback::GetProperty(PROPID propID, PROPVARIANT *value) -{ - //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(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().native(); - } - } 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, fmt::format(L"Unexpected property {}.", propID)); - } - return S_OK; -} - -STDMETHODIMP CArchiveOpenCallback::GetStream(const wchar_t *name, IInStream **inStream) -{ - *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 inFile(new InputStream); - - if (!inFile->Open(m_FileInfo.path())) { - return ::GetLastError(); - } - - *inStream = inFile.Detach(); - return S_OK; -} +/* +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 + +#include "inputstream.h" +#include "propertyvariant.h" + +#include + +#include +#include +#include +#include + +#include "fileio.h" + +#include +#include + +#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)) +{ + return S_OK; +} + +STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64* UNUSED(files), + const UInt64* UNUSED(bytes)) +{ + 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) +{ + 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) +{ + m_SubArchiveMode = true; + m_SubArchiveName = name; + return S_OK; +} + +/* -------------------- IArchiveOpenVolumeCallback -------------------- */ + +STDMETHODIMP CArchiveOpenCallback::GetProperty(PROPID propID, PROPVARIANT* value) +{ + // 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(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().native(); + } + } 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, + fmt::format(L"Unexpected property {}.", propID)); + } + return S_OK; +} + +STDMETHODIMP CArchiveOpenCallback::GetStream(const wchar_t* name, IInStream** inStream) +{ + *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 inFile(new InputStream); + + if (!inFile->Open(m_FileInfo.path())) { + return ::GetLastError(); + } + + *inStream = inFile.Detach(); + return S_OK; +} diff --git a/src/opencallback.h b/src/opencallback.h index 6c4ce33..6fb2dd0 100644 --- a/src/opencallback.h +++ b/src/opencallback.h @@ -1,78 +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 -#include - -#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); - - ~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); - //Not implemented STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR *password); - - // IArchiveOpenSetSubArchiveName interface - STDMETHOD(SetSubArchiveName)(const wchar_t *name); - -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 +/* +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 +#include + +#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); + + ~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); + // Not implemented STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR + // *password); + + // IArchiveOpenSetSubArchiveName interface + STDMETHOD(SetSubArchiveName)(const wchar_t* name); + +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/src/propertyvariant.cpp b/src/propertyvariant.cpp index 5ed1434..a9f2d8c 100644 --- a/src/propertyvariant.cpp +++ b/src/propertyvariant.cpp @@ -22,9 +22,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include +#include #include #include -#include PropertyVariant::PropertyVariant() { @@ -41,131 +41,132 @@ void PropertyVariant::clear() PropVariantClear(this); } -//Arguably the behviours for empty here are wrong. -template <> PropertyVariant::operator bool() const +// Arguably the behviours for empty here are wrong. +template <> +PropertyVariant::operator bool() const { - switch (vt) - { - case VT_EMPTY: - return false; + switch (vt) { + case VT_EMPTY: + return false; - case VT_BOOL: - return boolVal != VARIANT_FALSE; + case VT_BOOL: + return boolVal != VARIANT_FALSE; - default: - throw std::runtime_error("Property is not a bool"); + default: + throw std::runtime_error("Property is not a bool"); } } -template <> PropertyVariant::operator uint64_t() const +template <> +PropertyVariant::operator uint64_t() const { - switch (vt) - { - case VT_EMPTY: - return 0; + switch (vt) { + case VT_EMPTY: + return 0; - case VT_UI1: - return bVal; + case VT_UI1: + return bVal; - case VT_UI2: - return uiVal; + case VT_UI2: + return uiVal; - case VT_UI4: - return ulVal; + case VT_UI4: + return ulVal; - case VT_UI8: - return static_cast(uhVal.QuadPart); + case VT_UI8: + return static_cast(uhVal.QuadPart); - default: - throw std::runtime_error("Property is not an unsigned integer"); + default: + throw std::runtime_error("Property is not an unsigned integer"); } } -template <> PropertyVariant::operator uint32_t() const +template <> +PropertyVariant::operator uint32_t() const { - switch (vt) - { - case VT_EMPTY: - return 0; + switch (vt) { + case VT_EMPTY: + return 0; - case VT_UI1: - return bVal; + case VT_UI1: + return bVal; - case VT_UI2: - return uiVal; + case VT_UI2: + return uiVal; - case VT_UI4: - return ulVal; + case VT_UI4: + return ulVal; - default: - throw std::runtime_error("Property is not an unsigned integer"); + default: + throw std::runtime_error("Property is not an unsigned integer"); } } - -template <> PropertyVariant::operator std::wstring() const +template <> +PropertyVariant::operator std::wstring() const { - switch (vt) - { - case VT_EMPTY: - return L""; + switch (vt) { + case VT_EMPTY: + return L""; - case VT_BSTR: - return std::wstring(bstrVal, ::SysStringLen(bstrVal)); + case VT_BSTR: + return std::wstring(bstrVal, ::SysStringLen(bstrVal)); - default: - throw std::runtime_error("Property is not a string"); + 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 +// 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 ""; + 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(bstrVal), ::SysStringByteLen(bstrVal)); + case VT_BSTR: + // If he can do a memcpy, I can do a reinterpret case + return std::string(reinterpret_cast(bstrVal), + ::SysStringByteLen(bstrVal)); - default: - throw std::runtime_error("Property is not a string"); + 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 +// 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(bstrVal); - - default: - throw std::runtime_error("Property is not a GUID (string)"); + switch (vt) { + case VT_BSTR: + // He did a cast too! + return *reinterpret_cast(bstrVal); + + default: + throw std::runtime_error("Property is not a GUID (string)"); } } -template <> PropertyVariant::operator FILETIME() const +template <> +PropertyVariant::operator FILETIME() const { - switch (vt) - { - case VT_FILETIME: - return filetime; + switch (vt) { + case VT_FILETIME: + return filetime; - default: - throw std::runtime_error("Property is not a file time"); + default: + throw std::runtime_error("Property is not a file time"); } } -//Assignments -template <> PropertyVariant& PropertyVariant::operator=(std::wstring const &str) +// Assignments +template <> +PropertyVariant& PropertyVariant::operator=(std::wstring const& str) { clear(); - vt = VT_BSTR; + vt = VT_BSTR; bstrVal = ::SysAllocString(str.c_str()); if (bstrVal == NULL) { throw std::bad_alloc(); @@ -173,34 +174,38 @@ template <> PropertyVariant& PropertyVariant::operator=(std::wstring const &str) return *this; } -template <> PropertyVariant& PropertyVariant::operator=(bool const& n) +template <> +PropertyVariant& PropertyVariant::operator=(bool const& n) { clear(); - vt = VT_BOOL; + vt = VT_BOOL; boolVal = n ? VARIANT_TRUE : VARIANT_FALSE; return *this; } -template <> PropertyVariant& PropertyVariant::operator=(FILETIME const& n) +template <> +PropertyVariant& PropertyVariant::operator=(FILETIME const& n) { clear(); - vt = VT_FILETIME; + vt = VT_FILETIME; filetime = n; return *this; } -template <> PropertyVariant& PropertyVariant::operator=(uint32_t const& n) +template <> +PropertyVariant& PropertyVariant::operator=(uint32_t const& n) { clear(); - vt = VT_UI4; + vt = VT_UI4; ulVal = n; return *this; } -template <> PropertyVariant& PropertyVariant::operator=(uint64_t const &n) +template <> +PropertyVariant& PropertyVariant::operator=(uint64_t const& n) { clear(); - vt = VT_UI8; + vt = VT_UI8; uhVal.QuadPart = n; return *this; } diff --git a/src/propertyvariant.h b/src/propertyvariant.h index caac845..d2038ff 100644 --- a/src/propertyvariant.h +++ b/src/propertyvariant.h @@ -35,19 +35,18 @@ class PropertyVariant : public tagPROPVARIANT PropertyVariant(); ~PropertyVariant(); - //clears the property should you wish to overwrite it with another one. + // clears the property should you wish to overwrite it with another one. void clear(); - bool is_empty() { - return vt == VT_EMPTY; - } + 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 explicit operator T() const; - - template PropertyVariant& operator=(T const &); + // It's too much like hard work listing all the valid ones. If it doesn't + // link, then you need to implement it... + template + explicit operator T() const; + template + PropertyVariant& operator=(T const&); }; -#endif // PROPVARIANT_H +#endif // PROPVARIANT_H diff --git a/src/unknown_impl.h b/src/unknown_impl.h index 8e12463..a3b5d31 100644 --- a/src/unknown_impl.h +++ b/src/unknown_impl.h @@ -59,75 +59,78 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* 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)\ - {\ +#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(static_cast(this));\ - } - -#define UNKNOWN__INTERFACE_NAME(interface)\ - else if (iid == IID_##interface) {\ - *outObject = static_cast(this);\ - } - -#define UNKNOWN__INTERFACE_END\ - else {\ - *outObject = nullptr;\ - return E_NOINTERFACE;\ - }\ - AddRef();\ - return S_OK;\ - }\ - private:\ +#define UNKNOWN__INTERFACE_UNKNOWN(interface) \ + *outObject = static_cast(static_cast(this)); \ + } + +#define UNKNOWN__INTERFACE_NAME(interface) \ + else if (iid == IID_##interface) \ + { \ + *outObject = static_cast(this); \ + } + +#define UNKNOWN__INTERFACE_END \ + else \ + { \ + *outObject = nullptr; \ + return E_NOINTERFACE; \ + } \ + AddRef(); \ + return S_OK; \ + } \ + \ +private: \ ULONG m_RefCount__ = 0 /* These are the macros you should be using */ -#define UNKNOWN_1_INTERFACE(interface)\ - UNKNOWN__INTERFACE_BEGIN\ - UNKNOWN__INTERFACE_UNKNOWN(interface)\ - UNKNOWN__INTERFACE_NAME(interface)\ +#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)\ +#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)\ +#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)\ +#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 +#endif // UNKNOWN_IMPL_H From fdae4b598ee2798bc6a5e00491bea83ac887a1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 18 May 2024 19:12:40 +0200 Subject: [PATCH 2/4] Add .git-blame-ignore-revs. --- .git-blame-ignore-revs | 1 + 1 file changed, 1 insertion(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..be37b84 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1 @@ +bc77d2a270cc24fd69a8b46ccad4cca2b334a19d From d7ece5c6a8b1a7923b2cca7687b6032d10980daf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Sat, 18 May 2024 19:18:00 +0200 Subject: [PATCH 3/4] Switch to gh-actions. --- .github/workflows/build.yml | 15 +++++++++++++ .github/workflows/linting.yml | 15 +++++++++++++ CMakeLists.txt | 6 +----- appveyor.yml | 40 ----------------------------------- 4 files changed, 31 insertions(+), 45 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/linting.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c9104cd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,15 @@ +name: Build Archive +on: + push: + branches: master + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + runs-on: windows-2022 + steps: + - name: Build Archive + uses: ModOrganizer2/build-with-mob-action@master + with: + mo2-third-parties: 7z + mo2-dependencies: cmake_common diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..beba4f4 --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,15 @@ +name: Lint Archive +on: + push: + pull_request: + types: [opened, synchronize, reopened] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run clang-format + uses: jidicula/clang-format-action@v4.12.0 + with: + clang-format-version: "17" + check-path: "." diff --git a/CMakeLists.txt b/CMakeLists.txt index 8871a9d..44fcf2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,6 @@ cmake_minimum_required(VERSION 3.16) -if(DEFINED DEPENDENCIES_DIR) - include(${DEPENDENCIES_DIR}/modorganizer_super/cmake_common/mo2.cmake) -else() - include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake) -endif() +include(${CMAKE_CURRENT_LIST_DIR}/../cmake_common/mo2.cmake) project(archive) add_subdirectory(src) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 659d66b..0000000 --- a/appveyor.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: 1.0.{build} -skip_branch_with_pr: true -image: Visual Studio 2019 -environment: - WEBHOOK_URL: - secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1Vbg57wBaeLhi49uGjxPw5GVujHku6kxN6ab89zhbS5GVeluR76GM83IbKV4Sh7udXzoYZZdg6YudtYHzdhCgUeiedpswbuczTq9ceIkkfSEWZuh/lMAAVVwvcGsJAnoPFw= -build: - parallel: true -build_script: -- pwsh: >- - $ErrorActionPreference = 'Stop' - - git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella - - New-Item -ItemType Directory -Path c:\projects\modorganizer-build - - cd c:\projects\modorganizer-umbrella - - ($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) - - git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch} - - C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True ${env:APPVEYOR_PROJECT_NAME} - - if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) } -artifacts: -- path: vsbuild\src\RelWithDebInfo\archive.dll - name: archive_dll -- path: vsbuild\src\RelWithDebInfo\archive.pdb - name: archive_pdb -on_success: - - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER - - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - - ps: ./send.ps1 success $env:WEBHOOK_URL -on_failure: - - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER - - ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stdout.log - - ps: Push-AppveyorArtifact ${env:APPVEYOR_BUILD_FOLDER}\stderr.log - - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 - - ps: ./send.ps1 failure $env:WEBHOOK_URL \ No newline at end of file From cd200d36e73e2d903f727bfaf201b6374ff75c1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Capelle?= Date: Tue, 21 May 2024 21:02:43 +0200 Subject: [PATCH 4/4] Switch from fmtlib to std::format. --- src/CMakeLists.txt | 3 +-- src/archive.cpp | 14 +++++++------- src/extractcallback.cpp | 20 +++++++++----------- src/extractcallback.h | 8 +++----- src/formatter.h | 37 +++++++++++++++---------------------- src/instrument.h | 5 ++--- src/opencallback.cpp | 6 ++---- 7 files changed, 39 insertions(+), 54 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 97b5db4..d59df83 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,6 @@ cmake_minimum_required(VERSION 3.16) add_library(archive SHARED) -mo2_configure_library(archive - WARNINGS OFF PRIVATE_DEPENDS 7z fmt) +mo2_configure_library(archive WARNINGS OFF PRIVATE_DEPENDS 7z) target_compile_definitions(archive PRIVATE -DMODORGANIZER_ARCHIVE_BUILDING) mo2_install_target(archive) diff --git a/src/archive.cpp b/src/archive.cpp index 9583fad..52d899e 100644 --- a/src/archive.cpp +++ b/src/archive.cpp @@ -301,7 +301,7 @@ ArchiveImpl::ArchiveImpl() m_Valid = true; return; } catch (std::exception const& e) { - m_LogCallback(LogLevel::Error, fmt::format(L"Caught exception {}.", e)); + m_LogCallback(LogLevel::Error, std::format(L"Caught exception {}.", e)); m_LastError = Error::ERROR_LIBRARY_INVALID; } } @@ -371,12 +371,12 @@ bool ArchiveImpl::open(std::wstring const& archiveName, if (m_ArchivePtr->Open(file, 0, openCallbackPtr) != S_OK) { m_LogCallback(LogLevel::Debug, - fmt::format(L"Failed to open {} using {} (from signature).", + std::format(L"Failed to open {} using {} (from signature).", archiveName, signatureInfo.second.m_Name)); m_ArchivePtr.Release(); } else { m_LogCallback(LogLevel::Debug, - fmt::format(L"Opened {} using {} (from signature).", + std::format(L"Opened {} using {} (from signature).", archiveName, signatureInfo.second.m_Name)); // Retrieve the extension (warning: .extension() contains the dot): @@ -433,12 +433,12 @@ bool ArchiveImpl::open(std::wstring const& archiveName, if (m_ArchivePtr->Open(file, 0, openCallbackPtr) != S_OK) { m_LogCallback(LogLevel::Debug, - fmt::format(L"Failed to open {} using {} (from signature).", + std::format(L"Failed to open {} using {} (from signature).", archiveName, format.m_Name)); m_ArchivePtr.Release(); } else { m_LogCallback(LogLevel::Debug, - fmt::format(L"Opened {} using {} (from signature).", + std::format(L"Opened {} using {} (from signature).", archiveName, format.m_Name)); break; } @@ -457,7 +457,7 @@ bool ArchiveImpl::open(std::wstring const& archiveName, } m_LogCallback( LogLevel::Warning, - fmt::format(L"The format(s) expected for this extension are: {}.", + std::format(L"The format(s) expected for this extension are: {}.", ArchiveStrings::join(vformats, L", "))); } } @@ -478,7 +478,7 @@ bool ArchiveImpl::open(std::wstring const& archiveName, } if (m_ArchivePtr->Open(file, 0, openCallbackPtr) == S_OK) { m_LogCallback(LogLevel::Debug, - fmt::format(L"Opened {} using {} (from signature).", archiveName, + std::format(L"Opened {} using {} (from signature).", archiveName, format.m_Name)); m_LogCallback(LogLevel::Warning, L"This archive likely has an incorrect extension."); diff --git a/src/extractcallback.cpp b/src/extractcallback.cpp index 3ad0181..fff51cd 100644 --- a/src/extractcallback.cpp +++ b/src/extractcallback.cpp @@ -20,17 +20,15 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include -#include -#include +#include +#include +#include +#include #include "archive.h" #include "extractcallback.h" #include "propertyvariant.h" -#include -#include -#include - std::wstring operationResultToString(Int32 operationResult) { namespace R = NArchive::NExtract::NOperationResult; @@ -67,7 +65,7 @@ std::wstring operationResultToString(Int32 operationResult) return L"Wrong password"; default: - return fmt::format(L"Unknown error {}", operationResult); + return std::format(L"Unknown error {}", operationResult); } } @@ -127,7 +125,7 @@ bool CArchiveExtractCallback::getOptionalProperty(UInt32 index, int property, PropertyVariant prop; if (m_ArchiveHandler->GetProperty(index, property, &prop) != S_OK) { m_LogCallback(Archive::LogLevel::Error, - fmt::format(L"Error getting property {}.", property)); + std::format(L"Error getting property {}.", property)); return false; } if (prop.is_empty()) { @@ -143,7 +141,7 @@ bool CArchiveExtractCallback::getProperty(UInt32 index, int property, T* result) PropertyVariant prop; if (m_ArchiveHandler->GetProperty(index, property, &prop) != S_OK) { m_LogCallback(Archive::LogLevel::Error, - fmt::format(L"Error getting property {}.", property)); + std::format(L"Error getting property {}.", property)); return false; } @@ -243,7 +241,7 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, auto fileSizeFound = getOptionalProperty(index, kpidSize, &fileSize); if (fileSizeFound && m_OutputFileStream->SetSize(fileSize) != S_OK) { m_LogCallback(Archive::LogLevel::Error, - fmt::format(L"SetSize() failed on {}.", m_FullProcessedPaths[0])); + std::format(L"SetSize() failed on {}.", m_FullProcessedPaths[0])); } // This is messy but I can't find another way of doing it. A simple @@ -260,7 +258,7 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, return S_OK; } catch (std::exception const& e) { m_LogCallback(Archive::LogLevel::Error, - fmt::format(L"Caught exception {} in GetStream.", e)); + std::format(L"Caught exception {} in GetStream.", e)); } return E_FAIL; } diff --git a/src/extractcallback.h b/src/extractcallback.h index 1b61dfa..c69737e 100644 --- a/src/extractcallback.h +++ b/src/extractcallback.h @@ -24,15 +24,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include #include #include +#include #include "7zip/Archive/IArchive.h" #include "7zip/IPassword.h" #include -#include -#include - #include "archive.h" #include "formatter.h" #include "instrument.h" @@ -73,9 +71,9 @@ class CArchiveExtractCallback : public IArchiveExtractCallback, void reportError(const std::wstring& message); template - void reportError(const wchar_t* format, Args&&... args) + void reportError(std::wformat_string format, Args&&... args) { - reportError(fmt::format(format, std::forward(args)...)); + reportError(std::format(format, std::forward(args)...)); } template diff --git a/src/formatter.h b/src/formatter.h index 0a8cad7..24d2c36 100644 --- a/src/formatter.h +++ b/src/formatter.h @@ -25,62 +25,55 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // exposed outside of the library. It also contains some useful methods for // string manipulation. -#include +#include #include #include #include #include -// Specializing fmt::formatter works, but gives warning, for whatever reason... So -// putting everything in the namespace. -namespace fmt -{ - -// I don't know why fmt does not provide this... template <> -struct formatter : formatter +struct std::formatter : std::formatter { template - auto format(std::string const& s, FormatContext& ctx) + auto format(std::string const& s, FormatContext& ctx) const { - return formatter::format(std::wstring(s.begin(), s.end()), - ctx); + return std::formatter::format( + std::wstring(s.begin(), s.end()), ctx); } }; template <> -struct formatter : formatter +struct std::formatter : std::formatter { template - auto format(std::exception const& ex, FormatContext& ctx) + auto format(std::exception const& ex, FormatContext& ctx) const { - return formatter::format(ex.what(), ctx); + return std::formatter::format(ex.what(), ctx); } }; template <> -struct formatter : formatter +struct std::formatter : std::formatter { template - auto format(std::error_code const& ec, FormatContext& ctx) + auto format(std::error_code const& ec, FormatContext& ctx) const { - return formatter::format(ec.message(), ctx); + return std::formatter::format(ec.message(), ctx); } }; template <> -struct formatter : formatter +struct std::formatter + : std::formatter { template - auto format(std::filesystem::path const& path, FormatContext& ctx) + auto format(std::filesystem::path const& path, FormatContext& ctx) const { - return formatter::format(path.native(), ctx); + return std::formatter::format(path.native(), ctx); } }; -} // namespace fmt - namespace ArchiveStrings { diff --git a/src/instrument.h b/src/instrument.h index 8bbf85a..82dc3f7 100644 --- a/src/instrument.h +++ b/src/instrument.h @@ -22,8 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #define ARCHIVE_INSTRUMENT_H #include - -#include +#include namespace ArchiveTimers { @@ -87,7 +86,7 @@ class Timer auto ms = [](auto&& t) { return std::chrono::duration(t); }; - return fmt::format( + return std::format( L"Instrument '{}': {} calls, total of {}ms, {:.3f}ms per call on average.", name, ncalls, ms(time).count(), ms(time).count() / ncalls); } diff --git a/src/opencallback.cpp b/src/opencallback.cpp index 95e3619..1bf8f97 100644 --- a/src/opencallback.cpp +++ b/src/opencallback.cpp @@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include +#include #include #include #include @@ -33,9 +34,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "fileio.h" -#include -#include - #define UNUSED(x) CArchiveOpenCallback::CArchiveOpenCallback(Archive::PasswordCallback passwordCallback, @@ -130,7 +128,7 @@ STDMETHODIMP CArchiveOpenCallback::GetProperty(PROPID propID, PROPVARIANT* value default: m_LogCallback(Archive::LogLevel::Warning, - fmt::format(L"Unexpected property {}.", propID)); + std::format(L"Unexpected property {}.", propID)); } return S_OK; }