aboutsummaryrefslogtreecommitdiff
path: root/libs/bsapacker/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/bsapacker/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/bsapacker/src')
-rw-r--r--libs/bsapacker/src/ArchiveAutoService.cpp102
-rw-r--r--libs/bsapacker/src/ArchiveBuildDirector.cpp28
-rw-r--r--libs/bsapacker/src/ArchiveBuilderFactory.cpp59
-rw-r--r--libs/bsapacker/src/ArchiveBuilderHelper.cpp77
-rw-r--r--libs/bsapacker/src/ArchiveNameService.cpp75
-rw-r--r--libs/bsapacker/src/ArchiveNameService.h24
-rw-r--r--libs/bsapacker/src/BSArchive.cpp184
-rw-r--r--libs/bsapacker/src/BSArchiveAuto.cpp101
-rw-r--r--libs/bsapacker/src/BSArchiveEntries.cpp65
-rw-r--r--libs/bsapacker/src/BsaPacker.cpp99
-rw-r--r--libs/bsapacker/src/BsaPacker.h42
-rw-r--r--libs/bsapacker/src/BsaPackerWorker.cpp66
-rw-r--r--libs/bsapacker/src/BsaPackerWorker.h42
-rw-r--r--libs/bsapacker/src/CMakeLists.txt36
-rw-r--r--libs/bsapacker/src/DummyPluginLogic.cpp58
-rw-r--r--libs/bsapacker/src/DummyPluginLogic.h30
-rw-r--r--libs/bsapacker/src/DummyPluginServiceFactory.cpp54
-rw-r--r--libs/bsapacker/src/DummyPluginServiceFactory.h29
-rw-r--r--libs/bsapacker/src/Fallout3DummyPluginService.cpp26
-rw-r--r--libs/bsapacker/src/Fallout3DummyPluginService.h41
-rw-r--r--libs/bsapacker/src/Fallout4DummyPluginService.cpp26
-rw-r--r--libs/bsapacker/src/Fallout4DummyPluginService.h34
-rw-r--r--libs/bsapacker/src/FileWriterService.cpp18
-rw-r--r--libs/bsapacker/src/FileWriterService.h16
-rw-r--r--libs/bsapacker/src/GeneralArchiveBuilder.cpp97
-rw-r--r--libs/bsapacker/src/HideLooseAssetService.cpp69
-rw-r--r--libs/bsapacker/src/HideLooseAssetService.h23
-rw-r--r--libs/bsapacker/src/ModContext.cpp66
-rw-r--r--libs/bsapacker/src/ModContext.h25
-rw-r--r--libs/bsapacker/src/ModDto.cpp43
-rw-r--r--libs/bsapacker/src/ModDto.h29
-rw-r--r--libs/bsapacker/src/ModDtoFactory.cpp90
-rw-r--r--libs/bsapacker/src/NewVegasDummyPluginService.cpp28
-rw-r--r--libs/bsapacker/src/NewVegasDummyPluginService.h42
-rw-r--r--libs/bsapacker/src/NexusId.h22
-rw-r--r--libs/bsapacker/src/NullArchiveBuilder.cpp32
-rw-r--r--libs/bsapacker/src/NullDummyPluginService.cpp9
-rw-r--r--libs/bsapacker/src/NullDummyPluginService.h17
-rw-r--r--libs/bsapacker/src/NullModDto.cpp29
-rw-r--r--libs/bsapacker/src/NullModDto.h20
-rw-r--r--libs/bsapacker/src/OblivionDummyPluginService.cpp26
-rw-r--r--libs/bsapacker/src/OblivionDummyPluginService.h34
-rw-r--r--libs/bsapacker/src/OverrideFileService.cpp44
-rw-r--r--libs/bsapacker/src/OverrideFileService.h21
-rw-r--r--libs/bsapacker/src/PackerDialog.cpp98
-rw-r--r--libs/bsapacker/src/PackerDialog.h48
-rw-r--r--libs/bsapacker/src/PackerDialogFactory.cpp5
-rw-r--r--libs/bsapacker/src/PackerDialogFactory.h16
-rw-r--r--libs/bsapacker/src/SettingsService.cpp28
-rw-r--r--libs/bsapacker/src/SettingsService.h32
-rw-r--r--libs/bsapacker/src/SkyrimDummyPluginService.cpp26
-rw-r--r--libs/bsapacker/src/SkyrimDummyPluginService.h34
-rw-r--r--libs/bsapacker/src/SkyrimSEDummyPluginService.cpp26
-rw-r--r--libs/bsapacker/src/SkyrimSEDummyPluginService.h35
-rw-r--r--libs/bsapacker/src/StarfieldDummyPluginService.cpp26
-rw-r--r--libs/bsapacker/src/StarfieldDummyPluginService.h34
-rw-r--r--libs/bsapacker/src/TextureArchiveBuilder.cpp137
-rw-r--r--libs/bsapacker/src/TexturelessArchiveBuilder.cpp98
-rw-r--r--libs/bsapacker/src/bsa_packer.json1
-rw-r--r--libs/bsapacker/src/bsa_packer_en.ts87
-rw-r--r--libs/bsapacker/src/bsapacker/ArchiveAutoService.h19
-rw-r--r--libs/bsapacker/src/bsapacker/ArchiveBuildDirector.h27
-rw-r--r--libs/bsapacker/src/bsapacker/ArchiveBuilderFactory.h31
-rw-r--r--libs/bsapacker/src/bsapacker/ArchiveBuilderHelper.h29
-rw-r--r--libs/bsapacker/src/bsapacker/GeneralArchiveBuilder.h37
-rw-r--r--libs/bsapacker/src/bsapacker/IArchiveAutoService.h18
-rw-r--r--libs/bsapacker/src/bsapacker/IArchiveBuilder.h24
-rw-r--r--libs/bsapacker/src/bsapacker/IArchiveBuilderFactory.h18
-rw-r--r--libs/bsapacker/src/bsapacker/IArchiveBuilderHelper.h22
-rw-r--r--libs/bsapacker/src/bsapacker/IArchiveNameService.h21
-rw-r--r--libs/bsapacker/src/bsapacker/IDummyPluginLogic.h19
-rw-r--r--libs/bsapacker/src/bsapacker/IDummyPluginService.h17
-rw-r--r--libs/bsapacker/src/bsapacker/IDummyPluginServiceFactory.h17
-rw-r--r--libs/bsapacker/src/bsapacker/IEmitsValueChanged.h25
-rw-r--r--libs/bsapacker/src/bsapacker/IFileWriterService.h16
-rw-r--r--libs/bsapacker/src/bsapacker/IHideLooseAssetService.h16
-rw-r--r--libs/bsapacker/src/bsapacker/IModContext.h20
-rw-r--r--libs/bsapacker/src/bsapacker/IModDto.h20
-rw-r--r--libs/bsapacker/src/bsapacker/IModDtoFactory.h17
-rw-r--r--libs/bsapacker/src/bsapacker/IOverrideFileService.h16
-rw-r--r--libs/bsapacker/src/bsapacker/IPackerDialog.h25
-rw-r--r--libs/bsapacker/src/bsapacker/IPackerDialogFactory.h16
-rw-r--r--libs/bsapacker/src/bsapacker/ISettingsService.h17
-rw-r--r--libs/bsapacker/src/bsapacker/ModDtoFactory.h33
-rw-r--r--libs/bsapacker/src/bsapacker/NullArchiveBuilder.h28
-rw-r--r--libs/bsapacker/src/bsapacker/TextureArchiveBuilder.h41
-rw-r--r--libs/bsapacker/src/bsapacker/TexturelessArchiveBuilder.h39
-rw-r--r--libs/bsapacker/src/bsapacker_global.h7
-rw-r--r--libs/bsapacker/src/qlibbsarch/BSArchive.h38
-rw-r--r--libs/bsapacker/src/qlibbsarch/BSArchiveAuto.h91
-rw-r--r--libs/bsapacker/src/qlibbsarch/BSArchiveEntries.h23
-rw-r--r--libs/bsapacker/src/qlibbsarch/QLibbsarch.h42
92 files changed, 3658 insertions, 0 deletions
diff --git a/libs/bsapacker/src/ArchiveAutoService.cpp b/libs/bsapacker/src/ArchiveAutoService.cpp
new file mode 100644
index 0000000..4c89c69
--- /dev/null
+++ b/libs/bsapacker/src/ArchiveAutoService.cpp
@@ -0,0 +1,102 @@
+#include <bsapacker/ArchiveAutoService.h>
+#include <uibase/utility.h>
+#include <QProgressDialog>
+#include <QtConcurrent>
+#include <QLabel>
+#include <QDebug>
+
+#include "NexusId.h"
+
+#ifdef __linux__
+#include <bsa_ffi.h>
+#endif
+
+namespace BsaPacker
+{
+ static const char* gameIdFromNexusId(const int nexusId)
+ {
+ switch (nexusId) {
+ case NexusId::Morrowind: return "morrowind";
+ case NexusId::Oblivion: return "oblivion";
+ case NexusId::Fallout3: return "fo3";
+ case NexusId::NewVegas: return "fonv";
+ case NexusId::Skyrim:
+ case NexusId::Enderal: return "skyrimle";
+ case NexusId::SkyrimSE:
+ case NexusId::EnderalSE: return "skyrimse";
+ case NexusId::Fallout4: return "fo4-fo76";
+ case NexusId::Starfield: return "starfield-v3";
+ default: return "fo4-fo76";
+ }
+ }
+
+ static int includeModeFromArchiveType(const bsa_archive_type_e type)
+ {
+ switch (type) {
+ case bsa_archive_type_e::baFO4: return 1; // non-dds
+ case bsa_archive_type_e::baFO4dds: return 2; // dds only
+ case bsa_archive_type_e::baSF: return 1; // non-dds
+ case bsa_archive_type_e::baSFdds: return 2; // dds only
+ default: return 0; // all files
+ }
+ }
+
+ bool ArchiveAutoService::CreateBSA(libbsarch::bs_archive_auto* archive,
+ const QString& archiveName,
+ const bsa_archive_type_e type,
+ const QString& sourceDir,
+ const int nexusId) const
+ {
+ const QString hostArchiveName = MOBase::normalizePathForHost(archiveName);
+ const QString hostSourceDir = MOBase::normalizePathForHost(sourceDir);
+ const char* gameId = gameIdFromNexusId(nexusId);
+ const int includeMode = includeModeFromArchiveType(type);
+
+ QProgressDialog savingDialog;
+ savingDialog.setWindowFlags(savingDialog.windowFlags() & ~Qt::WindowCloseButtonHint);
+ savingDialog.setWindowTitle(QObject::tr("Writing Archive"));
+ savingDialog.setCancelButton(0);
+ QLabel text;
+ text.setText(QObject::tr("Writing %1").arg(hostArchiveName));
+ savingDialog.setLabel(&text);
+ savingDialog.setRange(0, 0);
+ savingDialog.show();
+ auto future = QtConcurrent::run([=]() -> bool {
+#ifdef __linux__
+ char* err = bsa_ffi_pack_dir_filtered(
+ hostSourceDir.toUtf8().constData(),
+ hostArchiveName.toUtf8().constData(),
+ gameId,
+ includeMode,
+ nullptr,
+ nullptr);
+ if (err == nullptr) {
+ qDebug() << "packed archive via bsa_ffi for" << hostArchiveName;
+ return true;
+ }
+
+ qWarning() << "bsa_ffi primary pack failed for" << hostArchiveName
+ << ":" << QString::fromUtf8(err)
+ << "- falling back to libbsarch";
+ bsa_ffi_string_free(err);
+#endif
+ try {
+ archive->save_to_disk(hostArchiveName.toStdString());
+ } catch (std::exception&) {
+ return false;
+ }
+ return true;
+ });
+ while (!future.isFinished())
+ {
+ QCoreApplication::processEvents();
+ }
+ savingDialog.hide();
+
+ if (future.result()) {
+ return true;
+ }
+
+ return false;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/ArchiveBuildDirector.cpp b/libs/bsapacker/src/ArchiveBuildDirector.cpp
new file mode 100644
index 0000000..300d570
--- /dev/null
+++ b/libs/bsapacker/src/ArchiveBuildDirector.cpp
@@ -0,0 +1,28 @@
+#include <bsapacker/ArchiveBuildDirector.h>
+
+#include <QProgressDialog>
+
+namespace BsaPacker
+{
+ ArchiveBuildDirector::ArchiveBuildDirector(
+ const ISettingsService* settingsService,
+ IArchiveBuilder* archiveFileBuildService)
+ : m_ArchiveFileBuildService(archiveFileBuildService),
+ m_SettingsService(settingsService)
+ {
+ }
+
+ void ArchiveBuildDirector::Construct()
+ {
+ QProgressDialog dialog("Adding to archive",
+ "Abort",
+ 0,
+ static_cast<int>(this->m_ArchiveFileBuildService->getFileCount()));
+ QObject::connect(&dialog, &QProgressDialog::canceled, this->m_ArchiveFileBuildService, &IArchiveBuilder::cancel);
+ QObject::connect(this->m_ArchiveFileBuildService, SIGNAL(valueChanged(int)), &dialog, SLOT(setValue(int)));
+ dialog.show();
+ this->m_ArchiveFileBuildService->setShareData(true);
+ this->m_ArchiveFileBuildService->setFiles();
+ dialog.close();
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/ArchiveBuilderFactory.cpp b/libs/bsapacker/src/ArchiveBuilderFactory.cpp
new file mode 100644
index 0000000..fc72695
--- /dev/null
+++ b/libs/bsapacker/src/ArchiveBuilderFactory.cpp
@@ -0,0 +1,59 @@
+#include <bsapacker/ArchiveBuilderFactory.h>
+
+#include <bsapacker/GeneralArchiveBuilder.h>
+#include <bsapacker/NullArchiveBuilder.h>
+#include <bsapacker/TextureArchiveBuilder.h>
+#include <bsapacker/TexturelessArchiveBuilder.h>
+#include "NexusId.h"
+
+namespace BsaPacker
+{
+ ArchiveBuilderFactory::ArchiveBuilderFactory(const IArchiveBuilderHelper* archiveBuilderHelper)
+ : m_ArchiveBuilderHelper(archiveBuilderHelper)
+ {
+ }
+
+ std::vector<bsa_archive_type_e> ArchiveBuilderFactory::GetArchiveTypes(const IModDto* modDto) const
+ {
+ switch (modDto->NexusId()) {
+ case NexusId::Morrowind:
+ return std::vector<bsa_archive_type_e> { baTES3 };
+ case NexusId::Oblivion:
+ return std::vector<bsa_archive_type_e> { baTES4 };
+ case NexusId::Fallout3:
+ case NexusId::NewVegas:
+ case NexusId::Skyrim:
+ case NexusId::Enderal:
+ return std::vector<bsa_archive_type_e> { baFO3 };
+ case NexusId::SkyrimSE:
+ case NexusId::EnderalSE:
+ return std::vector<bsa_archive_type_e> { baSSE };
+ case NexusId::Fallout4:
+ return std::vector<bsa_archive_type_e> { baFO4, baFO4dds };
+ case NexusId::Starfield:
+ return std::vector<bsa_archive_type_e> { baSF, baSFdds };
+ default:
+ return std::vector<bsa_archive_type_e> { baNone };
+ }
+ }
+
+ std::unique_ptr<IArchiveBuilder> ArchiveBuilderFactory::Create(const bsa_archive_type_e archiveType, const IModDto* modDto) const
+ {
+ switch (archiveType) {
+ case baTES3:
+ case baTES4:
+ case baFO3:
+ case baSSE:
+ return std::make_unique<GeneralArchiveBuilder>(this->m_ArchiveBuilderHelper, modDto->Directory(), archiveType);
+ case baFO4:
+ case baSF:
+ return std::make_unique<TexturelessArchiveBuilder>(this->m_ArchiveBuilderHelper, modDto->Directory(), archiveType);
+ case baFO4dds:
+ case baSFdds:
+ return std::make_unique<TextureArchiveBuilder>(this->m_ArchiveBuilderHelper, modDto->Directory(), archiveType);
+ case baNone:
+ default:
+ return std::make_unique<NullArchiveBuilder>();
+ }
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/ArchiveBuilderHelper.cpp b/libs/bsapacker/src/ArchiveBuilderHelper.cpp
new file mode 100644
index 0000000..c5c956b
--- /dev/null
+++ b/libs/bsapacker/src/ArchiveBuilderHelper.cpp
@@ -0,0 +1,77 @@
+#include <bsapacker/ArchiveBuilderHelper.h>
+
+#include <algorithm>
+#include <ranges>
+
+#include "SettingsService.h"
+
+#include <QDebug>
+
+using std::filesystem::path;
+using std::filesystem::directory_entry;
+using std::filesystem::directory_iterator;
+using std::filesystem::recursive_directory_iterator;
+
+namespace BsaPacker
+{
+ const std::set<std::string> ArchiveBuilderHelper::INCOMPRESSIBLE_TYPES = { ".wav", ".ogg", ".mp3" };
+
+ ArchiveBuilderHelper::ArchiveBuilderHelper(const ISettingsService* settingsService)
+ : m_SettingsService(settingsService)
+ {
+ }
+ uint32_t ArchiveBuilderHelper::getFileCount(const path& rootDirectory) const
+ {
+ uint32_t count = 0;
+ for(auto& p : recursive_directory_iterator(rootDirectory)) {
+ if (p.is_regular_file()) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ std::vector<path::string_type> ArchiveBuilderHelper::getRootDirectoryFilenames(const path& rootDirectory) const
+ {
+ std::vector<path::string_type> filenames;
+ for (const auto& entry : directory_iterator(rootDirectory)) {
+ filenames.push_back(entry.path().filename().native());
+ }
+ return filenames;
+ }
+
+ bool ArchiveBuilderHelper::isFileIgnorable(const path& filepath, const std::vector<path::string_type>& rootDirFilenames) const
+ {
+ return this->doesPathContainFiles(filepath, rootDirFilenames) || // ignore files within mod directory
+ this->isExtensionBlacklisted(filepath); // ignore user blacklisted file types
+ }
+
+ bool ArchiveBuilderHelper::isIncompressible(const path& filename) const
+ {
+ if (!this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_COMPRESS_ARCHIVES).toBool()) {
+ return true;
+ }
+
+ const auto& extension = filename.extension().string();
+ const auto& count = ArchiveBuilderHelper::INCOMPRESSIBLE_TYPES.count(extension);
+ const auto& result = count > 0;
+ return result;
+ }
+
+ bool ArchiveBuilderHelper::isExtensionBlacklisted(const path& filepath) const
+ {
+ const auto& setting = this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_BLACKLISTED_FILES).toString().toStdString();
+ const auto &extension = filepath.extension().string();
+ const auto count = std::ranges::count(
+ setting | std::views::split(' '),
+ extension,
+ [](auto r)
+ { return std::string_view(r.data(), r.size()); });
+ return count > 0;
+ }
+
+ bool ArchiveBuilderHelper::doesPathContainFiles(const path& filepath, const std::vector<path::string_type>& files) const
+ {
+ return std::find(files.begin(), files.end(), filepath.filename()) != files.end();
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/ArchiveNameService.cpp b/libs/bsapacker/src/ArchiveNameService.cpp
new file mode 100644
index 0000000..9cea91b
--- /dev/null
+++ b/libs/bsapacker/src/ArchiveNameService.cpp
@@ -0,0 +1,75 @@
+#include "ArchiveNameService.h"
+
+#include "NexusId.h"
+
+#include <QFileInfo>
+
+namespace BsaPacker
+{
+ ArchiveNameService::ArchiveNameService(const IModContext* modContext)
+ : m_ModContext(modContext)
+ {
+ }
+
+ QString ArchiveNameService::GetFileExtension() const
+ {
+ switch (this->m_ModContext->GetNexusId()) {
+ case NexusId::Morrowind:
+ case NexusId::Oblivion:
+ case NexusId::Fallout3:
+ case NexusId::NewVegas:
+ case NexusId::Skyrim:
+ case NexusId::SkyrimSE:
+ case NexusId::Enderal:
+ case NexusId::EnderalSE:
+ return QStringLiteral(".bsa");
+ case NexusId::Fallout4:
+ case NexusId::Starfield:
+ return QStringLiteral(".ba2");
+ default:
+ return QString();
+ }
+ }
+
+ QString ArchiveNameService::GetArchiveFullPath(const bsa_archive_type_e type, const IModDto* modDto) const
+ {
+ const QString& pathNoExt(QDir::toNativeSeparators(modDto->Directory() + '/' + modDto->ArchiveName() + this->Infix(type)));
+ const QString& suffix = this->Suffix(pathNoExt);
+ return QDir::toNativeSeparators(pathNoExt + suffix + this->GetFileExtension());
+ }
+
+ QString ArchiveNameService::Infix(const bsa_archive_type_e type) const
+ {
+ switch (type) {
+ case baFO4:
+ case baSF:
+ return QStringLiteral(" - Main");
+ case baSFdds:
+ case baFO4dds:
+ return QStringLiteral(" - Textures");
+ case baTES3:
+ case baTES4:
+ case baFO3:
+ case baSSE:
+ case baNone:
+ default:
+ return QString();
+ };
+ }
+
+ // gets the number to append when there are multiple archives
+ // a way to avoid overwriting any existing files
+ QString ArchiveNameService::Suffix(const QString& pathNoExt) const {
+ int archiveIndex = 0;
+ const QString& fileExt = this->GetFileExtension();
+ QFileInfo fileInfo(pathNoExt + fileExt);
+ while (fileInfo.exists()) {
+ ++archiveIndex;
+ fileInfo.setFile(pathNoExt + QString::number(archiveIndex) + fileExt);
+ }
+ if (archiveIndex != 0) {
+ return QString::number(archiveIndex);
+ }
+ return QString();
+ }
+}
diff --git a/libs/bsapacker/src/ArchiveNameService.h b/libs/bsapacker/src/ArchiveNameService.h
new file mode 100644
index 0000000..1ee8490
--- /dev/null
+++ b/libs/bsapacker/src/ArchiveNameService.h
@@ -0,0 +1,24 @@
+#ifndef ARCHIVENAMESERVICE_H
+#define ARCHIVENAMESERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveNameService.h>
+#include <bsapacker/IModContext.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ArchiveNameService : public IArchiveNameService
+ {
+ public:
+ ArchiveNameService(const IModContext* modContext);
+ ~ArchiveNameService() override = default;
+ QString GetArchiveFullPath(bsa_archive_type_e type, const IModDto* modDto) const override;
+ QString GetFileExtension() const override;
+ QString Infix(bsa_archive_type_e type) const override;
+ QString Suffix(const QString& pathNoExt) const override;
+ private:
+ const IModContext* m_ModContext = nullptr;
+ };
+}
+
+#endif // ARCHIVENAMESERVICE_H
diff --git a/libs/bsapacker/src/BSArchive.cpp b/libs/bsapacker/src/BSArchive.cpp
new file mode 100644
index 0000000..6ce4278
--- /dev/null
+++ b/libs/bsapacker/src/BSArchive.cpp
@@ -0,0 +1,184 @@
+#include <qlibbsarch/BSArchive.h>
+
+BSArchive::BSArchive()
+ : _archive(bsa_create())
+{
+}
+
+BSArchive::~BSArchive()
+{
+ if (_openedArchive)
+ close();
+ free();
+}
+
+void BSArchive::free()
+{
+ LOG_LIBBSARCH << "Freeing archive: " << _archive;
+
+ const auto &result = bsa_free(_archive);
+ _openedArchive = false;
+
+ QLibBsarch::checkResult(result);
+}
+
+void BSArchive::open(const QString &archivePath)
+{
+ LOG_LIBBSARCH << "Opening archive: " << archivePath;
+
+ const auto &result = bsa_load_from_file(_archive, PREPARE_PATH_LIBBSARCH(archivePath));
+ _openedArchive = true;
+
+ QLibBsarch::checkResult(result);
+}
+
+void BSArchive::close()
+{
+ LOG_LIBBSARCH << "Closing archive: " << _archive;
+
+ bsa_close(_archive);
+ _openedArchive = false;
+}
+
+void BSArchive::create(const QString &archiveName, const bsa_archive_type_e& type, const BSArchiveEntries& entries)
+{
+ LOG_LIBBSARCH << "Creating archive. Archive name: " << archiveName;
+ LOG_LIBBSARCH << "type: " << type;
+ LOG_LIBBSARCH << "entries: " << entries.getEntries();
+
+ bsa_create_archive(_archive, PREPARE_PATH_LIBBSARCH(archiveName), type, entries.getEntries());
+
+ _openedArchive = true;
+}
+
+void BSArchive::save()
+{
+ LOG_LIBBSARCH << "Saving archive: " << _archive;
+
+ const auto &result = bsa_save(_archive);
+
+ QLibBsarch::checkResult(result);
+}
+
+void BSArchive::addFileFromDiskRoot(const QString &rootDir, const QString &filename)
+{
+ LOG_LIBBSARCH << "Adding file from disk root. Root directory: " << rootDir;
+ LOG_LIBBSARCH << "Filename: " << filename;
+ LOG_LIBBSARCH << "Archive: " << _archive;
+
+ const auto &result = bsa_add_file_from_disk_root(_archive,
+ PREPARE_PATH_LIBBSARCH(rootDir),
+ PREPARE_PATH_LIBBSARCH(filename));
+
+ QLibBsarch::checkResult(result);
+}
+
+void BSArchive::addFileFromDiskRoot(const QString &rootDir, const QStringList &files)
+{
+ for (const auto &file : files)
+ addFileFromDiskRoot(rootDir, file);
+}
+
+void BSArchive::addFileFromDisk(const QString &pathInArchive, const QString &filePath)
+{
+ LOG_LIBBSARCH << "Adding file from disk root. Path in archive: " << pathInArchive;
+ LOG_LIBBSARCH << "Filepath: " << filePath;
+ LOG_LIBBSARCH << "Archive: " << _archive;
+
+ const auto &result = bsa_add_file_from_disk_root(_archive,
+ PREPARE_PATH_LIBBSARCH(pathInArchive),
+ PREPARE_PATH_LIBBSARCH(filePath));
+
+ QLibBsarch::checkResult(result);
+}
+
+void BSArchive::addFileFromMemory(const QString &filename, const QByteArray &data) //NOTE UNTESTED
+{
+ LOG_LIBBSARCH << "Adding file from memory. Filename: " << filename;
+ LOG_LIBBSARCH << "Data size: " << data.size();
+
+ uint32_t size = static_cast<uint32_t>(data.size());
+ bsa_buffer_t buffer = const_cast<char *>(data.data());
+ const auto &result = bsa_add_file_from_memory(_archive, PREPARE_PATH_LIBBSARCH(filename), size, buffer);
+
+ QLibBsarch::checkResult(result);
+}
+void BSArchive::setCompressed(bool value)
+{
+ LOG_LIBBSARCH << "Setting compressed flag to " << value;
+ bsa_compress_set(_archive, value);
+}
+
+void BSArchive::setShareData(bool value)
+{
+ LOG_LIBBSARCH << "Setting share data flag to " << value;
+ bsa_share_data_set(_archive, value);
+}
+
+bsa_file_record_t BSArchive::findFileRecord(const QString &filename)
+{
+ LOG_LIBBSARCH << "Finding file record of: " << filename;
+ const auto &result = bsa_find_file_record(_archive, PREPARE_PATH_LIBBSARCH(filename));
+ return result;
+}
+
+QByteArray BSArchive::extractFileDataByRecord(bsa_file_record_t record)
+{
+ const auto &result = bsa_extract_file_data_by_record(_archive, record);
+
+ QLibBsarch::checkResult(result);
+
+ char *buffer = static_cast<char *>(result.buffer.data);
+ int size = static_cast<int>(result.buffer.size);
+
+ QByteArray byte_array(buffer, size);
+ return byte_array;
+}
+
+QByteArray BSArchive::extractFileDataByFilename(const QString &filename)
+{
+ const auto &result = bsa_extract_file_data_by_filename(_archive, PREPARE_PATH_LIBBSARCH(filename));
+
+ QLibBsarch::checkResult(result);
+
+ char *buffer = static_cast<char *>(result.buffer.data);
+ int size = static_cast<int>(result.buffer.size);
+
+ QByteArray byte_array(buffer, size);
+ return byte_array;
+}
+
+void BSArchive::extract(const QString &filename, const QString &saveAs)
+{
+ qDebug() << "Extracting: " << filename << " saved as " << saveAs;
+ const auto &result = bsa_extract_file(_archive, PREPARE_PATH_LIBBSARCH(filename), PREPARE_PATH_LIBBSARCH(saveAs));
+
+ QLibBsarch::checkResult(result);
+}
+
+QStringList BSArchive::listFiles()
+{
+ bsa_entry_list_t list = bsa_entry_list_create();
+ const auto &result = bsa_get_resource_list(_archive, list, L"");
+
+ QLibBsarch::checkResult(result);
+
+ return BSArchiveEntries(list).list();
+}
+
+bsa_archive_t BSArchive::getArchive() const
+{
+ return _archive;
+}
+
+void BSArchive::reset()
+{
+ free();
+ _archive = bsa_create();
+}
+
+void BSArchive::setDDSCallback(bsa_file_dds_info_proc_t file_dds_info_proc, void *context)
+{
+ LOG_LIBBSARCH << "Setting DDS callback for archive: " << _archive << "\nCallback adress: " << &file_dds_info_proc;
+ bsa_file_dds_info_callback_set(_archive, file_dds_info_proc, context);
+}
diff --git a/libs/bsapacker/src/BSArchiveAuto.cpp b/libs/bsapacker/src/BSArchiveAuto.cpp
new file mode 100644
index 0000000..f2ef7f8
--- /dev/null
+++ b/libs/bsapacker/src/BSArchiveAuto.cpp
@@ -0,0 +1,101 @@
+#include <qlibbsarch/BSArchiveAuto.h>
+#include <qlibbsarch/QLibbsarch.h>
+
+BSArchiveAuto::BSArchiveAuto(const QString &rootDirectory)
+ : _rootDirectory(QDir::toNativeSeparators(QDir::cleanPath(rootDirectory)))
+{
+}
+
+void BSArchiveAuto::open(const QString &archivePath)
+{
+ _archive.open(archivePath);
+ qDebug() << "Opening archive: " << archivePath;
+}
+
+void BSArchiveAuto::create(const QString &archiveName, const bsa_archive_type_e &type)
+{
+ _archive.create(archiveName, type, _entries);
+
+ for (auto it = _filesfromMemory.constBegin(); it != _filesfromMemory.constEnd(); ++it)
+ _archive.addFileFromMemory(it.key(), it.value());
+
+ for (const auto &file : _filesFromDiskRoot)
+ _archive.addFileFromDiskRoot(_rootDirectory.path(), file);
+
+ for (auto it = _filesFromDisk.constBegin(); it != _filesFromDisk.constEnd(); ++it)
+ _archive.addFileFromDisk(it.key(), it.value());
+}
+
+
+void BSArchiveAuto::addFileFromDiskRoot(const QString &filename)
+{
+ _entries.add(_rootDirectory.relativeFilePath(filename));
+ _filesFromDiskRoot << filename;
+}
+
+
+void BSArchiveAuto::addFileFromDiskRoot(const QStringList& files)
+{
+ for (const auto &file : files)
+ addFileFromDiskRoot(file);
+}
+
+void BSArchiveAuto::addFileFromDisk(const QString &saveAs, const QString &diskPath)
+{
+ _entries.add(saveAs);
+ _filesFromDisk.insert(saveAs, diskPath);
+}
+
+void BSArchiveAuto::addFileFromDisk(const QMap<QString, QString> &map)
+{
+ for (auto it = map.cbegin(); it != map.cend(); ++it)
+ _filesFromDisk.insert(it.key(), it.value());
+}
+
+void BSArchiveAuto::addFileFromMemory(const QString &filename, const QByteArray &data)
+{
+ _entries.add(filename);
+ _filesfromMemory.insert(filename, data);
+}
+
+void BSArchiveAuto::extractAll(const QString& destinationDirectory, const bool &overwriteExistingFiles)
+{
+ for (const auto &file : _archive.listFiles())
+ {
+ QFile currentFile(destinationDirectory + "/" + file);
+ _rootDirectory.mkpath(destinationDirectory + "/" + QFileInfo(file).path());
+ if(currentFile.exists() && overwriteExistingFiles)
+ {
+ currentFile.remove();
+ _archive.extract(file, currentFile.fileName());
+ }
+ else if (!currentFile.exists())
+ _archive.extract(file, currentFile.fileName());
+ }
+}
+
+void BSArchiveAuto::save()
+{
+ _archive.save();
+}
+
+void BSArchiveAuto::reset()
+{
+ _archive.reset();
+ _entries.reset();
+}
+
+void BSArchiveAuto::setShareData(const bool state)
+{
+ _archive.setShareData(state);
+}
+
+void BSArchiveAuto::setCompressed(const bool state)
+{
+ _archive.setCompressed(state);
+}
+
+void BSArchiveAuto::setDDSCallback(bsa_file_dds_info_proc_t fileDDSInfoProc, void *context)
+{
+ _archive.setDDSCallback(fileDDSInfoProc, context);
+}
diff --git a/libs/bsapacker/src/BSArchiveEntries.cpp b/libs/bsapacker/src/BSArchiveEntries.cpp
new file mode 100644
index 0000000..64ef838
--- /dev/null
+++ b/libs/bsapacker/src/BSArchiveEntries.cpp
@@ -0,0 +1,65 @@
+#include <qlibbsarch/BSArchiveEntries.h>
+
+BSArchiveEntries::BSArchiveEntries()
+ : _entries(bsa_entry_list_create())
+{
+}
+
+BSArchiveEntries::BSArchiveEntries(const QStringList &QSLEntries)
+ : _entries(bsa_entry_list_create())
+{
+ for (const auto &entry : QSLEntries)
+ add (entry);
+}
+
+BSArchiveEntries::BSArchiveEntries(const bsa_entry_list_t &entries)
+ : _entries(entries)
+{
+}
+
+BSArchiveEntries::~BSArchiveEntries()
+{
+ free();
+}
+
+void BSArchiveEntries::free()
+{
+ bsa_entry_list_free(_entries);
+}
+
+void BSArchiveEntries::reset()
+{
+ free();
+ _entries = bsa_entry_list_create();
+}
+
+void BSArchiveEntries::add(const QString &filepath)
+{
+ LOG_LIBBSARCH << "Adding to entries: " << filepath;
+
+ const auto &result = bsa_entry_list_add(_entries, PREPARE_PATH_LIBBSARCH(filepath));
+
+ QLibBsarch::checkResult(result);
+}
+
+uint32_t BSArchiveEntries::count()
+{
+ return bsa_entry_list_count(_entries);
+}
+
+QStringList BSArchiveEntries::list()
+{
+ QStringList list;
+ for(uint32_t i = 0 ; i < count() ; ++i)
+ {
+ wchar_t buffer[1024];
+ bsa_entry_list_get(_entries, i, 1024, buffer);
+ list << QString::fromWCharArray(buffer);
+ }
+ return list;
+}
+
+bsa_entry_list_t BSArchiveEntries::getEntries() const
+{
+ return _entries;
+}
diff --git a/libs/bsapacker/src/BsaPacker.cpp b/libs/bsapacker/src/BsaPacker.cpp
new file mode 100644
index 0000000..f356b78
--- /dev/null
+++ b/libs/bsapacker/src/BsaPacker.cpp
@@ -0,0 +1,99 @@
+#include <BsaPacker.h>
+
+#include <bsapacker/ArchiveAutoService.h>
+#include <bsapacker/ArchiveBuildDirector.h>
+#include <bsapacker/ArchiveBuilderHelper.h>
+#include <bsapacker/ArchiveBuilderFactory.h>
+#include <ArchiveNameService.h>
+#include "BsaPackerWorker.h"
+#include "DummyPluginLogic.h"
+#include "DummyPluginServiceFactory.h"
+#include "FileWriterService.h"
+#include "HideLooseAssetService.h"
+#include "ModContext.h"
+#include "ModDto.h"
+#include "OverrideFileService.h"
+#include "PackerDialog.h"
+#include "SettingsService.h"
+#include <bsapacker/ModDtoFactory.h>
+#include <QMessageBox>
+#include <iplugingame.h>
+
+namespace BsaPacker
+{
+ bool Bsa_Packer::init(MOBase::IOrganizer* moInfo)
+ {
+ this->m_Organizer = moInfo;
+ this->m_ModContext = std::make_unique<ModContext>(this->m_Organizer);
+ this->m_SettingsService = std::make_unique<SettingsService>(this->m_Organizer);
+ return true;
+ }
+
+ QString Bsa_Packer::name() const
+ {
+ return QStringLiteral("BSA Packer");
+ }
+
+ QString Bsa_Packer::author() const
+ {
+ return QStringLiteral("MattyFez & MO2 Team");
+ }
+
+ QString Bsa_Packer::description() const
+ {
+ return tr("Transform loose files into a Bethesda Softworks Archive file (.bsa/.ba2).");
+ }
+
+ MOBase::VersionInfo Bsa_Packer::version() const
+ {
+ return MOBase::VersionInfo(1, 1, 0, MOBase::VersionInfo::RELEASE_FINAL);
+ }
+
+ QList<MOBase::PluginSetting> Bsa_Packer::settings() const
+ {
+ return SettingsService::PluginSettings;
+ }
+
+ QString Bsa_Packer::tooltip() const
+ {
+ return tr("Transform loose files into a Bethesda Softworks Archive file (.bsa/.ba2).");
+ }
+
+ QIcon Bsa_Packer::icon() const
+ {
+ return QIcon();
+ }
+
+ QString Bsa_Packer::displayName() const
+ {
+ return tr("BSA Packer");
+ }
+
+ void Bsa_Packer::display() const
+ {
+ ArchiveBuilderHelper archiveBuilderHelper(this->m_SettingsService.get());
+ ArchiveBuilderFactory archiveBuilderFactory(&archiveBuilderHelper);
+ ArchiveAutoService archiveAutoService;
+ FileWriterService fileWriterService;
+ ArchiveNameService archiveNameService(this->m_ModContext.get());
+ DummyPluginLogic dummyPluginLogic(this->m_SettingsService.get(), &archiveNameService);
+ DummyPluginServiceFactory dummyPluginServiceFactory(
+ this->m_ModContext.get(), &fileWriterService, &dummyPluginLogic);
+ HideLooseAssetService hideLooseAssetService(this->m_SettingsService.get());
+ PackerDialog packerDialog(this->m_ModContext.get());
+ ModDtoFactory modDtoFactory(this->m_ModContext.get(), &packerDialog);
+ OverrideFileService overrideFileService(&fileWriterService);
+
+ BsaPackerWorker worker(
+ this->m_SettingsService.get(),
+ &modDtoFactory,
+ &archiveBuilderFactory,
+ &archiveAutoService,
+ &dummyPluginServiceFactory,
+ &hideLooseAssetService,
+ &archiveNameService,
+ &overrideFileService);
+ worker.DoWork();
+ }
+
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/BsaPacker.h b/libs/bsapacker/src/BsaPacker.h
new file mode 100644
index 0000000..a624586
--- /dev/null
+++ b/libs/bsapacker/src/BsaPacker.h
@@ -0,0 +1,42 @@
+#ifndef BSA_PACKER_H
+#define BSA_PACKER_H
+
+#include "bsapacker_global.h"
+#include <iplugintool.h>
+#include <bsapacker/IModContext.h>
+#include <bsapacker/ISettingsService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT Bsa_Packer : public MOBase::IPluginTool
+ {
+ Q_OBJECT
+ Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool)
+#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
+ Q_PLUGIN_METADATA(IID "org.MattyFez.BSA_Packer" FILE "bsa_packer.json")
+#endif
+
+ public:
+ // IPlugin interface
+ bool init(MOBase::IOrganizer* moInfo) override;
+ [[nodiscard]] QString name() const override;
+ [[nodiscard]] QString author() const override;
+ [[nodiscard]] QString description() const override;
+ [[nodiscard]] MOBase::VersionInfo version() const override;
+ [[nodiscard]] QList<MOBase::PluginSetting> settings() const override;
+
+ // IPluginTool interface
+ [[nodiscard]] QString displayName() const override;
+ [[nodiscard]] QString tooltip() const override;
+ [[nodiscard]] QIcon icon() const override;
+
+ public Q_SLOTS:
+ void display() const override;
+
+ private:
+ MOBase::IOrganizer* m_Organizer = nullptr;
+ std::unique_ptr<IModContext> m_ModContext = nullptr;
+ std::unique_ptr<ISettingsService> m_SettingsService = nullptr;
+ };
+} // namespace BsaPacker
+#endif //BSA_PACKER_H
diff --git a/libs/bsapacker/src/BsaPackerWorker.cpp b/libs/bsapacker/src/BsaPackerWorker.cpp
new file mode 100644
index 0000000..3b9e558
--- /dev/null
+++ b/libs/bsapacker/src/BsaPackerWorker.cpp
@@ -0,0 +1,66 @@
+#include "BsaPackerWorker.h"
+
+#include <QMessageBox>
+
+#include <bsapacker/ArchiveBuildDirector.h>
+#include <bsapacker/ModDtoFactory.h>
+
+namespace BsaPacker
+{
+ BsaPackerWorker::BsaPackerWorker(
+ const ISettingsService* settingsService,
+ const IModDtoFactory* modDtoFactory,
+ const IArchiveBuilderFactory* archiveBuilderFactory,
+ const IArchiveAutoService* archiveAutoService,
+ const IDummyPluginServiceFactory* dummyPluginServiceFactory,
+ const IHideLooseAssetService* hideLooseAssetService,
+ const IArchiveNameService* archiveNameService,
+ const IOverrideFileService* overrideFileService) :
+ m_SettingsService(settingsService),
+ m_ModDtoFactory(modDtoFactory),
+ m_ArchiveBuilderFactory(archiveBuilderFactory),
+ m_ArchiveAutoService(archiveAutoService),
+ m_DummyPluginServiceFactory(dummyPluginServiceFactory),
+ m_HideLooseAssetService(hideLooseAssetService),
+ m_ArchiveNameService(archiveNameService),
+ m_OverrideFileService(overrideFileService)
+ {
+ }
+
+ void BsaPackerWorker::DoWork() const
+ {
+ QStringList createdArchives;
+ const std::unique_ptr<IModDto> modDto = this->m_ModDtoFactory->Create(); // handles PackerDialog and validation, implements Null Object pattern
+ const std::vector<bsa_archive_type_e> types = this->m_ArchiveBuilderFactory->GetArchiveTypes(modDto.get());
+ for (auto&& type : types) {
+ const std::unique_ptr<IArchiveBuilder> builder = this->m_ArchiveBuilderFactory->Create(type, modDto.get());
+ ArchiveBuildDirector director(this->m_SettingsService, builder.get());
+ director.Construct(); // must check if cancelled
+ const std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> archives = builder->getArchives();
+ for (const auto& archive : archives) {
+ if (archive) {
+ const QFileInfo fileInfo(this->m_ArchiveNameService->GetArchiveFullPath(type, modDto.get()));
+ bool res = this->m_ArchiveAutoService->CreateBSA(
+ archive.get(), fileInfo.absoluteFilePath(), type,
+ modDto->Directory(), modDto->NexusId());
+ if (res) {
+ createdArchives.append(fileInfo.completeBaseName());
+ }
+ }
+ }
+ }
+
+ if (!createdArchives.isEmpty()) {
+ QMessageBox::information(nullptr, "",
+ QObject::tr("Created archive(s):") + "\n" + createdArchives.join(modDto->ArchiveExtension() +",\n") + modDto->ArchiveExtension());
+ this->m_OverrideFileService->CreateOverrideFile(modDto->NexusId(), modDto->Directory(), createdArchives);
+ }
+
+ const std::unique_ptr<IDummyPluginService> pluginService = this->m_DummyPluginServiceFactory->Create();
+ pluginService->CreatePlugin(modDto->Directory(), modDto->ArchiveName());
+
+ if (!modDto->Directory().isEmpty()) {
+ this->m_HideLooseAssetService->HideLooseAssets(modDto->Directory());
+ }
+ }
+}
diff --git a/libs/bsapacker/src/BsaPackerWorker.h b/libs/bsapacker/src/BsaPackerWorker.h
new file mode 100644
index 0000000..1610e8b
--- /dev/null
+++ b/libs/bsapacker/src/BsaPackerWorker.h
@@ -0,0 +1,42 @@
+#ifndef BSAPACKERWORKER_H
+#define BSAPACKERWORKER_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/ISettingsService.h>
+#include <bsapacker/IModDtoFactory.h>
+#include <bsapacker/IArchiveBuilderFactory.h>
+#include <bsapacker/IArchiveAutoService.h>
+#include <bsapacker/IDummyPluginServiceFactory.h>
+#include <bsapacker/IHideLooseAssetService.h>
+#include <bsapacker/IArchiveNameService.h>
+#include <bsapacker/IOverrideFileService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT BsaPackerWorker
+ {
+ public:
+ BsaPackerWorker(
+ const ISettingsService* settingsService,
+ const IModDtoFactory* modDtoFactory,
+ const IArchiveBuilderFactory* archiveBuilderFactory,
+ const IArchiveAutoService* archiveAutoService,
+ const IDummyPluginServiceFactory* dummyPluginService,
+ const IHideLooseAssetService* hideLooseAssetService,
+ const IArchiveNameService* archiveNameService,
+ const IOverrideFileService* overrideFileService);
+ void DoWork() const;
+
+ private:
+ const ISettingsService* m_SettingsService = nullptr;
+ const IModDtoFactory* m_ModDtoFactory = nullptr;
+ const IArchiveBuilderFactory* m_ArchiveBuilderFactory = nullptr;
+ const IArchiveAutoService* m_ArchiveAutoService = nullptr;
+ const IDummyPluginServiceFactory* m_DummyPluginServiceFactory = nullptr;
+ const IHideLooseAssetService* m_HideLooseAssetService = nullptr;
+ const IArchiveNameService* m_ArchiveNameService = nullptr;
+ const IOverrideFileService* m_OverrideFileService = nullptr;
+ };
+}
+
+#endif // BSAPACKERWORKER_H
diff --git a/libs/bsapacker/src/CMakeLists.txt b/libs/bsapacker/src/CMakeLists.txt
new file mode 100644
index 0000000..f002b13
--- /dev/null
+++ b/libs/bsapacker/src/CMakeLists.txt
@@ -0,0 +1,36 @@
+cmake_minimum_required(VERSION 3.16)
+
+find_package(Qt6 REQUIRED COMPONENTS Concurrent)
+find_path(BEXT_DI_INCLUDE_DIRS "boost/di.hpp")
+
+file(GLOB bsapacker_SOURCES CONFIGURE_DEPENDS
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.ui
+ ${CMAKE_CURRENT_SOURCE_DIR}/*.qrc
+ ${CMAKE_CURRENT_SOURCE_DIR}/bsapacker/*.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/qlibbsarch/*.h
+)
+
+add_library(bsa_packer SHARED ${bsapacker_SOURCES})
+mo2_configure_plugin(bsa_packer NO_SOURCES WARNINGS OFF)
+
+target_link_libraries(bsa_packer PUBLIC mo2::uibase Qt6::Concurrent libbsarch_OOP)
+target_include_directories(bsa_packer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+if(BEXT_DI_INCLUDE_DIRS)
+ target_include_directories(bsa_packer PUBLIC ${BEXT_DI_INCLUDE_DIRS})
+endif()
+target_compile_definitions(bsa_packer PRIVATE BSAPACKER_LIBRARY)
+
+if(TARGET mo2::bsa_ffi)
+ target_link_libraries(bsa_packer PUBLIC mo2::bsa_ffi)
+endif()
+
+# need to deploy this here as MO2 does not depends on Qt6::Concurrent
+install(FILES $<TARGET_FILE:Qt6::Concurrent> DESTINATION bin/dlls)
+
+# this is done by modorganizer itself now
+#
+# install(FILES $<TARGET_FILE:mo2::libbsarch> DESTINATION bin/dlls)
+
+mo2_install_plugin(bsa_packer)
diff --git a/libs/bsapacker/src/DummyPluginLogic.cpp b/libs/bsapacker/src/DummyPluginLogic.cpp
new file mode 100644
index 0000000..a8e73ff
--- /dev/null
+++ b/libs/bsapacker/src/DummyPluginLogic.cpp
@@ -0,0 +1,58 @@
+#include "DummyPluginLogic.h"
+
+#include <QFileInfo>
+#include <array>
+#include "SettingsService.h"
+
+namespace BsaPacker
+{
+ DummyPluginLogic::DummyPluginLogic(
+ const ISettingsService* settingsService,
+ const IArchiveNameService* archiveNameService)
+ : m_SettingsService(settingsService),
+ m_ArchiveNameService(archiveNameService)
+ {
+ }
+
+ bool DummyPluginLogic::canCreateDummyESP(const QString& fileNameNoExtension, const bsa_archive_type_e type) const
+ {
+ const std::array<QString, 2>& fileList = {
+ fileNameNoExtension + ".esm",
+ fileNameNoExtension + ".esp"
+ };
+ return this->canCreateDummy(fileList, fileNameNoExtension, type);
+ }
+
+ bool DummyPluginLogic::canCreateDummyESL(const QString& fileNameNoExtension, const bsa_archive_type_e type) const
+ {
+ const std::array<QString, 3>& fileList = {
+ fileNameNoExtension + ".esm",
+ fileNameNoExtension + ".esp",
+ fileNameNoExtension + ".esl"
+ };
+ return this->canCreateDummy(fileList, fileNameNoExtension, type);
+ }
+
+ template<std::size_t SIZE>
+ bool DummyPluginLogic::canCreateDummy(const std::array<QString, SIZE>& fileList,
+ const QString& fileNameNoExtension,
+ const bsa_archive_type_e type) const
+ {
+ const QFileInfo archive(fileNameNoExtension + this->m_ArchiveNameService->Infix(type) + this->m_ArchiveNameService->GetFileExtension());
+ if (!(archive.exists() && archive.isFile())) {
+ return false;
+ }
+
+ if (!this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_CREATE_PLUGINS).toBool()) {
+ return false;
+ }
+
+ for (const QString& info : fileList) {
+ const QFileInfo fileInfo(info);
+ if (fileInfo.exists() && fileInfo.isFile()) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/libs/bsapacker/src/DummyPluginLogic.h b/libs/bsapacker/src/DummyPluginLogic.h
new file mode 100644
index 0000000..ee9e893
--- /dev/null
+++ b/libs/bsapacker/src/DummyPluginLogic.h
@@ -0,0 +1,30 @@
+#ifndef DUMMYPLUGINLOGIC_H
+#define DUMMYPLUGINLOGIC_H
+
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/ISettingsService.h>
+#include <bsapacker/IArchiveNameService.h>
+
+namespace BsaPacker
+{
+ class DummyPluginLogic : public IDummyPluginLogic
+ {
+ public:
+ DummyPluginLogic(const ISettingsService* settingsService,
+ const IArchiveNameService* archiveNameService);
+ ~DummyPluginLogic() override = default;
+ [[nodiscard]] bool canCreateDummyESP(const QString& fileNameNoExtension, const bsa_archive_type_e type) const override;
+ [[nodiscard]] bool canCreateDummyESL(const QString& fileNameNoExtension, const bsa_archive_type_e type) const override;
+
+ private:
+ template<std::size_t SIZE>
+ bool canCreateDummy(const std::array<QString, SIZE>& fileList,
+ const QString& fileNameNoExtension,
+ const bsa_archive_type_e type) const;
+
+ const ISettingsService* m_SettingsService = nullptr;
+ const IArchiveNameService* m_ArchiveNameService = nullptr;
+ };
+}
+
+#endif // DUMMYPLUGINLOGIC_H
diff --git a/libs/bsapacker/src/DummyPluginServiceFactory.cpp b/libs/bsapacker/src/DummyPluginServiceFactory.cpp
new file mode 100644
index 0000000..fcd0705
--- /dev/null
+++ b/libs/bsapacker/src/DummyPluginServiceFactory.cpp
@@ -0,0 +1,54 @@
+#include "DummyPluginServiceFactory.h"
+
+#include "NexusId.h"
+
+#include "Fallout3DummyPluginService.h"
+#include "Fallout4DummyPluginService.h"
+#include "NewVegasDummyPluginService.h"
+#include "NullDummyPluginService.h"
+#include "OblivionDummyPluginService.h"
+#include "SkyrimDummyPluginService.h"
+#include "SkyrimSEDummyPluginService.h"
+#include "StarfieldDummyPluginService.h"
+
+#include <QMessageBox>
+
+namespace BsaPacker
+{
+ DummyPluginServiceFactory::DummyPluginServiceFactory(
+ const IModContext* modContext,
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_ModContext(modContext),
+ m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ std::unique_ptr<IDummyPluginService> DummyPluginServiceFactory::Create() const
+ {
+ // violates SRP! Must seperate choosing logic from construction. This class is not a factory, but a strategy!
+ switch (this->m_ModContext->GetNexusId()) {
+ case NexusId::Oblivion:
+ return std::make_unique<OblivionDummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::Fallout3:
+ return std::make_unique<Fallout3DummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::NewVegas:
+ return std::make_unique<NewVegasDummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::Skyrim:
+ case NexusId::Enderal:
+ return std::make_unique<SkyrimDummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::SkyrimSE:
+ case NexusId::EnderalSE:
+ return std::make_unique<SkyrimSEDummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::Fallout4:
+ return std::make_unique<Fallout4DummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::Starfield:
+ return std::make_unique<StarfieldDummyPluginService>(m_FileWriterService, m_DummyPluginLogic);
+ case NexusId::Morrowind:
+ default:
+ break;
+ }
+ return std::make_unique<NullDummyPluginService>();
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/DummyPluginServiceFactory.h b/libs/bsapacker/src/DummyPluginServiceFactory.h
new file mode 100644
index 0000000..219c023
--- /dev/null
+++ b/libs/bsapacker/src/DummyPluginServiceFactory.h
@@ -0,0 +1,29 @@
+#ifndef DUMMYPLUGINSERVICEFACTORY_H
+#define DUMMYPLUGINSERVICEFACTORY_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginServiceFactory.h>
+#include <bsapacker/IFileWriterService.h>
+#include <bsapacker/IModContext.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT DummyPluginServiceFactory : public IDummyPluginServiceFactory
+ {
+ public:
+ explicit DummyPluginServiceFactory(
+ const IModContext* modContext,
+ const IFileWriterService* fileWriterService, // bad! only passing this to another constructor!
+ const IDummyPluginLogic* dummyPluginLogic); // TODO: perhaps remedy with strategy pattern?
+ ~DummyPluginServiceFactory() override = default;
+ [[nodiscard]] std::unique_ptr<IDummyPluginService> Create() const override;
+
+ private:
+ const IModContext* m_ModContext = nullptr;
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+} // namespace BsaPacker
+
+#endif // DUMMYPLUGINSERVICEFACTORY_H
diff --git a/libs/bsapacker/src/Fallout3DummyPluginService.cpp b/libs/bsapacker/src/Fallout3DummyPluginService.cpp
new file mode 100644
index 0000000..db90eeb
--- /dev/null
+++ b/libs/bsapacker/src/Fallout3DummyPluginService.cpp
@@ -0,0 +1,26 @@
+#include "Fallout3DummyPluginService.h"
+
+namespace BsaPacker
+{
+ Fallout3DummyPluginService::Fallout3DummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool Fallout3DummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& fileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESP(fileNameNoExtension, bsa_archive_type_e::baFO3))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(Fallout3DummyPluginService::RAW_FALLOUT3),
+ sizeof(Fallout3DummyPluginService::RAW_FALLOUT3));
+ }
+}
diff --git a/libs/bsapacker/src/Fallout3DummyPluginService.h b/libs/bsapacker/src/Fallout3DummyPluginService.h
new file mode 100644
index 0000000..83103ad
--- /dev/null
+++ b/libs/bsapacker/src/Fallout3DummyPluginService.h
@@ -0,0 +1,41 @@
+#ifndef FALLOUT3DUMMYPLUGINSERVICE_H
+#define FALLOUT3DUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT Fallout3DummyPluginService : public IDummyPluginService
+ {
+ public:
+ Fallout3DummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~Fallout3DummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_FALLOUT3[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x41, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0x48, 0x45, 0x44, 0x52, 0x0C, 0x00, 0xD7, 0xA3,
+ 0x70, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xA6, 0x0E,
+ 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x08, 0x00,
+ 0x44, 0x45, 0x46, 0x41, 0x55, 0x4C, 0x54, 0x00,
+ 0x4D, 0x41, 0x53, 0x54, 0x0D, 0x00, 0x46, 0x61,
+ 0x6C, 0x6C, 0x6F, 0x75, 0x74, 0x33, 0x2E, 0x65,
+ 0x73, 0x6D, 0x00, 0x44, 0x41, 0x54, 0x41, 0x08,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00,
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+#endif // FALLOUT3DUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/Fallout4DummyPluginService.cpp b/libs/bsapacker/src/Fallout4DummyPluginService.cpp
new file mode 100644
index 0000000..844ab21
--- /dev/null
+++ b/libs/bsapacker/src/Fallout4DummyPluginService.cpp
@@ -0,0 +1,26 @@
+#include "Fallout4DummyPluginService.h"
+
+namespace BsaPacker
+{
+ Fallout4DummyPluginService::Fallout4DummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool Fallout4DummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& fileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESL(fileNameNoExtension, bsa_archive_type_e::baFO4) && !this->m_DummyPluginLogic->canCreateDummyESL(fileNameNoExtension, bsa_archive_type_e::baFO4dds))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(Fallout4DummyPluginService::RAW_FALLOUT4),
+ sizeof(Fallout4DummyPluginService::RAW_FALLOUT4));
+ }
+}
diff --git a/libs/bsapacker/src/Fallout4DummyPluginService.h b/libs/bsapacker/src/Fallout4DummyPluginService.h
new file mode 100644
index 0000000..534dd5c
--- /dev/null
+++ b/libs/bsapacker/src/Fallout4DummyPluginService.h
@@ -0,0 +1,34 @@
+#ifndef FALLOUT4DUMMYPLUGINSERVICE_H
+#define FALLOUT4DUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT Fallout4DummyPluginService : public IDummyPluginService
+ {
+ public:
+ Fallout4DummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~Fallout4DummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_FALLOUT4[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x19, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00,
+ 0x48, 0x45, 0x44, 0x52, 0x0C, 0x00, 0x33, 0x33, 0x73, 0x3F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x01, 0x00,
+ 0x00
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+#endif // FALLOUT4DUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/FileWriterService.cpp b/libs/bsapacker/src/FileWriterService.cpp
new file mode 100644
index 0000000..0863ccd
--- /dev/null
+++ b/libs/bsapacker/src/FileWriterService.cpp
@@ -0,0 +1,18 @@
+#include "FileWriterService.h"
+
+#include <fstream>
+
+namespace BsaPacker
+{
+ bool FileWriterService::Write(const std::string& path, const char* payload, const uint32_t size) const
+ {
+ std::ofstream file;
+ file.open(path, std::ios::out | std::ios::binary);
+ if (file.is_open()) {
+ file.write(reinterpret_cast<const char*>(payload), size);
+ file.close();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/libs/bsapacker/src/FileWriterService.h b/libs/bsapacker/src/FileWriterService.h
new file mode 100644
index 0000000..b63b563
--- /dev/null
+++ b/libs/bsapacker/src/FileWriterService.h
@@ -0,0 +1,16 @@
+#ifndef FILEWRITERSERVICE_H
+#define FILEWRITERSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT FileWriterService : public IFileWriterService
+ {
+ public:
+ FileWriterService() = default;
+ bool Write(const std::string& path, const char* payload, const uint32_t size) const override;
+ };
+}
+#endif // FILEWRITERSERVICE_H
diff --git a/libs/bsapacker/src/GeneralArchiveBuilder.cpp b/libs/bsapacker/src/GeneralArchiveBuilder.cpp
new file mode 100644
index 0000000..aaf93c7
--- /dev/null
+++ b/libs/bsapacker/src/GeneralArchiveBuilder.cpp
@@ -0,0 +1,97 @@
+#include <bsapacker/GeneralArchiveBuilder.h>
+
+#include <bsapacker/ArchiveBuilderHelper.h>
+#include <QDirIterator>
+#include <QApplication>
+#include <QDebug>
+
+using namespace libbsarch;
+
+namespace BsaPacker
+{
+ // 2 GiB limit. This does not consider size after compression or share data
+ const qint64 GeneralArchiveBuilder::SIZE_LIMIT = (qint64)1024 * 1024 * 1024 * 2;
+
+ GeneralArchiveBuilder::GeneralArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir, const bsa_archive_type_t& type)
+ : m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir), m_ArchiveType(type)
+ {
+ this->m_Cancelled = false;
+ this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
+ }
+
+ uint32_t GeneralArchiveBuilder::setFiles()
+ {
+ uint32_t incompressibleFiles = 0;
+ uint32_t compressibleFiles = 0;
+ int count = 0;
+ qint64 currentSize = 0;
+ const auto& dirString = (this->m_RootDirectory.path() + '/').toStdWString();
+ const auto& rootDirFiles = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(dirString);
+ qDebug() << "root is: " << m_RootDirectory.path() + '/';
+
+ QDirIterator iterator(this->m_RootDirectory.absolutePath(), QDir::Files, QDirIterator::Subdirectories);
+ while (iterator.hasNext()) {
+ QApplication::processEvents();
+
+ if (this->m_Cancelled) {
+ for (auto& archive : this->m_Archives) {
+ archive.reset();
+ }
+ return 0;
+ }
+
+ const QFileInfo& fileInfo = iterator.nextFileInfo();
+ const QString& filepath = fileInfo.absoluteFilePath();
+ const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath.toStdWString(), rootDirFiles);
+
+ Q_EMIT this->valueChanged(++count);
+ if (ignored) {
+ continue;
+ }
+
+ currentSize += fileInfo.size();
+ if (currentSize > SIZE_LIMIT) {
+ currentSize = fileInfo.size();
+ this->m_Archives.back()->set_compressed(!static_cast<bool>(incompressibleFiles));
+ incompressibleFiles = 0;
+ compressibleFiles = 0;
+ this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
+ this->setShareData(true);
+ }
+
+ this->m_ArchiveBuilderHelper->isIncompressible(filepath.toStdWString()) ? ++incompressibleFiles : ++compressibleFiles;
+ auto fileBlob = disk_blob(
+ dirString,
+ filepath.toStdWString());
+ this->m_Archives.back()->add_file_from_disk(fileBlob);
+ qDebug() << "file is: " << filepath;
+ }
+ this->m_Archives.back()->set_compressed(!static_cast<bool>(incompressibleFiles));
+ return incompressibleFiles + compressibleFiles;
+ }
+
+ void GeneralArchiveBuilder::setShareData(const bool value)
+ {
+ this->m_Archives.back()->set_share_data(value);
+ }
+
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> GeneralArchiveBuilder::getArchives()
+ {
+ return std::move(this->m_Archives);
+ }
+
+ uint32_t GeneralArchiveBuilder::getFileCount() const
+ {
+ return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory.path().toStdWString());
+ }
+
+ QString GeneralArchiveBuilder::getRootPath() const
+ {
+ return this->m_RootDirectory.path();
+ }
+
+ void GeneralArchiveBuilder::cancel()
+ {
+ this->m_Cancelled = true;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/HideLooseAssetService.cpp b/libs/bsapacker/src/HideLooseAssetService.cpp
new file mode 100644
index 0000000..8342e56
--- /dev/null
+++ b/libs/bsapacker/src/HideLooseAssetService.cpp
@@ -0,0 +1,69 @@
+#include "HideLooseAssetService.h"
+
+#include <QDir>
+#include <QDirIterator>
+#include <QString>
+#include <QtConcurrent/QtConcurrentMap>
+
+#include "SettingsService.h"
+
+namespace BsaPacker
+{
+ QString HideLooseAssetService::s_HiddenExt(".mohidden");
+
+ HideLooseAssetService::HideLooseAssetService(const ISettingsService* settingsService)
+ : m_SettingsService(settingsService)
+ {
+ }
+
+ bool HideLooseAssetService::HideLooseAssets(const QDir& modDirectory) const
+ {
+ if (!this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_HIDE_LOOSE_ASSETS).toBool()) {
+ return false;
+ }
+
+ // Only hide loose files that were not excluded when creating the archive (blacklisted), so might still be needed by the mod
+ QStringList blacklistExtensions = this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_BLACKLISTED_FILES).toString().split(';');
+ for (auto& ext : blacklistExtensions) {
+ ext.prepend("*");
+ }
+
+ const QString& absModDir = modDirectory.absolutePath();
+ for (const QString& subDir : modDirectory.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
+ // Hide subdirectories
+ const QString& absPath = absModDir + '/' + subDir;
+ QDir originalDir(absPath);
+ if (originalDir.dirName().endsWith(s_HiddenExt) || originalDir.isEmpty()) {
+ continue;
+ }
+ if (!originalDir.rename(originalDir.absolutePath(), originalDir.absolutePath() + s_HiddenExt)) {
+ qWarning() << "Failed to hide " << originalDir.absolutePath();
+ continue;
+ }
+
+ // Restore files with blacklisted extension to their original directories
+ QDir hiddenDir(originalDir.absolutePath() + s_HiddenExt);
+ QDirIterator iterator(hiddenDir.absolutePath(), blacklistExtensions, QDir::Files, QDirIterator::Subdirectories);
+ while (iterator.hasNext()) {
+ QString hiddenFilePath = iterator.next();
+ QString originalFilePath = originalDir.absoluteFilePath(hiddenDir.relativeFilePath(hiddenFilePath));
+ originalDir.mkpath(originalFilePath.left(originalFilePath.lastIndexOf("/")));
+ if (!originalDir.rename(hiddenFilePath, originalFilePath)) {
+ qWarning() << "Failed to unhide " << hiddenFilePath;
+ }
+ }
+ }
+ return true;
+
+ /*
+ const std::function<void(const QString&)> hideFolder = [&](const QString& subDir)
+ {
+ const QString& absPath = absModDir + '/' + subDir;
+ QDir dir(absPath);
+ if (!dir.dirName().endsWith(".mohidden"))
+ dir.rename(absPath, absPath + ".mohidden");
+ };
+ */
+ //QtConcurrent::blockingMap(modDirectory.entryList(QDir::Dirs | QDir::NoDotAndDotDot), hideFolder);
+ }
+}
diff --git a/libs/bsapacker/src/HideLooseAssetService.h b/libs/bsapacker/src/HideLooseAssetService.h
new file mode 100644
index 0000000..9acb60c
--- /dev/null
+++ b/libs/bsapacker/src/HideLooseAssetService.h
@@ -0,0 +1,23 @@
+#ifndef HIDELOOSEASSETSERVICE_H
+#define HIDELOOSEASSETSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IHideLooseAssetService.h>
+#include <bsapacker/ISettingsService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT HideLooseAssetService : public IHideLooseAssetService
+ {
+ public:
+ HideLooseAssetService(const ISettingsService* settingsService);
+ bool HideLooseAssets(const QDir& modDirectory) const override;
+
+ static QString s_HiddenExt;
+
+ private:
+ const ISettingsService* m_SettingsService = nullptr;
+ };
+}
+
+#endif // HIDELOOSEASSETSERVICE_H
diff --git a/libs/bsapacker/src/ModContext.cpp b/libs/bsapacker/src/ModContext.cpp
new file mode 100644
index 0000000..896df9b
--- /dev/null
+++ b/libs/bsapacker/src/ModContext.cpp
@@ -0,0 +1,66 @@
+#include "ModContext.h"
+
+#include <array>
+#include <functional>
+#include "iplugingame.h"
+#include "imodinterface.h"
+#include "imodlist.h"
+#include "NexusId.h"
+#include <QtConcurrent/QtConcurrentFilter>
+#include <QtConcurrent/QtConcurrentMap>
+
+namespace BsaPacker
+{
+ const QStringList& ModContext::PLUGIN_TYPES = { "*.esm", "*.esp", "*.esl" };
+
+ ModContext::ModContext(MOBase::IOrganizer *moInfo) : m_Organizer(moInfo)
+ {
+ }
+
+ QString ModContext::GetAbsoluteModPath(const QString &modName) const
+ {
+ const MOBase::IModInterface* const mod = m_Organizer->modList()->getMod(modName);
+ return mod->absolutePath();
+ }
+
+ int ModContext::GetNexusId() const
+ {
+ const MOBase::IPluginGame* managedGame = this->m_Organizer->managedGame();
+ int nexusId = managedGame->nexusGameID();
+
+ // Games with no Nexus page have an ID of 0. Use primarySources in that case
+ if (nexusId != 0) {
+ return nexusId;
+ }
+ if (!managedGame->primarySources().isEmpty()) {
+ QString primarySource = managedGame->primarySources().first();
+ if (primarySource == "FalloutNV") { // TTW
+ return NexusId::NewVegas;
+ }
+ }
+ return nexusId;
+ }
+
+ QStringList ModContext::GetPlugins(const QDir& modDirectory) const
+ {
+ const int nexusId = this->GetNexusId();
+ const QString extension = (nexusId == NexusId::Fallout4 || nexusId == NexusId::Starfield) ? ".ba2" : ".bsa";
+ const std::function<QString(QString)> replace_extension = [&](QString fname) {
+ return fname.replace(fname.lastIndexOf('.'), 4, extension);
+ };
+
+ QStringList filenames = QtConcurrent::blockingMapped(modDirectory.entryList(PLUGIN_TYPES, QDir::Files), replace_extension);
+ filenames.removeDuplicates();
+ return filenames << QStringLiteral("<new filename>") + extension;
+ }
+
+ QStringList ModContext::GetValidMods() const
+ {
+ const MOBase::IModList* const list = m_Organizer->modList();
+ const std::function<bool(const QString&)> modStateValid = [&](const QString& mod)
+ {
+ return !mod.endsWith("_separator", Qt::CaseInsensitive) && (list->state(mod) & MOBase::IModList::STATE_VALID);
+ };
+ return QtConcurrent::blockingFiltered(list->allMods(), modStateValid);
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/ModContext.h b/libs/bsapacker/src/ModContext.h
new file mode 100644
index 0000000..43f57ef
--- /dev/null
+++ b/libs/bsapacker/src/ModContext.h
@@ -0,0 +1,25 @@
+#ifndef MOD_CONTEXT_H
+#define MOD_CONTEXT_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IModContext.h>
+#include <imoinfo.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ModContext : public IModContext
+ {
+ public:
+ explicit ModContext(MOBase::IOrganizer* moInfo);
+ [[nodiscard]] QString GetAbsoluteModPath(const QString& modName) const override;
+ [[nodiscard]] int GetNexusId() const override;
+ [[nodiscard]] QStringList GetPlugins(const QDir& modDirectory) const override;
+ [[nodiscard]] QStringList GetValidMods() const override;
+
+ private:
+ const MOBase::IOrganizer* m_Organizer = nullptr;
+ const static QStringList& PLUGIN_TYPES;
+ };
+} // namespace BsaPacker
+
+#endif // MOD_CONTEXT_H
diff --git a/libs/bsapacker/src/ModDto.cpp b/libs/bsapacker/src/ModDto.cpp
new file mode 100644
index 0000000..bf07dd9
--- /dev/null
+++ b/libs/bsapacker/src/ModDto.cpp
@@ -0,0 +1,43 @@
+#include "ModDto.h"
+
+#include <QDir>
+
+namespace BsaPacker
+{
+ ModDto::ModDto(
+ const int nexusId,
+ const QString& modDir,
+ const QString& archiveName,
+ const QString& archiveExtension)
+ : m_NexusId(nexusId),
+ m_Path(modDir),
+ m_ArchiveName(archiveName),
+ m_ArchiveExtension(archiveExtension)
+ {
+ }
+
+ QString ModDto::ArchiveExtension() const
+ {
+ return this->m_ArchiveExtension;
+ }
+
+ QString ModDto::ArchiveName() const
+ {
+ return this->m_ArchiveName;
+ }
+
+ QString ModDto::Directory() const
+ {
+ return this->m_Path.path();
+ }
+
+ QString ModDto::ModForename() const
+ {
+ return this->m_Path.dirName();
+ }
+
+ int ModDto::NexusId() const
+ {
+ return this->m_NexusId;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/ModDto.h b/libs/bsapacker/src/ModDto.h
new file mode 100644
index 0000000..9976065
--- /dev/null
+++ b/libs/bsapacker/src/ModDto.h
@@ -0,0 +1,29 @@
+#ifndef MODMETADTO_H
+#define MODMETADTO_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IModDto.h>
+#include <QDir>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ModDto : public IModDto
+ {
+ public:
+ ModDto(const int nexusId, const QString& modDir, const QString& archiveName, const QString& archiveExtension);
+ ~ModDto() override = default;
+ [[nodiscard]] QString ArchiveExtension() const override;
+ [[nodiscard]] QString ArchiveName() const override;
+ [[nodiscard]] QString Directory() const override;
+ [[nodiscard]] QString ModForename() const override;
+ [[nodiscard]] int NexusId() const override;
+
+ private:
+ const int m_NexusId;
+ const QDir m_Path;
+ const QString m_ArchiveName;
+ const QString m_ArchiveExtension;
+ };
+} // namespace BsaPacker
+
+#endif // MODMETADTO_H
diff --git a/libs/bsapacker/src/ModDtoFactory.cpp b/libs/bsapacker/src/ModDtoFactory.cpp
new file mode 100644
index 0000000..3faed36
--- /dev/null
+++ b/libs/bsapacker/src/ModDtoFactory.cpp
@@ -0,0 +1,90 @@
+#include <bsapacker/ModDtoFactory.h>
+
+#include "ModDto.h"
+#include "NullModDto.h"
+#include "PackerDialog.h"
+#include <QInputDialog>
+#include <QMessageBox>
+
+namespace BsaPacker
+{
+ const uint16_t FALLOUT_4_NEXUS_ID = 1151;
+ const uint16_t STARFIELD_NEXUS_ID = 4187;
+
+ ModDtoFactory::ModDtoFactory(
+ const IModContext* modContext,
+ IPackerDialog* packerDialog) :
+ m_ModContext(modContext),
+ m_PackerDialog(packerDialog)
+ {
+ }
+
+ std::unique_ptr<IModDto> ModDtoFactory::Create() const
+ {
+ this->m_PackerDialog->RefreshModList();
+ int result = m_PackerDialog->Exec();
+ if (result != QDialog::DialogCode::Accepted)
+ {
+ return std::make_unique<NullModDto>();
+ }
+
+ const int nexusId = this->m_ModContext->GetNexusId();
+ const QString& modName = this->m_PackerDialog->SelectedMod();
+ const QString& modDir = this->m_ModContext->GetAbsoluteModPath(modName);
+ const QString& pluginName = this->m_PackerDialog->SelectedPluginItem();
+ const bool needsNewName = this->m_PackerDialog->IsNewFilename();
+ const QString& archiveName = ModDtoFactory::ArchiveNameValidator(modName, pluginName, needsNewName);
+ if (archiveName == nullptr) {
+ return std::make_unique<NullModDto>();
+ }
+
+ const QString& archiveExtension = (nexusId == FALLOUT_4_NEXUS_ID || nexusId == STARFIELD_NEXUS_ID)
+ ? QStringLiteral(".ba2")
+ : QStringLiteral(".bsa");
+
+ return std::make_unique<ModDto>(nexusId, modDir, archiveName, archiveExtension);
+ }
+
+ QString ModDtoFactory::ArchiveNameValidator(
+ const QString& modName,
+ const QString& pluginName,
+ const bool needsNewName)
+ {
+ QString archive_name_base;
+ if (needsNewName) {
+ bool ok = false;
+ const QString& name = QInputDialog::getText(nullptr,
+ QStringLiteral("BSA Packer"),
+ QObject::tr("Archive name (no file extension):"),
+ QLineEdit::Normal,
+ modName,
+ &ok).simplified();
+ if (!ok) {
+ return nullptr;
+ }
+ // Nameless plugins are not loaded in the games I tested. Nameless archives can be loaded in
+ // some games using the SArchiveList INI setting or equivalent. It is simpler to just prevent nameless archives
+ else if (name.isEmpty()) {
+ qWarning("Archive name cannot be empty. Cancelling archive creation.");
+ return nullptr;
+ }
+ archive_name_base = name;
+ } else {
+ archive_name_base = pluginName.chopped(4); // trims the file extension off
+ }
+ return archive_name_base;
+ }
+
+ bool ModDtoFactory::CanOverwriteFile(const QString& filePath,
+ const QString& fileName)
+ {
+ const QString& absoluteFileName = filePath + '/' + fileName;
+ const QFileInfo fileInfo(absoluteFileName);
+ if (!fileInfo.exists() || !fileInfo.isFile()) {
+ return true;
+ }
+
+ const QString& message = QObject::tr("File \"") + absoluteFileName + QObject::tr("\" already exists. Overwrite?");
+ return QMessageBox::question(nullptr, QStringLiteral("BSA Packer"), message, QMessageBox::Ok | QMessageBox::Cancel) != QMessageBox::Cancel;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/NewVegasDummyPluginService.cpp b/libs/bsapacker/src/NewVegasDummyPluginService.cpp
new file mode 100644
index 0000000..d48f4b3
--- /dev/null
+++ b/libs/bsapacker/src/NewVegasDummyPluginService.cpp
@@ -0,0 +1,28 @@
+#include "NewVegasDummyPluginService.h"
+
+#include <QMessageBox>
+
+namespace BsaPacker
+{
+ NewVegasDummyPluginService::NewVegasDummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool NewVegasDummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& absoluteFileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESP(absoluteFileNameNoExtension, bsa_archive_type_e::baFO3))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = absoluteFileNameNoExtension.toStdString() + ".esp";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(NewVegasDummyPluginService::RAW_NEWVEGAS),
+ sizeof(NewVegasDummyPluginService::RAW_NEWVEGAS));
+ }
+}
diff --git a/libs/bsapacker/src/NewVegasDummyPluginService.h b/libs/bsapacker/src/NewVegasDummyPluginService.h
new file mode 100644
index 0000000..69c6be9
--- /dev/null
+++ b/libs/bsapacker/src/NewVegasDummyPluginService.h
@@ -0,0 +1,42 @@
+#ifndef NEWVEGASDUMMYPLUGINSERVICE_H
+#define NEWVEGASDUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT NewVegasDummyPluginService : public IDummyPluginService
+ {
+ public:
+ NewVegasDummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~NewVegasDummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_NEWVEGAS[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x42, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
+ 0x48, 0x45, 0x44, 0x52, 0x0C, 0x00, 0x1F, 0x85,
+ 0xAB, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xDD, 0x0A,
+ 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x08, 0x00,
+ 0x44, 0x45, 0x46, 0x41, 0x55, 0x4C, 0x54, 0x00,
+ 0x4D, 0x41, 0x53, 0x54, 0x0E, 0x00, 0x46, 0x61,
+ 0x6C, 0x6C, 0x6F, 0x75, 0x74, 0x4E, 0x56, 0x2E,
+ 0x65, 0x73, 0x6D, 0x00, 0x44, 0x41, 0x54, 0x41,
+ 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+
+#endif // NEWVEGASDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/NexusId.h b/libs/bsapacker/src/NexusId.h
new file mode 100644
index 0000000..c1971d9
--- /dev/null
+++ b/libs/bsapacker/src/NexusId.h
@@ -0,0 +1,22 @@
+#ifndef NEXUSID_H
+#define NEXUSID_H
+
+namespace BsaPacker
+{
+ enum NexusId
+ {
+ // Skyrim VR, Fallout 4 VR, and TTW don't have Nexus pages so are 0
+ Morrowind = 100,
+ Oblivion = 101,
+ Fallout3 = 120,
+ NewVegas = 130,
+ Skyrim = 110,
+ SkyrimSE = 1704,
+ Fallout4 = 1151,
+ Enderal = 2736,
+ EnderalSE = 3685,
+ Starfield = 4187
+ };
+} // namespace BsaPacker
+
+#endif // NEXUSID_H
diff --git a/libs/bsapacker/src/NullArchiveBuilder.cpp b/libs/bsapacker/src/NullArchiveBuilder.cpp
new file mode 100644
index 0000000..ce2fd8d
--- /dev/null
+++ b/libs/bsapacker/src/NullArchiveBuilder.cpp
@@ -0,0 +1,32 @@
+#include <bsapacker/NullArchiveBuilder.h>
+
+namespace BsaPacker
+{
+ uint32_t NullArchiveBuilder::setFiles()
+ {
+ return 0;
+ }
+
+ void NullArchiveBuilder::setShareData(const bool)
+ {
+ }
+
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> NullArchiveBuilder::getArchives()
+ {
+ return {};
+ }
+
+ uint32_t NullArchiveBuilder::getFileCount() const
+ {
+ return 0;
+ }
+
+ QString NullArchiveBuilder::getRootPath() const
+ {
+ return QString();
+ }
+
+ void NullArchiveBuilder::cancel()
+ {
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/NullDummyPluginService.cpp b/libs/bsapacker/src/NullDummyPluginService.cpp
new file mode 100644
index 0000000..52d8609
--- /dev/null
+++ b/libs/bsapacker/src/NullDummyPluginService.cpp
@@ -0,0 +1,9 @@
+#include "NullDummyPluginService.h"
+
+namespace BsaPacker
+{
+ bool NullDummyPluginService::CreatePlugin([[maybe_unused]] const QString& modPath, [[maybe_unused]] const QString& archiveNameBase) const
+ {
+ return false;
+ }
+}
diff --git a/libs/bsapacker/src/NullDummyPluginService.h b/libs/bsapacker/src/NullDummyPluginService.h
new file mode 100644
index 0000000..c67af02
--- /dev/null
+++ b/libs/bsapacker/src/NullDummyPluginService.h
@@ -0,0 +1,17 @@
+#ifndef NULLDUMMYPLUGINSERVICE_H
+#define NULLDUMMYPLUGINSERVICE_H
+
+#include <bsapacker/IDummyPluginService.h>
+
+namespace BsaPacker
+{
+ class NullDummyPluginService : public IDummyPluginService
+ {
+ public:
+ NullDummyPluginService() = default;
+ ~NullDummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath, const QString& archiveNameBase) const override;
+ };
+}
+
+#endif // NULLDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/NullModDto.cpp b/libs/bsapacker/src/NullModDto.cpp
new file mode 100644
index 0000000..255219b
--- /dev/null
+++ b/libs/bsapacker/src/NullModDto.cpp
@@ -0,0 +1,29 @@
+#include "NullModDto.h"
+
+namespace BsaPacker
+{
+ QString NullModDto::ArchiveExtension() const
+ {
+ return QString();
+ }
+
+ QString NullModDto::ArchiveName() const
+ {
+ return QString();
+ }
+
+ QString NullModDto::Directory() const
+ {
+ return QString();
+ }
+
+ QString NullModDto::ModForename() const
+ {
+ return QString();
+ }
+
+ int NullModDto::NexusId() const
+ {
+ return 0;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/NullModDto.h b/libs/bsapacker/src/NullModDto.h
new file mode 100644
index 0000000..841e792
--- /dev/null
+++ b/libs/bsapacker/src/NullModDto.h
@@ -0,0 +1,20 @@
+#ifndef NULLMODDTO_H
+#define NULLMODDTO_H
+
+#include <bsapacker/IModDto.h>
+#include <QString>
+
+namespace BsaPacker
+{
+ class NullModDto : public IModDto
+ {
+ public:
+ [[nodiscard]] QString ArchiveExtension() const override;
+ [[nodiscard]] QString ArchiveName() const override;
+ [[nodiscard]] QString Directory() const override;
+ [[nodiscard]] QString ModForename() const override;
+ [[nodiscard]] int NexusId() const override;
+ };
+} // namespace BsaPacker
+
+#endif // NULLMODDTO_H
diff --git a/libs/bsapacker/src/OblivionDummyPluginService.cpp b/libs/bsapacker/src/OblivionDummyPluginService.cpp
new file mode 100644
index 0000000..2f771f3
--- /dev/null
+++ b/libs/bsapacker/src/OblivionDummyPluginService.cpp
@@ -0,0 +1,26 @@
+#include "OblivionDummyPluginService.h"
+
+namespace BsaPacker
+{
+ OblivionDummyPluginService::OblivionDummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool BsaPacker::OblivionDummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& fileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESP(fileNameNoExtension, bsa_archive_type_e::baTES4))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(OblivionDummyPluginService::RAW_OBLIVION),
+ sizeof(OblivionDummyPluginService::RAW_OBLIVION));
+ }
+}
diff --git a/libs/bsapacker/src/OblivionDummyPluginService.h b/libs/bsapacker/src/OblivionDummyPluginService.h
new file mode 100644
index 0000000..7c7b40a
--- /dev/null
+++ b/libs/bsapacker/src/OblivionDummyPluginService.h
@@ -0,0 +1,34 @@
+#ifndef OBLIVIONDUMMYPLUGINSERVICE_H
+#define OBLIVIONDUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT OblivionDummyPluginService : public IDummyPluginService
+ {
+ public:
+ OblivionDummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~OblivionDummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_OBLIVION[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x45, 0x44, 0x52,
+ 0x0C, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
+ 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x08, 0x00, 0x44, 0x45, 0x46, 0x41,
+ 0x55, 0x4C, 0x54, 0x00
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+#endif // OBLIVIONDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/OverrideFileService.cpp b/libs/bsapacker/src/OverrideFileService.cpp
new file mode 100644
index 0000000..3e4ebec
--- /dev/null
+++ b/libs/bsapacker/src/OverrideFileService.cpp
@@ -0,0 +1,44 @@
+#include "OverrideFileService.h"
+
+#include <questionboxmemory.h>
+#include <QApplication>
+#include <QDialogButtonBox>
+
+namespace BsaPacker {
+ const uint16_t FALLOUT_3_NEXUS_ID = 120;
+ const uint16_t NEW_VEGAS_NEXUS_ID = 130;
+
+ OverrideFileService::OverrideFileService(
+ const IFileWriterService* fileWriterService)
+ : m_FileWriterService(fileWriterService)
+ {
+ }
+
+ // TODO: Add detection for Command Extender and JIP LN NVSE and warn if missing
+ bool OverrideFileService::CreateOverrideFile(const int nexusId,
+ const QString& modPath,
+ const QStringList& archiveNames) const {
+
+ if (nexusId != FALLOUT_3_NEXUS_ID && nexusId != NEW_VEGAS_NEXUS_ID) {
+ return false;
+ }
+
+ if (MOBase::QuestionBoxMemory::query(QApplication::activeModalWidget(), "BSAPacker", "Create .override file?",
+ "Do you want to create an override file for the archive(s)?",
+ QDialogButtonBox::No | QDialogButtonBox::Yes, QDialogButtonBox::No) & QDialogButtonBox::No) {
+ return false;
+ }
+
+ bool res = true;
+ for (const QString& baseName : archiveNames) {
+ const QString& fileNameNoExtension = modPath + '/' + baseName;
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".override";
+ if (!this->m_FileWriterService->Write(absoluteFileName, nullptr, 0)) {
+ qWarning() << "Failed to create" << absoluteFileName;
+ res = false;
+ }
+ }
+
+ return res;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/OverrideFileService.h b/libs/bsapacker/src/OverrideFileService.h
new file mode 100644
index 0000000..ffcad17
--- /dev/null
+++ b/libs/bsapacker/src/OverrideFileService.h
@@ -0,0 +1,21 @@
+#ifndef OVERRIDEFILESERVICE_H
+#define OVERRIDEFILESERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IFileWriterService.h>
+#include <bsapacker/IOverrideFileService.h>
+
+namespace BsaPacker {
+ class BSAPACKER_EXPORT OverrideFileService : public IOverrideFileService {
+ public:
+ OverrideFileService(const IFileWriterService* fileWriterService);
+ bool CreateOverrideFile(const int nexusId,
+ const QString& modPath,
+ const QStringList& archiveNames) const override;
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ };
+} // namespace BsaPacker
+
+#endif // OVERRIDEFILESERVICE_H
diff --git a/libs/bsapacker/src/PackerDialog.cpp b/libs/bsapacker/src/PackerDialog.cpp
new file mode 100644
index 0000000..cb6bd33
--- /dev/null
+++ b/libs/bsapacker/src/PackerDialog.cpp
@@ -0,0 +1,98 @@
+#include "PackerDialog.h"
+
+#include <QObject>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QStringList>
+
+namespace BsaPacker
+{
+ constexpr uint32_t DIALOG_WIDTH = 240;
+ constexpr uint32_t DIALOG_HEIGHT = 200;
+ constexpr uint32_t COMBO_MIN_WIDTH = 100;
+ constexpr uint32_t LIST_MIN_HEIGHT = 100;
+
+ PackerDialog::PackerDialog(const IModContext* modContext) :
+ m_ModContext(modContext),
+ buttonsOkCancelMod(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
+ {
+ QPushButton* ok = this->buttonsOkCancelMod.button(QDialogButtonBox::Ok);
+ ok->setEnabled(false);
+
+ this->labelChooseMod.setText(QObject::tr("Choose a mod to pack:"));
+ this->labelChooseName.setText(QObject::tr("Choose a name for the packed archive:"));
+
+ this->resize(DIALOG_WIDTH, DIALOG_HEIGHT);
+ this->setWindowTitle(QObject::tr("BSA Packer"));
+
+ this->comboModList.setMinimumWidth(COMBO_MIN_WIDTH);
+
+ this->listArchiveNames.setMinimumHeight(LIST_MIN_HEIGHT);
+
+ this->layoutHorizontal.addStretch(1);
+ this->layoutHorizontal.addWidget(&buttonsOkCancelMod);
+
+ this->layoutVertical.addWidget(&labelChooseMod);
+ this->layoutVertical.addWidget(&comboModList);
+ this->layoutVertical.addWidget(&labelChooseName);
+ this->layoutVertical.addWidget(&listArchiveNames);
+ this->layoutVertical.addLayout(&layoutHorizontal);
+
+ this->setLayout(&layoutVertical);
+
+ QObject::connect(&listArchiveNames, &QListWidget::itemSelectionChanged, this, &PackerDialog::RefreshOkButton, Qt::QueuedConnection);
+ QObject::connect(&listArchiveNames, qOverload<QListWidgetItem*>(&QListWidget::itemDoubleClicked), this, &QDialog::accept);
+ QObject::connect(&buttonsOkCancelMod, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ QObject::connect(&buttonsOkCancelMod, &QDialogButtonBox::rejected, this, &QDialog::reject);
+ QObject::connect(&comboModList, qOverload<const QString&>(&QComboBox::currentTextChanged), [this](auto&& text) { this->UpdateNameList(text); });
+ }
+
+ bool PackerDialog::IsNewFilename() const
+ {
+ const int currentIndex = this->listArchiveNames.currentIndex().row() + 1;
+ const int count = this->listArchiveNames.count();
+ return currentIndex == count;
+ }
+
+ QString PackerDialog::SelectedMod() const
+ {
+ return this->comboModList.currentText();
+ }
+
+ QString PackerDialog::SelectedPluginItem() const
+ {
+ const QListWidgetItem* const currentItem = this->listArchiveNames.currentItem();
+ return currentItem != nullptr ? currentItem->text() : QString();
+ }
+
+ void PackerDialog::RefreshModList()
+ {
+ const QStringList validMods = this->m_ModContext->GetValidMods();
+ this->comboModList.clear();
+ this->comboModList.addItems(validMods);
+ }
+
+ void PackerDialog::UpdateNameList(const QString& modName)
+ {
+ this->listArchiveNames.clear();
+ const QString& modPath = this->m_ModContext->GetAbsoluteModPath(modName);
+ const QStringList& filenames = this->m_ModContext->GetPlugins(modPath);
+ this->listArchiveNames.addItems(filenames);
+ }
+
+ int PackerDialog::Exec()
+ {
+ return QDialog::exec();
+ }
+
+ void PackerDialog::RefreshSelectedName()
+ {
+ Q_EMIT this->comboModList.currentTextChanged(this->comboModList.currentText());
+ }
+
+ void PackerDialog::RefreshOkButton()
+ {
+ QPushButton* ok = this->buttonsOkCancelMod.button(QDialogButtonBox::Ok);
+ ok->setEnabled(this->listArchiveNames.currentItem() != nullptr);
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/PackerDialog.h b/libs/bsapacker/src/PackerDialog.h
new file mode 100644
index 0000000..843e842
--- /dev/null
+++ b/libs/bsapacker/src/PackerDialog.h
@@ -0,0 +1,48 @@
+#ifndef PACKERDIALOG_H
+#define PACKERDIALOG_H
+
+#include <bsapacker/IPackerDialog.h>
+#include <bsapacker/IModContext.h>
+
+#include <QComboBox>
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+
+
+namespace BsaPacker
+{
+ class PackerDialog : public QDialog, public IPackerDialog
+ {
+ Q_OBJECT
+ public:
+ explicit PackerDialog(const IModContext* modContext);
+ ~PackerDialog() override = default;
+
+ // IPackerDialog interface
+ [[nodiscard]] bool IsNewFilename() const override;
+ void RefreshModList() override;
+ [[nodiscard]] QString SelectedMod() const override;
+ [[nodiscard]] QString SelectedPluginItem() const override;
+ void UpdateNameList(const QString&) override;
+ void RefreshSelectedName() override;
+ int Exec() override;
+
+ public Q_SLOTS:
+ void RefreshOkButton() override;
+
+ private:
+ const IModContext* m_ModContext = nullptr;
+
+ QComboBox comboModList;
+ QLabel labelChooseMod;
+ QLabel labelChooseName;
+ QListWidget listArchiveNames;
+ QVBoxLayout layoutVertical;
+ QHBoxLayout layoutHorizontal;
+ QDialogButtonBox buttonsOkCancelMod;
+ };
+} // namespace BsaPacker
+
+#endif // PACKERDIALOG_H
diff --git a/libs/bsapacker/src/PackerDialogFactory.cpp b/libs/bsapacker/src/PackerDialogFactory.cpp
new file mode 100644
index 0000000..1face6a
--- /dev/null
+++ b/libs/bsapacker/src/PackerDialogFactory.cpp
@@ -0,0 +1,5 @@
+#include "PackerDialogFactory.h"
+
+namespace BsaPacker
+{
+}
diff --git a/libs/bsapacker/src/PackerDialogFactory.h b/libs/bsapacker/src/PackerDialogFactory.h
new file mode 100644
index 0000000..fcc03d8
--- /dev/null
+++ b/libs/bsapacker/src/PackerDialogFactory.h
@@ -0,0 +1,16 @@
+#ifndef PACKERDIALOGFACTORY_H
+#define PACKERDIALOGFACTORY_H
+
+#include <bsapacker/IPackerDialogFactory.h>
+
+namespace BsaPacker
+{
+ class PackerDialogFactory : public IPackerDialogFactory
+ {
+ public:
+ ~PackerDialogFactory() override = default;
+ std::unique_ptr<IPackerDialog> Create() const override;
+ };
+}
+
+#endif // PACKERDIALOGFACTORY_H
diff --git a/libs/bsapacker/src/SettingsService.cpp b/libs/bsapacker/src/SettingsService.cpp
new file mode 100644
index 0000000..6ad8839
--- /dev/null
+++ b/libs/bsapacker/src/SettingsService.cpp
@@ -0,0 +1,28 @@
+#include "SettingsService.h"
+
+namespace BsaPacker
+{
+ const QString& SettingsService::SETTING_HIDE_LOOSE_ASSETS = QStringLiteral("hide_loose_assets");
+ const QString& SettingsService::SETTING_CREATE_PLUGINS = QStringLiteral("create_plugins");
+ const QString& SettingsService::SETTING_BLACKLISTED_FILES = QStringLiteral("blacklisted_files");
+ const QString& SettingsService::SETTING_COMPRESS_ARCHIVES = QStringLiteral("compress_archives");
+ //const QString& SettingsService::SETTING_SPLIT_ARCHIVES = "split_archives";
+
+ const QList<MOBase::PluginSetting>& SettingsService::PluginSettings = {
+ MOBase::PluginSetting(SettingsService::SETTING_HIDE_LOOSE_ASSETS, QObject::tr("After creating the archive, set loose assets to hidden."), false),
+ MOBase::PluginSetting(SettingsService::SETTING_CREATE_PLUGINS, QObject::tr("Create a dummy plugin to load the archive if one does not exist."), false),
+ MOBase::PluginSetting(SettingsService::SETTING_BLACKLISTED_FILES, QObject::tr("Specify a semi-colon separated list of file extensions to ignore when packing."), ".txt;.hkx;.xml;.ini;.bk2"),
+ MOBase::PluginSetting(SettingsService::SETTING_COMPRESS_ARCHIVES, QObject::tr("Compress archives if they do not contain incompressible files. Texture archives for Fallout 4 and Starfield will always be compressed. Morrowind archives will never be compressed."), true)
+ //MOBase::PluginSetting(SettingsService::SETTING_SPLIT_ARCHIVES, QObject::tr("Automatically create multiple archives if they exceed the size limit."), false);
+ };
+
+ SettingsService::SettingsService(MOBase::IOrganizer* organizer)
+ : m_Organizer(organizer)
+ {
+ }
+
+ QVariant SettingsService::GetPluginSetting(const QString& setting) const
+ {
+ return this->m_Organizer->pluginSetting(QStringLiteral("BSA Packer"), setting);
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/SettingsService.h b/libs/bsapacker/src/SettingsService.h
new file mode 100644
index 0000000..42f9299
--- /dev/null
+++ b/libs/bsapacker/src/SettingsService.h
@@ -0,0 +1,32 @@
+#ifndef SETTINGSSERVICE_H
+#define SETTINGSSERVICE_H
+
+#include <bsapacker/ISettingsService.h>
+
+#include <imoinfo.h>
+#include <pluginsetting.h>
+
+namespace BsaPacker
+{
+ class SettingsService : public ISettingsService
+ {
+ public:
+ explicit SettingsService(MOBase::IOrganizer* organizer);
+ ~SettingsService() override = default;
+ [[nodiscard]] QVariant GetPluginSetting(const QString& setting) const override;
+
+ static const QString& SETTING_ENABLED;
+ static const QString& SETTING_HIDE_LOOSE_ASSETS;
+ static const QString& SETTING_CREATE_PLUGINS;
+ static const QString& SETTING_BLACKLISTED_FILES;
+ static const QString& SETTING_COMPRESS_ARCHIVES;
+ //static const QString& SETTING_SPLIT_ARCHIVES;
+
+ static const QList<MOBase::PluginSetting>& PluginSettings;
+
+ private:
+ MOBase::IOrganizer* m_Organizer = nullptr;
+ };
+} // namespace BsaPacker
+
+#endif // SETTINGSSERVICE_H
diff --git a/libs/bsapacker/src/SkyrimDummyPluginService.cpp b/libs/bsapacker/src/SkyrimDummyPluginService.cpp
new file mode 100644
index 0000000..81c606e
--- /dev/null
+++ b/libs/bsapacker/src/SkyrimDummyPluginService.cpp
@@ -0,0 +1,26 @@
+#include "SkyrimDummyPluginService.h"
+
+namespace BsaPacker
+{
+ SkyrimDummyPluginService::SkyrimDummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool SkyrimDummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& fileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESP(fileNameNoExtension, bsa_archive_type_e::baFO3))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(SkyrimDummyPluginService::RAW_SKYRIM),
+ sizeof(SkyrimDummyPluginService::RAW_SKYRIM));
+ }
+}
diff --git a/libs/bsapacker/src/SkyrimDummyPluginService.h b/libs/bsapacker/src/SkyrimDummyPluginService.h
new file mode 100644
index 0000000..2866ef8
--- /dev/null
+++ b/libs/bsapacker/src/SkyrimDummyPluginService.h
@@ -0,0 +1,34 @@
+#ifndef SKYRIMDUMMYPLUGINSERVICE_H
+#define SKYRIMDUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT SkyrimDummyPluginService : public IDummyPluginService
+ {
+ public:
+ SkyrimDummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~SkyrimDummyPluginService() override = default;
+ [[nodiscard]] bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_SKYRIM[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
+ 0x48, 0x45, 0x44, 0x52, 0x0C, 0x00, 0x9A, 0x99, 0xD9, 0x3F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x01, 0x00,
+ 0x00
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+#endif // SKYRIMDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/SkyrimSEDummyPluginService.cpp b/libs/bsapacker/src/SkyrimSEDummyPluginService.cpp
new file mode 100644
index 0000000..4aa4b7a
--- /dev/null
+++ b/libs/bsapacker/src/SkyrimSEDummyPluginService.cpp
@@ -0,0 +1,26 @@
+#include "SkyrimSEDummyPluginService.h"
+
+namespace BsaPacker
+{
+ SkyrimSEDummyPluginService::SkyrimSEDummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool SkyrimSEDummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& fileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESP(fileNameNoExtension, bsa_archive_type_e::baSSE))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(SkyrimSEDummyPluginService::RAW_SKYRIMSE),
+ sizeof(SkyrimSEDummyPluginService::RAW_SKYRIMSE));
+ }
+}
diff --git a/libs/bsapacker/src/SkyrimSEDummyPluginService.h b/libs/bsapacker/src/SkyrimSEDummyPluginService.h
new file mode 100644
index 0000000..dc348a8
--- /dev/null
+++ b/libs/bsapacker/src/SkyrimSEDummyPluginService.h
@@ -0,0 +1,35 @@
+#ifndef SKYRIMSEDUMMYPLUGINSERVICE_H
+#define SKYRIMSEDUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT SkyrimSEDummyPluginService : public IDummyPluginService
+ {
+ public:
+ SkyrimSEDummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~SkyrimSEDummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_SKYRIMSE[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x19, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
+ 0x48, 0x45, 0x44, 0x52, 0x0C, 0x00, 0x9A, 0x99, 0xD9, 0x3F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x01, 0x00,
+ 0x00
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+
+#endif // SKYRIMSEDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/StarfieldDummyPluginService.cpp b/libs/bsapacker/src/StarfieldDummyPluginService.cpp
new file mode 100644
index 0000000..ca3e8e4
--- /dev/null
+++ b/libs/bsapacker/src/StarfieldDummyPluginService.cpp
@@ -0,0 +1,26 @@
+#include "StarfieldDummyPluginService.h"
+
+namespace BsaPacker
+{
+ StarfieldDummyPluginService::StarfieldDummyPluginService(
+ const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic)
+ : m_FileWriterService(fileWriterService),
+ m_DummyPluginLogic(dummyPluginLogic)
+ {
+ }
+
+ bool StarfieldDummyPluginService::CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const
+ {
+ const QString& fileNameNoExtension = modPath + '/' + archiveNameBase;
+ if (!this->m_DummyPluginLogic->canCreateDummyESL(fileNameNoExtension, bsa_archive_type_e::baSF) && !this->m_DummyPluginLogic->canCreateDummyESL(fileNameNoExtension, bsa_archive_type_e::baSFdds))
+ {
+ return false;
+ }
+ const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esm";
+ return this->m_FileWriterService->Write(absoluteFileName,
+ reinterpret_cast<const char*>(StarfieldDummyPluginService::RAW_STARFIELD),
+ sizeof(StarfieldDummyPluginService::RAW_STARFIELD));
+ }
+}
diff --git a/libs/bsapacker/src/StarfieldDummyPluginService.h b/libs/bsapacker/src/StarfieldDummyPluginService.h
new file mode 100644
index 0000000..a7776f3
--- /dev/null
+++ b/libs/bsapacker/src/StarfieldDummyPluginService.h
@@ -0,0 +1,34 @@
+#ifndef STARFIELDDUMMYPLUGINSERVICE_H
+#define STARFIELDDUMMYPLUGINSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IDummyPluginLogic.h>
+#include <bsapacker/IDummyPluginService.h>
+#include <bsapacker/IFileWriterService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT StarfieldDummyPluginService : public IDummyPluginService
+ {
+ public:
+ StarfieldDummyPluginService(const IFileWriterService* fileWriterService,
+ const IDummyPluginLogic* dummyPluginLogic);
+ ~StarfieldDummyPluginService() override = default;
+ bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const override;
+
+ static constexpr unsigned char RAW_STARFIELD[] = {
+ 0x54, 0x45, 0x53, 0x34, 0x19, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x02, 0x00, 0x00,
+ 0x48, 0x45, 0x44, 0x52, 0x0C, 0x00, 0x8F, 0xC2, 0x75, 0x3F, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x01, 0x00,
+ 0x00
+ };
+
+ private:
+ const IFileWriterService* m_FileWriterService = nullptr;
+ const IDummyPluginLogic* m_DummyPluginLogic = nullptr;
+ };
+}
+
+#endif // STARFIELDDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/TextureArchiveBuilder.cpp b/libs/bsapacker/src/TextureArchiveBuilder.cpp
new file mode 100644
index 0000000..123a48d
--- /dev/null
+++ b/libs/bsapacker/src/TextureArchiveBuilder.cpp
@@ -0,0 +1,137 @@
+#include <bsapacker/TextureArchiveBuilder.h>
+
+#include <bsapacker/ArchiveBuilderHelper.h>
+#include <QDirIterator>
+#include <QApplication>
+#include <QDebug>
+
+#include <cstdint>
+#include <filesystem>
+#include <fstream>
+
+using namespace libbsarch;
+
+namespace BsaPacker
+{
+ // 4 GiB limit for the Fallout 4 Creation Kit. The game itself has no limit so could make an optional setting
+ // This does not consider size after compression or share data
+ const qint64 TextureArchiveBuilder::SIZE_LIMIT = (qint64)1024 * 1024 * 1024 * 4;
+
+ TextureArchiveBuilder::TextureArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir, const bsa_archive_type_t& type)
+ : m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir), m_ArchiveType(type)
+ {
+ this->m_Cancelled = false;
+ this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
+ }
+
+ uint32_t TextureArchiveBuilder::setFiles()
+ {
+ uint32_t compressibleFiles = 0;
+ int count = 0;
+ qint64 currentSize = 0;
+ const auto& dirString = (this->m_RootDirectory.path() + '/').toStdWString();
+ const auto& rootDirFiles = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(dirString);
+ qDebug() << "root is: " << m_RootDirectory.path() + '/';
+
+ QDirIterator iterator(this->m_RootDirectory.absolutePath(), QDir::Files, QDirIterator::Subdirectories);
+ while (iterator.hasNext()) {
+ QApplication::processEvents();
+
+ if (this->m_Cancelled) {
+ for (auto& archive : this->m_Archives) {
+ archive.reset();
+ }
+ return 0;
+ }
+
+ const QFileInfo& fileInfo = iterator.nextFileInfo();
+ const QString& filepath = fileInfo.absoluteFilePath();
+ const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath.toStdWString(), rootDirFiles);
+
+ Q_EMIT this->valueChanged(++count);
+ if (ignored || !filepath.endsWith(".dds", Qt::CaseInsensitive)) {
+ continue;
+ }
+
+ currentSize += fileInfo.size();
+ if (currentSize > SIZE_LIMIT) {
+ currentSize = fileInfo.size();
+ this->m_Archives.back()->set_compressed(true);
+ this->m_Archives.back()->set_dds_callback(TextureArchiveBuilder::DDSCallback, this->getRootPath().toStdWString());
+ compressibleFiles = 0;
+ this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
+ this->setShareData(true);
+ }
+
+ ++compressibleFiles;
+ auto fileBlob = disk_blob(
+ dirString,
+ filepath.toStdWString());
+ this->m_Archives.back()->add_file_from_disk(fileBlob);
+ qDebug() << "file is: " << filepath;
+ }
+ this->m_Archives.back()->set_compressed(true);
+ this->m_Archives.back()->set_dds_callback(TextureArchiveBuilder::DDSCallback, this->getRootPath().toStdWString());
+ return compressibleFiles;
+ }
+
+ void TextureArchiveBuilder::setShareData(const bool value)
+ {
+ this->m_Archives.back()->set_share_data(value);
+ }
+
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> TextureArchiveBuilder::getArchives()
+ {
+ return std::move(this->m_Archives);
+ }
+
+ uint32_t TextureArchiveBuilder::getFileCount() const
+ {
+ return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory.path().toStdWString());
+ }
+
+ QString TextureArchiveBuilder::getRootPath() const
+ {
+ return this->m_RootDirectory.path();
+ }
+
+ void TextureArchiveBuilder::cancel()
+ {
+ this->m_Cancelled = true;
+ }
+
+ void TextureArchiveBuilder::DDSCallback(bsa_archive_t, const wchar_t* file_path, bsa_dds_info_t* dds_info, void* context)
+ {
+ const auto& path = *static_cast<std::wstring*>(context) + L'/' + std::wstring(file_path);
+ std::ifstream file(std::filesystem::path(path), std::ios::binary);
+ if (!file) {
+ throw std::runtime_error("Failed to open DDS");
+ }
+
+ uint8_t header[128] = {};
+ file.read(reinterpret_cast<char*>(header), sizeof(header));
+ if (file.gcount() != static_cast<std::streamsize>(sizeof(header))) {
+ throw std::runtime_error("Invalid DDS header");
+ }
+
+ if (!(header[0] == 'D' && header[1] == 'D' && header[2] == 'S' && header[3] == ' ')) {
+ throw std::runtime_error("Not a DDS file");
+ }
+
+ auto le32 = [](const uint8_t* p) -> uint32_t {
+ return static_cast<uint32_t>(p[0]) |
+ (static_cast<uint32_t>(p[1]) << 8) |
+ (static_cast<uint32_t>(p[2]) << 16) |
+ (static_cast<uint32_t>(p[3]) << 24);
+ };
+
+ // DDS_HEADER starts at byte 4.
+ const uint32_t height = le32(&header[12]); // 4 + 8
+ const uint32_t width = le32(&header[16]); // 4 + 12
+ const uint32_t mips = le32(&header[28]); // 4 + 24
+
+ dds_info->width = width;
+ dds_info->height = height;
+ dds_info->mipmaps = mips > 0 ? mips : 1;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/TexturelessArchiveBuilder.cpp b/libs/bsapacker/src/TexturelessArchiveBuilder.cpp
new file mode 100644
index 0000000..586a489
--- /dev/null
+++ b/libs/bsapacker/src/TexturelessArchiveBuilder.cpp
@@ -0,0 +1,98 @@
+#include <bsapacker/TexturelessArchiveBuilder.h>
+
+#include <bsapacker/ArchiveBuilderHelper.h>
+#include <QDirIterator>
+#include <QApplication>
+#include <QDebug>
+
+using namespace libbsarch;
+
+namespace BsaPacker
+{
+ // 4 GiB limit for the Fallout 4 Creation Kit. The game itself has no limit so could make an optional setting
+ // This does not consider size after compression or share data
+ const qint64 TexturelessArchiveBuilder::SIZE_LIMIT = (qint64)1024 * 1024 * 1024 * 4;
+
+ TexturelessArchiveBuilder::TexturelessArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir, const bsa_archive_type_t& type)
+ : m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir), m_ArchiveType(type)
+ {
+ this->m_Cancelled = false;
+ this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
+ }
+
+ uint32_t TexturelessArchiveBuilder::setFiles()
+ {
+ uint32_t incompressibleFiles = 0;
+ uint32_t compressibleFiles = 0;
+ int count = 0;
+ qint64 currentSize = 0;
+ const auto& dirString = (this->m_RootDirectory.path() + '/').toStdWString();
+ const auto& rootDirFiles = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(dirString);
+ qDebug() << "root is: " << m_RootDirectory.path() + '/';
+
+ QDirIterator iterator(this->m_RootDirectory.absolutePath(), QDir::Files, QDirIterator::Subdirectories);
+ while (iterator.hasNext()) {
+ QApplication::processEvents();
+
+ if (this->m_Cancelled) {
+ for (auto& archive : this->m_Archives) {
+ archive.reset();
+ }
+ return 0;
+ }
+
+ const QFileInfo& fileInfo = iterator.nextFileInfo();
+ const QString& filepath = fileInfo.absoluteFilePath();
+ const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath.toStdWString(), rootDirFiles);
+
+ Q_EMIT this->valueChanged(++count);
+ if (ignored || filepath.endsWith(".dds", Qt::CaseInsensitive)) {
+ continue;
+ }
+
+ currentSize += fileInfo.size();
+ if (currentSize > SIZE_LIMIT) {
+ currentSize = fileInfo.size();
+ this->m_Archives.back()->set_compressed(!static_cast<bool>(incompressibleFiles));
+ incompressibleFiles = 0;
+ compressibleFiles = 0;
+ this->m_Archives.emplace_back(std::make_unique<libbsarch::bs_archive_auto>(this->m_ArchiveType));
+ this->setShareData(true);
+ }
+
+ this->m_ArchiveBuilderHelper->isIncompressible(filepath.toStdWString()) ? ++incompressibleFiles : ++compressibleFiles;
+ auto fileBlob = disk_blob(
+ dirString,
+ filepath.toStdWString());
+ this->m_Archives.back()->add_file_from_disk(fileBlob);
+ qDebug() << "file is: " << filepath;
+ }
+ this->m_Archives.back()->set_compressed(!static_cast<bool>(incompressibleFiles));
+ return incompressibleFiles + compressibleFiles;
+ }
+
+ void TexturelessArchiveBuilder::setShareData(const bool value)
+ {
+ this->m_Archives.back()->set_share_data(value);
+ }
+
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> TexturelessArchiveBuilder::getArchives()
+ {
+ return std::move(this->m_Archives);
+ }
+
+ uint32_t TexturelessArchiveBuilder::getFileCount() const
+ {
+ return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory.path().toStdWString());
+ }
+
+ QString TexturelessArchiveBuilder::getRootPath() const
+ {
+ return this->m_RootDirectory.path();
+ }
+
+ void TexturelessArchiveBuilder::cancel()
+ {
+ this->m_Cancelled = true;
+ }
+} // namespace BsaPacker
diff --git a/libs/bsapacker/src/bsa_packer.json b/libs/bsapacker/src/bsa_packer.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/libs/bsapacker/src/bsa_packer.json
@@ -0,0 +1 @@
+{}
diff --git a/libs/bsapacker/src/bsa_packer_en.ts b/libs/bsapacker/src/bsa_packer_en.ts
new file mode 100644
index 0000000..138987e
--- /dev/null
+++ b/libs/bsapacker/src/bsa_packer_en.ts
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+ <name>BsaPacker::Bsa_Packer</name>
+ <message>
+ <location filename="BsaPacker.cpp" line="47"/>
+ <location filename="BsaPacker.cpp" line="62"/>
+ <source>Transform loose files into a Bethesda Softworks Archive file (.bsa/.ba2).</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="BsaPacker.cpp" line="72"/>
+ <source>BSA Packer</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="ModDtoFactory.cpp" line="58"/>
+ <source>Archive name (no file extension):</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="ModDtoFactory.cpp" line="87"/>
+ <source>File &quot;</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="ModDtoFactory.cpp" line="87"/>
+ <source>&quot; already exists. Overwrite?</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="PackerDialog.cpp" line="22"/>
+ <source>Choose a mod to pack:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="PackerDialog.cpp" line="23"/>
+ <source>Choose a name for the packed archive:</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="PackerDialog.cpp" line="26"/>
+ <source>BSA Packer</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SettingsService.cpp" line="12"/>
+ <source>After creating the archive, set loose assets to hidden.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SettingsService.cpp" line="13"/>
+ <source>Create a dummy plugin to load the archive if one does not exist.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SettingsService.cpp" line="14"/>
+ <source>Specify a semi-colon separated list of file extensions to ignore when packing.</source>
+ <oldsource>Specify a semi-colon seperated list of file extensions to ignore when packing.</oldsource>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="SettingsService.cpp" line="15"/>
+ <source>Compress archives if they do not contain incompressible files. Texture archives for Fallout 4 and Starfield will always be compressed. Morrowind archives will never be compressed.</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="ArchiveAutoService.cpp" line="12"/>
+ <source>Writing Archive</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="ArchiveAutoService.cpp" line="15"/>
+ <source>Writing %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="BsaPackerWorker.cpp" line="53"/>
+ <source>Created archive(s):</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/libs/bsapacker/src/bsapacker/ArchiveAutoService.h b/libs/bsapacker/src/bsapacker/ArchiveAutoService.h
new file mode 100644
index 0000000..52cc247
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/ArchiveAutoService.h
@@ -0,0 +1,19 @@
+#ifndef ARCHIVEAUTOSERVICE_H
+#define ARCHIVEAUTOSERVICE_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveAutoService.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ArchiveAutoService : public IArchiveAutoService
+ {
+ public:
+ ArchiveAutoService() = default;
+ bool CreateBSA(libbsarch::bs_archive_auto* archive, const QString& archiveName,
+ bsa_archive_type_e type, const QString& sourceDir,
+ int nexusId) const override;
+ };
+} // namespace BsaPacker
+
+#endif // ARCHIVEAUTOSERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/ArchiveBuildDirector.h b/libs/bsapacker/src/bsapacker/ArchiveBuildDirector.h
new file mode 100644
index 0000000..a939900
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/ArchiveBuildDirector.h
@@ -0,0 +1,27 @@
+#ifndef ARCHIVEBUILDDIRECTOR_H
+#define ARCHIVEBUILDDIRECTOR_H
+
+#include <bsapacker/IArchiveBuilder.h>
+#include <bsapacker/ISettingsService.h>
+
+namespace BsaPacker
+{
+ class ArchiveBuildDirector
+ {
+ public:
+ explicit ArchiveBuildDirector(const ISettingsService* settingsService, IArchiveBuilder* archiveFileBuildService);
+ ~ArchiveBuildDirector() = default;
+ ArchiveBuildDirector(const ArchiveBuildDirector&) = default;
+ ArchiveBuildDirector& operator=(const ArchiveBuildDirector&) = default;
+ ArchiveBuildDirector(ArchiveBuildDirector&&) = default;
+ ArchiveBuildDirector& operator=(ArchiveBuildDirector&&) = default;
+
+ void Construct();
+
+ private:
+ IArchiveBuilder* m_ArchiveFileBuildService = nullptr;
+ const ISettingsService* m_SettingsService = nullptr;
+ };
+} // namespace BsaPacker
+
+#endif // ARCHIVEBUILDDIRECTOR_H
diff --git a/libs/bsapacker/src/bsapacker/ArchiveBuilderFactory.h b/libs/bsapacker/src/bsapacker/ArchiveBuilderFactory.h
new file mode 100644
index 0000000..98ce8e1
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/ArchiveBuilderFactory.h
@@ -0,0 +1,31 @@
+#ifndef ARCHIVEBUILDERFACTORY_H
+#define ARCHIVEBUILDERFACTORY_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveBuilderFactory.h>
+#include <bsapacker/IArchiveBuilder.h>
+#include <bsapacker/IArchiveBuilderHelper.h>
+#include <bsapacker/IModDto.h>
+#include <libbsarch/libbsarch.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ArchiveBuilderFactory : public IArchiveBuilderFactory
+ {
+ public:
+ ArchiveBuilderFactory(const IArchiveBuilderHelper* archiveBuilderHelper);
+ ~ArchiveBuilderFactory() override = default;
+ ArchiveBuilderFactory(const ArchiveBuilderFactory&) = delete;
+ ArchiveBuilderFactory& operator=(const ArchiveBuilderFactory&) = delete;
+ ArchiveBuilderFactory(ArchiveBuilderFactory&&) = delete;
+ ArchiveBuilderFactory& operator=(ArchiveBuilderFactory&&) = delete;
+
+ [[nodiscard]] std::vector<bsa_archive_type_e> GetArchiveTypes(const IModDto* modDto) const override;
+ [[nodiscard]] std::unique_ptr<IArchiveBuilder> Create(bsa_archive_type_e archiveType, const IModDto* modDto) const override;
+
+ private:
+ const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr;
+ };
+} // namespace BsaPacker
+
+#endif // ARCHIVEBUILDERFACTORY_H
diff --git a/libs/bsapacker/src/bsapacker/ArchiveBuilderHelper.h b/libs/bsapacker/src/bsapacker/ArchiveBuilderHelper.h
new file mode 100644
index 0000000..43194d1
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/ArchiveBuilderHelper.h
@@ -0,0 +1,29 @@
+#ifndef ARCHIVEBUILDERHELPER_H
+#define ARCHIVEBUILDERHELPER_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveBuilderHelper.h>
+#include <bsapacker/ISettingsService.h>
+#include <set>
+#include <string>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ArchiveBuilderHelper : public IArchiveBuilderHelper
+ {
+ public:
+ ArchiveBuilderHelper(const ISettingsService* settingsService);
+ [[nodiscard]] bool isFileIgnorable(const std::filesystem::path&, const std::vector<std::filesystem::path::string_type>&) const override;
+ [[nodiscard]] bool isIncompressible(const std::filesystem::path&) const override;
+ [[nodiscard]] bool isExtensionBlacklisted(const std::filesystem::path&) const override;
+ [[nodiscard]] uint32_t getFileCount(const std::filesystem::path&) const override;
+ [[nodiscard]] std::vector<std::filesystem::path::string_type> getRootDirectoryFilenames(const std::filesystem::path&) const override;
+ [[nodiscard]] bool doesPathContainFiles(const std::filesystem::path&, const std::vector<std::filesystem::path::string_type>&) const override;
+
+ private:
+ const static std::set<std::string> INCOMPRESSIBLE_TYPES;
+ const ISettingsService* m_SettingsService = nullptr;
+ };
+} // namespace BsaPacker
+
+#endif // ARCHIVEBUILDERHELPER_H
diff --git a/libs/bsapacker/src/bsapacker/GeneralArchiveBuilder.h b/libs/bsapacker/src/bsapacker/GeneralArchiveBuilder.h
new file mode 100644
index 0000000..3f2d387
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/GeneralArchiveBuilder.h
@@ -0,0 +1,37 @@
+#ifndef GENERALARCHIVEBUILDER_H
+#define GENERALARCHIVEBUILDER_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveBuilder.h>
+#include <bsapacker/IArchiveBuilderHelper.h>
+#include <QDir>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT GeneralArchiveBuilder : public IArchiveBuilder
+ {
+ Q_OBJECT
+ Q_INTERFACES(BsaPacker::IEmitsValueChanged)
+
+ public:
+ GeneralArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir, const bsa_archive_type_t& type);
+ uint32_t setFiles() override;
+ void setShareData(bool value) override;
+ [[nodiscard]] std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> getArchives() override;
+ [[nodiscard]] uint32_t getFileCount() const override;
+ [[nodiscard]] QString getRootPath() const override;
+
+ public Q_SLOTS:
+ void cancel() override;
+
+ private:
+ const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr;
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> m_Archives;
+ const bsa_archive_type_t m_ArchiveType;
+ bool m_Cancelled;
+ QDir m_RootDirectory;
+ const static qint64 SIZE_LIMIT;
+ };
+} // namespace BsaPacker
+
+#endif // GENERALARCHIVEBUILDER_H
diff --git a/libs/bsapacker/src/bsapacker/IArchiveAutoService.h b/libs/bsapacker/src/bsapacker/IArchiveAutoService.h
new file mode 100644
index 0000000..03fba4a
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IArchiveAutoService.h
@@ -0,0 +1,18 @@
+#ifndef IARCHIVEAUTOSERVICE_H
+#define IARCHIVEAUTOSERVICE_H
+
+#include <libbsarch/bs_archive_auto.hpp>
+#include <bsapacker/IModDto.h>
+
+namespace BsaPacker
+{
+ class IArchiveAutoService
+ {
+ public:
+ virtual ~IArchiveAutoService() = default;
+ virtual bool CreateBSA(libbsarch::bs_archive_auto*, const QString&, bsa_archive_type_e,
+ const QString& sourceDir, int nexusId) const = 0;
+ };
+}
+
+#endif // IARCHIVEAUTOSERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/IArchiveBuilder.h b/libs/bsapacker/src/bsapacker/IArchiveBuilder.h
new file mode 100644
index 0000000..6c6ca4d
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IArchiveBuilder.h
@@ -0,0 +1,24 @@
+#ifndef IARCHIVEBUILDER_H
+#define IARCHIVEBUILDER_H
+
+#include <bsapacker/IEmitsValueChanged.h>
+#include <libbsarch/bs_archive_auto.hpp>
+
+namespace BsaPacker
+{
+ class IArchiveBuilder : public IEmitsValueChanged
+ {
+ Q_OBJECT
+ Q_INTERFACES(BsaPacker::IEmitsValueChanged)
+
+ public:
+ virtual ~IArchiveBuilder() = default;
+ virtual uint32_t setFiles() = 0;
+ virtual void setShareData(bool) = 0;
+ [[nodiscard]] virtual std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> getArchives() = 0;
+ [[nodiscard]] virtual uint32_t getFileCount() const = 0;
+ [[nodiscard]] virtual QString getRootPath() const = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IARCHIVEBUILDER_H
diff --git a/libs/bsapacker/src/bsapacker/IArchiveBuilderFactory.h b/libs/bsapacker/src/bsapacker/IArchiveBuilderFactory.h
new file mode 100644
index 0000000..23da46d
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IArchiveBuilderFactory.h
@@ -0,0 +1,18 @@
+#ifndef IARCHIVEBUILDERFACTORY_H
+#define IARCHIVEBUILDERFACTORY_H
+
+#include <bsapacker/IModDto.h>
+#include <bsapacker/IArchiveBuilder.h>
+
+namespace BsaPacker
+{
+ class IArchiveBuilderFactory
+ {
+ public:
+ virtual ~IArchiveBuilderFactory() = default;
+ virtual std::vector<bsa_archive_type_e> GetArchiveTypes(const IModDto* modDto) const = 0;
+ virtual std::unique_ptr<IArchiveBuilder> Create(bsa_archive_type_e archiveType, const IModDto* modDto) const = 0;
+ };
+}
+
+#endif // IARCHIVEBUILDERFACTORY_H
diff --git a/libs/bsapacker/src/bsapacker/IArchiveBuilderHelper.h b/libs/bsapacker/src/bsapacker/IArchiveBuilderHelper.h
new file mode 100644
index 0000000..3d697c5
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IArchiveBuilderHelper.h
@@ -0,0 +1,22 @@
+#ifndef IARCHIVEBUILDERHELPER_H
+#define IARCHIVEBUILDERHELPER_H
+
+#include <filesystem>
+#include <vector>
+
+namespace BsaPacker
+{
+ class IArchiveBuilderHelper
+ {
+ public:
+ virtual ~IArchiveBuilderHelper() = default;
+ [[nodiscard]] virtual bool isFileIgnorable(const std::filesystem::path&, const std::vector<std::filesystem::path::string_type>&) const = 0;
+ [[nodiscard]] virtual bool isIncompressible(const std::filesystem::path&) const = 0;
+ [[nodiscard]] virtual bool isExtensionBlacklisted(const std::filesystem::path&) const = 0;
+ [[nodiscard]] virtual uint32_t getFileCount(const std::filesystem::path&) const = 0;
+ [[nodiscard]] virtual std::vector<std::filesystem::path::string_type> getRootDirectoryFilenames(const std::filesystem::path&) const = 0;
+ [[nodiscard]] virtual bool doesPathContainFiles(const std::filesystem::path&, const std::vector<std::filesystem::path::string_type>&) const = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IARCHIVEBUILDERHELPER_H
diff --git a/libs/bsapacker/src/bsapacker/IArchiveNameService.h b/libs/bsapacker/src/bsapacker/IArchiveNameService.h
new file mode 100644
index 0000000..3853e28
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IArchiveNameService.h
@@ -0,0 +1,21 @@
+#ifndef IARCHIVENAMESERVICE_H
+#define IARCHIVENAMESERVICE_H
+
+#include <QString>
+#include <qlibbsarch/QLibbsarch.h>
+#include <bsapacker/IModDto.h>
+
+namespace BsaPacker
+{
+ class IArchiveNameService
+ {
+ public:
+ virtual ~IArchiveNameService() = default;
+ virtual QString GetFileExtension() const = 0;
+ virtual QString GetArchiveFullPath(bsa_archive_type_e, const IModDto*) const = 0;
+ virtual QString Infix(bsa_archive_type_e type) const = 0;
+ virtual QString Suffix(const QString& pathNoExt) const = 0;
+ };
+}
+
+#endif // !IARCHIVENAMESERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/IDummyPluginLogic.h b/libs/bsapacker/src/bsapacker/IDummyPluginLogic.h
new file mode 100644
index 0000000..b837d9c
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IDummyPluginLogic.h
@@ -0,0 +1,19 @@
+#ifndef IDUMMYPLUGINLOGIC_H
+#define IDUMMYPLUGINLOGIC_H
+
+#include <QString>
+#include <array>
+#include <qlibbsarch/QLibbsarch.h>
+
+namespace BsaPacker
+{
+ class IDummyPluginLogic
+ {
+ public:
+ virtual ~IDummyPluginLogic() = default;
+ [[nodiscard]] virtual bool canCreateDummyESP(const QString& fileNameNoExtension, const bsa_archive_type_e type) const = 0;
+ [[nodiscard]] virtual bool canCreateDummyESL(const QString& fileNameNoExtension, const bsa_archive_type_e type) const = 0;
+ };
+}
+
+#endif // IDUMMYPLUGINLOGIC_H
diff --git a/libs/bsapacker/src/bsapacker/IDummyPluginService.h b/libs/bsapacker/src/bsapacker/IDummyPluginService.h
new file mode 100644
index 0000000..3421337
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IDummyPluginService.h
@@ -0,0 +1,17 @@
+#ifndef IDUMMYPLUGINSERVICE_H
+#define IDUMMYPLUGINSERVICE_H
+
+#include <QString>
+
+namespace BsaPacker
+{
+ class IDummyPluginService
+ {
+ public:
+ virtual ~IDummyPluginService() = default;
+ virtual bool CreatePlugin(const QString& modPath,
+ const QString& archiveNameBase) const = 0;
+ };
+}
+
+#endif // IDUMMYPLUGINSERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/IDummyPluginServiceFactory.h b/libs/bsapacker/src/bsapacker/IDummyPluginServiceFactory.h
new file mode 100644
index 0000000..157ebb1
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IDummyPluginServiceFactory.h
@@ -0,0 +1,17 @@
+#ifndef IDUMMYPLUGINSERVICEFACTORY_H
+#define IDUMMYPLUGINSERVICEFACTORY_H
+
+#include <bsapacker/IDummyPluginService.h>
+#include <memory>
+
+namespace BsaPacker
+{
+ class IDummyPluginServiceFactory
+ {
+ public:
+ virtual ~IDummyPluginServiceFactory() = default;
+ [[nodiscard]] virtual std::unique_ptr<IDummyPluginService> Create() const = 0;
+ };
+}
+
+#endif // IDUMMYPLUGINSERVICEFACTORY_H
diff --git a/libs/bsapacker/src/bsapacker/IEmitsValueChanged.h b/libs/bsapacker/src/bsapacker/IEmitsValueChanged.h
new file mode 100644
index 0000000..b4f96d0
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IEmitsValueChanged.h
@@ -0,0 +1,25 @@
+#ifndef IEMITSVALUECHANGED_H
+#define IEMITSVALUECHANGED_H
+
+#include <QObject>
+
+namespace BsaPacker
+{
+ class IEmitsValueChanged : public QObject
+ {
+ Q_OBJECT
+
+ public:
+ virtual ~IEmitsValueChanged() = default;
+
+ public Q_SLOTS:
+ virtual void cancel() = 0;
+
+ Q_SIGNALS:
+ void valueChanged(int);
+ };
+} // namespace BsaPacker
+
+Q_DECLARE_INTERFACE(BsaPacker::IEmitsValueChanged, "IEmitsValueChanged");
+
+#endif // IEMITSVALUECHANGED_H
diff --git a/libs/bsapacker/src/bsapacker/IFileWriterService.h b/libs/bsapacker/src/bsapacker/IFileWriterService.h
new file mode 100644
index 0000000..e855549
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IFileWriterService.h
@@ -0,0 +1,16 @@
+#ifndef IFILEWRITERSERVICE_H
+#define IFILEWRITERSERVICE_H
+
+#include <string>
+
+namespace BsaPacker
+{
+ class IFileWriterService
+ {
+ public:
+ virtual ~IFileWriterService() = default;
+ virtual bool Write(const std::string&, const char*, uint32_t) const = 0;
+ };
+}
+
+#endif // IFILEWRITERSERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/IHideLooseAssetService.h b/libs/bsapacker/src/bsapacker/IHideLooseAssetService.h
new file mode 100644
index 0000000..d8a92cd
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IHideLooseAssetService.h
@@ -0,0 +1,16 @@
+#ifndef IHIDELOOSEASSETSERVICE_H
+#define IHIDELOOSEASSETSERVICE_H
+
+#include <QDir>
+
+namespace BsaPacker
+{
+ class IHideLooseAssetService
+ {
+ public:
+ virtual ~IHideLooseAssetService() = default;
+ virtual bool HideLooseAssets(const QDir& modDirectory) const = 0;
+ };
+}
+
+#endif // IHIDELOOSEASSETSERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/IModContext.h b/libs/bsapacker/src/bsapacker/IModContext.h
new file mode 100644
index 0000000..197854c
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IModContext.h
@@ -0,0 +1,20 @@
+#ifndef IMODCONTEXT_H
+#define IMODCONTEXT_H
+
+#include <QDir>
+#include <QStringList>
+
+namespace BsaPacker
+{
+ class IModContext
+ {
+ public:
+ virtual ~IModContext() = default;
+ [[nodiscard]] virtual QString GetAbsoluteModPath(const QString& modName) const = 0;
+ [[nodiscard]] virtual int GetNexusId() const = 0;
+ [[nodiscard]] virtual QStringList GetPlugins(const QDir& modDirectory) const = 0;
+ [[nodiscard]] virtual QStringList GetValidMods() const = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IMODCONTEXT_H
diff --git a/libs/bsapacker/src/bsapacker/IModDto.h b/libs/bsapacker/src/bsapacker/IModDto.h
new file mode 100644
index 0000000..621fc57
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IModDto.h
@@ -0,0 +1,20 @@
+#ifndef IMODDTO_H
+#define IMODDTO_H
+
+#include <QString>
+
+namespace BsaPacker
+{
+ class IModDto
+ {
+ public:
+ virtual ~IModDto() = default;
+ [[nodiscard]] virtual QString ArchiveExtension() const = 0;
+ [[nodiscard]] virtual QString ArchiveName() const = 0;
+ [[nodiscard]] virtual QString Directory() const = 0;
+ [[nodiscard]] virtual QString ModForename() const = 0;
+ [[nodiscard]] virtual int NexusId() const = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IMODDTO_H
diff --git a/libs/bsapacker/src/bsapacker/IModDtoFactory.h b/libs/bsapacker/src/bsapacker/IModDtoFactory.h
new file mode 100644
index 0000000..ea97bbe
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IModDtoFactory.h
@@ -0,0 +1,17 @@
+#ifndef IMODDTOFACTORY_H
+#define IMODDTOFACTORY_H
+
+#include "IModDto.h"
+#include <memory>
+
+namespace BsaPacker
+{
+ class IModDtoFactory
+ {
+ public:
+ virtual ~IModDtoFactory() = default;
+ [[nodiscard]] virtual std::unique_ptr<IModDto> Create() const = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IMODDTOFACTORY_H
diff --git a/libs/bsapacker/src/bsapacker/IOverrideFileService.h b/libs/bsapacker/src/bsapacker/IOverrideFileService.h
new file mode 100644
index 0000000..6e87c48
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IOverrideFileService.h
@@ -0,0 +1,16 @@
+#ifndef IOVERRIDEFILESERVICE_H
+#define IOVERRIDEFILESERVICE_H
+
+#include <QStringList>
+
+namespace BsaPacker {
+ class IOverrideFileService {
+ public:
+ virtual ~IOverrideFileService() = default;
+ virtual bool CreateOverrideFile(const int nexusId,
+ const QString& modPath,
+ const QStringList& archiveNames) const = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IOVERRIDEFILESERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/IPackerDialog.h b/libs/bsapacker/src/bsapacker/IPackerDialog.h
new file mode 100644
index 0000000..b4fc38c
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IPackerDialog.h
@@ -0,0 +1,25 @@
+#ifndef IPACKERDIALOG_H
+#define IPACKERDIALOG_H
+
+#include <QString>
+#include <QDialog>
+#include <QListWidgetItem>
+
+namespace BsaPacker
+{
+ class IPackerDialog
+ {
+ public:
+ virtual ~IPackerDialog() = default;
+ [[nodiscard]] virtual bool IsNewFilename() const = 0;
+ [[nodiscard]] virtual QString SelectedMod() const = 0;
+ virtual void RefreshModList() = 0;
+ [[nodiscard]] virtual QString SelectedPluginItem() const = 0;
+ virtual void UpdateNameList(const QString& modName) = 0;
+ virtual void RefreshSelectedName() = 0;
+ virtual int Exec() = 0;
+ virtual void RefreshOkButton() = 0;
+ };
+} // namespace BsaPacker
+
+#endif // IPACKERDIALOG_H
diff --git a/libs/bsapacker/src/bsapacker/IPackerDialogFactory.h b/libs/bsapacker/src/bsapacker/IPackerDialogFactory.h
new file mode 100644
index 0000000..1123ccc
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/IPackerDialogFactory.h
@@ -0,0 +1,16 @@
+#ifndef IPACKERDIALOGFACTORY_H
+#define IPACKERDIALOGFACTORY_H
+
+#include <bsapacker/IPackerDialog.h>
+
+namespace BsaPacker
+{
+ class IPackerDialogFactory
+ {
+ public:
+ virtual ~IPackerDialogFactory() = default;
+ virtual std::unique_ptr<IPackerDialog> Create() const = 0;
+ };
+}
+
+#endif // IPACKERDIALOGFACTORY_H
diff --git a/libs/bsapacker/src/bsapacker/ISettingsService.h b/libs/bsapacker/src/bsapacker/ISettingsService.h
new file mode 100644
index 0000000..75a03c9
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/ISettingsService.h
@@ -0,0 +1,17 @@
+#ifndef ISETTINGSSERVICE_H
+#define ISETTINGSSERVICE_H
+
+#include <QString>
+#include <QVariant>
+
+namespace BsaPacker
+{
+ class ISettingsService
+ {
+ public:
+ virtual ~ISettingsService() = default;
+ [[nodiscard]] virtual QVariant GetPluginSetting(const QString&) const = 0;
+ };
+}
+
+#endif // ISETTINGSSERVICE_H
diff --git a/libs/bsapacker/src/bsapacker/ModDtoFactory.h b/libs/bsapacker/src/bsapacker/ModDtoFactory.h
new file mode 100644
index 0000000..eb28372
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/ModDtoFactory.h
@@ -0,0 +1,33 @@
+#ifndef MODDTOFACTORY_H
+#define MODDTOFACTORY_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IModDtoFactory.h>
+#include <bsapacker/IModContext.h>
+#include <bsapacker/IModDto.h>
+#include <bsapacker/IPackerDialog.h>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT ModDtoFactory : public IModDtoFactory
+ {
+ public:
+ ModDtoFactory(
+ const IModContext* modContext,
+ IPackerDialog* packerDialog);
+ ~ModDtoFactory() override = default;
+ [[nodiscard]] std::unique_ptr<IModDto> Create() const override;
+ [[nodiscard]] static QString ArchiveNameValidator(
+ const QString& modName,
+ const QString& pluginName,
+ const bool needsNewName);
+ [[nodiscard]] static bool CanOverwriteFile(
+ const QString& filePath,
+ const QString& fileName);
+
+ private:
+ const IModContext* m_ModContext = nullptr;
+ IPackerDialog* m_PackerDialog = nullptr;
+ };
+} // namespace BsaPacker
+#endif // MODDTOFACTORY_H
diff --git a/libs/bsapacker/src/bsapacker/NullArchiveBuilder.h b/libs/bsapacker/src/bsapacker/NullArchiveBuilder.h
new file mode 100644
index 0000000..c69cea7
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/NullArchiveBuilder.h
@@ -0,0 +1,28 @@
+#ifndef NULLARCHIVEBUILDER_H
+#define NULLARCHIVEBUILDER_H
+
+#include <bsapacker/IArchiveBuilder.h>
+
+namespace BsaPacker
+{
+ class NullArchiveBuilder : public IArchiveBuilder
+ {
+ Q_OBJECT
+ Q_INTERFACES(BsaPacker::IEmitsValueChanged)
+
+ public:
+ NullArchiveBuilder() = default;
+ ~NullArchiveBuilder() override = default;
+
+ uint32_t setFiles() override;
+ void setShareData(bool) override;
+ [[nodiscard]] std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> getArchives() override;
+ [[nodiscard]] uint32_t getFileCount() const override;
+ [[nodiscard]] QString getRootPath() const override;
+
+ public Q_SLOTS:
+ void cancel() override;
+ };
+} // namespace BsaPacker
+
+#endif // NULLARCHIVEBUILDER_H
diff --git a/libs/bsapacker/src/bsapacker/TextureArchiveBuilder.h b/libs/bsapacker/src/bsapacker/TextureArchiveBuilder.h
new file mode 100644
index 0000000..c785931
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/TextureArchiveBuilder.h
@@ -0,0 +1,41 @@
+#ifndef TEXTUREARCHIVEBUILDER_H
+#define TEXTUREARCHIVEBUILDER_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveBuilder.h>
+#include <bsapacker/IArchiveBuilderHelper.h>
+#include <QDir>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT TextureArchiveBuilder : public IArchiveBuilder
+ {
+ Q_OBJECT
+ Q_INTERFACES(BsaPacker::IEmitsValueChanged)
+
+ public:
+ TextureArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir&, const bsa_archive_type_t& type);
+ ~TextureArchiveBuilder() override = default;
+
+ uint32_t setFiles() override;
+ void setShareData(bool) override;
+ [[nodiscard]] std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> getArchives() override;
+ [[nodiscard]] uint32_t getFileCount() const override;
+ [[nodiscard]] QString getRootPath() const override;
+
+ public Q_SLOTS:
+ void cancel() override;
+
+ private:
+ const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr;
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> m_Archives;
+ const bsa_archive_type_t m_ArchiveType;
+ bool m_Cancelled;
+ QDir m_RootDirectory;
+ const static qint64 SIZE_LIMIT;
+
+ static void DDSCallback(bsa_archive_t archive, const wchar_t* file_path, bsa_dds_info_t* dds_info, void* context);
+ };
+} // namespace BsaPacker
+
+#endif // TEXTUREARCHIVEBUILDER_H
diff --git a/libs/bsapacker/src/bsapacker/TexturelessArchiveBuilder.h b/libs/bsapacker/src/bsapacker/TexturelessArchiveBuilder.h
new file mode 100644
index 0000000..7c1b6af
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker/TexturelessArchiveBuilder.h
@@ -0,0 +1,39 @@
+#ifndef TEXTURELESSARCHIVEBUILDER_H
+#define TEXTURELESSARCHIVEBUILDER_H
+
+#include "bsapacker_global.h"
+#include <bsapacker/IArchiveBuilder.h>
+#include <bsapacker/IArchiveBuilderHelper.h>
+#include <QDir>
+
+namespace BsaPacker
+{
+ class BSAPACKER_EXPORT TexturelessArchiveBuilder : public IArchiveBuilder
+ {
+ Q_OBJECT
+ Q_INTERFACES(BsaPacker::IEmitsValueChanged)
+
+ public:
+ TexturelessArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir, const bsa_archive_type_t& type);
+ ~TexturelessArchiveBuilder() override = default;
+
+ uint32_t setFiles() override;
+ void setShareData(bool) override;
+ [[nodiscard]] std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> getArchives() override;
+ [[nodiscard]] uint32_t getFileCount() const override;
+ [[nodiscard]] QString getRootPath() const override;
+
+ public Q_SLOTS:
+ void cancel() override;
+
+ private:
+ const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr;
+ std::vector<std::unique_ptr<libbsarch::bs_archive_auto>> m_Archives;
+ const bsa_archive_type_t m_ArchiveType;
+ bool m_Cancelled;
+ QDir m_RootDirectory;
+ const static qint64 SIZE_LIMIT;
+ };
+} // namespace BsaPacker
+
+#endif // TEXTURELESSARCHIVEBUILDER_H
diff --git a/libs/bsapacker/src/bsapacker_global.h b/libs/bsapacker/src/bsapacker_global.h
new file mode 100644
index 0000000..2ad3dc3
--- /dev/null
+++ b/libs/bsapacker/src/bsapacker_global.h
@@ -0,0 +1,7 @@
+#include <QtCore/QtGlobal>
+
+#if defined(BSAPACKER_LIBRARY)
+# define BSAPACKER_EXPORT Q_DECL_EXPORT
+#else
+# define BSAPACKER_EXPORT Q_DECL_IMPORT
+#endif
diff --git a/libs/bsapacker/src/qlibbsarch/BSArchive.h b/libs/bsapacker/src/qlibbsarch/BSArchive.h
new file mode 100644
index 0000000..a159ffd
--- /dev/null
+++ b/libs/bsapacker/src/qlibbsarch/BSArchive.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include <qlibbsarch/QLibbsarch.h>
+#include "BSArchiveEntries.h"
+#include <QDataStream>
+
+class BSArchive
+{
+public:
+ BSArchive();
+ virtual ~BSArchive();
+
+ void open(const QString& archivePath);
+ void close();
+ void create(const QString &archiveName, const bsa_archive_type_e &type, const BSArchiveEntries &entries);
+ void save();
+ void addFileFromDiskRoot(const QString &rootDir, const QString &filename);
+ void addFileFromDiskRoot(const QString &rootDir, const QStringList &files);
+ void addFileFromDisk(const QString& pathInArchive, const QString& filePath);
+ void addFileFromMemory(const QString& filename, const QByteArray& data);
+ void setCompressed(bool value);
+ void setShareData(bool value);
+ bsa_file_record_t findFileRecord (const QString& filename);
+ QByteArray extractFileDataByRecord(bsa_file_record_t record);
+ QByteArray extractFileDataByFilename(const QString& filename);
+ void extract(const QString& filename, const QString& saveAs);
+ QStringList listFiles();
+ void free();
+ void reset();
+
+ void setDDSCallback(bsa_file_dds_info_proc_t fileDDSInfoProc, void *context);
+
+ bsa_archive_t getArchive() const;
+
+private:
+ bool _openedArchive = false;
+ bsa_archive_t _archive;
+};
diff --git a/libs/bsapacker/src/qlibbsarch/BSArchiveAuto.h b/libs/bsapacker/src/qlibbsarch/BSArchiveAuto.h
new file mode 100644
index 0000000..d632fa4
--- /dev/null
+++ b/libs/bsapacker/src/qlibbsarch/BSArchiveAuto.h
@@ -0,0 +1,91 @@
+#pragma once
+
+#include "BSArchive.h"
+#include "BSArchiveEntries.h"
+#include <QMap>
+
+/*!
+ * \brief A convenience class for BSArchive and BSArchiveEntries. Its performance is worse than using these
+ * two classes separately, but it removes the need to manually handle the BSArchiveEntries.
+ */
+class BSArchiveAuto
+{
+public:
+ /*!
+ * \brief Constructor
+ * \param rootDirectory The root directory of the BSA. This directory is the one containing folders such as textures and meshes.
+ */
+ BSArchiveAuto(const QString &_rootDirectory);
+ /*!
+ * \brief Opens an existing archive
+ * \param archivePath The path of the archive
+ */
+ void open(const QString &archivePath);
+ /*!
+ * \brief Creates a BSA in memory
+ * \param archiveName The BSA name
+ * \param type The BSA type
+ */
+ void create(const QString& archiveName, const bsa_archive_type_e& type);
+ /*!
+ * \brief Adds a file from the disk to the BSA. Also adds it to the BSA entries.
+ * \param rootDir The root directory of the file.
+ * \param filename The complete path to the file.
+ */
+ void addFileFromDiskRoot(const QString& filename);
+ /*!
+ * \brief Adds files from the disk to the BSA. Also adds them to the BSA entries.
+ * \param rootDir The root directory of the file. This directory is the one containing folders such as textures and meshes.
+ * \param files A list containing the complete paths to the files.
+ */
+ void addFileFromDiskRoot(const QStringList& files);
+ /*!
+ * \brief Adds file from the memory to the BSA. Also adds it to the BSA entries.
+ * \param filename The name of the file. It connot be a full path, and has to be a relative path.
+ * \param data The file bytes data.
+ */
+ void addFileFromMemory(const QString& filename, const QByteArray& data);
+ /*!
+ * \brief Adds a file from the disk to the BSA. Also adds it to the BSA entries.
+ * \param saveAs The path of the file in the BSA
+ * \param diskPath The path of the file on disk
+ */
+ void addFileFromDisk(const QString& saveAs, const QString& diskPath);
+ /*!
+ * \brief Adds files from the disk to the BSA. Also adds it to the BSA entries.
+ * \param map A map containing "save as" path as keys and disk path as values
+ */
+ void addFileFromDisk(const QMap<QString, QString>& map);
+ /*!
+ * \brief Extracts all files from the BSA to the destination.
+ * \param destinationDirectory The directory where all files will be extracted.
+ * \param overwriteExistingFiles Whether files in archive will overwrite existing loose files or not
+ */
+ void extractAll(const QString &destinationDirectory, const bool &overwriteExistingFiles);
+ /*!
+ * \brief Saves the archive to the disk
+ */
+ void save();
+
+ void setShareData(const bool state);
+ void setCompressed(const bool state);
+
+ void reset();
+ /*!
+ * \brief Required in order to create FO4 DDS archive
+ * \param fileDDSInfoProc The function that will process the DDS file
+ */
+ void setDDSCallback(bsa_file_dds_info_proc_t fileDDSInfoProc, void *context);
+
+private:
+ /*!
+ * \brief The key will store the "save as" path, while the value will hold the disk path
+ */
+ QMap<QString, QString> _filesFromDisk;
+ QStringList _filesFromDiskRoot;
+ QMap<QString, QByteArray> _filesfromMemory;
+ QDir _rootDirectory;
+
+ BSArchive _archive;
+ BSArchiveEntries _entries;
+};
diff --git a/libs/bsapacker/src/qlibbsarch/BSArchiveEntries.h b/libs/bsapacker/src/qlibbsarch/BSArchiveEntries.h
new file mode 100644
index 0000000..3f3cb63
--- /dev/null
+++ b/libs/bsapacker/src/qlibbsarch/BSArchiveEntries.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "QLibbsarch.h"
+
+class BSArchiveEntries
+{
+public:
+ BSArchiveEntries();
+ explicit BSArchiveEntries(const QStringList& QSLEntries);
+ explicit BSArchiveEntries(const bsa_entry_list_t& entries);
+ ~BSArchiveEntries();
+
+ void add(const QString& filepath);
+ void free();
+ void reset();
+ uint32_t count() ;
+ QStringList list();
+
+ bsa_entry_list_t getEntries() const;
+
+private:
+ bsa_entry_list_t _entries;
+};
diff --git a/libs/bsapacker/src/qlibbsarch/QLibbsarch.h b/libs/bsapacker/src/qlibbsarch/QLibbsarch.h
new file mode 100644
index 0000000..7af53f4
--- /dev/null
+++ b/libs/bsapacker/src/qlibbsarch/QLibbsarch.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include <libbsarch/libbsarch.h>
+#include <string>
+#include <stdexcept>
+#include <QDebug>
+#include <QDir>
+#include <QStringList>
+
+namespace QLibBsarch
+{
+ constexpr bool enableDebugLog = true;
+
+#define LOG_LIBBSARCH \
+ if constexpr (QLibBsarch::enableDebugLog) \
+ qDebug() << "[QLIBBSARCH] " << __FUNCTION__ << ' '
+
+#define PREPARE_PATH_LIBBSARCH(qstring) reinterpret_cast<const wchar_t *>(QDir::toNativeSeparators(qstring).utf16())
+
+ inline const std::string wcharToString(const wchar_t *text) { return QString::fromWCharArray(text).toStdString(); }
+
+ inline void checkResult(const bsa_result_message_s &result)
+ {
+ if (result.code == BSA_RESULT_EXCEPTION)
+ {
+ const std::string &error = QLibBsarch::wcharToString(result.text);
+ LOG_LIBBSARCH << QString::fromStdString(error);
+ throw std::runtime_error(error);
+ }
+ }
+
+ inline void checkResult(const bsa_result_message_buffer_s &result)
+ {
+ if (result.message.code == BSA_RESULT_EXCEPTION)
+ {
+ const std::string &error = QLibBsarch::wcharToString(result.message.text);
+ LOG_LIBBSARCH << QString::fromStdString(error);
+ throw std::runtime_error(error);
+ }
+ }
+
+} // namespace QLibBsarch