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