aboutsummaryrefslogtreecommitdiff
path: root/libs/bsapacker
diff options
context:
space:
mode:
Diffstat (limited to 'libs/bsapacker')
-rw-r--r--libs/bsapacker/.gitattributes7
-rw-r--r--libs/bsapacker/.github/workflows/build.yml20
-rw-r--r--libs/bsapacker/.gitignore8
-rw-r--r--libs/bsapacker/.pre-commit-config.yaml20
-rw-r--r--libs/bsapacker/CMakeLists.txt13
-rw-r--r--libs/bsapacker/CMakePresets.json63
-rw-r--r--libs/bsapacker/LICENSE.md21
-rw-r--r--libs/bsapacker/README.md2
-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
-rw-r--r--libs/bsapacker/tests/ArchiveAutoServiceFacts.cpp26
-rw-r--r--libs/bsapacker/tests/ArchiveBuilderFactoryFacts.cpp242
-rw-r--r--libs/bsapacker/tests/ArchiveBuilderHelperFacts.cpp37
-rw-r--r--libs/bsapacker/tests/ArchiveNameServiceFacts.cpp260
-rw-r--r--libs/bsapacker/tests/BsaPackerFacts.cpp34
-rw-r--r--libs/bsapacker/tests/BsaPackerWorkerFacts.cpp67
-rw-r--r--libs/bsapacker/tests/CMakeLists.txt5
-rw-r--r--libs/bsapacker/tests/DummyPluginServiceFactoryFacts.cpp44
-rw-r--r--libs/bsapacker/tests/Fallout3DummyPluginServiceFacts.cpp39
-rw-r--r--libs/bsapacker/tests/Fallout4DummyPluginServiceFacts.cpp39
-rw-r--r--libs/bsapacker/tests/FileWriterServiceFacts.cpp26
-rw-r--r--libs/bsapacker/tests/GeneralArchiveBuilderFacts.cpp35
-rw-r--r--libs/bsapacker/tests/HideLooseAssetServiceFacts.cpp33
-rw-r--r--libs/bsapacker/tests/MockArchiveAutoService.h10
-rw-r--r--libs/bsapacker/tests/MockArchiveBuilderFactory.h11
-rw-r--r--libs/bsapacker/tests/MockArchiveBuilderHelper.h15
-rw-r--r--libs/bsapacker/tests/MockArchiveNameService.h13
-rw-r--r--libs/bsapacker/tests/MockDummyPluginLogic.h11
-rw-r--r--libs/bsapacker/tests/MockDummyPluginServiceFactory.h10
-rw-r--r--libs/bsapacker/tests/MockFileWriterService.h10
-rw-r--r--libs/bsapacker/tests/MockHideLooseAssetService.h10
-rw-r--r--libs/bsapacker/tests/MockModContext.h13
-rw-r--r--libs/bsapacker/tests/MockModDtoFactory.h10
-rw-r--r--libs/bsapacker/tests/MockOrganizer.h61
-rw-r--r--libs/bsapacker/tests/MockOverrideFileService.h10
-rw-r--r--libs/bsapacker/tests/MockPackerDialog.h17
-rw-r--r--libs/bsapacker/tests/MockSettingsService.h10
-rw-r--r--libs/bsapacker/tests/ModContextFacts.cpp33
-rw-r--r--libs/bsapacker/tests/ModDtoFactoryFacts.cpp53
-rw-r--r--libs/bsapacker/tests/NewVegasDummyPluginServiceFacts.cpp39
-rw-r--r--libs/bsapacker/tests/OblivionDummyPluginServiceFacts.cpp39
-rw-r--r--libs/bsapacker/tests/SkyrimDummyPluginServiceFacts.cpp39
-rw-r--r--libs/bsapacker/tests/SkyrimSEDummyPluginServiceFacts.cpp39
-rw-r--r--libs/bsapacker/tests/TextureArchiveBuilderFacts.cpp35
-rw-r--r--libs/bsapacker/tests/TexturelessArchiveBuilderFacts.cpp35
-rw-r--r--libs/bsapacker/vcpkg.json34
136 files changed, 5256 insertions, 0 deletions
diff --git a/libs/bsapacker/.gitattributes b/libs/bsapacker/.gitattributes
new file mode 100644
index 0000000..f869712
--- /dev/null
+++ b/libs/bsapacker/.gitattributes
@@ -0,0 +1,7 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.cpp text eol=crlf
+*.h text eol=crlf
diff --git a/libs/bsapacker/.github/workflows/build.yml b/libs/bsapacker/.github/workflows/build.yml
new file mode 100644
index 0000000..a1435cc
--- /dev/null
+++ b/libs/bsapacker/.github/workflows/build.yml
@@ -0,0 +1,20 @@
+name: Build BSA Packer
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+env:
+ VCPKG_BINARY_SOURCES: clear;x-azblob,${{ vars.AZ_BLOB_VCPKG_URL }},${{ secrets.AZ_BLOB_SAS }},readwrite
+
+jobs:
+ build:
+ runs-on: windows-2022
+ steps:
+ - name: Build BSA Packer
+ id: build-bsa-packer
+ uses: ModOrganizer2/build-with-mob-action@master
+ with:
+ mo2-dependencies: uibase
diff --git a/libs/bsapacker/.gitignore b/libs/bsapacker/.gitignore
new file mode 100644
index 0000000..d86263e
--- /dev/null
+++ b/libs/bsapacker/.gitignore
@@ -0,0 +1,8 @@
+edit
+CMakeLists.txt.user
+/msbuild.log
+/*std*.log
+/*build
+.vs
+CMakeSettings.json
+.vscode
diff --git a/libs/bsapacker/.pre-commit-config.yaml b/libs/bsapacker/.pre-commit-config.yaml
new file mode 100644
index 0000000..7bd1380
--- /dev/null
+++ b/libs/bsapacker/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-merge-conflict
+ - id: check-case-conflict
+ # - repo: https://github.com/pre-commit/mirrors-clang-format
+ # rev: v19.1.5
+ # hooks:
+ # - id: clang-format
+ # 'types_or': [c++, c]
+
+ci:
+ autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks."
+ autofix_prs: true
+ autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate."
+ autoupdate_schedule: quarterly
+ submodules: false
diff --git a/libs/bsapacker/CMakeLists.txt b/libs/bsapacker/CMakeLists.txt
new file mode 100644
index 0000000..48f6a1e
--- /dev/null
+++ b/libs/bsapacker/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 3.16)
+
+set(MO2_CMAKE_DEPRECATED_UIBASE_INCLUDE ON)
+
+project(bsa_packer)
+
+add_subdirectory(src)
+
+set(BUILD_TESTING ${BUILD_TESTING} CACHE BOOL "build tests for bsapacker plugin")
+if(BUILD_TESTING)
+ enable_testing()
+ add_subdirectory(tests)
+endif()
diff --git a/libs/bsapacker/CMakePresets.json b/libs/bsapacker/CMakePresets.json
new file mode 100644
index 0000000..0c06d1a
--- /dev/null
+++ b/libs/bsapacker/CMakePresets.json
@@ -0,0 +1,63 @@
+{
+ "configurePresets": [
+ {
+ "errors": {
+ "deprecated": true
+ },
+ "hidden": true,
+ "name": "cmake-dev",
+ "warnings": {
+ "deprecated": true,
+ "dev": true
+ }
+ },
+ {
+ "cacheVariables": {
+ "VCPKG_MANIFEST_NO_DEFAULT_FEATURES": {
+ "type": "BOOL",
+ "value": "ON"
+ }
+ },
+ "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
+ "hidden": true,
+ "name": "vcpkg"
+ },
+ {
+ "cacheVariables": {
+ "VCPKG_MANIFEST_FEATURES": {
+ "type": "STRING",
+ "value": "testing"
+ }
+ },
+ "hidden": true,
+ "inherits": ["vcpkg"],
+ "name": "vcpkg-dev"
+ },
+ {
+ "binaryDir": "${sourceDir}/vsbuild",
+ "architecture": {
+ "strategy": "set",
+ "value": "x64"
+ },
+ "cacheVariables": {
+ "CMAKE_CXX_FLAGS": "/EHsc /MP /W4",
+ "VCPKG_TARGET_TRIPLET": {
+ "type": "STRING",
+ "value": "x64-windows-static-md"
+ }
+ },
+ "generator": "Visual Studio 17 2022",
+ "inherits": ["cmake-dev", "vcpkg-dev"],
+ "name": "vs2022-windows",
+ "toolset": "v143"
+ }
+ ],
+ "buildPresets": [
+ {
+ "name": "vs2022-windows",
+ "resolvePackageReferences": "on",
+ "configurePreset": "vs2022-windows"
+ }
+ ],
+ "version": 4
+}
diff --git a/libs/bsapacker/LICENSE.md b/libs/bsapacker/LICENSE.md
new file mode 100644
index 0000000..15a90fc
--- /dev/null
+++ b/libs/bsapacker/LICENSE.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 PurpleFez
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/libs/bsapacker/README.md b/libs/bsapacker/README.md
new file mode 100644
index 0000000..8b5d490
--- /dev/null
+++ b/libs/bsapacker/README.md
@@ -0,0 +1,2 @@
+# bsapacker
+During development, I used Qt 5.12.5 for building and running it in MO2. It also depends upon QtConcurrent, so if you're finding MO explodes make sure you add that to the dll folder.
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
diff --git a/libs/bsapacker/tests/ArchiveAutoServiceFacts.cpp b/libs/bsapacker/tests/ArchiveAutoServiceFacts.cpp
new file mode 100644
index 0000000..8fd37f1
--- /dev/null
+++ b/libs/bsapacker/tests/ArchiveAutoServiceFacts.cpp
@@ -0,0 +1,26 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/ArchiveAutoService.h>
+
+using namespace BsaPacker;
+
+namespace BsaPackerTests
+{
+ class ArchiveAutoServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ }
+
+ void TearDown() override {
+ }
+ };
+
+ TEST_F(ArchiveAutoServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = ArchiveAutoService();
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/ArchiveBuilderFactoryFacts.cpp b/libs/bsapacker/tests/ArchiveBuilderFactoryFacts.cpp
new file mode 100644
index 0000000..352fc00
--- /dev/null
+++ b/libs/bsapacker/tests/ArchiveBuilderFactoryFacts.cpp
@@ -0,0 +1,242 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/ArchiveBuilderFactory.h>
+#include "MockArchiveBuilderHelper.h"
+#include "ModDto.h"
+#include "NexusId.h"
+#include <bsapacker/NullArchiveBuilder.h>
+#include <bsapacker/GeneralArchiveBuilder.h>
+#include <bsapacker/TexturelessArchiveBuilder.h>
+#include <bsapacker/TextureArchiveBuilder.h>
+#include <typeindex>
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class ArchiveBuilderFactoryFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockArchiveBuilderHelper = new NaggyMock<MockArchiveBuilderHelper>();
+ }
+
+ void TearDown() override {
+ delete naggyMockArchiveBuilderHelper;
+ }
+
+ NaggyMock<MockArchiveBuilderHelper>* naggyMockArchiveBuilderHelper;
+ };
+
+ TEST_F(ArchiveBuilderFactoryFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ );
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsMorrowind_ReturnsbaTES3)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::Morrowind, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baTES3, result.at(0));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsOblivion_ReturnsbaTES4)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::Oblivion, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baTES4, result.at(0));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsFallout3_ReturnsbaFO3)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::Fallout3, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baFO3, result.at(0));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsNewVegas_ReturnsbaFO3)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::NewVegas, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baFO3, result.at(0));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsSkyrim_ReturnsbaFO3)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::Skyrim, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baFO3, result.at(0));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsSkyrimSE_ReturnsbaSSE)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::SkyrimSE, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baSSE, result.at(0));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, GetArchiveTypes_WhenNexusIdIsFallout4_ReturnsbaFO4andbaFO4dds)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto dto = ModDto(NexusId::Fallout4, "", "", "");
+
+ // Act
+ auto result = sut.GetArchiveTypes(&dto);
+
+ // Assert
+ EXPECT_EQ(bsa_archive_type_e::baFO4, result.at(0));
+ EXPECT_EQ(bsa_archive_type_e::baFO4dds, result.at(1));
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeNone_ReturnsNullArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baNone;
+
+ // Act
+ auto returned = sut.Create(type, nullptr);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<NullArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(NullArchiveBuilder).name(), typeid(*resultType).name());
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeTES3_ReturnsGeneralArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baTES3;
+ auto dto = ModDto(0, "", "", "");
+
+ // Act
+ auto returned = sut.Create(type, &dto);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<GeneralArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(GeneralArchiveBuilder).name(), typeid(*resultType).name());
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeTES4_ReturnsGeneralArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baTES4;
+ auto dto = ModDto(0, "", "", "");
+
+ // Act
+ auto returned = sut.Create(type, &dto);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<GeneralArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(GeneralArchiveBuilder).name(), typeid(*resultType).name());
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeFO3_ReturnsGeneralArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baFO3;
+ auto dto = ModDto(0, "", "", "");
+
+ // Act
+ auto returned = sut.Create(type, &dto);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<GeneralArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(GeneralArchiveBuilder).name(), typeid(*resultType).name());
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeSSE_ReturnsGeneralArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baSSE;
+ auto dto = ModDto(0, "", "", "");
+
+ // Act
+ auto returned = sut.Create(type, &dto);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<GeneralArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(GeneralArchiveBuilder).name(), typeid(*resultType).name());
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeFO4_ReturnsTexturelessArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baFO4;
+ auto dto = ModDto(0, "", "", "");
+
+ // Act
+ auto returned = sut.Create(type, &dto);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<TexturelessArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(TexturelessArchiveBuilder).name(), typeid(*resultType).name());
+ }
+
+ TEST_F(ArchiveBuilderFactoryFacts, Create_WhenArchiveTypeFO4dds_ReturnsTextureArchiveBuilder)
+ {
+ // Arrange
+ auto sut = ArchiveBuilderFactory(naggyMockArchiveBuilderHelper);
+ auto type = bsa_archive_type_e::baFO4dds;
+ auto dto = ModDto(0, "", "", "");
+
+ // Act
+ auto returned = sut.Create(type, &dto);
+ auto result = returned.get();
+
+ // Assert
+ auto resultType = dynamic_cast<TextureArchiveBuilder*>(result);
+ EXPECT_STREQ(typeid(TextureArchiveBuilder).name(), typeid(*resultType).name());
+ }
+}
diff --git a/libs/bsapacker/tests/ArchiveBuilderHelperFacts.cpp b/libs/bsapacker/tests/ArchiveBuilderHelperFacts.cpp
new file mode 100644
index 0000000..a970c24
--- /dev/null
+++ b/libs/bsapacker/tests/ArchiveBuilderHelperFacts.cpp
@@ -0,0 +1,37 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/ArchiveBuilderHelper.h>
+#include "MockSettingsService.h"
+
+using namespace BsaPacker;
+using std::filesystem::path;
+using ::testing::NaggyMock;
+using ::testing::Return;
+
+namespace BsaPackerTests
+{
+ class ArchiveBuilderHelperFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockSettingsService = new NaggyMock<MockSettingsService>();
+ naggyPath = new NaggyMock<path>();
+ }
+
+ void TearDown() override {
+ delete naggyMockSettingsService;
+ delete naggyPath;
+ }
+
+ NaggyMock<MockSettingsService>* naggyMockSettingsService;
+ NaggyMock<std::filesystem::path>* naggyPath;
+ };
+
+ TEST_F(ArchiveBuilderHelperFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = ArchiveBuilderHelper(naggyMockSettingsService);
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/ArchiveNameServiceFacts.cpp b/libs/bsapacker/tests/ArchiveNameServiceFacts.cpp
new file mode 100644
index 0000000..c79c545
--- /dev/null
+++ b/libs/bsapacker/tests/ArchiveNameServiceFacts.cpp
@@ -0,0 +1,260 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "ArchiveNameService.h"
+#include "MockModContext.h"
+#include "ModDto.h"
+#include "NexusId.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+using ::testing::Eq;
+using ::testing::Return;
+
+namespace BsaPackerTests
+{
+ class ArchiveNameServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockModContext = new NaggyMock<MockModContext>();
+ }
+
+ void TearDown() override {
+ delete naggyMockModContext;
+ }
+
+ NaggyMock<MockModContext>* naggyMockModContext;
+ };
+
+ TEST_F(ArchiveNameServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = ArchiveNameService(
+ naggyMockModContext
+ );
+ );
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsMorrowind_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Morrowind));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsOblivion_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Oblivion));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsFallout3_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Fallout3));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsNewVegas_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::NewVegas));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsSkyrim_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Skyrim));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsSkyrimSE_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::SkyrimSE));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_WhenNexusIdIsFallout4_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Fallout4));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ(".ba2", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetFileExtension_InvalidNexusId_Returnsbsa)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(0));
+
+ // Act
+ auto result = sut.GetFileExtension();
+
+ // Assert
+ EXPECT_EQ("", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetArchiveFullPath_WhenTypeIsbaFO4_ReturnsFullPathWithMainInfix)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ auto type = bsa_archive_type_e::baFO4;
+ auto dto = ModDto(0, "C:/directory", "archive", ".ba2");
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Fallout4));
+
+ // Act
+ auto result = sut.GetArchiveFullPath(type, &dto);
+
+ // Assert
+ EXPECT_EQ("C:/directory/archive - Main.ba2", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetArchiveFullPath_WhenTypeIsbaFO4dds_ReturnsFullPathWithTextureInfix)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ auto type = bsa_archive_type_e::baFO4dds;
+ auto dto = ModDto(0, "C:/directory", "archive", ".ba2");
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Fallout4));
+
+ // Act
+ auto result = sut.GetArchiveFullPath(type, &dto);
+
+ // Assert
+ EXPECT_EQ("C:/directory/archive - Textures.ba2", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetArchiveFullPath_WhenTypeIsbaTES3_ReturnsFullPathWithoutInfix)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ auto type = bsa_archive_type_e::baTES3;
+ auto dto = ModDto(0, "C:/directory", "archive", ".ba2");
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Morrowind));
+
+ // Act
+ auto result = sut.GetArchiveFullPath(type, &dto);
+
+ // Assert
+ EXPECT_EQ("C:/directory/archive.bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetArchiveFullPath_WhenTypeIsbaTES4_ReturnsFullPathWithoutInfix)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ auto type = bsa_archive_type_e::baTES3;
+ auto dto = ModDto(0, "C:/directory", "archive", ".ba2");
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Oblivion));
+
+ // Act
+ auto result = sut.GetArchiveFullPath(type, &dto);
+
+ // Assert
+ EXPECT_EQ("C:/directory/archive.bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetArchiveFullPath_WhenTypeIsbaFO3_ReturnsFullPathWithoutInfix)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ auto type = bsa_archive_type_e::baFO3;
+ auto dto = ModDto(0, "C:/directory", "archive", ".ba2");
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::Fallout3));
+
+ // Act
+ auto result = sut.GetArchiveFullPath(type, &dto);
+
+ // Assert
+ EXPECT_EQ("C:/directory/archive.bsa", result.toStdString());
+ }
+
+ TEST_F(ArchiveNameServiceFacts, GetArchiveFullPath_WhenTypeIsbaSSE_ReturnsFullPathWithoutInfix)
+ {
+ // Arrange
+ auto sut = ArchiveNameService(
+ naggyMockModContext);
+ auto type = bsa_archive_type_e::baSSE;
+ auto dto = ModDto(0, "C:/directory", "archive", ".ba2");
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(NexusId::SkyrimSE));
+
+ // Act
+ auto result = sut.GetArchiveFullPath(type, &dto);
+
+ // Assert
+ EXPECT_EQ("C:/directory/archive.bsa", result.toStdString());
+ }
+}
diff --git a/libs/bsapacker/tests/BsaPackerFacts.cpp b/libs/bsapacker/tests/BsaPackerFacts.cpp
new file mode 100644
index 0000000..d3e8e59
--- /dev/null
+++ b/libs/bsapacker/tests/BsaPackerFacts.cpp
@@ -0,0 +1,34 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "BsaPacker.h"
+#include "MockOrganizer.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+using ::testing::Return;
+using ::testing::Eq;
+
+namespace BsaPackerTests
+{
+ class BsaPackerFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockOrganizer = new NaggyMock<MockOrganizer>();
+ }
+
+ void TearDown() override {
+ delete naggyMockOrganizer;
+ }
+
+ NaggyMock<MockOrganizer>* naggyMockOrganizer;
+ };
+
+ TEST_F(BsaPackerFacts, CtorAndInit_Always_Constructs)
+ {
+ auto result = Bsa_Packer();
+ EXPECT_NO_THROW(result.init(naggyMockOrganizer));
+ }
+
+}
diff --git a/libs/bsapacker/tests/BsaPackerWorkerFacts.cpp b/libs/bsapacker/tests/BsaPackerWorkerFacts.cpp
new file mode 100644
index 0000000..851ae3c
--- /dev/null
+++ b/libs/bsapacker/tests/BsaPackerWorkerFacts.cpp
@@ -0,0 +1,67 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "BsaPackerWorker.h"
+#include "MockSettingsService.h"
+#include "MockModDtoFactory.h"
+#include "MockArchiveBuilderFactory.h"
+#include "MockDummyPluginServiceFactory.h"
+#include "MockArchiveAutoService.h"
+#include "MockHideLooseAssetService.h"
+#include "MockArchiveNameService.h"
+#include "MockOverrideFileService.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class BsaPackerWorkerFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override
+ {
+ naggyMockSettingsService = new NaggyMock<MockSettingsService>();
+ naggyMockModDtoFactory = new NaggyMock<MockModDtoFactory>();
+ naggyMockArchiveBuilderFactory = new NaggyMock<MockArchiveBuilderFactory>();
+ naggyMockArchiveAutoService = new NaggyMock<MockArchiveAutoService>();
+ naggyMockDummyPluginServiceFactory = new NaggyMock<MockDummyPluginServiceFactory>();
+ naggyMockHideLooseAssetService = new NaggyMock<MockHideLooseAssetService>();
+ naggyMockArchiveNameService = new NaggyMock<MockArchiveNameService>();
+ naggyOverrideFileService = new NaggyMock<MockOverrideFileService>();
+ }
+
+ void TearDown() override
+ {
+ delete naggyMockSettingsService;
+ delete naggyMockModDtoFactory;
+ delete naggyMockArchiveBuilderFactory;
+ delete naggyMockArchiveAutoService;
+ delete naggyMockDummyPluginServiceFactory;
+ delete naggyMockHideLooseAssetService;
+ delete naggyMockArchiveNameService;
+ }
+
+ NaggyMock<MockSettingsService> *naggyMockSettingsService;
+ NaggyMock<MockModDtoFactory> *naggyMockModDtoFactory;
+ NaggyMock<MockArchiveBuilderFactory> *naggyMockArchiveBuilderFactory;
+ NaggyMock<MockArchiveAutoService> *naggyMockArchiveAutoService;
+ NaggyMock<MockDummyPluginServiceFactory> *naggyMockDummyPluginServiceFactory;
+ NaggyMock<MockHideLooseAssetService> *naggyMockHideLooseAssetService;
+ NaggyMock<MockArchiveNameService> *naggyMockArchiveNameService;
+ NaggyMock<MockOverrideFileService> *naggyOverrideFileService;
+ };
+
+ TEST_F(BsaPackerWorkerFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(BsaPackerWorker(
+ naggyMockSettingsService,
+ naggyMockModDtoFactory,
+ naggyMockArchiveBuilderFactory,
+ naggyMockArchiveAutoService,
+ naggyMockDummyPluginServiceFactory,
+ naggyMockHideLooseAssetService,
+ naggyMockArchiveNameService,
+ naggyOverrideFileService));
+ }
+}
diff --git a/libs/bsapacker/tests/CMakeLists.txt b/libs/bsapacker/tests/CMakeLists.txt
new file mode 100644
index 0000000..5f482a7
--- /dev/null
+++ b/libs/bsapacker/tests/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.22)
+
+add_executable(bsa_packer_tests)
+mo2_configure_tests(bsa_packer_tests WARNINGS OFF)
+target_link_libraries(bsa_packer_tests PRIVATE bsa_packer)
diff --git a/libs/bsapacker/tests/DummyPluginServiceFactoryFacts.cpp b/libs/bsapacker/tests/DummyPluginServiceFactoryFacts.cpp
new file mode 100644
index 0000000..c662438
--- /dev/null
+++ b/libs/bsapacker/tests/DummyPluginServiceFactoryFacts.cpp
@@ -0,0 +1,44 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "DummyPluginServiceFactory.h"
+#include "MockModContext.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class DummyPluginServiceFactoryFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockModContext = new NaggyMock<MockModContext>();
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockModContext;
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockModContext>* naggyMockModContext;
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(DummyPluginServiceFactoryFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = DummyPluginServiceFactory(
+ naggyMockModContext,
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/Fallout3DummyPluginServiceFacts.cpp b/libs/bsapacker/tests/Fallout3DummyPluginServiceFacts.cpp
new file mode 100644
index 0000000..e485d56
--- /dev/null
+++ b/libs/bsapacker/tests/Fallout3DummyPluginServiceFacts.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "Fallout3DummyPluginService.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class Fallout3DummyPluginServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(Fallout3DummyPluginServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = Fallout3DummyPluginService(
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/Fallout4DummyPluginServiceFacts.cpp b/libs/bsapacker/tests/Fallout4DummyPluginServiceFacts.cpp
new file mode 100644
index 0000000..07d7139
--- /dev/null
+++ b/libs/bsapacker/tests/Fallout4DummyPluginServiceFacts.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "Fallout4DummyPluginService.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class Fallout4DummyPluginServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(Fallout4DummyPluginServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = Fallout4DummyPluginService(
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/FileWriterServiceFacts.cpp b/libs/bsapacker/tests/FileWriterServiceFacts.cpp
new file mode 100644
index 0000000..5038640
--- /dev/null
+++ b/libs/bsapacker/tests/FileWriterServiceFacts.cpp
@@ -0,0 +1,26 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "FileWriterService.h"
+
+using namespace BsaPacker;
+
+namespace BsaPackerTests
+{
+ class FileWriterServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ }
+
+ void TearDown() override {
+ }
+ };
+
+ TEST_F(FileWriterServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = FileWriterService();
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/GeneralArchiveBuilderFacts.cpp b/libs/bsapacker/tests/GeneralArchiveBuilderFacts.cpp
new file mode 100644
index 0000000..2139a44
--- /dev/null
+++ b/libs/bsapacker/tests/GeneralArchiveBuilderFacts.cpp
@@ -0,0 +1,35 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/GeneralArchiveBuilder.h>
+#include "MockArchiveBuilderHelper.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class GeneralArchiveBuilderFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockArchiveBuilderHelper = new NaggyMock<MockArchiveBuilderHelper>();
+ }
+
+ void TearDown() override {
+ delete naggyMockArchiveBuilderHelper;
+ }
+
+ NaggyMock<MockArchiveBuilderHelper>* naggyMockArchiveBuilderHelper;
+ };
+
+ TEST_F(GeneralArchiveBuilderFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = GeneralArchiveBuilder(
+ naggyMockArchiveBuilderHelper,
+ QDir(),
+ baNone);
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/HideLooseAssetServiceFacts.cpp b/libs/bsapacker/tests/HideLooseAssetServiceFacts.cpp
new file mode 100644
index 0000000..c868b8d
--- /dev/null
+++ b/libs/bsapacker/tests/HideLooseAssetServiceFacts.cpp
@@ -0,0 +1,33 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "HideLooseAssetService.h"
+#include "MockSettingsService.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class HideLooseAssetServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockSettingsService = new NaggyMock<MockSettingsService>();
+ }
+
+ void TearDown() override {
+ delete naggyMockSettingsService;
+ }
+
+ NaggyMock<MockSettingsService>* naggyMockSettingsService;
+ };
+
+ TEST_F(HideLooseAssetServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = HideLooseAssetService(
+ naggyMockSettingsService);
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/MockArchiveAutoService.h b/libs/bsapacker/tests/MockArchiveAutoService.h
new file mode 100644
index 0000000..ebbbcfa
--- /dev/null
+++ b/libs/bsapacker/tests/MockArchiveAutoService.h
@@ -0,0 +1,10 @@
+#include <bsapacker/IArchiveAutoService.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockArchiveAutoService : public IArchiveAutoService
+{
+public:
+ MOCK_METHOD(bool, CreateBSA, (libbsarch::bs_archive_auto *, const QString &, bsa_archive_type_e), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockArchiveBuilderFactory.h b/libs/bsapacker/tests/MockArchiveBuilderFactory.h
new file mode 100644
index 0000000..61a7ce4
--- /dev/null
+++ b/libs/bsapacker/tests/MockArchiveBuilderFactory.h
@@ -0,0 +1,11 @@
+#include <bsapacker/IArchiveBuilderFactory.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockArchiveBuilderFactory : public IArchiveBuilderFactory
+{
+public:
+ MOCK_METHOD(std::vector<bsa_archive_type_e>, GetArchiveTypes, (const IModDto*), (const, override));
+ MOCK_METHOD(std::unique_ptr<IArchiveBuilder>, Create, (bsa_archive_type_e, const IModDto*), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockArchiveBuilderHelper.h b/libs/bsapacker/tests/MockArchiveBuilderHelper.h
new file mode 100644
index 0000000..632977e
--- /dev/null
+++ b/libs/bsapacker/tests/MockArchiveBuilderHelper.h
@@ -0,0 +1,15 @@
+#include <bsapacker/IArchiveBuilderHelper.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockArchiveBuilderHelper : public IArchiveBuilderHelper
+{
+public:
+ MOCK_METHOD(bool, isFileIgnorable, (const std::filesystem::path&, const std::vector<std::filesystem::path::string_type>&), (const, override));
+ MOCK_METHOD(bool, isIncompressible, (const std::filesystem::path&), (const, override));
+ MOCK_METHOD(bool, isExtensionBlacklisted, (const std::filesystem::path&), (const, override));
+ MOCK_METHOD(uint32_t, getFileCount, (const std::filesystem::path&), (const, override));
+ MOCK_METHOD(std::vector<std::filesystem::path::string_type>, getRootDirectoryFilenames, (const std::filesystem::path&), (const, override));
+ MOCK_METHOD(bool, doesPathContainFiles, (const std::filesystem::path&, const std::vector<std::filesystem::path::string_type>&), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockArchiveNameService.h b/libs/bsapacker/tests/MockArchiveNameService.h
new file mode 100644
index 0000000..94dd68b
--- /dev/null
+++ b/libs/bsapacker/tests/MockArchiveNameService.h
@@ -0,0 +1,13 @@
+#include <bsapacker/IArchiveNameService.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockArchiveNameService : public IArchiveNameService
+{
+public:
+ MOCK_METHOD(QString, GetFileExtension, (), (const, override));
+ MOCK_METHOD(QString, GetArchiveFullPath, (bsa_archive_type_e, const IModDto *), (const, override));
+ MOCK_METHOD(QString, Infix, (bsa_archive_type_e type), (const, override));
+ MOCK_METHOD(QString, Suffix, (const QString &pathNoExt), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockDummyPluginLogic.h b/libs/bsapacker/tests/MockDummyPluginLogic.h
new file mode 100644
index 0000000..5bdaa50
--- /dev/null
+++ b/libs/bsapacker/tests/MockDummyPluginLogic.h
@@ -0,0 +1,11 @@
+#include <bsapacker/IDummyPluginLogic.h>
+#include "gmock/gmock.h"
+
+using namespace BsaPacker;
+
+class MockDummyPluginLogic : public IDummyPluginLogic
+{
+public:
+ MOCK_METHOD(bool, canCreateDummyESP, (const QString &, const bsa_archive_type_e), (const, override));
+ MOCK_METHOD(bool, canCreateDummyESL, (const QString &, const bsa_archive_type_e), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockDummyPluginServiceFactory.h b/libs/bsapacker/tests/MockDummyPluginServiceFactory.h
new file mode 100644
index 0000000..36ccc29
--- /dev/null
+++ b/libs/bsapacker/tests/MockDummyPluginServiceFactory.h
@@ -0,0 +1,10 @@
+#include <bsapacker/IDummyPluginServiceFactory.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockDummyPluginServiceFactory : public IDummyPluginServiceFactory
+{
+public:
+ MOCK_METHOD(std::unique_ptr<IDummyPluginService>, Create, (), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockFileWriterService.h b/libs/bsapacker/tests/MockFileWriterService.h
new file mode 100644
index 0000000..164ba5a
--- /dev/null
+++ b/libs/bsapacker/tests/MockFileWriterService.h
@@ -0,0 +1,10 @@
+#include <bsapacker/IFileWriterService.h>
+#include "gmock/gmock.h"
+
+using namespace BsaPacker;
+
+class MockFileWriterService : public IFileWriterService
+{
+public:
+ MOCK_METHOD(bool, Write, (const std::string&, const char*, uint32_t), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockHideLooseAssetService.h b/libs/bsapacker/tests/MockHideLooseAssetService.h
new file mode 100644
index 0000000..681534d
--- /dev/null
+++ b/libs/bsapacker/tests/MockHideLooseAssetService.h
@@ -0,0 +1,10 @@
+#include <bsapacker/IHideLooseAssetService.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockHideLooseAssetService : public IHideLooseAssetService
+{
+public:
+ MOCK_METHOD(bool, HideLooseAssets, (const QDir&), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockModContext.h b/libs/bsapacker/tests/MockModContext.h
new file mode 100644
index 0000000..fedb3ff
--- /dev/null
+++ b/libs/bsapacker/tests/MockModContext.h
@@ -0,0 +1,13 @@
+#include <bsapacker/IModContext.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockModContext : public IModContext
+{
+public:
+ MOCK_METHOD(QString, GetAbsoluteModPath, (const QString& modName), (const, override));
+ MOCK_METHOD(int, GetNexusId, (), (const, override));
+ MOCK_METHOD(QStringList, GetPlugins, (const QDir& modDirectory), (const, override));
+ MOCK_METHOD(QStringList, GetValidMods, (), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockModDtoFactory.h b/libs/bsapacker/tests/MockModDtoFactory.h
new file mode 100644
index 0000000..d1f0331
--- /dev/null
+++ b/libs/bsapacker/tests/MockModDtoFactory.h
@@ -0,0 +1,10 @@
+#include <bsapacker/IModDtoFactory.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockModDtoFactory : public IModDtoFactory
+{
+public:
+ MOCK_METHOD(std::unique_ptr<IModDto>, Create, (), (const));
+};
diff --git a/libs/bsapacker/tests/MockOrganizer.h b/libs/bsapacker/tests/MockOrganizer.h
new file mode 100644
index 0000000..4c6762e
--- /dev/null
+++ b/libs/bsapacker/tests/MockOrganizer.h
@@ -0,0 +1,61 @@
+#include "imoinfo.h"
+#include <gmock/gmock.h>
+
+using namespace MOBase;
+
+class MockOrganizer : public IOrganizer
+{
+public:
+ // clang-format off
+ MOCK_METHOD(IModRepositoryBridge*, createNexusBridge, (), (const, override));
+ MOCK_METHOD(QString, profileName, (), (const, override));
+ MOCK_METHOD(QString, profilePath, (), (const, override));
+ MOCK_METHOD(QString, downloadsPath, (), (const, override));
+ MOCK_METHOD(QString, overwritePath, (), (const, override));
+ MOCK_METHOD(QString, basePath, (), (const, override));
+ MOCK_METHOD(QString, modsPath, (), (const, override));
+ MOCK_METHOD(Version, version, (), (const, override));
+ MOCK_METHOD(VersionInfo, appVersion, (), (const, override));
+ MOCK_METHOD(IModInterface*, createMod, (GuessedValue<QString> &name), (override));
+ MOCK_METHOD(IPluginGame*, getGame, (const QString &gameName), (const, override));
+ MOCK_METHOD(void, modDataChanged, (IModInterface *mod), (override));
+ MOCK_METHOD(QVariant, pluginSetting, (const QString &pluginName, const QString &key), (const, override));
+ MOCK_METHOD(void, setPluginSetting, (const QString &pluginName, const QString &key, const QVariant &value), (override));
+ MOCK_METHOD(bool, isPluginEnabled, (const QString& pluginName), (const, override));
+ MOCK_METHOD(bool, isPluginEnabled, (IPlugin *plugin), (const, override));
+ MOCK_METHOD(QVariant, persistent, (const QString &pluginName, const QString &key, const QVariant &def), (const, override));
+ MOCK_METHOD(void, setPersistent, (const QString &pluginName, const QString &key, const QVariant &value, bool sync), (override));
+ MOCK_METHOD(QString, pluginDataPath, (), (const, override));
+ MOCK_METHOD(IModInterface*, installMod, (const QString &fileName, const QString &nameSuggestion), (override));
+ MOCK_METHOD(QString, resolvePath, (const QString &fileName), (const, override));
+ MOCK_METHOD(QStringList, listDirectories, (const QString &directoryName), (const, override));
+ MOCK_METHOD(QStringList, findFiles, (const QString &path, const std::function<bool(const QString&)> &filter), (const, override));
+ MOCK_METHOD(QStringList, findFiles, (const QString &path, const QStringList &filter), (const, override));
+ MOCK_METHOD(QStringList, getFileOrigins, (const QString &fileName) ,(const, override));
+ MOCK_METHOD(QList<FileInfo>, findFileInfos, (const QString &path, const std::function<bool(const FileInfo&)> &filter), (const, override));
+ MOCK_METHOD(std::shared_ptr<const IFileTree>, virtualFileTree, (), (const, override));
+ MOCK_METHOD(MOBase::IDownloadManager*, downloadManager, (), (const, override));
+ MOCK_METHOD(MOBase::IPluginList*, pluginList, (), (const, override));
+ MOCK_METHOD(MOBase::IModList*, modList, (), (const, override));
+ MOCK_METHOD(MOBase::IProfile*, profile, (), (const, override));
+ MOCK_METHOD(IGameFeatures*, gameFeatures, (), (const, override));
+ MOCK_METHOD(HANDLE, startApplication, (const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite), (override));
+ MOCK_METHOD(bool, waitForApplication, (HANDLE handle, bool refresh, LPDWORD exitCode), (const, override));
+ MOCK_METHOD(bool, onAboutToRun, (const std::function<bool(const QString&)> &func), (override));
+ MOCK_METHOD(bool, onAboutToRun, (const std::function<bool(const QString&, const QDir&, const QString&)>&), (override));
+ MOCK_METHOD(bool, onFinishedRun, (const std::function<void(const QString&, unsigned int)> &func), (override));
+ MOCK_METHOD(void, refresh, (bool saveChanges), (override));
+ MOCK_METHOD(MOBase::IPluginGame const *, managedGame, (), (const, override));
+ MOCK_METHOD(bool, onUserInterfaceInitialized, (const std::function<void (QMainWindow *)> &), (override));
+ MOCK_METHOD(bool, onNextRefresh, (const std::function<void()> &, bool), (override));
+ MOCK_METHOD(bool, onProfileCreated, (const std::function<void(MOBase::IProfile*)>&), (override));
+ MOCK_METHOD(bool, onProfileRemoved, (const std::function<void(const QString&)>&), (override));
+ MOCK_METHOD(bool, onProfileRenamed, (const std::function<void(MOBase::IProfile*, QString const&, QString const&)>&), (override));
+ MOCK_METHOD(bool, onProfileChanged, (const std::function<void (MOBase::IProfile*, MOBase::IProfile*)> &), (override));
+ MOCK_METHOD(bool, onPluginSettingChanged, (const std::function<void (const QString &,const QString &,const QVariant &,const QVariant &)> &), (override));
+ MOCK_METHOD(bool, onPluginEnabled, (const std::function<void(const MOBase::IPlugin*)>&), (override));
+ MOCK_METHOD(bool, onPluginEnabled, (const QString&, const std::function<void()>&), (override));
+ MOCK_METHOD(bool, onPluginDisabled, (const std::function<void(const MOBase::IPlugin*)>&), (override));
+ MOCK_METHOD(bool, onPluginDisabled, (const QString&, const std::function<void()>&), (override));
+ // clang-format on
+};
diff --git a/libs/bsapacker/tests/MockOverrideFileService.h b/libs/bsapacker/tests/MockOverrideFileService.h
new file mode 100644
index 0000000..b7c0414
--- /dev/null
+++ b/libs/bsapacker/tests/MockOverrideFileService.h
@@ -0,0 +1,10 @@
+#include <bsapacker/IOverrideFileService.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockOverrideFileService : public IOverrideFileService
+{
+public:
+ MOCK_METHOD(bool, CreateOverrideFile, (const int nexusId, const QString &modPath, const QStringList &archiveNames), (const, override));
+};
diff --git a/libs/bsapacker/tests/MockPackerDialog.h b/libs/bsapacker/tests/MockPackerDialog.h
new file mode 100644
index 0000000..aafad52
--- /dev/null
+++ b/libs/bsapacker/tests/MockPackerDialog.h
@@ -0,0 +1,17 @@
+#include <bsapacker/IPackerDialog.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockPackerDialog : public IPackerDialog
+{
+public:
+ MOCK_METHOD(bool, IsNewFilename, (), (const, override));
+ MOCK_METHOD(QString, SelectedMod, (), (const, override));
+ MOCK_METHOD(void, RefreshModList, (), (override));
+ MOCK_METHOD(QString, SelectedPluginItem, (), (const, override));
+ MOCK_METHOD(void, UpdateNameList, (const QString&), (override));
+ MOCK_METHOD(void, RefreshSelectedName, (), (override));
+ MOCK_METHOD(int, Exec, (), (override));
+ MOCK_METHOD(void, RefreshOkButton, (), (override));
+};
diff --git a/libs/bsapacker/tests/MockSettingsService.h b/libs/bsapacker/tests/MockSettingsService.h
new file mode 100644
index 0000000..73bfea0
--- /dev/null
+++ b/libs/bsapacker/tests/MockSettingsService.h
@@ -0,0 +1,10 @@
+#include <bsapacker/ISettingsService.h>
+#include <gmock/gmock.h>
+
+using namespace BsaPacker;
+
+class MockSettingsService : public ISettingsService
+{
+public:
+ MOCK_METHOD(QVariant, GetPluginSetting, (const QString&), (const, override));
+};
diff --git a/libs/bsapacker/tests/ModContextFacts.cpp b/libs/bsapacker/tests/ModContextFacts.cpp
new file mode 100644
index 0000000..3ab1808
--- /dev/null
+++ b/libs/bsapacker/tests/ModContextFacts.cpp
@@ -0,0 +1,33 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "ModContext.h"
+#include "MockOrganizer.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class ModContextFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockOrganizer = new NaggyMock<MockOrganizer>();
+ }
+
+ void TearDown() override {
+ delete naggyMockOrganizer;
+ }
+
+ NaggyMock<MockOrganizer>* naggyMockOrganizer;
+ };
+
+ TEST_F(ModContextFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = ModContext(
+ naggyMockOrganizer);
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/ModDtoFactoryFacts.cpp b/libs/bsapacker/tests/ModDtoFactoryFacts.cpp
new file mode 100644
index 0000000..ae9a9f7
--- /dev/null
+++ b/libs/bsapacker/tests/ModDtoFactoryFacts.cpp
@@ -0,0 +1,53 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/ModDtoFactory.h>
+#include "MockModContext.h"
+#include "MockPackerDialog.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+using ::testing::Return;
+
+namespace BsaPackerTests
+{
+ class ModDtoFactoryFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockModContext = new NaggyMock<MockModContext>();
+ naggyMockPackerDialog = new NaggyMock<MockPackerDialog>();
+ }
+
+ void TearDown() override {
+ delete naggyMockModContext;
+ delete naggyMockPackerDialog;
+ }
+
+ NaggyMock<MockModContext>* naggyMockModContext;
+ NaggyMock<MockPackerDialog>* naggyMockPackerDialog;
+ };
+
+ TEST_F(ModDtoFactoryFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(ModDtoFactory(
+ naggyMockModContext,
+ naggyMockPackerDialog));
+ }
+
+ TEST_F(ModDtoFactoryFacts, Create_WhenGameIsNewVegas_ReturnsNewVegasIdInDto)
+ {
+ // Arrange
+ auto sut = ModDtoFactory(
+ naggyMockModContext,
+ naggyMockPackerDialog);
+ ON_CALL(*naggyMockModContext, GetNexusId())
+ .WillByDefault(Return(1));
+ ON_CALL(*naggyMockPackerDialog, Exec())
+ .WillByDefault(Return(QDialog::DialogCode::Accepted));
+
+ auto result = sut.Create();
+
+ EXPECT_EQ(1, result->NexusId());
+ }
+}
diff --git a/libs/bsapacker/tests/NewVegasDummyPluginServiceFacts.cpp b/libs/bsapacker/tests/NewVegasDummyPluginServiceFacts.cpp
new file mode 100644
index 0000000..98216ff
--- /dev/null
+++ b/libs/bsapacker/tests/NewVegasDummyPluginServiceFacts.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "NewVegasDummyPluginService.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class NewVegasDummyPluginServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(NewVegasDummyPluginServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = NewVegasDummyPluginService(
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/OblivionDummyPluginServiceFacts.cpp b/libs/bsapacker/tests/OblivionDummyPluginServiceFacts.cpp
new file mode 100644
index 0000000..dbebbb1
--- /dev/null
+++ b/libs/bsapacker/tests/OblivionDummyPluginServiceFacts.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "OblivionDummyPluginService.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class OblivionDummyPluginServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(OblivionDummyPluginServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = OblivionDummyPluginService(
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/SkyrimDummyPluginServiceFacts.cpp b/libs/bsapacker/tests/SkyrimDummyPluginServiceFacts.cpp
new file mode 100644
index 0000000..e699c10
--- /dev/null
+++ b/libs/bsapacker/tests/SkyrimDummyPluginServiceFacts.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "SkyrimDummyPluginService.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class SkyrimDummyPluginServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(SkyrimDummyPluginServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = SkyrimDummyPluginService(
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/SkyrimSEDummyPluginServiceFacts.cpp b/libs/bsapacker/tests/SkyrimSEDummyPluginServiceFacts.cpp
new file mode 100644
index 0000000..5a67937
--- /dev/null
+++ b/libs/bsapacker/tests/SkyrimSEDummyPluginServiceFacts.cpp
@@ -0,0 +1,39 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include "SkyrimSEDummyPluginService.h"
+#include "MockFileWriterService.h"
+#include "MockDummyPluginLogic.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class SkyrimSEDummyPluginServiceFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockFileWriterService = new NaggyMock<MockFileWriterService>();
+ naggyMockDummyPluginLogic = new NaggyMock<MockDummyPluginLogic>();
+ }
+
+ void TearDown() override {
+ delete naggyMockFileWriterService;
+ delete naggyMockDummyPluginLogic;
+ }
+
+ NaggyMock<MockFileWriterService>* naggyMockFileWriterService;
+ NaggyMock<MockDummyPluginLogic>* naggyMockDummyPluginLogic;
+ };
+
+ TEST_F(SkyrimSEDummyPluginServiceFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = SkyrimSEDummyPluginService(
+ naggyMockFileWriterService,
+ naggyMockDummyPluginLogic
+ );
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/TextureArchiveBuilderFacts.cpp b/libs/bsapacker/tests/TextureArchiveBuilderFacts.cpp
new file mode 100644
index 0000000..3ada0de
--- /dev/null
+++ b/libs/bsapacker/tests/TextureArchiveBuilderFacts.cpp
@@ -0,0 +1,35 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/TextureArchiveBuilder.h>
+#include "MockArchiveBuilderHelper.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class TextureArchiveBuilderFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockArchiveBuilderHelper = new NaggyMock<MockArchiveBuilderHelper>();
+ }
+
+ void TearDown() override {
+ delete naggyMockArchiveBuilderHelper;
+ }
+
+ NaggyMock<MockArchiveBuilderHelper>* naggyMockArchiveBuilderHelper;
+ };
+
+ TEST_F(TextureArchiveBuilderFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = TextureArchiveBuilder(
+ naggyMockArchiveBuilderHelper,
+ QDir(),
+ baNone);
+ );
+ }
+}
diff --git a/libs/bsapacker/tests/TexturelessArchiveBuilderFacts.cpp b/libs/bsapacker/tests/TexturelessArchiveBuilderFacts.cpp
new file mode 100644
index 0000000..f0aa7d2
--- /dev/null
+++ b/libs/bsapacker/tests/TexturelessArchiveBuilderFacts.cpp
@@ -0,0 +1,35 @@
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <bsapacker/TexturelessArchiveBuilder.h>
+#include "MockArchiveBuilderHelper.h"
+
+using namespace BsaPacker;
+using ::testing::NaggyMock;
+
+namespace BsaPackerTests
+{
+ class TexturelessArchiveBuilderFacts : public ::testing::Test
+ {
+ protected:
+ void SetUp() override {
+ naggyMockArchiveBuilderHelper = new NaggyMock<MockArchiveBuilderHelper>();
+ }
+
+ void TearDown() override {
+ delete naggyMockArchiveBuilderHelper;
+ }
+
+ NaggyMock<MockArchiveBuilderHelper>* naggyMockArchiveBuilderHelper;
+ };
+
+ TEST_F(TexturelessArchiveBuilderFacts, Ctor_Always_Constructs)
+ {
+ EXPECT_NO_THROW(
+ auto result = TexturelessArchiveBuilder(
+ naggyMockArchiveBuilderHelper,
+ QDir(),
+ baNone);
+ );
+ }
+}
diff --git a/libs/bsapacker/vcpkg.json b/libs/bsapacker/vcpkg.json
new file mode 100644
index 0000000..addd280
--- /dev/null
+++ b/libs/bsapacker/vcpkg.json
@@ -0,0 +1,34 @@
+{
+ "dependencies": ["mo2-libbsarch", "mo2-dds-header", "directxtex", "bext-di"],
+ "features": {
+ "standalone": {
+ "description": "Build Standalone.",
+ "dependencies": ["mo2-cmake"]
+ },
+ "testing": {
+ "description": "Build BSA Packer tests.",
+ "dependencies": ["gtest"]
+ }
+ },
+ "vcpkg-configuration": {
+ "default-registry": {
+ "kind": "git",
+ "repository": "https://github.com/Microsoft/vcpkg",
+ "baseline": "294f76666c3000630d828703e675814c05a4fd43"
+ },
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "https://github.com/Microsoft/vcpkg",
+ "baseline": "294f76666c3000630d828703e675814c05a4fd43",
+ "packages": ["boost*", "boost-*", "bext-*"]
+ },
+ {
+ "kind": "git",
+ "repository": "https://github.com/ModOrganizer2/vcpkg-registry",
+ "baseline": "9936967e7c0ba1968e54a2fa6e6ad27b4f992b9b",
+ "packages": ["mo2-*"]
+ }
+ ]
+ }
+}