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