commit 2fb5ea9a5987fc4d183b2dbe3405ec855401d2fb Author: MF Date: Wed Nov 13 21:19:08 2019 +0000 Initial commit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..23eed79 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,167 @@ +cmake_minimum_required(VERSION 2.8.12) + +add_compile_options($<$:/MP> $<$:$<$:/O2>> $<$:$<$:/O2>>) + +set(PROJ_NAME bsa_packer) + +project(${PROJ_NAME}) + +set(DEPENDENCIES_DIR CACHE PATH "") + +# hint to find qt in dependencies path +list(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) + +list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake) + + +# pasted + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +find_package(Qt5 COMPONENTS Core Concurrent Widgets REQUIRED) + +set(Boost_USE_STATIC_LIBS ON) +set(Boost_USE_MULTITHREADED ON) +set(Boost_USE_STATIC_RUNTIME OFF) +find_package(Boost REQUIRED) + +# MO projects +set(default_project_path "${DEPENDENCIES_DIR}/modorganizer_super") +get_filename_component(${default_project_path} ${default_project_path} REALPATH) + +set(project_path "${default_project_path}" CACHE PATH "path to the other mo projects") +set(lib_path "${default_project_path}/../../install/libs") + +add_library(${PROJ_NAME} SHARED + include/bsapacker/IArchiveAutoService.h + include/bsapacker/IArchiveBuilder.h + include/bsapacker/IArchiveBuilderFactory.h + include/bsapacker/IArchiveBuilderHelper.h + include/bsapacker/IArchiveExtensionService.h + include/bsapacker/IDummyPluginLogic.h + include/bsapacker/IDummyPluginService.h + include/bsapacker/IDummyPluginServiceFactory.h + include/bsapacker/IEmitsValueChanged.h + include/bsapacker/IFileWriterService.h + include/bsapacker/IHideLooseAssetService.h + include/bsapacker/IModDtoFactory.h + include/bsapacker/IPackerDialog.h + include/bsapacker/IPackerDialogFactory.h + include/bsapacker/IPackerProgressDialog.h + include/bsapacker/IPackerProgressDialogFactory.h + include/bsapacker/ISettingsService.h + + include/bsapacker/GeneralArchiveBuilder.h + include/bsapacker/ModDtoFactory.h + include/bsapacker/NullArchiveBuilder.h + include/bsapacker/TextureArchiveBuilder.h + include/bsapacker/TexturelessArchiveBuilder.h + + src/NexusId.h + src/BsaPackerWorker.h + src/BsaPackerWorker.cpp + + src/ArchiveAutoService.cpp + src/ArchiveBuildDirector.cpp + src/ArchiveBuilderFactory.cpp + src/ArchiveBuilderHelper.cpp + src/ArchiveExtensionService.cpp + src/BsaPacker.cpp + src/BSArchive.cpp + src/BSArchiveAuto.cpp + src/BSArchiveEntries.cpp + src/DummyPluginLogic.cpp + src/DummyPluginServiceFactory.cpp + src/Fallout3DummyPluginService.cpp + src/Fallout4DummyPluginService.cpp + src/FileWriterService.cpp + src/GeneralArchiveBuilder.cpp + src/HideLooseAssetService.cpp + src/ModContext.cpp + src/ModDto.cpp + src/ModDtoFactory.cpp + src/NewVegasDummyPluginService.cpp + src/NullArchiveBuilder.cpp + src/NullDummyPluginService.cpp + src/NullModDto.cpp + src/OblivionDummyPluginService.cpp + src/PackerDialog.cpp + src/PackerDialogFactory.cpp + src/PackerProgressDialog.cpp + src/PackerProgressDialogFactory.cpp + src/SettingsService.cpp + src/SkyrimDummyPluginService.cpp + src/SkyrimSEDummyPluginService.cpp + src/TextureArchiveBuilder.cpp + src/TexturelessArchiveBuilder.cpp +) + +add_library(BsaPacker::${PROJ_NAME} ALIAS ${PROJ_NAME}) + +target_compile_features(${PROJ_NAME} PRIVATE cxx_auto_type) + +target_compile_options(${PROJ_NAME} PRIVATE $<$:-Wall>) + +target_include_directories(${PROJ_NAME} + PUBLIC + $ + $ + PRIVATE + ${Boost_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${default_project_path}/uibase/src +) + +target_link_directories(${PROJ_NAME} + PUBLIC + ${lib_path} + $ + ${Boost_LIBRARY_DIRS}) + +message("Default path is: ${default_project_path}") +message("Project path is: ${project_path}") +message("Lib path is: ${lib_path}") +message("packer path is ${CMAKE_SOURCE_DIR}/include/bsapacker") + +target_link_libraries(${PROJ_NAME} + PUBLIC + Qt5::Widgets + Qt5::Concurrent + Qt5::Gui + ${Boost_LIBRARIES} + uibase + libbsarch + DirectXTex +) + +if (MSVC) + set_target_properties(${PROJ_NAME} PROPERTIES COMPILE_FLAGS "/std:c++latest") +endif() +if (MSVC AND CMAKE_SIZEOF_VOID_P EQUAL 4) + # 32 bits + set_target_properties(${PROJ_NAME} PROPERTIES LINK_FLAGS "/LARGEADDRESSAWARE") +endif() + +if (NOT "${OPTIMIZE_COMPILE_FLAGS}" STREQUAL "") + set_target_properties(${PROJ_NAME} PROPERTIES COMPILE_FLAGS_RELWITHDEBINFO ${OPTIMIZE_COMPILE_FLAGS}) +endif() +if (NOT "${OPTIMIZE_LINK_FLAGS}" STREQUAL "") + set_target_properties(${PROJ_NAME} PROPERTIES LINK_FLAGS_RELWITHDEBINFO ${OPTIMIZE_LINK_FLAGS}) +endif() + +############### +## Installation + +install(TARGETS ${PROJ_NAME} + EXPORT ${PROJ_NAME}-targets + #ARCHIVE DESTINATION libs + RUNTIME DESTINATION bin/plugins) +install(FILES $ + DESTINATION pdb) +install(EXPORT ${PROJ_NAME}-targets + FILE BsaPackerTargets.cmake + NAMESPACE BsaPacker:: + DESTINATION libs) + +#add_subdirectory(tests) diff --git a/cmake/FindDirectXTex.cmake b/cmake/FindDirectXTex.cmake new file mode 100644 index 0000000..cb8c5c8 --- /dev/null +++ b/cmake/FindDirectXTex.cmake @@ -0,0 +1,15 @@ +set(DirectXTex_INCLUDE_DIR ${DirectXTex_DIR}/include) +set(DirectXTex_LIBRARY_DIR ${DirectXTex_DIR}/lib) +set(DirectXTex_VERSION ${PC_modorganizer_VERSION}) + +mark_as_advanced(DirectXTex_FOUND DirectXTex_DIR DirectXTex_INCLUDE_DIR DirectXTex_LIBRARY_DIR DirectXTex_VERSION) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DirectXTex + REQUIRED_VARS DirectXTex_DIR DirectXTex_INCLUDE_DIR DirectXTex_LIBRARY_DIR + VERSION_VAR DirectXTex_VERSION +) + +if(DirectXTex_FOUND) + set(DirectXTex_INCLUDE_DIRS ${modorganizer_INCLUDE_DIR}) +endif() diff --git a/cmake/GoogleTest-CMakeLists.txt.in b/cmake/GoogleTest-CMakeLists.txt.in new file mode 100644 index 0000000..0ed8b37 --- /dev/null +++ b/cmake/GoogleTest-CMakeLists.txt.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.5.0) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) \ No newline at end of file diff --git a/include/bsapacker/ArchiveAutoService.h b/include/bsapacker/ArchiveAutoService.h new file mode 100644 index 0000000..cc46b77 --- /dev/null +++ b/include/bsapacker/ArchiveAutoService.h @@ -0,0 +1,19 @@ +#ifndef ARCHIVEAUTOSERVICE_H +#define ARCHIVEAUTOSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class ArchiveAutoService : public IArchiveAutoService + { + public: + ArchiveAutoService() = default; + ~ArchiveAutoService() override = default; + void CreateBSA(BSArchiveAuto* archive, const QString& archiveName, bsa_archive_type_e type) const override; + }; +} + +#endif // ARCHIVEAUTOSERVICE_H diff --git a/include/bsapacker/ArchiveBuildDirector.h b/include/bsapacker/ArchiveBuildDirector.h new file mode 100644 index 0000000..9ac226b --- /dev/null +++ b/include/bsapacker/ArchiveBuildDirector.h @@ -0,0 +1,27 @@ +#ifndef ARCHIVEBUILDDIRECTOR_H +#define ARCHIVEBUILDDIRECTOR_H + +#include "ISettingsService.h" + +#include +#include + + +namespace BsaPacker +{ + class ArchiveBuildDirector + { + public: + explicit ArchiveBuildDirector( + const ISettingsService* settingsService, + IArchiveBuilder* archiveFileBuildService); + ~ArchiveBuildDirector() = default; + void Construct(); + + private: + IArchiveBuilder* m_ArchiveFileBuildService = nullptr; + const ISettingsService* m_SettingsService = nullptr; + }; +} // namespace BsaPacker + +#endif // ARCHIVEBUILDDIRECTOR_H diff --git a/include/bsapacker/ArchiveBuilderFactory.h b/include/bsapacker/ArchiveBuilderFactory.h new file mode 100644 index 0000000..f7eaac1 --- /dev/null +++ b/include/bsapacker/ArchiveBuilderFactory.h @@ -0,0 +1,27 @@ +#ifndef ARCHIVEBUILDERFACTORY_H +#define ARCHIVEBUILDERFACTORY_H + + +#include +#include +#include +#include +#include + +namespace BsaPacker +{ + class ArchiveBuilderFactory : public IArchiveBuilderFactory + { + public: + explicit ArchiveBuilderFactory(const IArchiveBuilderHelper* archiveBuilderHelper); + ~ArchiveBuilderFactory() override = default; + // IArchiveBuilderFactory interface + [[nodiscard]] std::vector GetArchiveTypes(const IModDto* modDto) const override; + [[nodiscard]] std::unique_ptr 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/include/bsapacker/ArchiveBuilderHelper.h b/include/bsapacker/ArchiveBuilderHelper.h new file mode 100644 index 0000000..2242d57 --- /dev/null +++ b/include/bsapacker/ArchiveBuilderHelper.h @@ -0,0 +1,37 @@ +#ifndef ARCHIVEBUILDERHELPER_H +#define ARCHIVEBUILDERHELPER_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace BsaPacker +{ + class ArchiveBuilderHelper : public IArchiveBuilderHelper + { + public: + ArchiveBuilderHelper(const ISettingsService* settingsService); + ~ArchiveBuilderHelper() override = default; + [[nodiscard]] bool isFileIgnorable(const QString&, const QStringList&) const override; + [[nodiscard]] bool isIncompressible(const QString&) const override; + [[nodiscard]] bool isExtensionBlacklisted(const QString&) const override; + [[nodiscard]] uint32_t getFileCount(const QDir&) const override; + [[nodiscard]] QStringList getRootDirectoryFilenames(const QDir&) const override; + + const static std::array INCOMPRESSIBLE_TYPES; + //const static std::array BLACKLIST; + + private: + const ISettingsService* m_SettingsService = nullptr; + }; +} // namespace BsaPacker + +#endif // ARCHIVEBUILDERHELPER_H diff --git a/include/bsapacker/GeneralArchiveBuilder.h b/include/bsapacker/GeneralArchiveBuilder.h new file mode 100644 index 0000000..43f8677 --- /dev/null +++ b/include/bsapacker/GeneralArchiveBuilder.h @@ -0,0 +1,36 @@ +#ifndef GENERALARCHIVEBUILDER_H +#define GENERALARCHIVEBUILDER_H + +#include +#include + +namespace BsaPacker +{ + class GeneralArchiveBuilder : public IArchiveBuilder + { + Q_OBJECT + Q_INTERFACES(BsaPacker::IEmitsValueChanged) + + public: + explicit GeneralArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir); + ~GeneralArchiveBuilder() override = default; + uint32_t setFiles() override; + void setShareData(bool value) override; + [[nodiscard]] std::unique_ptr getArchive() override; + [[nodiscard]] uint32_t getFileCount() const override; + + public Q_SLOTS: + void cancel() override; + + Q_SIGNALS: + void valueChanged(int) override; + + private: + const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr; + std::unique_ptr m_Archive; + bool m_Cancelled; + QDir m_RootDirectory; + }; +} // namespace BsaPacker + +#endif // GENERALARCHIVEBUILDER_H diff --git a/include/bsapacker/IArchiveAutoService.h b/include/bsapacker/IArchiveAutoService.h new file mode 100644 index 0000000..ded4ac4 --- /dev/null +++ b/include/bsapacker/IArchiveAutoService.h @@ -0,0 +1,17 @@ +#ifndef IARCHIVEAUTOSERVICE_H +#define IARCHIVEAUTOSERVICE_H + +#include +#include + +namespace BsaPacker +{ + class IArchiveAutoService + { + public: + virtual ~IArchiveAutoService() = default; + virtual void CreateBSA(BSArchiveAuto*, const QString&, bsa_archive_type_e) const = 0; + }; +} + +#endif // IARCHIVEAUTOSERVICE_H diff --git a/include/bsapacker/IArchiveBuilder.h b/include/bsapacker/IArchiveBuilder.h new file mode 100644 index 0000000..7bbe30e --- /dev/null +++ b/include/bsapacker/IArchiveBuilder.h @@ -0,0 +1,23 @@ +#ifndef IARCHIVEBUILDER_H +#define IARCHIVEBUILDER_H + +#include +#include + +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::unique_ptr getArchive() = 0; + [[nodiscard]] virtual uint32_t getFileCount() const = 0; + }; +} // namespace BsaPacker + +#endif // IARCHIVEBUILDER_H diff --git a/include/bsapacker/IArchiveBuilderFactory.h b/include/bsapacker/IArchiveBuilderFactory.h new file mode 100644 index 0000000..23da46d --- /dev/null +++ b/include/bsapacker/IArchiveBuilderFactory.h @@ -0,0 +1,18 @@ +#ifndef IARCHIVEBUILDERFACTORY_H +#define IARCHIVEBUILDERFACTORY_H + +#include +#include + +namespace BsaPacker +{ + class IArchiveBuilderFactory + { + public: + virtual ~IArchiveBuilderFactory() = default; + virtual std::vector GetArchiveTypes(const IModDto* modDto) const = 0; + virtual std::unique_ptr Create(bsa_archive_type_e archiveType, const IModDto* modDto) const = 0; + }; +} + +#endif // IARCHIVEBUILDERFACTORY_H diff --git a/include/bsapacker/IArchiveBuilderHelper.h b/include/bsapacker/IArchiveBuilderHelper.h new file mode 100644 index 0000000..4a664e0 --- /dev/null +++ b/include/bsapacker/IArchiveBuilderHelper.h @@ -0,0 +1,23 @@ +#ifndef IARCHIVEBUILDERHELPER_H +#define IARCHIVEBUILDERHELPER_H + +#include +#include +#include +#include + +namespace BsaPacker +{ + class IArchiveBuilderHelper + { + public: + virtual ~IArchiveBuilderHelper() = default; + [[nodiscard]] virtual bool isFileIgnorable(const QString&, const QStringList&) const = 0; + [[nodiscard]] virtual bool isIncompressible(const QString&) const = 0; + [[nodiscard]] virtual bool isExtensionBlacklisted(const QString&) const = 0; + [[nodiscard]] virtual uint32_t getFileCount(const QDir&) const = 0; + [[nodiscard]] virtual QStringList getRootDirectoryFilenames(const QDir&) const = 0; + }; +} // namespace BsaPacker + +#endif // IARCHIVEBUILDERHELPER_H diff --git a/include/bsapacker/IArchiveExtensionService.h b/include/bsapacker/IArchiveExtensionService.h new file mode 100644 index 0000000..2da1d00 --- /dev/null +++ b/include/bsapacker/IArchiveExtensionService.h @@ -0,0 +1,16 @@ +#ifndef IARCHIVEEXTENSIONSERVICE_H +#define IARCHIVEEXTENSIONSERVICE_H + +#include + +namespace BsaPacker +{ + class IArchiveExtensionService + { + public: + virtual ~IArchiveExtensionService() = default; + virtual QString GetFileExtension() const = 0; + }; +} + +#endif // IARCHIVEEXTENSIONSERVICE_H diff --git a/include/bsapacker/IDummyPluginLogic.h b/include/bsapacker/IDummyPluginLogic.h new file mode 100644 index 0000000..74af17b --- /dev/null +++ b/include/bsapacker/IDummyPluginLogic.h @@ -0,0 +1,18 @@ +#ifndef IDUMMYPLUGINLOGIC_H +#define IDUMMYPLUGINLOGIC_H + +#include +#include + +namespace BsaPacker +{ + class IDummyPluginLogic + { + public: + virtual ~IDummyPluginLogic() = default; + [[nodiscard]] virtual bool canCreateDummyESP(const QString& fileNameNoExtension) const = 0; + [[nodiscard]] virtual bool canCreateDummyESL(const QString& fileNameNoExtension) const = 0; + }; +} + +#endif // IDUMMYPLUGINLOGIC_H diff --git a/include/bsapacker/IDummyPluginService.h b/include/bsapacker/IDummyPluginService.h new file mode 100644 index 0000000..31101a1 --- /dev/null +++ b/include/bsapacker/IDummyPluginService.h @@ -0,0 +1,17 @@ +#ifndef IDUMMYPLUGINSERVICE_H +#define IDUMMYPLUGINSERVICE_H + +#include + +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/include/bsapacker/IDummyPluginServiceFactory.h b/include/bsapacker/IDummyPluginServiceFactory.h new file mode 100644 index 0000000..3836946 --- /dev/null +++ b/include/bsapacker/IDummyPluginServiceFactory.h @@ -0,0 +1,16 @@ +#ifndef IDUMMYPLUGINSERVICEFACTORY_H +#define IDUMMYPLUGINSERVICEFACTORY_H + +#include + +namespace BsaPacker +{ + class IDummyPluginServiceFactory + { + public: + virtual ~IDummyPluginServiceFactory() = default; + [[nodiscard]] virtual std::unique_ptr Create() const = 0; + }; +} + +#endif // IDUMMYPLUGINSERVICEFACTORY_H diff --git a/include/bsapacker/IEmitsValueChanged.h b/include/bsapacker/IEmitsValueChanged.h new file mode 100644 index 0000000..03be3ae --- /dev/null +++ b/include/bsapacker/IEmitsValueChanged.h @@ -0,0 +1,25 @@ +#ifndef IEMITSVALUECHANGED_H +#define IEMITSVALUECHANGED_H + +#include + +namespace BsaPacker +{ + class IEmitsValueChanged : public QObject + { + Q_OBJECT + + public: + virtual ~IEmitsValueChanged() = default; + + public Q_SLOTS: + virtual void cancel() = 0; + + Q_SIGNALS: + virtual void valueChanged(int) = 0; + }; +} // namespace BsaPacker + +Q_DECLARE_INTERFACE(BsaPacker::IEmitsValueChanged, "IEmitsValueChanged"); + +#endif // IEMITSVALUECHANGED_H diff --git a/include/bsapacker/IFileWriterService.h b/include/bsapacker/IFileWriterService.h new file mode 100644 index 0000000..f507a85 --- /dev/null +++ b/include/bsapacker/IFileWriterService.h @@ -0,0 +1,16 @@ +#ifndef IFILEWRITERSERVICE_H +#define IFILEWRITERSERVICE_H + +#include + +namespace BsaPacker +{ + class IFileWriterService + { + public: + virtual ~IFileWriterService() = default; + virtual bool Write(const std::string& path, const char* payload, const uint32_t size) const = 0; + }; +} + +#endif // IFILEWRITERSERVICE_H diff --git a/include/bsapacker/IHideLooseAssetService.h b/include/bsapacker/IHideLooseAssetService.h new file mode 100644 index 0000000..d8a92cd --- /dev/null +++ b/include/bsapacker/IHideLooseAssetService.h @@ -0,0 +1,16 @@ +#ifndef IHIDELOOSEASSETSERVICE_H +#define IHIDELOOSEASSETSERVICE_H + +#include + +namespace BsaPacker +{ + class IHideLooseAssetService + { + public: + virtual ~IHideLooseAssetService() = default; + virtual bool HideLooseAssets(const QDir& modDirectory) const = 0; + }; +} + +#endif // IHIDELOOSEASSETSERVICE_H diff --git a/include/bsapacker/IModContext.h b/include/bsapacker/IModContext.h new file mode 100644 index 0000000..197854c --- /dev/null +++ b/include/bsapacker/IModContext.h @@ -0,0 +1,20 @@ +#ifndef IMODCONTEXT_H +#define IMODCONTEXT_H + +#include +#include + +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/include/bsapacker/IModDto.h b/include/bsapacker/IModDto.h new file mode 100644 index 0000000..e523981 --- /dev/null +++ b/include/bsapacker/IModDto.h @@ -0,0 +1,20 @@ +#ifndef IMODDTO_H +#define IMODDTO_H + +#include + +namespace BsaPacker +{ + class IModDto + { + public: + virtual ~IModDto() = default; + [[nodiscard]] virtual int NexusId() const = 0; + [[nodiscard]] virtual QString ModName() const = 0; + [[nodiscard]] virtual QString Path() const = 0; + [[nodiscard]] virtual QString ArchiveName() const = 0; + [[nodiscard]] virtual QString ArchiveExtension() const = 0; + }; +} // namespace BsaPacker + +#endif // IMODDTO_H diff --git a/include/bsapacker/IModDtoFactory.h b/include/bsapacker/IModDtoFactory.h new file mode 100644 index 0000000..ebf279e --- /dev/null +++ b/include/bsapacker/IModDtoFactory.h @@ -0,0 +1,16 @@ +#ifndef IMODDTOFACTORY_H +#define IMODDTOFACTORY_H + +#include "IModDto.h" + +namespace BsaPacker +{ + class IModDtoFactory + { + public: + virtual ~IModDtoFactory() = default; + [[nodiscard]] virtual std::unique_ptr Create() const = 0; + }; +} // namespace BsaPacker + +#endif // IMODDTOFACTORY_H diff --git a/include/bsapacker/IPackerDialog.h b/include/bsapacker/IPackerDialog.h new file mode 100644 index 0000000..371efc5 --- /dev/null +++ b/include/bsapacker/IPackerDialog.h @@ -0,0 +1,29 @@ +#ifndef IPACKERDIALOG_H +#define IPACKERDIALOG_H + +#include +#include +#include + +namespace BsaPacker +{ + class IPackerDialog : public QDialog + { + Q_OBJECT + + public: + virtual ~IPackerDialog() override = default; + [[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; + + public Q_SLOTS: + virtual void RefreshOkButton() = 0; + }; +} // namespace BsaPacker + +Q_DECLARE_INTERFACE(BsaPacker::IPackerDialog, "com.MattyFez.BSA_Packer/1.0") + +#endif // IPACKERDIALOG_H diff --git a/include/bsapacker/IPackerDialogFactory.h b/include/bsapacker/IPackerDialogFactory.h new file mode 100644 index 0000000..1123ccc --- /dev/null +++ b/include/bsapacker/IPackerDialogFactory.h @@ -0,0 +1,16 @@ +#ifndef IPACKERDIALOGFACTORY_H +#define IPACKERDIALOGFACTORY_H + +#include + +namespace BsaPacker +{ + class IPackerDialogFactory + { + public: + virtual ~IPackerDialogFactory() = default; + virtual std::unique_ptr Create() const = 0; + }; +} + +#endif // IPACKERDIALOGFACTORY_H diff --git a/include/bsapacker/IPackerProgressDialog.h b/include/bsapacker/IPackerProgressDialog.h new file mode 100644 index 0000000..3400300 --- /dev/null +++ b/include/bsapacker/IPackerProgressDialog.h @@ -0,0 +1,20 @@ +#ifndef IPACKERPROGRESSDIALOG_H +#define IPACKERPROGRESSDIALOG_H + +#include + + +namespace BsaPacker +{ + class IPackerProgressDialog : public QProgressDialog + { + Q_OBJECT + + public: + virtual ~IPackerProgressDialog() override = default; + }; +} // namespace BsaPacker + +Q_DECLARE_INTERFACE(BsaPacker::IPackerProgressDialog, "IPackerProgressDialog") + +#endif // IPACKERPROGRESSDIALOG_H diff --git a/include/bsapacker/IPackerProgressDialogFactory.h b/include/bsapacker/IPackerProgressDialogFactory.h new file mode 100644 index 0000000..446321e --- /dev/null +++ b/include/bsapacker/IPackerProgressDialogFactory.h @@ -0,0 +1,16 @@ +#ifndef IPACKERPROGRESSDIALOGFACTORY_H +#define IPACKERPROGRESSDIALOGFACTORY_H + +#include + +namespace BsaPacker +{ + class IPackerProgressDialogFactory + { + public: + virtual ~IPackerProgressDialogFactory() = default; + virtual std::unique_ptr Create() const = 0; + }; +} + +#endif // IPACKERPROGRESSDIALOGFACTORY_H diff --git a/include/bsapacker/ISettingsService.h b/include/bsapacker/ISettingsService.h new file mode 100644 index 0000000..a621445 --- /dev/null +++ b/include/bsapacker/ISettingsService.h @@ -0,0 +1,17 @@ +#ifndef ISETTINGSSERVICE_H +#define ISETTINGSSERVICE_H + +#include +#include + +namespace BsaPacker +{ + class ISettingsService + { + public: + virtual ~ISettingsService() = default; + [[nodiscard]] virtual QVariant GetPluginSetting(const QString&) const = 0; + }; +} + +#endif // ISETTINGSSERVICE_H diff --git a/include/bsapacker/ModDtoFactory.h b/include/bsapacker/ModDtoFactory.h new file mode 100644 index 0000000..f35852f --- /dev/null +++ b/include/bsapacker/ModDtoFactory.h @@ -0,0 +1,28 @@ +#ifndef MODDTOFACTORY_H +#define MODDTOFACTORY_H + +#include +#include +#include + +#include +#include + +namespace di = boost::di; +namespace BsaPacker +{ + class ModDtoFactory : public IModDtoFactory + { + public: + ModDtoFactory(const IModContext* modContext); + ~ModDtoFactory() override = default; + // IModDtoFactory interface + [[nodiscard]] std::unique_ptr Create() const override; + //static std::unique_ptr Create(const IModContext*); + static QString ArchiveNameValidator(const QString& modName, const QString& pluginName); + + private: + const IModContext* m_ModContext = nullptr; + }; +} // namespace BsaPacker +#endif // MODDTOFACTORY_H diff --git a/include/bsapacker/NullArchiveBuilder.h b/include/bsapacker/NullArchiveBuilder.h new file mode 100644 index 0000000..582cecc --- /dev/null +++ b/include/bsapacker/NullArchiveBuilder.h @@ -0,0 +1,32 @@ +#ifndef NULLARCHIVEBUILDER_H +#define NULLARCHIVEBUILDER_H + +#include + +namespace BsaPacker +{ + class NullArchiveBuilder : public IArchiveBuilder + { + Q_OBJECT + + public: + NullArchiveBuilder() = default; + ~NullArchiveBuilder() override = default; + + // ArchiveBuilder interface + uint32_t setFiles() override; + void setShareData(bool) override; + [[nodiscard]] std::unique_ptr getArchive() override; + [[nodiscard]] uint32_t getFileCount() const override; + + // IEmitsValueChanged interface + public Q_SLOTS: + void cancel() override; + + signals: + void valueChanged(int) override; + + }; +} // namespace BsaPacker + +#endif // NULLARCHIVEBUILDER_H diff --git a/include/bsapacker/PackerProgressDialogFactory.h b/include/bsapacker/PackerProgressDialogFactory.h new file mode 100644 index 0000000..dc063d0 --- /dev/null +++ b/include/bsapacker/PackerProgressDialogFactory.h @@ -0,0 +1,20 @@ +#ifndef PACKERPROGRESSDIALOGFACTORY_H +#define PACKERPROGRESSDIALOGFACTORY_H + +#include "IPackerProgressDialog.h" +#include "IPackerProgressDialogFactory.h" + + +namespace BsaPacker +{ + class PackerProgressDialogFactory : public IPackerProgressDialogFactory + { + public: + PackerProgressDialogFactory() = default; + ~PackerProgressDialogFactory() override = default; + std::unique_ptr Create() const override; + }; +} + + +#endif // PACKERPROGRESSDIALOGFACTORY_H diff --git a/include/bsapacker/TextureArchiveBuilder.h b/include/bsapacker/TextureArchiveBuilder.h new file mode 100644 index 0000000..d4bd130 --- /dev/null +++ b/include/bsapacker/TextureArchiveBuilder.h @@ -0,0 +1,44 @@ +#ifndef TEXTUREARCHIVEBUILDER_H +#define TEXTUREARCHIVEBUILDER_H + +#include +#include +#include + +namespace BsaPacker +{ + class TextureArchiveBuilder : public IArchiveBuilder + { + Q_OBJECT + Q_INTERFACES(BsaPacker::IEmitsValueChanged) + + public: + explicit TextureArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir&); + ~TextureArchiveBuilder() override = default; + uint32_t setFiles() override; + void setShareData(bool) override; + //void setExtensionBlacklist(const QStringList&) override; + + [[nodiscard]] std::unique_ptr getArchive() override; + [[nodiscard]] uint32_t getFileCount() const override; + + public Q_SLOTS: + void cancel() override; + + Q_SIGNALS: + void valueChanged(int) override; + + private: + const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr; + std::unique_ptr m_Archive; + bool m_Cancelled; + QDir m_RootDirectory; + + 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/include/bsapacker/TexturelessArchiveBuilder.h b/include/bsapacker/TexturelessArchiveBuilder.h new file mode 100644 index 0000000..ffc5a7b --- /dev/null +++ b/include/bsapacker/TexturelessArchiveBuilder.h @@ -0,0 +1,42 @@ +#ifndef TEXTURELESSARCHIVEBUILDER_H +#define TEXTURELESSARCHIVEBUILDER_H + +#include +#include +#include + +namespace BsaPacker +{ + class TexturelessArchiveBuilder : public IArchiveBuilder + { + Q_OBJECT + Q_INTERFACES(BsaPacker::IEmitsValueChanged) + + public: + explicit TexturelessArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir); + //TexturelessArchiveBuilder(const TexturelessArchiveBuilder&) = delete; + //TexturelessArchiveBuilder(const TexturelessArchiveBuilder&&) = delete; + ~TexturelessArchiveBuilder() override = default; + uint32_t setFiles() override; + void setShareData(bool) override; + //void setExtensionBlacklist(const QStringList&) override; + + [[nodiscard]] std::unique_ptr getArchive() override; + [[nodiscard]] uint32_t getFileCount() const override; + + // IEmitsValueChanged interface + public Q_SLOTS: + void cancel() override; + + signals: + void valueChanged(int) override; + + private: + const IArchiveBuilderHelper* m_ArchiveBuilderHelper = nullptr; + std::unique_ptr m_Archive; + bool m_Cancelled; + QDir m_RootDirectory; + }; +} // namespace BsaPacker + +#endif // TEXTURELESSARCHIVEBUILDER_H diff --git a/include/libbsarch/libbsarch.h b/include/libbsarch/libbsarch.h new file mode 100644 index 0000000..cc3f990 --- /dev/null +++ b/include/libbsarch/libbsarch.h @@ -0,0 +1,152 @@ +#pragma once + +#include + +#include + +#include "DDS.h" + +#ifdef BSARCH_DLL_EXPORT +#define BSARCH_DLL_API(ReturnType) extern "C" __declspec(dllexport) ReturnType __stdcall +#else +#define BSARCH_DLL_API(ReturnType) extern "C" __declspec(dllimport) ReturnType __stdcall +#endif + +#ifdef __GNUC__ +#define PACKED(datastructure) datastructure __attribute__((__packed__)) +#else +#define PACKED(datastructure) __pragma(pack(push, 1)) datastructure __pragma(pack(pop)) +#endif + +typedef enum bsa_result_code_e +{ + BSA_RESULT_NONE = 0, + BSA_RESULT_EXCEPTION = -1 +} bsa_result_code_t; + +typedef void *bsa_buffer_t; +typedef void *bsa_archive_t; +typedef void *bsa_entry_list_t; +typedef void *bsa_file_record_t; +typedef void *bsa_folder_record_t; + +typedef struct bsa_dds_info_s +{ + uint32_t width, height, mipmaps; +} bsa_dds_info_t; + +PACKED(struct bsa_dds_header_s { + uint32_t magic; // DDS_MAGIC + struct DirectX::DDS_HEADER header; +}); + +typedef struct bsa_dds_header_s bsa_dds_header_t; + +PACKED(struct bsa_result_message_s { + int8_t code; // bsa_result_code_t + wchar_t text[1024]; +}); + +typedef struct bsa_result_message_s bsa_result_message_t; + +PACKED(struct bsa_result_buffer_s { + uint32_t size; + bsa_buffer_t data; +}); + +typedef struct bsa_result_buffer_s bsa_result_buffer_t; + +PACKED(struct bsa_result_message_buffer_s { + bsa_result_buffer_t buffer; + bsa_result_message_t message; +}); + +typedef struct bsa_result_message_buffer_s bsa_result_message_buffer_t; + +typedef enum bsa_archive_state_e +{ + stReading, + stWriting +} bsa_archive_state_t; + +typedef enum bsa_archive_type_e +{ + baNone, + baTES3, + baTES4, + baFO3, + baSSE, + baFO4, + baFO4dds +} bsa_archive_type_t; + +typedef void (*bsa_file_dds_info_proc_t)(bsa_archive_t archive, + const wchar_t *file_path, + bsa_dds_info_t *dds_info, + void *context); +typedef bool (*bsa_file_iteration_proc_t)(bsa_archive_t archive, + const wchar_t *file_path, + bsa_file_record_t file_record, + bsa_folder_record_t folder_record, + void *context); + +BSARCH_DLL_API(bsa_entry_list_t) bsa_entry_list_create(); +BSARCH_DLL_API(bsa_result_message_t) bsa_entry_list_free(bsa_entry_list_t entry_list); +BSARCH_DLL_API(uint32_t) bsa_entry_list_count(bsa_entry_list_t entry_list); +BSARCH_DLL_API(bsa_result_message_t) bsa_entry_list_add(bsa_entry_list_t entry_list, const wchar_t *entry_string); +BSARCH_DLL_API(uint32_t) +bsa_entry_list_get(bsa_entry_list_t entry_list, + uint32_t index, + uint32_t string_buffer_size, + const wchar_t *string_buffer); + +BSARCH_DLL_API(bsa_archive_t) bsa_create(); +BSARCH_DLL_API(bsa_result_message_t) bsa_free(bsa_archive_t archive); +BSARCH_DLL_API(bsa_result_message_t) bsa_load_from_file(bsa_archive_t archive, const wchar_t *file_path); +BSARCH_DLL_API(bsa_result_message_t) +bsa_create_archive(bsa_archive_t archive, + const wchar_t *file_path, + bsa_archive_type_t archive_type, + bsa_entry_list_t entry_list); +BSARCH_DLL_API(bsa_result_message_t) bsa_save(bsa_archive_t archive); +BSARCH_DLL_API(bsa_result_message_t) +bsa_add_file_from_disk(bsa_archive_t archive, const wchar_t *file_path, const wchar_t *source_path); +BSARCH_DLL_API(bsa_result_message_t) +bsa_add_file_from_disk_root(bsa_archive_t archive, const wchar_t *root_dir, const wchar_t *source_path); +BSARCH_DLL_API(bsa_result_message_t) +bsa_add_file_from_memory(bsa_archive_t archive, const wchar_t *file_path, uint32_t size, bsa_buffer_t data); +BSARCH_DLL_API(bsa_file_record_t) bsa_find_file_record(bsa_archive_t archive, const wchar_t *file_path); +BSARCH_DLL_API(bsa_result_message_buffer_t) +bsa_extract_file_data_by_record(bsa_archive_t archive, bsa_file_record_t file_record); +BSARCH_DLL_API(bsa_result_message_buffer_t) +bsa_extract_file_data_by_filename(bsa_archive_t archive, const wchar_t *file_path); +BSARCH_DLL_API(bsa_result_message_t) bsa_file_data_free(bsa_archive_t archive, bsa_result_buffer_t file_data_result); +BSARCH_DLL_API(bsa_result_message_t) +bsa_extract_file(bsa_archive_t archive, const wchar_t *file_path, const wchar_t *save_as); +BSARCH_DLL_API(bsa_result_message_t) +bsa_iterate_files(bsa_archive_t archive, bsa_file_iteration_proc_t file_iteration_proc, void *context); +BSARCH_DLL_API(bool) bsa_file_exists(bsa_archive_t archive, const wchar_t *file_path); +BSARCH_DLL_API(bsa_result_message_t) +bsa_get_resource_list(bsa_archive_t archive, bsa_entry_list_t entry_result_list, const wchar_t *folder); +BSARCH_DLL_API(bsa_result_message_t) +bsa_resolve_hash(bsa_archive_t archive, uint64_t hash, bsa_entry_list_t entry_result_list); +BSARCH_DLL_API(bsa_result_message_t) bsa_close(bsa_archive_t archive); + +BSARCH_DLL_API(uint32_t) +bsa_filename_get(bsa_archive_t archive, uint32_t string_buffer_size, const wchar_t *string_buffer); +BSARCH_DLL_API(bsa_archive_type_t) bsa_archive_type_get(bsa_archive_t archive); +BSARCH_DLL_API(uint32_t) bsa_version_get(bsa_archive_t archive); +BSARCH_DLL_API(uint32_t) +bsa_format_name_get(bsa_archive_t archive, uint32_t string_buffer_size, const wchar_t *string_buffer); +BSARCH_DLL_API(uint32_t) bsa_file_count_get(bsa_archive_t archive); +BSARCH_DLL_API(uint32_t) bsa_archive_flags_get(bsa_archive_t archive); +BSARCH_DLL_API(void) bsa_archive_flags_set(bsa_archive_t archive, uint32_t flags); +BSARCH_DLL_API(uint32_t) bsa_file_flags_get(bsa_archive_t archive); +BSARCH_DLL_API(void) bsa_file_flags_set(bsa_archive_t archive, uint32_t flags); +BSARCH_DLL_API(bool) bsa_compress_get(bsa_archive_t archive); +BSARCH_DLL_API(void) bsa_compress_set(bsa_archive_t archive, bool flags); +BSARCH_DLL_API(bool) bsa_share_data_get(bsa_archive_t archive); +BSARCH_DLL_API(void) bsa_share_data_set(bsa_archive_t archive, bool flags); + +BSARCH_DLL_API(void) +bsa_file_dds_info_callback_set(bsa_archive_t archive, bsa_file_dds_info_proc_t file_dds_info_proc, void *context); diff --git a/include/qlibbsarch/BSArchive.h b/include/qlibbsarch/BSArchive.h new file mode 100644 index 0000000..a159ffd --- /dev/null +++ b/include/qlibbsarch/BSArchive.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include "BSArchiveEntries.h" +#include + +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/include/qlibbsarch/BSArchiveAuto.h b/include/qlibbsarch/BSArchiveAuto.h new file mode 100644 index 0000000..d4c3ee5 --- /dev/null +++ b/include/qlibbsarch/BSArchiveAuto.h @@ -0,0 +1,91 @@ +#pragma once + +#include "BSArchive.h" +#include "BSArchiveEntries.h" +#include + +/*! + * \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& 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 _filesFromDisk; + QStringList _filesFromDiskRoot; + QMap _filesfromMemory; + QDir _rootDirectory; + + BSArchive _archive; + BSArchiveEntries _entries; +}; diff --git a/include/qlibbsarch/BSArchiveEntries.h b/include/qlibbsarch/BSArchiveEntries.h new file mode 100644 index 0000000..3f3cb63 --- /dev/null +++ b/include/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/include/qlibbsarch/DDS.h b/include/qlibbsarch/DDS.h new file mode 100644 index 0000000..d7fff4d --- /dev/null +++ b/include/qlibbsarch/DDS.h @@ -0,0 +1,244 @@ +//-------------------------------------------------------------------------------------- +// dds.h +// +// This header defines constants and structures that are useful when parsing +// DDS files. DDS files were originally designed to use several structures +// and constants that are native to DirectDraw and are defined in ddraw.h, +// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar +// (compatible) constants and structures so that one can use DDS files +// without needing to include ddraw.h. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// http://go.microsoft.com/fwlink/?LinkId=248926 +// http://go.microsoft.com/fwlink/?LinkId=248929 +// http://go.microsoft.com/fwlink/?LinkID=615561 +//-------------------------------------------------------------------------------------- + +#pragma once + +#include +#include + + +namespace DirectX +{ + +#pragma pack(push,1) + +const uint32_t DDS_MAGIC = 0x20534444; // "DDS " + +struct DDS_PIXELFORMAT +{ + uint32_t size; + uint32_t flags; + uint32_t fourCC; + uint32_t RGBBitCount; + uint32_t RBitMask; + uint32_t GBitMask; + uint32_t BBitMask; + uint32_t ABitMask; +}; + +#define DDS_FOURCC 0x00000004 // DDPF_FOURCC +#define DDS_RGB 0x00000040 // DDPF_RGB +#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS +#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE +#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS +#define DDS_ALPHAPIXELS 0x00000001 // DDPF_ALPHAPIXELS +#define DDS_ALPHA 0x00000002 // DDPF_ALPHA +#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8 +#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS +#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV + +#ifndef MAKEFOURCC + #define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + (static_cast(static_cast(ch0)) \ + | (static_cast(static_cast(ch1)) << 8) \ + | (static_cast(static_cast(ch2)) << 16) \ + | (static_cast(static_cast(ch3)) << 24)) +#endif /* defined(MAKEFOURCC) */ + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT1 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','1'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT2 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','2'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT3 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','3'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT4 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','4'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT5 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','5'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC4_UNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','4','U'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC4_SNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','4','S'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC5_UNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','5','U'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC5_SNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','5','S'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_R8G8_B8G8 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('R','G','B','G'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_G8R8_G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('G','R','G','B'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_YUY2 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('Y','U','Y','2'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_X8R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8B8G8R8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_X8B8G8R8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_G16R16 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_R5G6B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 16, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A1R5G5B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A4R4G4B4 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_L8 = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCE, 0, 8, 0xff, 0x00, 0x00, 0x00 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_L16 = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCE, 0, 16, 0xffff, 0x0000, 0x0000, 0x0000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8L8 = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCEA, 0, 16, 0x00ff, 0x0000, 0x0000, 0xff00 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8L8_ALT = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCEA, 0, 8, 0x00ff, 0x0000, 0x0000, 0xff00 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8 = + { sizeof(DDS_PIXELFORMAT), DDS_ALPHA, 0, 8, 0x00, 0x00, 0x00, 0xff }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_V8U8 = + { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 16, 0x00ff, 0xff00, 0x0000, 0x0000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_Q8W8V8U8 = + { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_V16U16 = + { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }; + +// D3DFMT_A2R10G10B10/D3DFMT_A2B10G10R10 should be written using DX10 extension to avoid D3DX 10:10:10:2 reversal issue + +// This indicates the DDS_HEADER_DXT10 extension is present (the format is in dxgiFormat) +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DX10 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','1','0'), 0, 0, 0, 0, 0 }; + +#define DDS_HEADER_FLAGS_TEXTURE 0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT +#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT +#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH +#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH +#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE + +#define DDS_HEIGHT 0x00000002 // DDSD_HEIGHT +#define DDS_WIDTH 0x00000004 // DDSD_WIDTH + +#define DDS_SURFACE_FLAGS_TEXTURE 0x00001000 // DDSCAPS_TEXTURE +#define DDS_SURFACE_FLAGS_MIPMAP 0x00400008 // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP +#define DDS_SURFACE_FLAGS_CUBEMAP 0x00000008 // DDSCAPS_COMPLEX + +#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX +#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX +#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY +#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY +#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ +#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + +#define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\ + DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\ + DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ ) + +#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP + +#define DDS_FLAGS_VOLUME 0x00200000 // DDSCAPS2_VOLUME + +// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION +enum DDS_RESOURCE_DIMENSION +{ + DDS_DIMENSION_TEXTURE1D = 2, + DDS_DIMENSION_TEXTURE2D = 3, + DDS_DIMENSION_TEXTURE3D = 4, +}; + +// Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG +enum DDS_RESOURCE_MISC_FLAG +{ + DDS_RESOURCE_MISC_TEXTURECUBE = 0x4L, +}; + +enum DDS_MISC_FLAGS2 +{ + DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L, +}; + +enum DDS_ALPHA_MODE +{ + DDS_ALPHA_MODE_UNKNOWN = 0, + DDS_ALPHA_MODE_STRAIGHT = 1, + DDS_ALPHA_MODE_PREMULTIPLIED = 2, + DDS_ALPHA_MODE_OPAQUE = 3, + DDS_ALPHA_MODE_CUSTOM = 4, +}; + +struct DDS_HEADER +{ + uint32_t size; + uint32_t flags; + uint32_t height; + uint32_t width; + uint32_t pitchOrLinearSize; + uint32_t depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags + uint32_t mipMapCount; + uint32_t reserved1[11]; + DDS_PIXELFORMAT ddspf; + uint32_t caps; + uint32_t caps2; + uint32_t caps3; + uint32_t caps4; + uint32_t reserved2; +}; + +struct DDS_HEADER_DXT10 +{ + DXGI_FORMAT dxgiFormat; + uint32_t resourceDimension; + uint32_t miscFlag; // see D3D11_RESOURCE_MISC_FLAG + uint32_t arraySize; + uint32_t miscFlags2; // see DDS_MISC_FLAGS2 +}; + +#pragma pack(pop) + +static_assert( sizeof(DDS_HEADER) == 124, "DDS Header size mismatch" ); +static_assert( sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch"); + +} // namespace diff --git a/include/qlibbsarch/QLibbsarch.h b/include/qlibbsarch/QLibbsarch.h new file mode 100644 index 0000000..c25b1bb --- /dev/null +++ b/include/qlibbsarch/QLibbsarch.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace QLibBsarch +{ + constexpr bool enableDebugLog = true; + +#define LOG_LIBBSARCH \ + if constexpr (QLibBsarch::enableDebugLog) \ + qDebug() << "[QLIBBSARCH] " << __FUNCTION__ << ' ' + +#define PREPARE_PATH_LIBBSARCH(qstring) reinterpret_cast(QDir::toNativeSeparators(qstring).utf16()) + + inline const std::string wcharToString(const wchar_t *text) { return QString::fromWCharArray(text).toStdString(); } + + inline void checkResult(const bsa_result_message_s &result) + { + if (result.code == BSA_RESULT_EXCEPTION) + { + const std::string &error = QLibBsarch::wcharToString(result.text); + LOG_LIBBSARCH << QString::fromStdString(error); + throw std::runtime_error(error); + } + } + + inline void checkResult(const bsa_result_message_buffer_s &result) + { + if (result.message.code == BSA_RESULT_EXCEPTION) + { + const std::string &error = QLibBsarch::wcharToString(result.message.text); + LOG_LIBBSARCH << QString::fromStdString(error); + throw std::runtime_error(error); + } + } + +} // namespace QLibBsarch diff --git a/lib/DirectXTex.lib b/lib/DirectXTex.lib new file mode 100644 index 0000000..e72bf75 Binary files /dev/null and b/lib/DirectXTex.lib differ diff --git a/lib/DirectXTex.pdb b/lib/DirectXTex.pdb new file mode 100644 index 0000000..08b370a Binary files /dev/null and b/lib/DirectXTex.pdb differ diff --git a/lib/libbsarch.lib b/lib/libbsarch.lib new file mode 100644 index 0000000..fab2259 Binary files /dev/null and b/lib/libbsarch.lib differ diff --git a/src/ArchiveAutoService.cpp b/src/ArchiveAutoService.cpp new file mode 100644 index 0000000..ae93a6a --- /dev/null +++ b/src/ArchiveAutoService.cpp @@ -0,0 +1,12 @@ +#include + +namespace BsaPacker +{ + void ArchiveAutoService::CreateBSA(BSArchiveAuto* archive, + const QString& archiveName, + const bsa_archive_type_e type) const + { + archive->create(archiveName, type); + archive->save(); + } +} // namespace BsaPacker diff --git a/src/ArchiveBuildDirector.cpp b/src/ArchiveBuildDirector.cpp new file mode 100644 index 0000000..5f04edc --- /dev/null +++ b/src/ArchiveBuildDirector.cpp @@ -0,0 +1,34 @@ +#include + +#include +#include +#include +#include +#include + +#include "SettingsService.h" + +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(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/src/ArchiveBuilderFactory.cpp b/src/ArchiveBuilderFactory.cpp new file mode 100644 index 0000000..29c56cf --- /dev/null +++ b/src/ArchiveBuilderFactory.cpp @@ -0,0 +1,56 @@ +#include + +#include +#include +#include +#include + +#include + +#include "NexusId.h" + + +namespace BsaPacker +{ + ArchiveBuilderFactory::ArchiveBuilderFactory(const IArchiveBuilderHelper* archiveBuilderHelper) + : m_ArchiveBuilderHelper(archiveBuilderHelper) + { + } + + std::vector ArchiveBuilderFactory::GetArchiveTypes(const IModDto* modDto) const + { + switch (modDto->NexusId()) { + case NexusId::Morrowind: + return std::vector { baTES3 }; + case NexusId::Oblivion: + return std::vector { baTES4 }; + case NexusId::Fallout3: + case NexusId::NewVegas: + case NexusId::Skyrim: + return std::vector { baFO3 }; + case NexusId::SkyrimSE: + return std::vector { baSSE }; + case NexusId::Fallout4: + return std::vector { baFO4, baFO4dds }; + default: + return std::vector { baNone }; + } + } + + std::unique_ptr ArchiveBuilderFactory::Create(const bsa_archive_type_e archiveType, const IModDto* modDto) const + { + switch (archiveType) { + case baTES3: + case baTES4: + case baFO3: + return std::make_unique(this->m_ArchiveBuilderHelper, modDto->Path()); + case baFO4: + return std::make_unique(this->m_ArchiveBuilderHelper, modDto->Path()); + case baFO4dds: + return std::make_unique(this->m_ArchiveBuilderHelper, modDto->Path()); + case baNone: + default: + return std::make_unique(); + } + } +} // namespace BsaPacker diff --git a/src/ArchiveBuilderHelper.cpp b/src/ArchiveBuilderHelper.cpp new file mode 100644 index 0000000..5bd9e89 --- /dev/null +++ b/src/ArchiveBuilderHelper.cpp @@ -0,0 +1,67 @@ +#include + +#include +#include + +#include "SettingsService.h" + +namespace BsaPacker +{ + const std::array ArchiveBuilderHelper::INCOMPRESSIBLE_TYPES = { "wav", "ogg", "mp3" }; + //const std::array ArchiveBuilderHelper::BLACKLIST = { "ini", "txt" }; + + ArchiveBuilderHelper::ArchiveBuilderHelper(const ISettingsService* settingsService) + : m_SettingsService(settingsService) + { + } + uint32_t ArchiveBuilderHelper::getFileCount(const QDir& rootDirectory) const + { + QDirIterator it(rootDirectory, QDirIterator::Subdirectories); + uint32_t count = 0; + while (it.hasNext()) { + it.next(); + count++; + } + return count; + } + + QStringList ArchiveBuilderHelper::getRootDirectoryFilenames(const QDir& rootDirectory) const + { + const std::function getFilename = [](const QFileInfo& fi) { + return fi.fileName(); + }; + return QtConcurrent::blockingMapped(rootDirectory.entryInfoList(QDir::Files), + getFilename); + } + + bool ArchiveBuilderHelper::isFileIgnorable(const QString& filepath, const QStringList& rootDirFilenames) const + { + const QFileInfo& fileInfo(filepath); + const QString& filename = fileInfo.fileName(); + return rootDirFilenames.contains(filename) || // ignore files within mod directory + fileInfo.isDir() || // ignore directories + filename == "." || filename == ".." || // ignore current dir and parent dir entries + ArchiveBuilderHelper::isExtensionBlacklisted(filename); // ignore user blacklisted file types + } + + bool ArchiveBuilderHelper::isIncompressible(const QString& filename) const + { + for (const QString& str : ArchiveBuilderHelper::INCOMPRESSIBLE_TYPES) { + if (filename.endsWith(str, Qt::CaseInsensitive)) { + return true; + } + } + return false; + } + + bool ArchiveBuilderHelper::isExtensionBlacklisted(const QString& filename) const + { + const QStringList& blacklist = this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_BLACKLISTED_FILES).toString().split(';'); + for (const QString& ext : blacklist) { + if (filename.endsWith(ext, Qt::CaseInsensitive)) { + return true; + } + } + return false; + } +} // namespace BsaPacker diff --git a/src/ArchiveExtensionService.cpp b/src/ArchiveExtensionService.cpp new file mode 100644 index 0000000..98c4d8b --- /dev/null +++ b/src/ArchiveExtensionService.cpp @@ -0,0 +1,28 @@ +#include "ArchiveExtensionService.h" + +#include "NexusId.h" + +namespace BsaPacker +{ + ArchiveExtensionService::ArchiveExtensionService(const IModContext* modContext) + : m_ModContext(modContext) + { + } + + QString ArchiveExtensionService::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: + return QStringLiteral(".bsa"); + case NexusId::Fallout4: + return QStringLiteral(".ba2"); + default: + return QString(); + } + } +} diff --git a/src/ArchiveExtensionService.h b/src/ArchiveExtensionService.h new file mode 100644 index 0000000..5007e8b --- /dev/null +++ b/src/ArchiveExtensionService.h @@ -0,0 +1,21 @@ +#ifndef ARCHIVEEXTENSIONSERVICE_H +#define ARCHIVEEXTENSIONSERVICE_H + +#include +#include + +namespace BsaPacker +{ + class ArchiveExtensionService : public IArchiveExtensionService + { + public: + ArchiveExtensionService(const IModContext* modContext); + ~ArchiveExtensionService() override = default; + QString GetFileExtension() const override; + + private: + const IModContext* m_ModContext = nullptr; + }; +} + +#endif // ARCHIVEEXTENSIONSERVICE_H diff --git a/src/BSArchive.cpp b/src/BSArchive.cpp new file mode 100644 index 0000000..f20be8f --- /dev/null +++ b/src/BSArchive.cpp @@ -0,0 +1,183 @@ +#include + +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 << endl + << "type: " << type << endl + << "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 << endl + << "Filename: " << filename << endl + << "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 << endl + << "Filepath: " << filePath << endl + << "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 << endl << "Data size: " << data.size(); + + uint32_t size = static_cast(data.size()); + bsa_buffer_t buffer = const_cast(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(result.buffer.data); + int size = static_cast(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(result.buffer.data); + int size = static_cast(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/src/BSArchiveAuto.cpp b/src/BSArchiveAuto.cpp new file mode 100644 index 0000000..f2ef7f8 --- /dev/null +++ b/src/BSArchiveAuto.cpp @@ -0,0 +1,101 @@ +#include +#include + +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 &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/src/BSArchiveEntries.cpp b/src/BSArchiveEntries.cpp new file mode 100644 index 0000000..6a3045d --- /dev/null +++ b/src/BSArchiveEntries.cpp @@ -0,0 +1,66 @@ +#include + +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/src/BsaPacker.cpp b/src/BsaPacker.cpp new file mode 100644 index 0000000..d08023c --- /dev/null +++ b/src/BsaPacker.cpp @@ -0,0 +1,117 @@ +#include + +#include +#include +#include +#include +#include "ArchiveExtensionService.h" +#include "DummyPluginLogic.h" +#include "DummyPluginServiceFactory.h" +#include "FileWriterService.h" +#include "HideLooseAssetService.h" +#include "ModContext.h" +#include "ModDto.h" +#include "SettingsService.h" +#include "WorkClass.h" +#include +#include +#include + +#include + +#include + + +namespace BsaPacker +{ + bool Bsa_Packer::init(MOBase::IOrganizer* moInfo) + { + this->m_Organizer = moInfo; + this->m_ModContext = std::make_unique(this->m_Organizer); + this->m_SettingsService = std::make_unique(this->m_Organizer); + return true; + } + + QString Bsa_Packer::name() const + { + return QStringLiteral("BSA Packer"); + } + + QString Bsa_Packer::author() const + { + return QStringLiteral("MattyFez"); + } + + 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, 0, 0, MOBase::VersionInfo::RELEASE_FINAL); + } + + bool Bsa_Packer::isActive() const + { + return this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_ENABLED).toBool(); + //return true; //m_Organizer->pluginSetting(this->name(), SettingsService::SETTING_ENABLED).toBool(); + } + + QList Bsa_Packer::settings() const + { + return SettingsService::PluginSettings; + //return this->m_SettingsService->GetPluginSettings(); // reads this before settings IOrganizer + /* + return QList { + 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_ENABLED, QObject::tr("Allow this plugin to run."), false), + MOBase::PluginSetting(SettingsService::SETTING_BLACKLISTED_FILES, QObject::tr("Specify a semi-colon seperated list of file extensions to ignore when packing."), ".txt;.hkx;.xml;.ini") + //MOBase::PluginSetting(SettingsService::SETTING_SPLIT_ARCHIVES, QObject::tr("Automatically create multiple archives if they exceed the size limit."), false); + }; + */ + } + + 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 + { + const auto injector = di::make_injector( + di::bind.to(this->m_ModContext.get()), + di::bind.to(this->m_SettingsService.get()), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to(), + di::bind.to() + //di::bind>().to(di::extension::factory{}) + ); + + BsaPackerWorker worker = di::create(injector); + worker.DoWork(); + } + +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) + Q_EXPORT_PLUGIN2(myTool, BSA_Packer) +#endif + +} // namespace BsaPacker diff --git a/src/BsaPacker.h b/src/BsaPacker.h new file mode 100644 index 0000000..aae9850 --- /dev/null +++ b/src/BsaPacker.h @@ -0,0 +1,47 @@ +#ifndef BSA_PACKER_H +#define BSA_PACKER_H + +#include +#include +#include + +#include +#include + +namespace di = boost::di; + +namespace BsaPacker +{ + class 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]] bool isActive() const override; + [[nodiscard]] QList 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 m_ModContext = nullptr; + std::unique_ptr m_SettingsService = nullptr; + }; +} // namespace BsaPacker +#endif //BSA_PACKER_H diff --git a/src/BsaPackerWorker.cpp b/src/BsaPackerWorker.cpp new file mode 100644 index 0000000..2e8a5ba --- /dev/null +++ b/src/BsaPackerWorker.cpp @@ -0,0 +1,51 @@ +#include "WorkClass.h" + +#include + +#include +#include +#include + +#include + +namespace BsaPacker +{ + BsaPackerWorker::BsaPackerWorker( + const ISettingsService* settingsService, + const IModDtoFactory* modDtoFactory, + const IArchiveBuilderFactory* archiveBuilderFactory, + const IArchiveAutoService* archiveAutoService, + const IDummyPluginServiceFactory* dummyPluginServiceFactory, + const IHideLooseAssetService* hideLooseAssetService) + : m_SettingsService(settingsService), + m_ModDtoFactory(modDtoFactory), + m_ArchiveBuilderFactory(archiveBuilderFactory), + m_ArchiveAutoService(archiveAutoService), + m_DummyPluginServiceFactory(dummyPluginServiceFactory), + m_HideLooseAssetService(hideLooseAssetService) + { + } + + void BsaPackerWorker::DoWork() const + { + const std::unique_ptr modDto = this->m_ModDtoFactory->Create(); // handles PackerDialog and validation, implements Null Object pattern + const std::vector types = this->m_ArchiveBuilderFactory->GetArchiveTypes(modDto.get()); + for (auto&& type : types) { + const std::unique_ptr builder = this->m_ArchiveBuilderFactory->Create(type, modDto.get()); + ArchiveBuildDirector director(this->m_SettingsService, builder.get()); + director.Construct(); // must check if cancelled + const std::unique_ptr archive = builder->getArchive(); + if (archive) { + const QString& archiveName = modDto->ArchiveName() + modDto->ArchiveExtension(); + this->m_ArchiveAutoService->CreateBSA(archive.get(), archiveName, type); + QMessageBox::information(nullptr, "","Created " + archiveName); + } + } + const std::unique_ptr pluginService = this->m_DummyPluginServiceFactory->Create(); + pluginService->CreatePlugin(modDto->Path(), modDto->ArchiveName()); + + this->m_HideLooseAssetService->HideLooseAssets(modDto->Path()); + } +} + + diff --git a/src/BsaPackerWorker.h b/src/BsaPackerWorker.h new file mode 100644 index 0000000..18d22da --- /dev/null +++ b/src/BsaPackerWorker.h @@ -0,0 +1,43 @@ +#ifndef BSAPACKERWORKER_H +#define BSAPACKERWORKER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace di = boost::di; + +namespace BsaPacker +{ + class BsaPackerWorker + { + public: + //WorkClass(const di::extension::ifactory& modDtoFactory); + BsaPackerWorker( + const ISettingsService* settingsService, + const IModDtoFactory* modDtoFactory, + const IArchiveBuilderFactory* archiveBuilderFactory, + const IArchiveAutoService* archiveAutoService, + const IDummyPluginServiceFactory* dummyPluginService, + const IHideLooseAssetService* hideLooseAssetService); + void DoWork() const; + + private: + //const di::extension::ifactory& m_ModDtoFactory; + 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; + }; +} + +#endif // BSAPACKERWORKER_H diff --git a/src/DirectXTex.h b/src/DirectXTex.h new file mode 100644 index 0000000..a916d75 --- /dev/null +++ b/src/DirectXTex.h @@ -0,0 +1,783 @@ +//------------------------------------------------------------------------------------- +// DirectXTex.h +// +// DirectX Texture Library +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// http://go.microsoft.com/fwlink/?LinkId=248926 +//------------------------------------------------------------------------------------- + +#pragma once + +#include + +#include +#include +#include + +#if !defined(__d3d11_h__) && !defined(__d3d11_x_h__) && !defined(__d3d12_h__) && !defined(__d3d12_x_h__) +#if defined(_XBOX_ONE) && defined(_TITLE) +#include +#else +#include +#endif +#endif + +#include + +#include + +#define DIRECTX_TEX_VERSION 162 + +struct IWICImagingFactory; +struct IWICMetadataQueryReader; + + +namespace DirectX +{ + + //--------------------------------------------------------------------------------- + // DXGI Format Utilities + bool __cdecl IsValid(_In_ DXGI_FORMAT fmt); + bool __cdecl IsCompressed(_In_ DXGI_FORMAT fmt); + bool __cdecl IsPacked(_In_ DXGI_FORMAT fmt); + bool __cdecl IsVideo(_In_ DXGI_FORMAT fmt); + bool __cdecl IsPlanar(_In_ DXGI_FORMAT fmt); + bool __cdecl IsPalettized(_In_ DXGI_FORMAT fmt); + bool __cdecl IsDepthStencil(_In_ DXGI_FORMAT fmt); + bool __cdecl IsSRGB(_In_ DXGI_FORMAT fmt); + bool __cdecl IsTypeless(_In_ DXGI_FORMAT fmt, _In_ bool partialTypeless = true); + + bool __cdecl HasAlpha(_In_ DXGI_FORMAT fmt); + + size_t __cdecl BitsPerPixel(_In_ DXGI_FORMAT fmt); + + size_t __cdecl BitsPerColor(_In_ DXGI_FORMAT fmt); + + enum CP_FLAGS + { + CP_FLAGS_NONE = 0x0, // Normal operation + CP_FLAGS_LEGACY_DWORD = 0x1, // Assume pitch is DWORD aligned instead of BYTE aligned + CP_FLAGS_PARAGRAPH = 0x2, // Assume pitch is 16-byte aligned instead of BYTE aligned + CP_FLAGS_YMM = 0x4, // Assume pitch is 32-byte aligned instead of BYTE aligned + CP_FLAGS_ZMM = 0x8, // Assume pitch is 64-byte aligned instead of BYTE aligned + CP_FLAGS_PAGE4K = 0x200, // Assume pitch is 4096-byte aligned instead of BYTE aligned + CP_FLAGS_BAD_DXTN_TAILS = 0x1000, // BC formats with malformed mipchain blocks smaller than 4x4 + CP_FLAGS_24BPP = 0x10000, // Override with a legacy 24 bits-per-pixel format size + CP_FLAGS_16BPP = 0x20000, // Override with a legacy 16 bits-per-pixel format size + CP_FLAGS_8BPP = 0x40000, // Override with a legacy 8 bits-per-pixel format size + }; + + HRESULT __cdecl ComputePitch( + _In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, + _Out_ size_t& rowPitch, _Out_ size_t& slicePitch, _In_ DWORD flags = CP_FLAGS_NONE); + + size_t __cdecl ComputeScanlines(_In_ DXGI_FORMAT fmt, _In_ size_t height); + + DXGI_FORMAT __cdecl MakeSRGB(_In_ DXGI_FORMAT fmt); + DXGI_FORMAT __cdecl MakeTypeless(_In_ DXGI_FORMAT fmt); + DXGI_FORMAT __cdecl MakeTypelessUNORM(_In_ DXGI_FORMAT fmt); + DXGI_FORMAT __cdecl MakeTypelessFLOAT(_In_ DXGI_FORMAT fmt); + + //--------------------------------------------------------------------------------- + // Texture metadata + enum TEX_DIMENSION + // Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION + { + TEX_DIMENSION_TEXTURE1D = 2, + TEX_DIMENSION_TEXTURE2D = 3, + TEX_DIMENSION_TEXTURE3D = 4, + }; + + enum TEX_MISC_FLAG + // Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG + { + TEX_MISC_TEXTURECUBE = 0x4L, + }; + + enum TEX_MISC_FLAG2 + { + TEX_MISC2_ALPHA_MODE_MASK = 0x7L, + }; + + enum TEX_ALPHA_MODE + // Matches DDS_ALPHA_MODE, encoded in MISC_FLAGS2 + { + TEX_ALPHA_MODE_UNKNOWN = 0, + TEX_ALPHA_MODE_STRAIGHT = 1, + TEX_ALPHA_MODE_PREMULTIPLIED = 2, + TEX_ALPHA_MODE_OPAQUE = 3, + TEX_ALPHA_MODE_CUSTOM = 4, + }; + + struct TexMetadata + { + size_t width; + size_t height; // Should be 1 for 1D textures + size_t depth; // Should be 1 for 1D or 2D textures + size_t arraySize; // For cubemap, this is a multiple of 6 + size_t mipLevels; + uint32_t miscFlags; + uint32_t miscFlags2; + DXGI_FORMAT format; + TEX_DIMENSION dimension; + + size_t __cdecl ComputeIndex(_In_ size_t mip, _In_ size_t item, _In_ size_t slice) const; + // Returns size_t(-1) to indicate an out-of-range error + + bool __cdecl IsCubemap() const { return (miscFlags & TEX_MISC_TEXTURECUBE) != 0; } + // Helper for miscFlags + + bool __cdecl IsPMAlpha() const { return ((miscFlags2 & TEX_MISC2_ALPHA_MODE_MASK) == TEX_ALPHA_MODE_PREMULTIPLIED) != 0; } + void __cdecl SetAlphaMode(TEX_ALPHA_MODE mode) { miscFlags2 = (miscFlags2 & ~static_cast(TEX_MISC2_ALPHA_MODE_MASK)) | static_cast(mode); } + TEX_ALPHA_MODE __cdecl GetAlphaMode() const { return static_cast(miscFlags2 & TEX_MISC2_ALPHA_MODE_MASK); } + // Helpers for miscFlags2 + + bool __cdecl IsVolumemap() const { return (dimension == TEX_DIMENSION_TEXTURE3D); } + // Helper for dimension + }; + + enum DDS_FLAGS + { + DDS_FLAGS_NONE = 0x0, + + DDS_FLAGS_LEGACY_DWORD = 0x1, + // Assume pitch is DWORD aligned instead of BYTE aligned (used by some legacy DDS files) + + DDS_FLAGS_NO_LEGACY_EXPANSION = 0x2, + // Do not implicitly convert legacy formats that result in larger pixel sizes (24 bpp, 3:3:2, A8L8, A4L4, P8, A8P8) + + DDS_FLAGS_NO_R10B10G10A2_FIXUP = 0x4, + // Do not use work-around for long-standing D3DX DDS file format issue which reversed the 10:10:10:2 color order masks + + DDS_FLAGS_FORCE_RGB = 0x8, + // Convert DXGI 1.1 BGR formats to DXGI_FORMAT_R8G8B8A8_UNORM to avoid use of optional WDDM 1.1 formats + + DDS_FLAGS_NO_16BPP = 0x10, + // Conversions avoid use of 565, 5551, and 4444 formats and instead expand to 8888 to avoid use of optional WDDM 1.2 formats + + DDS_FLAGS_EXPAND_LUMINANCE = 0x20, + // When loading legacy luminance formats expand replicating the color channels rather than leaving them packed (L8, L16, A8L8) + + DDS_FLAGS_BAD_DXTN_TAILS = 0x40, + // Some older DXTn DDS files incorrectly handle mipchain tails for blocks smaller than 4x4 + + DDS_FLAGS_FORCE_DX10_EXT = 0x10000, + // Always use the 'DX10' header extension for DDS writer (i.e. don't try to write DX9 compatible DDS files) + + DDS_FLAGS_FORCE_DX10_EXT_MISC2 = 0x20000, + // DDS_FLAGS_FORCE_DX10_EXT including miscFlags2 information (result may not be compatible with D3DX10 or D3DX11) + }; + + enum WIC_FLAGS + { + WIC_FLAGS_NONE = 0x0, + + WIC_FLAGS_FORCE_RGB = 0x1, + // Loads DXGI 1.1 BGR formats as DXGI_FORMAT_R8G8B8A8_UNORM to avoid use of optional WDDM 1.1 formats + + WIC_FLAGS_NO_X2_BIAS = 0x2, + // Loads DXGI 1.1 X2 10:10:10:2 format as DXGI_FORMAT_R10G10B10A2_UNORM + + WIC_FLAGS_NO_16BPP = 0x4, + // Loads 565, 5551, and 4444 formats as 8888 to avoid use of optional WDDM 1.2 formats + + WIC_FLAGS_ALLOW_MONO = 0x8, + // Loads 1-bit monochrome (black & white) as R1_UNORM rather than 8-bit grayscale + + WIC_FLAGS_ALL_FRAMES = 0x10, + // Loads all images in a multi-frame file, converting/resizing to match the first frame as needed, defaults to 0th frame otherwise + + WIC_FLAGS_IGNORE_SRGB = 0x20, + // Ignores sRGB metadata if present in the file + + WIC_FLAGS_FORCE_SRGB = 0x40, + // Writes sRGB metadata into the file reguardless of format + + WIC_FLAGS_FORCE_LINEAR = 0x80, + // Writes linear gamma metadata into the file reguardless of format + + WIC_FLAGS_DITHER = 0x10000, + // Use ordered 4x4 dithering for any required conversions + + WIC_FLAGS_DITHER_DIFFUSION = 0x20000, + // Use error-diffusion dithering for any required conversions + + WIC_FLAGS_FILTER_POINT = 0x100000, + WIC_FLAGS_FILTER_LINEAR = 0x200000, + WIC_FLAGS_FILTER_CUBIC = 0x300000, + WIC_FLAGS_FILTER_FANT = 0x400000, // Combination of Linear and Box filter + // Filtering mode to use for any required image resizing (only needed when loading arrays of differently sized images; defaults to Fant) + }; + + HRESULT __cdecl GetMetadataFromDDSMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _In_ DWORD flags, + _Out_ TexMetadata& metadata); + HRESULT __cdecl GetMetadataFromDDSFile( + _In_z_ const wchar_t* szFile, + _In_ DWORD flags, + _Out_ TexMetadata& metadata); + + HRESULT __cdecl GetMetadataFromHDRMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _Out_ TexMetadata& metadata); + HRESULT __cdecl GetMetadataFromHDRFile( + _In_z_ const wchar_t* szFile, + _Out_ TexMetadata& metadata); + + HRESULT __cdecl GetMetadataFromTGAMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _Out_ TexMetadata& metadata); + HRESULT __cdecl GetMetadataFromTGAFile( + _In_z_ const wchar_t* szFile, + _Out_ TexMetadata& metadata); + + HRESULT __cdecl GetMetadataFromWICMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _In_ DWORD flags, + _Out_ TexMetadata& metadata, + _In_opt_ std::function getMQR = nullptr); + + HRESULT __cdecl GetMetadataFromWICFile( + _In_z_ const wchar_t* szFile, + _In_ DWORD flags, + _Out_ TexMetadata& metadata, + _In_opt_ std::function getMQR = nullptr); + + //--------------------------------------------------------------------------------- + // Bitmap image container + struct Image + { + size_t width; + size_t height; + DXGI_FORMAT format; + size_t rowPitch; + size_t slicePitch; + uint8_t* pixels; + }; + + class ScratchImage + { + public: + ScratchImage() noexcept + : m_nimages(0), m_size(0), m_metadata{}, m_image(nullptr), m_memory(nullptr) {} + ScratchImage(ScratchImage&& moveFrom) noexcept + : m_nimages(0), m_size(0), m_metadata{}, m_image(nullptr), m_memory(nullptr) { *this = std::move(moveFrom); } + ~ScratchImage() { Release(); } + + ScratchImage& __cdecl operator= (ScratchImage&& moveFrom) noexcept; + + ScratchImage(const ScratchImage&) = delete; + ScratchImage& operator=(const ScratchImage&) = delete; + + HRESULT __cdecl Initialize(_In_ const TexMetadata& mdata, _In_ DWORD flags = CP_FLAGS_NONE); + + HRESULT __cdecl Initialize1D(_In_ DXGI_FORMAT fmt, _In_ size_t length, _In_ size_t arraySize, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE); + HRESULT __cdecl Initialize2D(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t arraySize, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE); + HRESULT __cdecl Initialize3D(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t depth, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE); + HRESULT __cdecl InitializeCube(_In_ DXGI_FORMAT fmt, _In_ size_t width, _In_ size_t height, _In_ size_t nCubes, _In_ size_t mipLevels, _In_ DWORD flags = CP_FLAGS_NONE); + + HRESULT __cdecl InitializeFromImage(_In_ const Image& srcImage, _In_ bool allow1D = false, _In_ DWORD flags = CP_FLAGS_NONE); + HRESULT __cdecl InitializeArrayFromImages(_In_reads_(nImages) const Image* images, _In_ size_t nImages, _In_ bool allow1D = false, _In_ DWORD flags = CP_FLAGS_NONE); + HRESULT __cdecl InitializeCubeFromImages(_In_reads_(nImages) const Image* images, _In_ size_t nImages, _In_ DWORD flags = CP_FLAGS_NONE); + HRESULT __cdecl Initialize3DFromImages(_In_reads_(depth) const Image* images, _In_ size_t depth, _In_ DWORD flags = CP_FLAGS_NONE); + + void __cdecl Release(); + + bool __cdecl OverrideFormat(_In_ DXGI_FORMAT f); + + const TexMetadata& __cdecl GetMetadata() const { return m_metadata; } + const Image* __cdecl GetImage(_In_ size_t mip, _In_ size_t item, _In_ size_t slice) const; + + const Image* __cdecl GetImages() const { return m_image; } + size_t __cdecl GetImageCount() const { return m_nimages; } + + uint8_t* __cdecl GetPixels() const { return m_memory; } + size_t __cdecl GetPixelsSize() const { return m_size; } + + bool __cdecl IsAlphaAllOpaque() const; + + private: + size_t m_nimages; + size_t m_size; + TexMetadata m_metadata; + Image* m_image; + uint8_t* m_memory; + }; + + //--------------------------------------------------------------------------------- + // Memory blob (allocated buffer pointer is always 16-byte aligned) + class Blob + { + public: + Blob() noexcept : m_buffer(nullptr), m_size(0) {} + Blob(Blob&& moveFrom) noexcept : m_buffer(nullptr), m_size(0) { *this = std::move(moveFrom); } + ~Blob() { Release(); } + + Blob& __cdecl operator= (Blob&& moveFrom) noexcept; + + Blob(const Blob&) = delete; + Blob& operator=(const Blob&) = delete; + + HRESULT __cdecl Initialize(_In_ size_t size); + + void __cdecl Release(); + + void *__cdecl GetBufferPointer() const { return m_buffer; } + size_t __cdecl GetBufferSize() const { return m_size; } + + HRESULT __cdecl Trim(size_t size); + + private: + void* m_buffer; + size_t m_size; + }; + + //--------------------------------------------------------------------------------- + // Image I/O + + // DDS operations + HRESULT __cdecl LoadFromDDSMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _In_ DWORD flags, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); + HRESULT __cdecl LoadFromDDSFile( + _In_z_ const wchar_t* szFile, + _In_ DWORD flags, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); + + HRESULT __cdecl SaveToDDSMemory( + _In_ const Image& image, + _In_ DWORD flags, + _Out_ Blob& blob); + HRESULT __cdecl SaveToDDSMemory( + _In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD flags, + _Out_ Blob& blob); + + HRESULT __cdecl SaveToDDSFile(_In_ const Image& image, _In_ DWORD flags, _In_z_ const wchar_t* szFile); + HRESULT __cdecl SaveToDDSFile( + _In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD flags, _In_z_ const wchar_t* szFile); + + // HDR operations + HRESULT __cdecl LoadFromHDRMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); + HRESULT __cdecl LoadFromHDRFile( + _In_z_ const wchar_t* szFile, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); + + HRESULT __cdecl SaveToHDRMemory(_In_ const Image& image, _Out_ Blob& blob); + HRESULT __cdecl SaveToHDRFile(_In_ const Image& image, _In_z_ const wchar_t* szFile); + + // TGA operations + HRESULT __cdecl LoadFromTGAMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); + HRESULT __cdecl LoadFromTGAFile( + _In_z_ const wchar_t* szFile, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); + + HRESULT __cdecl SaveToTGAMemory(_In_ const Image& image, _Out_ Blob& blob); + HRESULT __cdecl SaveToTGAFile(_In_ const Image& image, _In_z_ const wchar_t* szFile); + + // WIC operations + HRESULT __cdecl LoadFromWICMemory( + _In_reads_bytes_(size) const void* pSource, _In_ size_t size, + _In_ DWORD flags, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image, + _In_opt_ std::function getMQR = nullptr); + HRESULT __cdecl LoadFromWICFile( + _In_z_ const wchar_t* szFile, _In_ DWORD flags, + _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image, + _In_opt_ std::function getMQR = nullptr); + + HRESULT __cdecl SaveToWICMemory( + _In_ const Image& image, _In_ DWORD flags, _In_ REFGUID guidContainerFormat, + _Out_ Blob& blob, _In_opt_ const GUID* targetFormat = nullptr, + _In_opt_ std::function setCustomProps = nullptr); + HRESULT __cdecl SaveToWICMemory( + _In_count_(nimages) const Image* images, _In_ size_t nimages, + _In_ DWORD flags, _In_ REFGUID guidContainerFormat, + _Out_ Blob& blob, _In_opt_ const GUID* targetFormat = nullptr, + _In_opt_ std::function setCustomProps = nullptr); + + HRESULT __cdecl SaveToWICFile( + _In_ const Image& image, _In_ DWORD flags, _In_ REFGUID guidContainerFormat, + _In_z_ const wchar_t* szFile, _In_opt_ const GUID* targetFormat = nullptr, + _In_opt_ std::function setCustomProps = nullptr); + HRESULT __cdecl SaveToWICFile( + _In_count_(nimages) const Image* images, _In_ size_t nimages, + _In_ DWORD flags, _In_ REFGUID guidContainerFormat, + _In_z_ const wchar_t* szFile, _In_opt_ const GUID* targetFormat = nullptr, + _In_opt_ std::function setCustomProps = nullptr); + + //--------------------------------------------------------------------------------- + // Texture conversion, resizing, mipmap generation, and block compression + + enum TEX_FR_FLAGS + { + TEX_FR_ROTATE0 = 0x0, + TEX_FR_ROTATE90 = 0x1, + TEX_FR_ROTATE180 = 0x2, + TEX_FR_ROTATE270 = 0x3, + TEX_FR_FLIP_HORIZONTAL = 0x08, + TEX_FR_FLIP_VERTICAL = 0x10, + }; + + HRESULT __cdecl FlipRotate(_In_ const Image& srcImage, _In_ DWORD flags, _Out_ ScratchImage& image); + HRESULT __cdecl FlipRotate( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD flags, _Out_ ScratchImage& result); + // Flip and/or rotate image + + enum TEX_FILTER_FLAGS + { + TEX_FILTER_DEFAULT = 0, + + TEX_FILTER_WRAP_U = 0x1, + TEX_FILTER_WRAP_V = 0x2, + TEX_FILTER_WRAP_W = 0x4, + TEX_FILTER_WRAP = (TEX_FILTER_WRAP_U | TEX_FILTER_WRAP_V | TEX_FILTER_WRAP_W), + TEX_FILTER_MIRROR_U = 0x10, + TEX_FILTER_MIRROR_V = 0x20, + TEX_FILTER_MIRROR_W = 0x40, + TEX_FILTER_MIRROR = (TEX_FILTER_MIRROR_U | TEX_FILTER_MIRROR_V | TEX_FILTER_MIRROR_W), + // Wrap vs. Mirror vs. Clamp filtering options + + TEX_FILTER_SEPARATE_ALPHA = 0x100, + // Resize color and alpha channel independently + + TEX_FILTER_FLOAT_X2BIAS = 0x200, + // Enable *2 - 1 conversion cases for unorm<->float and positive-only float formats + + TEX_FILTER_RGB_COPY_RED = 0x1000, + TEX_FILTER_RGB_COPY_GREEN = 0x2000, + TEX_FILTER_RGB_COPY_BLUE = 0x4000, + // When converting RGB to R, defaults to using grayscale. These flags indicate copying a specific channel instead + // When converting RGB to RG, defaults to copying RED | GREEN. These flags control which channels are selected instead. + + TEX_FILTER_DITHER = 0x10000, + // Use ordered 4x4 dithering for any required conversions + TEX_FILTER_DITHER_DIFFUSION = 0x20000, + // Use error-diffusion dithering for any required conversions + + TEX_FILTER_POINT = 0x100000, + TEX_FILTER_LINEAR = 0x200000, + TEX_FILTER_CUBIC = 0x300000, + TEX_FILTER_BOX = 0x400000, + TEX_FILTER_FANT = 0x400000, // Equiv to Box filtering for mipmap generation + TEX_FILTER_TRIANGLE = 0x500000, + // Filtering mode to use for any required image resizing + + TEX_FILTER_SRGB_IN = 0x1000000, + TEX_FILTER_SRGB_OUT = 0x2000000, + TEX_FILTER_SRGB = (TEX_FILTER_SRGB_IN | TEX_FILTER_SRGB_OUT), + // sRGB <-> RGB for use in conversion operations + // if the input format type is IsSRGB(), then SRGB_IN is on by default + // if the output format type is IsSRGB(), then SRGB_OUT is on by default + + TEX_FILTER_FORCE_NON_WIC = 0x10000000, + // Forces use of the non-WIC path when both are an option + + TEX_FILTER_FORCE_WIC = 0x20000000, + // Forces use of the WIC path even when logic would have picked a non-WIC path when both are an option + }; + + HRESULT __cdecl Resize( + _In_ const Image& srcImage, _In_ size_t width, _In_ size_t height, + _In_ DWORD filter, + _Out_ ScratchImage& image); + HRESULT __cdecl Resize( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ size_t width, _In_ size_t height, _In_ DWORD filter, _Out_ ScratchImage& result); + // Resize the image to width x height. Defaults to Fant filtering. + // Note for a complex resize, the result will always have mipLevels == 1 + + const float TEX_THRESHOLD_DEFAULT = 0.5f; + // Default value for alpha threshold used when converting to 1-bit alpha + + HRESULT __cdecl Convert( + _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ DWORD filter, _In_ float threshold, + _Out_ ScratchImage& image); + HRESULT __cdecl Convert( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DXGI_FORMAT format, _In_ DWORD filter, _In_ float threshold, _Out_ ScratchImage& result); + // Convert the image to a new format + + HRESULT __cdecl ConvertToSinglePlane(_In_ const Image& srcImage, _Out_ ScratchImage& image); + HRESULT __cdecl ConvertToSinglePlane( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _Out_ ScratchImage& image); + // Converts the image from a planar format to an equivalent non-planar format + + HRESULT __cdecl GenerateMipMaps( + _In_ const Image& baseImage, _In_ DWORD filter, _In_ size_t levels, + _Inout_ ScratchImage& mipChain, _In_ bool allow1D = false); + HRESULT __cdecl GenerateMipMaps( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD filter, _In_ size_t levels, _Inout_ ScratchImage& mipChain); + // levels of '0' indicates a full mipchain, otherwise is generates that number of total levels (including the source base image) + // Defaults to Fant filtering which is equivalent to a box filter + + HRESULT __cdecl GenerateMipMaps3D( + _In_reads_(depth) const Image* baseImages, _In_ size_t depth, _In_ DWORD filter, _In_ size_t levels, + _Out_ ScratchImage& mipChain); + HRESULT __cdecl GenerateMipMaps3D( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD filter, _In_ size_t levels, _Out_ ScratchImage& mipChain); + // levels of '0' indicates a full mipchain, otherwise is generates that number of total levels (including the source base image) + // Defaults to Fant filtering which is equivalent to a box filter + + HRESULT __cdecl ScaleMipMapsAlphaForCoverage( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, _In_ size_t item, + _In_ float alphaReference, _Inout_ ScratchImage& mipChain); + + + enum TEX_PMALPHA_FLAGS + { + TEX_PMALPHA_DEFAULT = 0, + + TEX_PMALPHA_IGNORE_SRGB = 0x1, + // ignores sRGB colorspace conversions + + TEX_PMALPHA_REVERSE = 0x2, + // converts from premultiplied alpha back to straight alpha + + TEX_PMALPHA_SRGB_IN = 0x1000000, + TEX_PMALPHA_SRGB_OUT = 0x2000000, + TEX_PMALPHA_SRGB = (TEX_PMALPHA_SRGB_IN | TEX_PMALPHA_SRGB_OUT), + // if the input format type is IsSRGB(), then SRGB_IN is on by default + // if the output format type is IsSRGB(), then SRGB_OUT is on by default + }; + + HRESULT __cdecl PremultiplyAlpha(_In_ const Image& srcImage, _In_ DWORD flags, _Out_ ScratchImage& image); + HRESULT __cdecl PremultiplyAlpha( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD flags, _Out_ ScratchImage& result); + // Converts to/from a premultiplied alpha version of the texture + + enum TEX_COMPRESS_FLAGS + { + TEX_COMPRESS_DEFAULT = 0, + + TEX_COMPRESS_RGB_DITHER = 0x10000, + // Enables dithering RGB colors for BC1-3 compression + + TEX_COMPRESS_A_DITHER = 0x20000, + // Enables dithering alpha for BC1-3 compression + + TEX_COMPRESS_DITHER = 0x30000, + // Enables both RGB and alpha dithering for BC1-3 compression + + TEX_COMPRESS_UNIFORM = 0x40000, + // Uniform color weighting for BC1-3 compression; by default uses perceptual weighting + + TEX_COMPRESS_BC7_USE_3SUBSETS = 0x80000, + // Enables exhaustive search for BC7 compress for mode 0 and 2; by default skips trying these modes + + TEX_COMPRESS_BC7_QUICK = 0x100000, + // Minimal modes (usually mode 6) for BC7 compression + + TEX_COMPRESS_SRGB_IN = 0x1000000, + TEX_COMPRESS_SRGB_OUT = 0x2000000, + TEX_COMPRESS_SRGB = (TEX_COMPRESS_SRGB_IN | TEX_COMPRESS_SRGB_OUT), + // if the input format type is IsSRGB(), then SRGB_IN is on by default + // if the output format type is IsSRGB(), then SRGB_OUT is on by default + + TEX_COMPRESS_PARALLEL = 0x10000000, + // Compress is free to use multithreading to improve performance (by default it does not use multithreading) + }; + + HRESULT __cdecl Compress( + _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float threshold, + _Out_ ScratchImage& cImage); + HRESULT __cdecl Compress( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float threshold, _Out_ ScratchImage& cImages); + // Note that threshold is only used by BC1. TEX_THRESHOLD_DEFAULT is a typical value to use + +#if defined(__d3d11_h__) || defined(__d3d11_x_h__) + HRESULT __cdecl Compress( + _In_ ID3D11Device* pDevice, _In_ const Image& srcImage, _In_ DXGI_FORMAT format, _In_ DWORD compress, + _In_ float alphaWeight, _Out_ ScratchImage& image); + HRESULT __cdecl Compress( + _In_ ID3D11Device* pDevice, _In_ const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DXGI_FORMAT format, _In_ DWORD compress, _In_ float alphaWeight, _Out_ ScratchImage& cImages); + // DirectCompute-based compression (alphaWeight is only used by BC7. 1.0 is the typical value to use) +#endif + + HRESULT __cdecl Decompress(_In_ const Image& cImage, _In_ DXGI_FORMAT format, _Out_ ScratchImage& image); + HRESULT __cdecl Decompress( + _In_reads_(nimages) const Image* cImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DXGI_FORMAT format, _Out_ ScratchImage& images); + + //--------------------------------------------------------------------------------- + // Normal map operations + + enum CNMAP_FLAGS + { + CNMAP_DEFAULT = 0, + + CNMAP_CHANNEL_RED = 0x1, + CNMAP_CHANNEL_GREEN = 0x2, + CNMAP_CHANNEL_BLUE = 0x3, + CNMAP_CHANNEL_ALPHA = 0x4, + CNMAP_CHANNEL_LUMINANCE = 0x5, + // Channel selection when evaluting color value for height + // Luminance is a combination of red, green, and blue + + CNMAP_MIRROR_U = 0x1000, + CNMAP_MIRROR_V = 0x2000, + CNMAP_MIRROR = 0x3000, + // Use mirror semantics for scanline references (defaults to wrap) + + CNMAP_INVERT_SIGN = 0x4000, + // Inverts normal sign + + CNMAP_COMPUTE_OCCLUSION = 0x8000, + // Computes a crude occlusion term stored in the alpha channel + }; + + HRESULT __cdecl ComputeNormalMap( + _In_ const Image& srcImage, _In_ DWORD flags, _In_ float amplitude, + _In_ DXGI_FORMAT format, _Out_ ScratchImage& normalMap); + HRESULT __cdecl ComputeNormalMap( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ DWORD flags, _In_ float amplitude, _In_ DXGI_FORMAT format, _Out_ ScratchImage& normalMaps); + + //--------------------------------------------------------------------------------- + // Misc image operations + + struct Rect + { + size_t x; + size_t y; + size_t w; + size_t h; + + Rect() = default; + Rect(size_t _x, size_t _y, size_t _w, size_t _h) : x(_x), y(_y), w(_w), h(_h) {} + }; + + HRESULT __cdecl CopyRectangle( + _In_ const Image& srcImage, _In_ const Rect& srcRect, _In_ const Image& dstImage, + _In_ DWORD filter, _In_ size_t xOffset, _In_ size_t yOffset); + + enum CMSE_FLAGS + { + CMSE_DEFAULT = 0, + + CMSE_IMAGE1_SRGB = 0x1, + CMSE_IMAGE2_SRGB = 0x2, + // Indicates that image needs gamma correction before comparision + + CMSE_IGNORE_RED = 0x10, + CMSE_IGNORE_GREEN = 0x20, + CMSE_IGNORE_BLUE = 0x40, + CMSE_IGNORE_ALPHA = 0x80, + // Ignore the channel when computing MSE + + CMSE_IMAGE1_X2_BIAS = 0x100, + CMSE_IMAGE2_X2_BIAS = 0x200, + // Indicates that image should be scaled and biased before comparison (i.e. UNORM -> SNORM) + }; + + HRESULT __cdecl ComputeMSE(_In_ const Image& image1, _In_ const Image& image2, _Out_ float& mse, _Out_writes_opt_(4) float* mseV, _In_ DWORD flags = 0); + + HRESULT __cdecl EvaluateImage( + _In_ const Image& image, + _In_ std::function pixelFunc); + HRESULT __cdecl EvaluateImage( + _In_reads_(nimages) const Image* images, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ std::function pixelFunc); + + HRESULT __cdecl TransformImage( + _In_ const Image& image, + _In_ std::function pixelFunc, + ScratchImage& result); + HRESULT __cdecl TransformImage( + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ std::function pixelFunc, + ScratchImage& result); + + //--------------------------------------------------------------------------------- + // WIC utility code + + enum WICCodecs + { + WIC_CODEC_BMP = 1, // Windows Bitmap (.bmp) + WIC_CODEC_JPEG, // Joint Photographic Experts Group (.jpg, .jpeg) + WIC_CODEC_PNG, // Portable Network Graphics (.png) + WIC_CODEC_TIFF, // Tagged Image File Format (.tif, .tiff) + WIC_CODEC_GIF, // Graphics Interchange Format (.gif) + WIC_CODEC_WMP, // Windows Media Photo / HD Photo / JPEG XR (.hdp, .jxr, .wdp) + WIC_CODEC_ICO, // Windows Icon (.ico) + }; + + REFGUID __cdecl GetWICCodec(_In_ WICCodecs codec); + + IWICImagingFactory* __cdecl GetWICFactory(bool& iswic2); + void __cdecl SetWICFactory(_In_opt_ IWICImagingFactory* pWIC); + + //--------------------------------------------------------------------------------- + // Direct3D 11 functions +#if defined(__d3d11_h__) || defined(__d3d11_x_h__) + bool __cdecl IsSupportedTexture(_In_ ID3D11Device* pDevice, _In_ const TexMetadata& metadata); + + HRESULT __cdecl CreateTexture( + _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _Outptr_ ID3D11Resource** ppResource); + + HRESULT __cdecl CreateShaderResourceView( + _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _Outptr_ ID3D11ShaderResourceView** ppSRV); + + HRESULT __cdecl CreateTextureEx( + _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags, _In_ bool forceSRGB, + _Outptr_ ID3D11Resource** ppResource); + + HRESULT __cdecl CreateShaderResourceViewEx( + _In_ ID3D11Device* pDevice, _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + _In_ D3D11_USAGE usage, _In_ unsigned int bindFlags, _In_ unsigned int cpuAccessFlags, _In_ unsigned int miscFlags, _In_ bool forceSRGB, + _Outptr_ ID3D11ShaderResourceView** ppSRV); + + HRESULT __cdecl CaptureTexture(_In_ ID3D11Device* pDevice, _In_ ID3D11DeviceContext* pContext, _In_ ID3D11Resource* pSource, _Out_ ScratchImage& result); +#endif + + //--------------------------------------------------------------------------------- + // Direct3D 12 functions +#if defined(__d3d12_h__) || defined(__d3d12_x_h__) + bool __cdecl IsSupportedTexture(_In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata); + + HRESULT __cdecl CreateTexture( + _In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata, + _Outptr_ ID3D12Resource** ppResource); + + HRESULT __cdecl CreateTextureEx( + _In_ ID3D12Device* pDevice, _In_ const TexMetadata& metadata, + _In_ D3D12_RESOURCE_FLAGS resFlags, _In_ bool forceSRGB, + _Outptr_ ID3D12Resource** ppResource); + + HRESULT __cdecl PrepareUpload( + _In_ ID3D12Device* pDevice, + _In_reads_(nimages) const Image* srcImages, _In_ size_t nimages, _In_ const TexMetadata& metadata, + std::vector& subresources); + + HRESULT __cdecl CaptureTexture( + _In_ ID3D12CommandQueue* pCommandQueue, _In_ ID3D12Resource* pSource, _In_ bool isCubeMap, + _Out_ ScratchImage& result, + _In_ D3D12_RESOURCE_STATES beforeState = D3D12_RESOURCE_STATE_RENDER_TARGET, + _In_ D3D12_RESOURCE_STATES afterState = D3D12_RESOURCE_STATE_RENDER_TARGET); +#endif + +#include "DirectXTex.inl" + +} // namespace diff --git a/src/DirectXTex.inl b/src/DirectXTex.inl new file mode 100644 index 0000000..a45abcd --- /dev/null +++ b/src/DirectXTex.inl @@ -0,0 +1,124 @@ +//------------------------------------------------------------------------------------- +// DirectXTex.inl +// +// DirectX Texture Library +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// http://go.microsoft.com/fwlink/?LinkId=248926 +//------------------------------------------------------------------------------------- + +#pragma once + +//===================================================================================== +// DXGI Format Utilities +//===================================================================================== + +_Use_decl_annotations_ +inline bool __cdecl IsValid(DXGI_FORMAT fmt) +{ + return (static_cast(fmt) >= 1 && static_cast(fmt) <= 190); +} + +_Use_decl_annotations_ +inline bool __cdecl IsCompressed(DXGI_FORMAT fmt) +{ + switch (fmt) + { + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return true; + + default: + return false; + } +} + +_Use_decl_annotations_ +inline bool __cdecl IsPalettized(DXGI_FORMAT fmt) +{ + switch (fmt) + { + case DXGI_FORMAT_AI44: + case DXGI_FORMAT_IA44: + case DXGI_FORMAT_P8: + case DXGI_FORMAT_A8P8: + return true; + + default: + return false; + } +} + +_Use_decl_annotations_ +inline bool __cdecl IsSRGB(DXGI_FORMAT fmt) +{ + switch (fmt) + { + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return true; + + default: + return false; + } +} + + +//===================================================================================== +// Image I/O +//===================================================================================== +_Use_decl_annotations_ +inline HRESULT __cdecl SaveToDDSMemory(const Image& image, DWORD flags, Blob& blob) +{ + TexMetadata mdata = {}; + mdata.width = image.width; + mdata.height = image.height; + mdata.depth = 1; + mdata.arraySize = 1; + mdata.mipLevels = 1; + mdata.format = image.format; + mdata.dimension = TEX_DIMENSION_TEXTURE2D; + + return SaveToDDSMemory(&image, 1, mdata, flags, blob); +} + +_Use_decl_annotations_ +inline HRESULT __cdecl SaveToDDSFile(const Image& image, DWORD flags, const wchar_t* szFile) +{ + TexMetadata mdata = {}; + mdata.width = image.width; + mdata.height = image.height; + mdata.depth = 1; + mdata.arraySize = 1; + mdata.mipLevels = 1; + mdata.format = image.format; + mdata.dimension = TEX_DIMENSION_TEXTURE2D; + + return SaveToDDSFile(&image, 1, mdata, flags, szFile); +} diff --git a/src/DummyPluginLogic.cpp b/src/DummyPluginLogic.cpp new file mode 100644 index 0000000..5227ef6 --- /dev/null +++ b/src/DummyPluginLogic.cpp @@ -0,0 +1,57 @@ +#include "DummyPluginLogic.h" + +#include +#include +#include "SettingsService.h" + +namespace BsaPacker +{ + DummyPluginLogic::DummyPluginLogic( + const ISettingsService* settingsService, + const IArchiveExtensionService* archiveExtensionService) + : m_SettingsService(settingsService), + m_ArchiveExtensionService(archiveExtensionService) + { + } + + bool DummyPluginLogic::canCreateDummyESP(const QString& fileNameNoExtension) const + { + const std::array& fileList = { + fileNameNoExtension + ".esm", + fileNameNoExtension + ".esp" + }; + return this->canCreateDummy(fileList, fileNameNoExtension); + } + + bool DummyPluginLogic::canCreateDummyESL(const QString& fileNameNoExtension) const + { + const std::array& fileList = { + fileNameNoExtension + ".esm", + fileNameNoExtension + ".esp", + fileNameNoExtension + ".esl" + }; + return this->canCreateDummy(fileList, fileNameNoExtension); + } + + template + bool DummyPluginLogic::canCreateDummy(const std::array& fileList, + const QString& fileNameNoExtension) const + { + const QFileInfo& archive(fileNameNoExtension + this->m_ArchiveExtensionService->GetFileExtension()); + if (!(archive.exists() && archive.isFile())) { + return false; + } + + if (!this->m_SettingsService->GetPluginSetting(SettingsService::SETTING_CREATE_PLUGINS).toBool()) { + return false; + } + + for (const QFileInfo fileInfo : fileList) { + if (fileInfo.exists() && fileInfo.isFile()) { + return false; + } + } + return true; + } +} + diff --git a/src/DummyPluginLogic.h b/src/DummyPluginLogic.h new file mode 100644 index 0000000..8e7c373 --- /dev/null +++ b/src/DummyPluginLogic.h @@ -0,0 +1,29 @@ +#ifndef DUMMYPLUGINLOGIC_H +#define DUMMYPLUGINLOGIC_H + +#include +#include +#include + +namespace BsaPacker +{ + class DummyPluginLogic : public IDummyPluginLogic + { + public: + DummyPluginLogic(const ISettingsService* settingsService, + const IArchiveExtensionService* archiveExtensionService); + ~DummyPluginLogic() override = default; + [[nodiscard]] bool canCreateDummyESP(const QString& fileNameNoExtension) const override; + [[nodiscard]] bool canCreateDummyESL(const QString& fileNameNoExtension) const override; + + private: + template + bool canCreateDummy(const std::array& fileList, + const QString& fileNameNoExtension) const; + + const ISettingsService* m_SettingsService = nullptr; + const IArchiveExtensionService* m_ArchiveExtensionService = nullptr; + }; +} + +#endif // DUMMYPLUGINLOGIC_H diff --git a/src/DummyPluginServiceFactory.cpp b/src/DummyPluginServiceFactory.cpp new file mode 100644 index 0000000..d2ef647 --- /dev/null +++ b/src/DummyPluginServiceFactory.cpp @@ -0,0 +1,50 @@ +#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 + +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 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(m_FileWriterService, m_DummyPluginLogic); + case NexusId::Fallout3: + return std::make_unique(m_FileWriterService, m_DummyPluginLogic); + case NexusId::NewVegas: + return std::make_unique(m_FileWriterService, m_DummyPluginLogic); + case NexusId::Skyrim: + return std::make_unique(m_FileWriterService, m_DummyPluginLogic); + case NexusId::SkyrimSE: + return std::make_unique(m_FileWriterService, m_DummyPluginLogic); + case NexusId::Fallout4: + return std::make_unique(m_FileWriterService, m_DummyPluginLogic); + case NexusId::Morrowind: + default: + break; + } + return std::make_unique(); + } +} // namespace BsaPacker + diff --git a/src/DummyPluginServiceFactory.h b/src/DummyPluginServiceFactory.h new file mode 100644 index 0000000..5abc13c --- /dev/null +++ b/src/DummyPluginServiceFactory.h @@ -0,0 +1,28 @@ +#ifndef DUMMYPLUGINSERVICEFACTORY_H +#define DUMMYPLUGINSERVICEFACTORY_H + +#include +#include +#include +#include + +namespace BsaPacker +{ + class DummyPluginServiceFactory : public IDummyPluginServiceFactory + { + public: + explicit DummyPluginServiceFactory( + const IModContext* modContext, + const IFileWriterService* fileWriterService, // bad! only passing this to another constructor! + const IDummyPluginLogic* dummyPluginLogic); // perhaps remedy with strategy pattern? + ~DummyPluginServiceFactory() override = default; + [[nodiscard]] std::unique_ptr 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/src/Fallout3DummyPluginService.cpp b/src/Fallout3DummyPluginService.cpp new file mode 100644 index 0000000..72bf693 --- /dev/null +++ b/src/Fallout3DummyPluginService.cpp @@ -0,0 +1,22 @@ +#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)) + { + return false; + } + const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp"; + return this->m_FileWriterService->Write(absoluteFileName, + reinterpret_cast(this->RAW_FALLOUT3), + this->RAW_FO3_SIZE); + } +} diff --git a/src/Fallout3DummyPluginService.h b/src/Fallout3DummyPluginService.h new file mode 100644 index 0000000..17ad305 --- /dev/null +++ b/src/Fallout3DummyPluginService.h @@ -0,0 +1,32 @@ +#ifndef FALLOUT3DUMMYPLUGINSERVICE_H +#define FALLOUT3DUMMYPLUGINSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class Fallout3DummyPluginService : public IDummyPluginService + { + public: + Fallout3DummyPluginService(const IFileWriterService* fileWriterService, const IDummyPluginLogic* dummyPluginLogic); + ~Fallout3DummyPluginService() override = default; + bool CreatePlugin(const QString& mod_path, const QString& archive_name_base) const override; + + static constexpr int RAW_FO3_SIZE = 49; + static constexpr unsigned char RAW_FALLOUT3[RAW_FO3_SIZE] = { + 0x54, 0x45, 0x53, 0x34, 0x19, 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, 0x00, 0x08, 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x01, 0x00, + 0x00 + }; + + private: + const IFileWriterService* m_FileWriterService = nullptr; + const IDummyPluginLogic* m_DummyPluginLogic = nullptr; + }; +} + +#endif // FALLOUT3DUMMYPLUGINSERVICE_H diff --git a/src/Fallout4DummyPluginService.cpp b/src/Fallout4DummyPluginService.cpp new file mode 100644 index 0000000..1fd19d4 --- /dev/null +++ b/src/Fallout4DummyPluginService.cpp @@ -0,0 +1,22 @@ +#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)) + { + return false; + } + const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp"; + return this->m_FileWriterService->Write(absoluteFileName, + reinterpret_cast(this->RAW_FALLOUT4), + this->RAW_FO4_SIZE); + } +} diff --git a/src/Fallout4DummyPluginService.h b/src/Fallout4DummyPluginService.h new file mode 100644 index 0000000..07ff1b6 --- /dev/null +++ b/src/Fallout4DummyPluginService.h @@ -0,0 +1,31 @@ +#ifndef FALLOUT4DUMMYPLUGINSERVICE_H +#define FALLOUT4DUMMYPLUGINSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class Fallout4DummyPluginService : public IDummyPluginService + { + public: + Fallout4DummyPluginService(const IFileWriterService* fileWriterService, const IDummyPluginLogic* dummyPluginLogic); + ~Fallout4DummyPluginService() override = default; + bool CreatePlugin(const QString& mod_path, const QString& archive_name_base) const override; + + static constexpr int RAW_FO4_SIZE = 49; + static constexpr unsigned char RAW_FALLOUT4[RAW_FO4_SIZE] = { + 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/src/FileWriterService.cpp b/src/FileWriterService.cpp new file mode 100644 index 0000000..0863ccd --- /dev/null +++ b/src/FileWriterService.cpp @@ -0,0 +1,18 @@ +#include "FileWriterService.h" + +#include + +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(payload), size); + file.close(); + return true; + } + return false; + } +} diff --git a/src/FileWriterService.h b/src/FileWriterService.h new file mode 100644 index 0000000..a77fc99 --- /dev/null +++ b/src/FileWriterService.h @@ -0,0 +1,16 @@ +#ifndef FILEWRITERSERVICE_H +#define FILEWRITERSERVICE_H + +#include + +namespace BsaPacker +{ + class FileWriterService : public IFileWriterService + { + public: + FileWriterService() = default; + ~FileWriterService() override = default; + bool Write(const std::string& path, const char* payload, const uint32_t size) const override; + }; +} +#endif // FILEWRITERSERVICE_H diff --git a/src/GeneralArchiveBuilder.cpp b/src/GeneralArchiveBuilder.cpp new file mode 100644 index 0000000..45f6de9 --- /dev/null +++ b/src/GeneralArchiveBuilder.cpp @@ -0,0 +1,74 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace BsaPacker +{ + GeneralArchiveBuilder::GeneralArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir) + : m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir) + { + this->m_Cancelled = false; + this->m_Archive = std::make_unique(this->m_RootDirectory.path()); + } + + uint32_t GeneralArchiveBuilder::setFiles() + { + uint32_t incompressibleFiles = 0; + uint32_t compressibleFiles = 0; + int count = 0; + const QStringList& rootDirFiles = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(this->m_RootDirectory); + QDirIterator iterator(this->m_RootDirectory, QDirIterator::Subdirectories); + while (iterator.hasNext()) { + QApplication::processEvents(); + + if (this->m_Cancelled) { + this->m_Archive.reset(); + return 0; + } + + const QString& filepath = iterator.next(); + const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath, rootDirFiles); + + Q_EMIT this->valueChanged(++count); + if (ignored) { + continue; + } + + this->m_ArchiveBuilderHelper->isIncompressible(filepath) ? ++incompressibleFiles : ++compressibleFiles; + this->m_Archive->addFileFromDiskRoot(filepath); + } + this->m_Archive->setCompressed(!static_cast(incompressibleFiles)); + //QMessageBox::information(nullptr, "", QString::number(count)); + return incompressibleFiles + compressibleFiles; + } + + void GeneralArchiveBuilder::setShareData(const bool value) + { + this->m_Archive->setShareData(value); + } + + std::unique_ptr GeneralArchiveBuilder::getArchive() + { + return std::move(this->m_Archive); + //return std::unique_ptr(this->m_Archive.get()); + //return this->m_Archive.get() == nullptr ? nullptr : std::unique_ptr(this->m_Archive.get()); + } + + uint32_t GeneralArchiveBuilder::getFileCount() const + { + return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory); + } + + void GeneralArchiveBuilder::cancel() + { + this->m_Cancelled = true; + } +} // namespace BsaPacker diff --git a/src/HideLooseAssetService.cpp b/src/HideLooseAssetService.cpp new file mode 100644 index 0000000..d50cb04 --- /dev/null +++ b/src/HideLooseAssetService.cpp @@ -0,0 +1,43 @@ +#include "HideLooseAssetService.h" + +#include +#include +#include + +#include "SettingsService.h" + +namespace BsaPacker +{ + 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; + } + + const QString& absModDir = modDirectory.absolutePath(); + for (const QString& subDir : modDirectory.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + const QString& absPath = absModDir + '/' + subDir; + QDir dir(absPath); + if (!dir.dirName().endsWith(".mohidden")) { + dir.rename(absPath, absPath + ".mohidden"); + } + } + return true; + + /* + const std::function 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/src/HideLooseAssetService.h b/src/HideLooseAssetService.h new file mode 100644 index 0000000..963d0ea --- /dev/null +++ b/src/HideLooseAssetService.h @@ -0,0 +1,22 @@ +#ifndef HIDELOOSEASSETSERVICE_H +#define HIDELOOSEASSETSERVICE_H + +#include +#include + +namespace BsaPacker +{ + class HideLooseAssetService : public IHideLooseAssetService + { + public: + HideLooseAssetService() = default; + HideLooseAssetService(const ISettingsService* settingsService); + ~HideLooseAssetService() override = default; + bool HideLooseAssets(const QDir& modDirectory) const override; + + private: + const ISettingsService* m_SettingsService = nullptr; + }; +} + +#endif // HIDELOOSEASSETSERVICE_H diff --git a/src/ModContext.cpp b/src/ModContext.cpp new file mode 100644 index 0000000..ad47f74 --- /dev/null +++ b/src/ModContext.cpp @@ -0,0 +1,52 @@ +#include "ModContext.h" + +#include +#include +#include "iplugingame.h" +#include "imodinterface.h" +#include "imodlist.h" +#include +#include + +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->getMod(modName); + return mod->absolutePath(); + } + + int ModContext::GetNexusId() const + { + const MOBase::IPluginGame* managedGame = this->m_Organizer->managedGame(); + return managedGame->nexusGameID(); + } + + QStringList ModContext::GetPlugins(const QDir& modDirectory) const + { + const QString extension = QStringLiteral(".bsa"); + const std::function 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(".bsa"); + } + + QStringList ModContext::GetValidMods() const + { + const MOBase::IModList* const list = m_Organizer->modList(); + const std::function modStateValid = [&](const QString& mod) + { + return list->state(mod) & MOBase::IModList::STATE_VALID; + }; + return QtConcurrent::blockingFiltered(list->allMods(), modStateValid); + } +} // namespace BsaPacker diff --git a/src/ModContext.h b/src/ModContext.h new file mode 100644 index 0000000..44b4ed0 --- /dev/null +++ b/src/ModContext.h @@ -0,0 +1,27 @@ +#ifndef MOD_CONTEXT_H +#define MOD_CONTEXT_H + +#include +#include + +namespace BsaPacker +{ + class ModContext : public IModContext + { + public: + explicit ModContext(MOBase::IOrganizer* moInfo); + ~ModContext() override = default; + + // IModContext interface + [[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/src/ModDto.cpp b/src/ModDto.cpp new file mode 100644 index 0000000..12b19b7 --- /dev/null +++ b/src/ModDto.cpp @@ -0,0 +1,43 @@ +#include "ModDto.h" + +#include + +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) + { + } + + int ModDto::NexusId() const + { + return this->m_NexusId; + } + + QString ModDto::ModName() const + { + return this->m_Path.dirName(); + } + + QString ModDto::Path() const + { + return this->m_Path.path(); + } + + QString ModDto::ArchiveName() const + { + return this->m_Path.path() + '/' + this->m_ArchiveName; + } + + QString ModDto::ArchiveExtension() const + { + return this->m_ArchiveExtension; + } +} // namespace BsaPacker diff --git a/src/ModDto.h b/src/ModDto.h new file mode 100644 index 0000000..6f3f231 --- /dev/null +++ b/src/ModDto.h @@ -0,0 +1,32 @@ +#ifndef MODMETADTO_H +#define MODMETADTO_H + +#include + +#include +#include + +namespace BsaPacker +{ + class ModDto : public IModDto + { + public: + ModDto(const int nexusId, const QString& modDir, const QString& archiveName, const QString& archiveExtension); + ~ModDto() override = default; + + // IModContext interface + [[nodiscard]] int NexusId() const override; + [[nodiscard]] QString ModName() const override; + [[nodiscard]] QString Path() const override; + [[nodiscard]] QString ArchiveName() const override; + [[nodiscard]] QString ArchiveExtension() 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/src/ModDtoFactory.cpp b/src/ModDtoFactory.cpp new file mode 100644 index 0000000..95e47c3 --- /dev/null +++ b/src/ModDtoFactory.cpp @@ -0,0 +1,66 @@ +#include + +#include "ModDto.h" +#include "NullModDto.h" +#include "PackerDialog.h" +#include +#include + +namespace BsaPacker +{ + const uint16_t FALLOUT_4_NEXUS_ID = 1151; + + ModDtoFactory::ModDtoFactory(const IModContext* modContext) + : m_ModContext(modContext) + { + + } + + std::unique_ptr ModDtoFactory::Create() const + { + PackerDialog packerDialog(this->m_ModContext); + packerDialog.RefreshModList(); + int result = packerDialog.exec(); + + const int nexusId = this->m_ModContext->GetNexusId(); + const QString& modName = packerDialog.SelectedMod(); + const QString& modDir = this->m_ModContext->GetAbsoluteModPath(modName); + const QString& pluginName = packerDialog.SelectedPluginItem(); + const QString& archiveName = ModDtoFactory::ArchiveNameValidator(modName, pluginName); + const QString& archiveExtension = nexusId == FALLOUT_4_NEXUS_ID + ? QStringLiteral(".ba2") + : QStringLiteral(".bsa"); + + switch (result) + { + case QDialog::DialogCode::Accepted: + return std::make_unique(nexusId, modDir, archiveName, archiveExtension); + case QDialog::DialogCode::Rejected: + default: + return std::make_unique(); + } + } + + QString ModDtoFactory::ArchiveNameValidator(const QString& modName, const QString& pluginName) + { + QString archive_name_base; + // check if it is the "new filename" text + const bool needsNewName = !static_cast(QString::compare(pluginName.chopped(4), QStringLiteral(""))); + 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 || name.isEmpty() || name.isNull()) { + return nullptr; + } + archive_name_base = name; + } else { + archive_name_base = pluginName.chopped(4); // trims the file extension off + } + return archive_name_base; + } +} // namespace BsaPacker diff --git a/src/NewVegasDummyPluginService.cpp b/src/NewVegasDummyPluginService.cpp new file mode 100644 index 0000000..54e9c1d --- /dev/null +++ b/src/NewVegasDummyPluginService.cpp @@ -0,0 +1,25 @@ +#include "NewVegasDummyPluginService.h" + +#include + +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& fileNameNoExtension = modPath + '/' + archiveNameBase; + if (!this->m_DummyPluginLogic->canCreateDummyESP(fileNameNoExtension)) + { + return false; + } + const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp"; + return this->m_FileWriterService->Write(absoluteFileName, + reinterpret_cast(this->RAW_NEWVEGAS), + this->RAW_FNV_SIZE); + } +} + diff --git a/src/NewVegasDummyPluginService.h b/src/NewVegasDummyPluginService.h new file mode 100644 index 0000000..6dcf3f6 --- /dev/null +++ b/src/NewVegasDummyPluginService.h @@ -0,0 +1,33 @@ +#ifndef NEWVEGASDUMMYPLUGINSERVICE_H +#define NEWVEGASDUMMYPLUGINSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class 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 int RAW_FNV_SIZE = 49; + static constexpr unsigned char RAW_NEWVEGAS[RAW_FNV_SIZE] = { + 0x54, 0x45, 0x53, 0x34, 0x19, 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, 0x00, 0x08, 0x00, 0x00, 0x43, 0x4E, 0x41, 0x4D, 0x01, 0x00, + 0x00 + }; + + private: + const IFileWriterService* m_FileWriterService = nullptr; + const IDummyPluginLogic* m_DummyPluginLogic = nullptr; + }; +} + + +#endif // NEWVEGASDUMMYPLUGINSERVICE_H diff --git a/src/NexusId.h b/src/NexusId.h new file mode 100644 index 0000000..e9c8560 --- /dev/null +++ b/src/NexusId.h @@ -0,0 +1,18 @@ +#ifndef NEXUSID_H +#define NEXUSID_H + +namespace BsaPacker +{ + enum NexusId + { + Morrowind = 100, + Oblivion = 101, + Fallout3 = 120, + NewVegas = 130, + Skyrim = 110, + SkyrimSE = 1704, + Fallout4 = 1151 + }; +} // namespace BsaPacker + +#endif // NEXUSID_H diff --git a/src/NullArchiveBuilder.cpp b/src/NullArchiveBuilder.cpp new file mode 100644 index 0000000..2a06e42 --- /dev/null +++ b/src/NullArchiveBuilder.cpp @@ -0,0 +1,27 @@ +#include + +namespace BsaPacker +{ + uint32_t NullArchiveBuilder::setFiles() + { + return 0; + } + + void NullArchiveBuilder::setShareData(const bool) + { + } + + std::unique_ptr NullArchiveBuilder::getArchive() + { + return nullptr; + } + + uint32_t NullArchiveBuilder::getFileCount() const + { + return 0; + } + + void NullArchiveBuilder::cancel() + { + } +} // namespace BsaPacker diff --git a/src/NullDummyPluginService.cpp b/src/NullDummyPluginService.cpp new file mode 100644 index 0000000..6779b5a --- /dev/null +++ b/src/NullDummyPluginService.cpp @@ -0,0 +1,9 @@ +#include "NullDummyPluginService.h" + +namespace BsaPacker +{ + bool NullDummyPluginService::CreatePlugin(const QString& mod_path, const QString& archive_name_base) const + { + return false; + } +} diff --git a/src/NullDummyPluginService.h b/src/NullDummyPluginService.h new file mode 100644 index 0000000..9b9c64b --- /dev/null +++ b/src/NullDummyPluginService.h @@ -0,0 +1,17 @@ +#ifndef NULLDUMMYPLUGINSERVICE_H +#define NULLDUMMYPLUGINSERVICE_H + +#include + +namespace BsaPacker +{ + class NullDummyPluginService : public IDummyPluginService + { + public: + NullDummyPluginService() = default; + ~NullDummyPluginService() override = default; + bool CreatePlugin(const QString& mod_path, const QString& archive_name_base) const override; + }; +} + +#endif // NULLDUMMYPLUGINSERVICE_H diff --git a/src/NullModDto.cpp b/src/NullModDto.cpp new file mode 100644 index 0000000..bcdbf59 --- /dev/null +++ b/src/NullModDto.cpp @@ -0,0 +1,29 @@ +#include "NullModDto.h" + +namespace BsaPacker +{ + int NullModDto::NexusId() const + { + return 0; + } + + QString BsaPacker::NullModDto::ModName() const + { + return QString(); + } + + QString NullModDto::Path() const + { + return QString(); + } + + QString NullModDto::ArchiveName() const + { + return QString(); + } + + QString NullModDto::ArchiveExtension() const + { + return QString(); + } +} // namespace BsaPacker diff --git a/src/NullModDto.h b/src/NullModDto.h new file mode 100644 index 0000000..4f335aa --- /dev/null +++ b/src/NullModDto.h @@ -0,0 +1,21 @@ +#ifndef NULLMODDTO_H +#define NULLMODDTO_H + +#include +#include + +namespace BsaPacker +{ + class NullModDto : public IModDto + { + public: + // IModContext interface + [[nodiscard]] int NexusId() const override; + [[nodiscard]] QString ModName() const override; + [[nodiscard]] QString Path() const override; + [[nodiscard]] QString ArchiveName() const override; + [[nodiscard]] QString ArchiveExtension() const override; + }; +} // namespace BsaPacker + +#endif // NULLMODDTO_H diff --git a/src/OblivionDummyPluginService.cpp b/src/OblivionDummyPluginService.cpp new file mode 100644 index 0000000..1b05fa3 --- /dev/null +++ b/src/OblivionDummyPluginService.cpp @@ -0,0 +1,23 @@ +#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)) + { + return false; + } + const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp"; + return this->m_FileWriterService->Write(absoluteFileName, + reinterpret_cast(this->RAW_OBLIVION), + this->RAW_TES4_SIZE); + } +} + diff --git a/src/OblivionDummyPluginService.h b/src/OblivionDummyPluginService.h new file mode 100644 index 0000000..74fe480 --- /dev/null +++ b/src/OblivionDummyPluginService.h @@ -0,0 +1,33 @@ +#ifndef OBLIVIONDUMMYPLUGINSERVICE_H +#define OBLIVIONDUMMYPLUGINSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class 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 int RAW_TES4_SIZE = 52; + static constexpr unsigned char RAW_OBLIVION[RAW_TES4_SIZE] = { + 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/src/PackerDialog.cpp b/src/PackerDialog.cpp new file mode 100644 index 0000000..df76679 --- /dev/null +++ b/src/PackerDialog.cpp @@ -0,0 +1,86 @@ +#include "PackerDialog.h" + +#include +#include +#include +#include + +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(&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(&QComboBox::currentTextChanged), [this](auto&& text) { this->UpdateNameList(text); }); + } + + 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); + } + + 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/src/PackerDialog.h b/src/PackerDialog.h new file mode 100644 index 0000000..030baf4 --- /dev/null +++ b/src/PackerDialog.h @@ -0,0 +1,47 @@ +#ifndef PACKERDIALOG_H +#define PACKERDIALOG_H + +#include +#include + +#include +#include +#include +#include +#include + + +namespace BsaPacker +{ + class PackerDialog : public IPackerDialog + { + Q_OBJECT + Q_INTERFACES(BsaPacker::IPackerDialog) + public: + explicit PackerDialog(const IModContext* modContext); + ~PackerDialog() override = default; + + // IPackerDialog interface + void RefreshModList() override; + [[nodiscard]] QString SelectedMod() const override; + [[nodiscard]] QString SelectedPluginItem() const override; + void UpdateNameList(const QString&) override; + void RefreshSelectedName() 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/src/PackerDialogFactory.cpp b/src/PackerDialogFactory.cpp new file mode 100644 index 0000000..1face6a --- /dev/null +++ b/src/PackerDialogFactory.cpp @@ -0,0 +1,5 @@ +#include "PackerDialogFactory.h" + +namespace BsaPacker +{ +} diff --git a/src/PackerDialogFactory.h b/src/PackerDialogFactory.h new file mode 100644 index 0000000..fcc03d8 --- /dev/null +++ b/src/PackerDialogFactory.h @@ -0,0 +1,16 @@ +#ifndef PACKERDIALOGFACTORY_H +#define PACKERDIALOGFACTORY_H + +#include + +namespace BsaPacker +{ + class PackerDialogFactory : public IPackerDialogFactory + { + public: + ~PackerDialogFactory() override = default; + std::unique_ptr Create() const override; + }; +} + +#endif // PACKERDIALOGFACTORY_H diff --git a/src/PackerProgressDialog.cpp b/src/PackerProgressDialog.cpp new file mode 100644 index 0000000..b8d3f34 --- /dev/null +++ b/src/PackerProgressDialog.cpp @@ -0,0 +1,14 @@ +#include "PackerProgressDialog.h" + +namespace BsaPacker +{ + PackerProgressDialog::PackerProgressDialog(QWidget *parent) + { + this->setLabelText("Counting files."); + this->setCancelButtonText("Abort"); + this->setMinimum(0); + this->setMaximum(0); + this->setParent(parent); + //this->setWindowFlags(Qt::MSWindowsFixedSizeDialogHint); + } +} // namespace BsaPacker diff --git a/src/PackerProgressDialog.h b/src/PackerProgressDialog.h new file mode 100644 index 0000000..3f27247 --- /dev/null +++ b/src/PackerProgressDialog.h @@ -0,0 +1,18 @@ +#ifndef PACKERPROGRESSDIALOG_H +#define PACKERPROGRESSDIALOG_H + +#include + +namespace BsaPacker +{ + class PackerProgressDialog : public IPackerProgressDialog + { + Q_OBJECT + Q_INTERFACES(BsaPacker::IPackerProgressDialog) + public: + explicit PackerProgressDialog(QWidget *parent = nullptr); + ~PackerProgressDialog() override = default; + }; +} + +#endif // PACKERPROGRESSDIALOG_H diff --git a/src/PackerProgressDialogFactory.cpp b/src/PackerProgressDialogFactory.cpp new file mode 100644 index 0000000..b916a8a --- /dev/null +++ b/src/PackerProgressDialogFactory.cpp @@ -0,0 +1,11 @@ +#include + +#include "PackerProgressDialog.h" + +namespace BsaPacker +{ + std::unique_ptr PackerProgressDialogFactory::Create() const + { + return std::make_unique(); + } +} diff --git a/src/SettingsService.cpp b/src/SettingsService.cpp new file mode 100644 index 0000000..6205e09 --- /dev/null +++ b/src/SettingsService.cpp @@ -0,0 +1,28 @@ +#include "SettingsService.h" + +namespace BsaPacker +{ + const QString& SettingsService::SETTING_ENABLED = "enabled"; + const QString& SettingsService::SETTING_HIDE_LOOSE_ASSETS = "hide_loose_assets"; + const QString& SettingsService::SETTING_CREATE_PLUGINS = "create_plugins"; + const QString& SettingsService::SETTING_BLACKLISTED_FILES = "blacklisted_files"; + //const QString& SettingsService::SETTING_SPLIT_ARCHIVES = "split_archives"; + + const QList& 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_ENABLED, QObject::tr("Allow this plugin to run."), false), + MOBase::PluginSetting(SettingsService::SETTING_BLACKLISTED_FILES, QObject::tr("Specify a semi-colon seperated list of file extensions to ignore when packing."), ".txt;.hkx;.xml;.ini") + //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("BSA Packer", setting); + } +} diff --git a/src/SettingsService.h b/src/SettingsService.h new file mode 100644 index 0000000..0eac36e --- /dev/null +++ b/src/SettingsService.h @@ -0,0 +1,30 @@ +#ifndef SETTINGSSERVICE_H +#define SETTINGSSERVICE_H + +#include + +#include + +namespace BsaPacker +{ + class SettingsService : public ISettingsService + { + public: + SettingsService(MOBase::IOrganizer* organizer); + ~SettingsService() override = default; + 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_SPLIT_ARCHIVES; + + static const QList& PluginSettings; + + private: + MOBase::IOrganizer* m_Organizer = nullptr; + }; +} + +#endif // SETTINGSSERVICE_H diff --git a/src/SkyrimDummyPluginService.cpp b/src/SkyrimDummyPluginService.cpp new file mode 100644 index 0000000..e547f3a --- /dev/null +++ b/src/SkyrimDummyPluginService.cpp @@ -0,0 +1,23 @@ +#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)) + { + return false; + } + const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp"; + return this->m_FileWriterService->Write(absoluteFileName, + reinterpret_cast(this->RAW_SKYRIM), + this->RAW_TES5_SIZE); + } +} diff --git a/src/SkyrimDummyPluginService.h b/src/SkyrimDummyPluginService.h new file mode 100644 index 0000000..f81448b --- /dev/null +++ b/src/SkyrimDummyPluginService.h @@ -0,0 +1,32 @@ +#ifndef SKYRIMDUMMYPLUGINSERVICE_H +#define SKYRIMDUMMYPLUGINSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class SkyrimDummyPluginService : public IDummyPluginService + { + public: + SkyrimDummyPluginService(const IFileWriterService* fileWriterService, const IDummyPluginLogic* dummyPluginLogic); + ~SkyrimDummyPluginService() override = default; + bool CreatePlugin(const QString& modPath, const QString& archiveNameBase) const override; + + static constexpr int RAW_TES5_SIZE = 49; + static constexpr unsigned char RAW_SKYRIM[RAW_TES5_SIZE] = { + 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/src/SkyrimSEDummyPluginService.cpp b/src/SkyrimSEDummyPluginService.cpp new file mode 100644 index 0000000..fa80d6b --- /dev/null +++ b/src/SkyrimSEDummyPluginService.cpp @@ -0,0 +1,20 @@ +#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)) + { + return false; + } + const std::string& absoluteFileName = fileNameNoExtension.toStdString() + ".esp"; + return this->m_FileWriterService->Write(absoluteFileName, reinterpret_cast(this->RAW_SKYRIMSE), this->RAW_SSE_SIZE); + } +} diff --git a/src/SkyrimSEDummyPluginService.h b/src/SkyrimSEDummyPluginService.h new file mode 100644 index 0000000..63902be --- /dev/null +++ b/src/SkyrimSEDummyPluginService.h @@ -0,0 +1,33 @@ +#ifndef SKYRIMSEDUMMYPLUGINSERVICE_H +#define SKYRIMSEDUMMYPLUGINSERVICE_H + +#include +#include +#include + +namespace BsaPacker +{ + class SkyrimSEDummyPluginService : public IDummyPluginService + { + public: + SkyrimSEDummyPluginService(const IFileWriterService* fileWriterService, const IDummyPluginLogic* dummyPluginLogic); + ~SkyrimSEDummyPluginService() override = default; + bool CreatePlugin(const QString& mod_path, const QString& archive_name_base) const override; + + static constexpr int RAW_SSE_SIZE = 49; + static constexpr unsigned char RAW_SKYRIMSE[RAW_SSE_SIZE] = { + 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/src/TextureArchiveBuilder.cpp b/src/TextureArchiveBuilder.cpp new file mode 100644 index 0000000..703961e --- /dev/null +++ b/src/TextureArchiveBuilder.cpp @@ -0,0 +1,87 @@ +#include + +#include +#include +#include + +#include "DirectXTex.h" + +namespace BsaPacker +{ + TextureArchiveBuilder::TextureArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir) + : m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir) + { + this->m_Cancelled = false; + this->m_Archive = std::make_unique(this->m_RootDirectory.path()); + } + + uint32_t TextureArchiveBuilder::setFiles() + { + uint32_t incompressibleFiles = 0; + uint32_t compressibleFiles = 0; + int count = 0; + const QStringList& rootDirFilenames = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(this->m_RootDirectory); + QDirIterator iterator(this->m_RootDirectory, QDirIterator::Subdirectories); + while (iterator.hasNext()) { + if (this->m_Cancelled) { + return 0; + } + + const QString& filepath = iterator.next(); + const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath, rootDirFilenames); + + Q_EMIT valueChanged(++count); + if (ignored) { + continue; + } + + this->m_ArchiveBuilderHelper->isIncompressible(filepath) ? ++incompressibleFiles : ++compressibleFiles; + this->m_Archive->addFileFromDiskRoot(filepath); + } + this->m_Archive->setCompressed(true); + QString path = this->m_RootDirectory.path(); + QString* pathPtr = &path; + this->m_Archive->setDDSCallback(TextureArchiveBuilder::DDSCallback, static_cast(pathPtr)); + return incompressibleFiles + compressibleFiles; + } + + void TextureArchiveBuilder::setShareData(const bool value) + { + this->m_Archive->setShareData(value); + } + + std::unique_ptr TextureArchiveBuilder::getArchive() + { + return std::move(this->m_Archive); + //return std::unique_ptr(this->m_Archive.get()); + } + + uint32_t TextureArchiveBuilder::getFileCount() const + { + return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory); + } + + void TextureArchiveBuilder::cancel() + { + this->m_Cancelled = true; + } + + void TextureArchiveBuilder::DDSCallback(bsa_archive_t archive, + const wchar_t* file_path, + bsa_dds_info_t* dds_info, + void* context) + { + auto image = std::make_unique(); + DirectX::TexMetadata info; + + const QString path = *static_cast(context) + '/' + QString::fromStdWString(file_path); + const auto hr = LoadFromDDSFile(PREPARE_PATH_LIBBSARCH(path), DirectX::DDS_FLAGS_NONE, &info, *image); + + if (FAILED(hr)) + throw std::runtime_error("Failed to open DDS"); + + dds_info->width = static_cast(info.width); + dds_info->height = static_cast(info.height); + dds_info->mipmaps = static_cast(info.mipLevels); + } +} // namespace BsaPacker diff --git a/src/TexturelessArchiveBuilder.cpp b/src/TexturelessArchiveBuilder.cpp new file mode 100644 index 0000000..ca3c6a5 --- /dev/null +++ b/src/TexturelessArchiveBuilder.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include + +namespace BsaPacker +{ + TexturelessArchiveBuilder::TexturelessArchiveBuilder(const IArchiveBuilderHelper* archiveBuilderHelper, const QDir& rootDir) + : m_ArchiveBuilderHelper(archiveBuilderHelper), m_RootDirectory(rootDir) + { + this->m_Cancelled = false; + this->m_Archive = std::make_unique(this->m_RootDirectory.path()); + } + + uint32_t TexturelessArchiveBuilder::setFiles() + { + uint32_t incompressibleFiles = 0; + uint32_t compressibleFiles = 0; + int count = 0; + const QStringList& rootDirFilenames = this->m_ArchiveBuilderHelper->getRootDirectoryFilenames(this->m_RootDirectory); + QDirIterator iterator(this->m_RootDirectory, QDirIterator::Subdirectories); + while (iterator.hasNext()) { + if (this->m_Cancelled) { + return 0; + } + + const QString& filepath = iterator.next(); + const bool ignored = this->m_ArchiveBuilderHelper->isFileIgnorable(filepath, rootDirFilenames); + + Q_EMIT valueChanged(++count); + if (ignored) { + continue; + } + + this->m_ArchiveBuilderHelper->isIncompressible(filepath) ? ++incompressibleFiles : ++compressibleFiles; + this->m_Archive->addFileFromDiskRoot(filepath); + } + this->m_Archive->setCompressed(!static_cast(incompressibleFiles)); + return incompressibleFiles + compressibleFiles; + } + + void TexturelessArchiveBuilder::setShareData(const bool value) + { + this->m_Archive->setShareData(value); + } + + std::unique_ptr TexturelessArchiveBuilder::getArchive() + { + return std::move(this->m_Archive); + //return std::unique_ptr(this->m_Archive.get()); + } + + uint32_t TexturelessArchiveBuilder::getFileCount() const + { + return this->m_ArchiveBuilderHelper->getFileCount(this->m_RootDirectory); + } + + void TexturelessArchiveBuilder::cancel() + { + this->m_Cancelled = true; + } +} // namespace BsaPacker diff --git a/src/bsa_packer.json b/src/bsa_packer.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/src/bsa_packer.json @@ -0,0 +1 @@ +{} \ No newline at end of file