From 5b3664ad8c9ec2abd3f0640ee33f7d797014cfa5 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 14 Oct 2025 16:48:20 +0100 Subject: [PATCH 1/6] chore: add possibility to provide loading progress callback when loading zones --- src/Common/Game/IGame.h | 14 +- src/Common/Utils/ProgressCallback.h | 9 + src/Linker/Linker.cpp | 2 +- src/ModMan/Context/FastFileContext.cpp | 2 +- src/Unlinker/Unlinker.cpp | 4 +- .../Game/IW3/ZoneLoaderFactoryIW3.cpp | 52 ++-- .../Game/IW3/ZoneLoaderFactoryIW3.h | 5 +- .../Game/IW4/ZoneLoaderFactoryIW4.cpp | 104 +++++--- .../Game/IW4/ZoneLoaderFactoryIW4.h | 5 +- .../Game/IW5/ZoneLoaderFactoryIW5.cpp | 81 +++--- .../Game/IW5/ZoneLoaderFactoryIW5.h | 5 +- .../Game/T5/ZoneLoaderFactoryT5.cpp | 55 ++-- src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.h | 5 +- .../Game/T6/ZoneLoaderFactoryT6.cpp | 245 +++++++++++------- src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.h | 5 +- src/ZoneLoading/Loading/IZoneLoaderFactory.h | 26 +- .../Loading/Steps/StepLoadZoneContent.cpp | 17 +- .../Loading/Steps/StepLoadZoneContent.h | 3 +- .../Zone/Stream/ZoneInputStream.cpp | 37 ++- src/ZoneLoading/Zone/Stream/ZoneInputStream.h | 11 +- src/ZoneLoading/ZoneLoading.cpp | 6 +- src/ZoneLoading/ZoneLoading.h | 4 +- 22 files changed, 451 insertions(+), 246 deletions(-) create mode 100644 src/Common/Utils/ProgressCallback.h diff --git a/src/Common/Game/IGame.h b/src/Common/Game/IGame.h index d8021ee2..a53ed7b6 100644 --- a/src/Common/Game/IGame.h +++ b/src/Common/Game/IGame.h @@ -2,10 +2,11 @@ #include "GameLanguage.h" +#include #include #include -enum class GameId +enum class GameId : std::uint8_t { IW3, IW4, @@ -18,7 +19,7 @@ enum class GameId // The full uppercase names are macros in the standard lib // So unfortunately not usable as values in the enum -enum class GameEndianness +enum class GameEndianness : std::uint8_t { /* Little endian */ LE, @@ -26,12 +27,19 @@ enum class GameEndianness BE }; -enum class GameWordSize +enum class GameWordSize : std::uint8_t { ARCH_32, ARCH_64 }; +enum class GamePlatform : std::uint8_t +{ + PC, + XBOX, + PS3 +}; + static constexpr const char* GameId_Names[]{ "IW3", "IW4", diff --git a/src/Common/Utils/ProgressCallback.h b/src/Common/Utils/ProgressCallback.h new file mode 100644 index 00000000..8c7a23e2 --- /dev/null +++ b/src/Common/Utils/ProgressCallback.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +class ProgressCallback +{ +public: + virtual void OnProgress(size_t current, size_t total) = 0; +}; diff --git a/src/Linker/Linker.cpp b/src/Linker/Linker.cpp index 3da46b23..b7dce2bd 100644 --- a/src/Linker/Linker.cpp +++ b/src/Linker/Linker.cpp @@ -364,7 +364,7 @@ class LinkerImpl final : public Linker zoneDirectory = fs::current_path(); auto absoluteZoneDirectory = absolute(zoneDirectory).string(); - auto maybeZone = ZoneLoading::LoadZone(zonePath); + auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); if (!maybeZone) { con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); diff --git a/src/ModMan/Context/FastFileContext.cpp b/src/ModMan/Context/FastFileContext.cpp index 3af0d92d..d0fb2411 100644 --- a/src/ModMan/Context/FastFileContext.cpp +++ b/src/ModMan/Context/FastFileContext.cpp @@ -12,7 +12,7 @@ void FastFileContext::Destroy() result::Expected FastFileContext::LoadFastFile(const std::string& path) { - auto zone = ZoneLoading::LoadZone(path); + auto zone = ZoneLoading::LoadZone(path, std::nullopt); if (!zone) return result::Unexpected(std::move(zone.error())); diff --git a/src/Unlinker/Unlinker.cpp b/src/Unlinker/Unlinker.cpp index 1503d215..35d0d25b 100644 --- a/src/Unlinker/Unlinker.cpp +++ b/src/Unlinker/Unlinker.cpp @@ -227,7 +227,7 @@ private: auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); - auto maybeZone = ZoneLoading::LoadZone(zonePath); + auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); if (!maybeZone) { con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); @@ -289,7 +289,7 @@ private: auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); - auto maybeZone = ZoneLoading::LoadZone(zonePath); + auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt); if (!maybeZone) { con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); diff --git a/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.cpp b/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.cpp index 3a387efe..ae0708d5 100644 --- a/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.cpp +++ b/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.cpp @@ -11,7 +11,6 @@ #include "Loading/Steps/StepAllocXBlocks.h" #include "Loading/Steps/StepLoadZoneContent.h" #include "Loading/Steps/StepLoadZoneSizes.h" -#include "Loading/Steps/StepSkipBytes.h" #include "Utils/ClassUtils.h" #include @@ -22,24 +21,6 @@ using namespace IW3; namespace { - bool CanLoad(const ZoneHeader& header, bool* isSecure, bool* isOfficial) - { - assert(isSecure != nullptr); - assert(isOfficial != nullptr); - - if (header.m_version != ZoneConstants::ZONE_VERSION) - return false; - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) - { - *isSecure = false; - *isOfficial = true; - return true; - } - - return false; - } - void SetupBlock(ZoneLoader& zoneLoader) { #define XBLOCK_DEF(name, type) std::make_unique(STR(name), name, type) @@ -58,13 +39,33 @@ namespace } } // namespace -std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const +std::optional ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const { - bool isSecure; - bool isOfficial; + if (header.m_version != ZoneConstants::ZONE_VERSION) + return std::nullopt; - // Check if this file is a supported IW4 zone. - if (!CanLoad(header, &isSecure, &isOfficial)) + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) + { + return ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW3, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = true, + .m_is_signed = false, + .m_is_encrypted = false, + }; + } + + return std::nullopt; +} + +std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const +{ + const auto inspectResult = InspectZoneHeader(header); + if (!inspectResult) return nullptr; // Create new zone @@ -93,7 +94,8 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& 32u, ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::INSERT_BLOCK, - zonePtr->Memory())); + zonePtr->Memory(), + std::move(progressCallback))); return zoneLoader; } diff --git a/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.h b/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.h index 2e6ba8e6..e5957295 100644 --- a/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.h +++ b/src/ZoneLoading/Game/IW3/ZoneLoaderFactoryIW3.h @@ -9,6 +9,9 @@ namespace IW3 class ZoneLoaderFactory final : public IZoneLoaderFactory { public: - std::unique_ptr CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; + [[nodiscard]] std::optional InspectZoneHeader(const ZoneHeader& header) const override; + [[nodiscard]] std::unique_ptr CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const override; }; } // namespace IW3 diff --git a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp index 1bfd60d6..3a23cbc9 100644 --- a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp +++ b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.cpp @@ -34,47 +34,75 @@ using namespace IW4; namespace { - bool CanLoad(ZoneHeader& header, bool* isSecure, bool* isOfficial, bool* isIw4x) + struct ZoneLoaderInspectionResultIW4 { - assert(isSecure != nullptr); - assert(isOfficial != nullptr); - assert(isIw4x != nullptr); + ZoneLoaderInspectionResult m_generic_result; + bool m_is_iw4x; + }; + std::optional InspectZoneHeaderIw4(const ZoneHeader& header) + { if (header.m_version != ZoneConstants::ZONE_VERSION) - { - return false; - } + return std::nullopt; if (!memcmp(header.m_magic, ZoneConstants::MAGIC_IW4X, std::char_traits::length(ZoneConstants::MAGIC_IW4X))) { - if (*reinterpret_cast(&header.m_magic[std::char_traits::length(ZoneConstants::MAGIC_IW4X)]) == ZoneConstants::IW4X_ZONE_VERSION) + if (*reinterpret_cast(&header.m_magic[std::char_traits::length(ZoneConstants::MAGIC_IW4X)]) + == ZoneConstants::IW4X_ZONE_VERSION) { - *isSecure = false; - *isOfficial = false; - *isIw4x = true; - return true; + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = false, + }, + .m_is_iw4x = true, + }; } - return false; + return std::nullopt; } if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) { - *isSecure = true; - *isOfficial = true; - *isIw4x = false; - return true; + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = false, + }, + .m_is_iw4x = false, + }; } if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) { - *isSecure = false; - *isOfficial = true; - *isIw4x = false; - return true; + return ZoneLoaderInspectionResultIW4{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW4, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = false, + }, + .m_is_iw4x = false, + }; } - return false; + return std::nullopt; } void SetupBlock(ZoneLoader& zoneLoader) @@ -116,14 +144,14 @@ namespace } } - void AddAuthHeaderSteps(const bool isSecure, const bool isOfficial, ZoneLoader& zoneLoader, std::string& fileName) + void AddAuthHeaderSteps(const ZoneLoaderInspectionResultIW4& inspectResult, ZoneLoader& zoneLoader, const std::string& fileName) { // Unsigned zones do not have an auth header - if (!isSecure) + if (!inspectResult.m_generic_result.m_is_signed) return; // If file is signed setup a RSA instance. - auto rsa = SetupRsa(isOfficial); + auto rsa = SetupRsa(inspectResult.m_generic_result.m_is_official); zoneLoader.AddLoadingStep(step::CreateStepVerifyMagic(ZoneConstants::MAGIC_AUTH_HEADER)); zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved @@ -165,14 +193,21 @@ namespace } } // namespace -std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const +std::optional ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const { - bool isSecure; - bool isOfficial; - bool isIw4x; + auto resultIw4 = InspectZoneHeaderIw4(header); + if (!resultIw4) + return std::nullopt; - // Check if this file is a supported IW4 zone. - if (!CanLoad(header, &isSecure, &isOfficial, &isIw4x)) + return resultIw4->m_generic_result; +} + +std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const +{ + const auto inspectResult = InspectZoneHeaderIw4(header); + if (!inspectResult) return nullptr; // Create new zone @@ -193,11 +228,11 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8)); // Add steps for loading the auth header which also contain the signature of the zone if it is signed. - AddAuthHeaderSteps(isSecure, isOfficial, *zoneLoader, fileName); + AddAuthHeaderSteps(*inspectResult, *zoneLoader, fileName); zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE))); - if (isIw4x) // IW4x has one extra byte of padding here for protection purposes + if (inspectResult->m_is_iw4x) // IW4x has one extra byte of padding here for protection purposes { zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorIW4xDecryption())); zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(1)); @@ -216,7 +251,8 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& 32u, ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::INSERT_BLOCK, - zonePtr->Memory())); + zonePtr->Memory(), + std::move(progressCallback))); return zoneLoader; } diff --git a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.h b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.h index a840e686..5513c726 100644 --- a/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.h +++ b/src/ZoneLoading/Game/IW4/ZoneLoaderFactoryIW4.h @@ -9,6 +9,9 @@ namespace IW4 class ZoneLoaderFactory final : public IZoneLoaderFactory { public: - std::unique_ptr CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; + [[nodiscard]] std::optional InspectZoneHeader(const ZoneHeader& header) const override; + [[nodiscard]] std::unique_ptr CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const override; }; } // namespace IW4 diff --git a/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.cpp b/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.cpp index b7e97184..d3d3cbb7 100644 --- a/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.cpp +++ b/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.cpp @@ -33,33 +33,6 @@ using namespace IW5; namespace { - bool CanLoad(const ZoneHeader& header, bool* isSecure, bool* isOfficial) - { - assert(isSecure != nullptr); - assert(isOfficial != nullptr); - - if (header.m_version != ZoneConstants::ZONE_VERSION) - { - return false; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) - { - *isSecure = true; - *isOfficial = true; - return true; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) - { - *isSecure = false; - *isOfficial = true; - return true; - } - - return false; - } - void SetupBlock(ZoneLoader& zoneLoader) { #define XBLOCK_DEF(name, type) std::make_unique(STR(name), name, type) @@ -100,14 +73,14 @@ namespace } } - void AddAuthHeaderSteps(const bool isSecure, const bool isOfficial, ZoneLoader& zoneLoader, std::string& fileName) + void AddAuthHeaderSteps(const ZoneLoaderInspectionResult& inspectResult, ZoneLoader& zoneLoader, const std::string& fileName) { // Unsigned zones do not have an auth header - if (!isSecure) + if (!inspectResult.m_is_signed) return; // If file is signed setup a RSA instance. - auto rsa = SetupRsa(isOfficial); + auto rsa = SetupRsa(inspectResult.m_is_official); zoneLoader.AddLoadingStep(step::CreateStepVerifyMagic(ZoneConstants::MAGIC_AUTH_HEADER)); zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved @@ -149,13 +122,46 @@ namespace } } // namespace -std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const +std::optional ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const { - bool isSecure; - bool isOfficial; + if (header.m_version != ZoneConstants::ZONE_VERSION) + return std::nullopt; - // Check if this file is a supported IW4 zone. - if (!CanLoad(header, &isSecure, &isOfficial)) + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) + { + return ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW5, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = false, + }; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) + { + return ZoneLoaderInspectionResult{ + .m_game_id = GameId::IW5, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = false, + }; + } + + return std::nullopt; +} + +std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const +{ + const auto inspectResult = InspectZoneHeader(header); + if (!inspectResult) return nullptr; // Create new zone @@ -176,7 +182,7 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8)); // Add steps for loading the auth header which also contain the signature of the zone if it is signed. - AddAuthHeaderSteps(isSecure, isOfficial, *zoneLoader, fileName); + AddAuthHeaderSteps(*inspectResult, *zoneLoader, fileName); zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE))); @@ -193,7 +199,8 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& 32u, ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::INSERT_BLOCK, - zonePtr->Memory())); + zonePtr->Memory(), + std::move(progressCallback))); return zoneLoader; } diff --git a/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.h b/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.h index a3a522a1..847272aa 100644 --- a/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.h +++ b/src/ZoneLoading/Game/IW5/ZoneLoaderFactoryIW5.h @@ -9,6 +9,9 @@ namespace IW5 class ZoneLoaderFactory final : public IZoneLoaderFactory { public: - std::unique_ptr CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; + [[nodiscard]] std::optional InspectZoneHeader(const ZoneHeader& header) const override; + [[nodiscard]] std::unique_ptr CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const override; }; } // namespace IW5 diff --git a/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.cpp b/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.cpp index 34ee2025..861a0d4d 100644 --- a/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.cpp +++ b/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.cpp @@ -11,7 +11,6 @@ #include "Loading/Steps/StepAllocXBlocks.h" #include "Loading/Steps/StepLoadZoneContent.h" #include "Loading/Steps/StepLoadZoneSizes.h" -#include "Loading/Steps/StepSkipBytes.h" #include "Utils/ClassUtils.h" #include @@ -22,26 +21,6 @@ using namespace T5; namespace { - bool CanLoad(const ZoneHeader& header, bool* isSecure, bool* isOfficial) - { - assert(isSecure != nullptr); - assert(isOfficial != nullptr); - - if (header.m_version != ZoneConstants::ZONE_VERSION) - { - return false; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) - { - *isSecure = false; - *isOfficial = true; - return true; - } - - return false; - } - void SetupBlock(ZoneLoader& zoneLoader) { #define XBLOCK_DEF(name, type) std::make_unique(STR(name), name, type) @@ -58,13 +37,34 @@ namespace } } // namespace -std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const +std::optional ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const { - bool isSecure; - bool isOfficial; + if (header.m_version != ZoneConstants::ZONE_VERSION) + return std::nullopt; - // Check if this file is a supported IW4 zone. - if (!CanLoad(header, &isSecure, &isOfficial)) + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits::length(ZoneConstants::MAGIC_UNSIGNED))) + { + return ZoneLoaderInspectionResult{ + .m_game_id = GameId::T5, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + // There is no way to know whether unsigned zones are official. + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = false, + }; + } + + return std::nullopt; +} + +std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const +{ + const auto inspectResult = InspectZoneHeader(header); + if (!inspectResult) return nullptr; // Create new zone @@ -93,7 +93,8 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& 32u, ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::INSERT_BLOCK, - zonePtr->Memory())); + zonePtr->Memory(), + std::move(progressCallback))); return zoneLoader; } diff --git a/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.h b/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.h index 12c21ffb..efd298c9 100644 --- a/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.h +++ b/src/ZoneLoading/Game/T5/ZoneLoaderFactoryT5.h @@ -9,6 +9,9 @@ namespace T5 class ZoneLoaderFactory final : public IZoneLoaderFactory { public: - std::unique_ptr CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; + [[nodiscard]] std::optional InspectZoneHeader(const ZoneHeader& header) const override; + [[nodiscard]] std::unique_ptr CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const override; }; } // namespace T5 diff --git a/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.cpp b/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.cpp index 813d8602..b4023dc0 100644 --- a/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.cpp +++ b/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.cpp @@ -24,11 +24,10 @@ #include "Zone/XChunk/XChunkProcessorSalsa20Decryption.h" #include -#include +#include #include #include #include -#include #include using namespace T6; @@ -36,6 +35,130 @@ namespace fs = std::filesystem; namespace { + enum class ZoneCompressionTypeT6 : std::uint8_t + { + DEFLATE, + LZX + }; + + struct ZoneLoaderInspectionResultT6 + { + ZoneLoaderInspectionResult m_generic_result; + ZoneCompressionTypeT6 m_compression_type; + }; + + std::optional InspectZoneHeaderT6(const ZoneHeader& header) + { + if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC) + { + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_TREYARCH, 8)) + { + return ZoneLoaderInspectionResultT6{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::T6, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = true, + }, + .m_compression_type = ZoneCompressionTypeT6::DEFLATE, + }; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_OAT, 8)) + { + return ZoneLoaderInspectionResultT6{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::T6, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = false, + .m_is_signed = true, + .m_is_encrypted = true, + }, + .m_compression_type = ZoneCompressionTypeT6::DEFLATE, + }; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, 8)) + { + return ZoneLoaderInspectionResultT6{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::T6, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = false, + .m_is_signed = false, + .m_is_encrypted = true, + }, + .m_compression_type = ZoneCompressionTypeT6::DEFLATE, + }; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED_SERVER, 8)) + { + return ZoneLoaderInspectionResultT6{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::T6, + .m_endianness = GameEndianness::LE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::PC, + .m_is_official = true, + .m_is_signed = false, + .m_is_encrypted = false, + }, + .m_compression_type = ZoneCompressionTypeT6::DEFLATE, + }; + } + } + else if (endianness::FromBigEndian(header.m_version) == ZoneConstants::ZONE_VERSION_XENON) + { + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_TREYARCH, 8)) + { + return ZoneLoaderInspectionResultT6{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::T6, + .m_endianness = GameEndianness::BE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::XBOX, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = true, + }, + .m_compression_type = ZoneCompressionTypeT6::DEFLATE, + }; + } + + if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_LZX_TREYARCH, 8)) + { + return ZoneLoaderInspectionResultT6{ + .m_generic_result = + ZoneLoaderInspectionResult{ + .m_game_id = GameId::T6, + .m_endianness = GameEndianness::BE, + .m_word_size = GameWordSize::ARCH_32, + .m_platform = GamePlatform::XBOX, + .m_is_official = true, + .m_is_signed = true, + .m_is_encrypted = true, + }, + .m_compression_type = ZoneCompressionTypeT6::LZX, + }; + } + } + + return std::nullopt; + } + GameLanguage GetZoneLanguage(const std::string& zoneName) { const auto& languagePrefixes = IGame::GetGameById(GameId::T6)->GetLanguagePrefixes(); @@ -51,68 +174,6 @@ namespace return GameLanguage::LANGUAGE_NONE; } - bool CanLoad(const ZoneHeader& header, bool& isBigEndian, bool& isSecure, bool& isOfficial, bool& isEncrypted, bool& isLzxCompressed) - { - if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC) - { - isBigEndian = false; - isLzxCompressed = false; - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_TREYARCH, 8)) - { - isSecure = true; - isOfficial = true; - isEncrypted = true; - return true; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_OAT, 8)) - { - isSecure = true; - isOfficial = false; - isEncrypted = true; - return true; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, 8)) - { - isSecure = false; - isOfficial = true; - isEncrypted = true; - return true; - } - - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED_SERVER, 8)) - { - isSecure = false; - isOfficial = true; - isEncrypted = false; - return true; - } - } - else if (endianness::FromBigEndian(header.m_version) == ZoneConstants::ZONE_VERSION_XENON) - { - isBigEndian = true; - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_TREYARCH, 8)) - { - isSecure = true; - isOfficial = true; - isEncrypted = true; - isLzxCompressed = false; - return true; - } - if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_LZX_TREYARCH, 8)) - { - isSecure = true; - isOfficial = true; - isEncrypted = true; - isLzxCompressed = true; - return true; - } - } - - return false; - } - void SetupBlock(ZoneLoader& zoneLoader) { #define XBLOCK_DEF(name, type) std::make_unique(STR(name), name, type) @@ -169,26 +230,16 @@ namespace return signatureLoadStepPtr; } - ICapturedDataProvider* - AddXChunkProcessor(const bool isBigEndian, const bool isEncrypted, const bool isLzxCompressed, ZoneLoader& zoneLoader, std::string& fileName) + ICapturedDataProvider* AddXChunkProcessor(const ZoneLoaderInspectionResultT6& inspectResult, ZoneLoader& zoneLoader, const std::string& fileName) { ICapturedDataProvider* result = nullptr; - std::unique_ptr xChunkProcessor; + auto xChunkProcessor = processor::CreateProcessorXChunks( + ZoneConstants::STREAM_COUNT, ZoneConstants::XCHUNK_SIZE, inspectResult.m_generic_result.m_endianness, ZoneConstants::VANILLA_BUFFER_SIZE); - if (isBigEndian) - { - xChunkProcessor = processor::CreateProcessorXChunks( - ZoneConstants::STREAM_COUNT, ZoneConstants::XCHUNK_SIZE, GameEndianness::BE, ZoneConstants::VANILLA_BUFFER_SIZE); - } - else - { - xChunkProcessor = processor::CreateProcessorXChunks( - ZoneConstants::STREAM_COUNT, ZoneConstants::XCHUNK_SIZE, GameEndianness::LE, ZoneConstants::VANILLA_BUFFER_SIZE); - } + const uint8_t (&salsa20Key)[32] = inspectResult.m_generic_result.m_platform == GamePlatform::XBOX ? ZoneConstants::SALSA20_KEY_TREYARCH_XENON + : ZoneConstants::SALSA20_KEY_TREYARCH_PC; - const uint8_t (&salsa20Key)[32] = isBigEndian ? ZoneConstants::SALSA20_KEY_TREYARCH_XENON : ZoneConstants::SALSA20_KEY_TREYARCH_PC; - - if (isEncrypted) + if (inspectResult.m_generic_result.m_is_encrypted) { // If zone is encrypted, the decryption is applied before the decompression. T6 Zones always use Salsa20. auto chunkProcessorSalsa20 = @@ -197,7 +248,7 @@ namespace xChunkProcessor->AddChunkProcessor(std::move(chunkProcessorSalsa20)); } - if (isLzxCompressed) + if (inspectResult.m_compression_type == ZoneCompressionTypeT6::LZX) { // Decompress the chunks using lzx xChunkProcessor->AddChunkProcessor(std::make_unique(ZoneConstants::STREAM_COUNT)); @@ -215,12 +266,21 @@ namespace } } // namespace -std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const +std::optional ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const { - bool isBigEndian, isSecure, isOfficial, isEncrypted, isLzxCompressed; + auto resultT6 = InspectZoneHeaderT6(header); + if (!resultT6) + return std::nullopt; - // Check if this file is a supported T6 zone. - if (!CanLoad(header, isBigEndian, isSecure, isOfficial, isEncrypted, isLzxCompressed)) + return resultT6->m_generic_result; +} + +std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const +{ + const auto inspectResult = InspectZoneHeaderT6(header); + if (!inspectResult) return nullptr; // Create new zone @@ -235,15 +295,15 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& SetupBlock(*zoneLoader); // If file is signed setup a RSA instance. - auto rsa = isSecure ? SetupRsa(isOfficial) : nullptr; + auto rsa = inspectResult->m_generic_result.m_is_signed ? SetupRsa(inspectResult->m_generic_result.m_is_official) : nullptr; // Add steps for loading the auth header which also contain the signature of the zone if it is signed. - ISignatureProvider* signatureProvider = AddAuthHeaderSteps(isSecure, *zoneLoader, fileName); + ISignatureProvider* signatureProvider = AddAuthHeaderSteps(inspectResult->m_generic_result.m_is_signed, *zoneLoader, fileName); // Setup loading XChunks from the zone from this point on. - ICapturedDataProvider* signatureDataProvider = AddXChunkProcessor(isBigEndian, isEncrypted, isLzxCompressed, *zoneLoader, fileName); + ICapturedDataProvider* signatureDataProvider = AddXChunkProcessor(*inspectResult, *zoneLoader, fileName); - if (!isBigEndian) + if (inspectResult->m_generic_result.m_endianness == GameEndianness::LE) { // Start of the XFile struct zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes()); @@ -258,12 +318,11 @@ std::unique_ptr ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& 32u, ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::INSERT_BLOCK, - zonePtr->Memory())); + zonePtr->Memory(), + std::move(progressCallback))); - if (isSecure) - { + if (inspectResult->m_generic_result.m_is_signed) zoneLoader->AddLoadingStep(step::CreateStepVerifySignature(std::move(rsa), signatureProvider, signatureDataProvider)); - } } else { diff --git a/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.h b/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.h index 0890f555..fd396d61 100644 --- a/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.h +++ b/src/ZoneLoading/Game/T6/ZoneLoaderFactoryT6.h @@ -9,6 +9,9 @@ namespace T6 class ZoneLoaderFactory final : public IZoneLoaderFactory { public: - std::unique_ptr CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; + [[nodiscard]] std::optional InspectZoneHeader(const ZoneHeader& header) const override; + [[nodiscard]] std::unique_ptr CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const override; }; } // namespace T6 diff --git a/src/ZoneLoading/Loading/IZoneLoaderFactory.h b/src/ZoneLoading/Loading/IZoneLoaderFactory.h index 46ab7393..3d9f356c 100644 --- a/src/ZoneLoading/Loading/IZoneLoaderFactory.h +++ b/src/ZoneLoading/Loading/IZoneLoaderFactory.h @@ -1,9 +1,30 @@ #pragma once +#include "Game/IGame.h" +#include "Utils/ProgressCallback.h" #include "Zone/ZoneTypes.h" #include "ZoneLoader.h" #include +#include + +struct ZoneLoaderInspectionResult +{ + // The game this zone is created for. + GameId m_game_id; + // Whether the zone is meant for a little-endian or big-endian loader. + GameEndianness m_endianness; + // Whether the zone is meant for a 32bit or 64bit loader. + GameWordSize m_word_size; + // The platform this zone is for. + GamePlatform m_platform; + // Whether this zone is confirmed official. False if not official or unknown. + bool m_is_official; + // Whether this zone contains a signature confirming the identity of the creator. + bool m_is_signed; + // Whether this zone is encrypted. + bool m_is_encrypted; +}; class IZoneLoaderFactory { @@ -15,7 +36,10 @@ public: IZoneLoaderFactory& operator=(const IZoneLoaderFactory& other) = default; IZoneLoaderFactory& operator=(IZoneLoaderFactory&& other) noexcept = default; - virtual std::unique_ptr CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const = 0; + [[nodiscard]] virtual std::optional InspectZoneHeader(const ZoneHeader& header) const = 0; + [[nodiscard]] virtual std::unique_ptr CreateLoaderForHeader(const ZoneHeader& header, + const std::string& fileName, + std::optional> progressCallback) const = 0; static const IZoneLoaderFactory* GetZoneLoaderFactoryForGame(GameId game); }; diff --git a/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.cpp b/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.cpp index 80cb3591..b4cbfb82 100644 --- a/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.cpp +++ b/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.cpp @@ -11,19 +11,21 @@ namespace const unsigned pointerBitCount, const unsigned offsetBlockBitCount, const block_t insertBlock, - MemoryManager& memory) + MemoryManager& memory, + std::optional> progressCallback) : m_entry_point_factory(std::move(entryPointFactory)), m_pointer_bit_count(pointerBitCount), m_offset_block_bit_count(offsetBlockBitCount), m_insert_block(insertBlock), - m_memory(memory) + m_memory(memory), + m_progress_callback(std::move(progressCallback)) { } void PerformStep(ZoneLoader& zoneLoader, ILoadingStream& stream) override { - const auto inputStream = - ZoneInputStream::Create(m_pointer_bit_count, m_offset_block_bit_count, zoneLoader.m_blocks, m_insert_block, stream, m_memory); + const auto inputStream = ZoneInputStream::Create( + m_pointer_bit_count, m_offset_block_bit_count, zoneLoader.m_blocks, m_insert_block, stream, m_memory, std::move(m_progress_callback)); const auto entryPoint = m_entry_point_factory(*inputStream); assert(entryPoint); @@ -37,6 +39,7 @@ namespace unsigned m_offset_block_bit_count; block_t m_insert_block; MemoryManager& m_memory; + std::optional> m_progress_callback; }; } // namespace @@ -46,8 +49,10 @@ namespace step const unsigned pointerBitCount, const unsigned offsetBlockBitCount, const block_t insertBlock, - MemoryManager& memory) + MemoryManager& memory, + std::optional> progressCallback) { - return std::make_unique(std::move(entryPointFactory), pointerBitCount, offsetBlockBitCount, insertBlock, memory); + return std::make_unique( + std::move(entryPointFactory), pointerBitCount, offsetBlockBitCount, insertBlock, memory, std::move(progressCallback)); } } // namespace step diff --git a/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.h b/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.h index 621fa2a2..f832f1dd 100644 --- a/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.h +++ b/src/ZoneLoading/Loading/Steps/StepLoadZoneContent.h @@ -13,5 +13,6 @@ namespace step unsigned pointerBitCount, unsigned offsetBlockBitCount, block_t insertBlock, - MemoryManager& memory); + MemoryManager& memory, + std::optional> progressCallback); } diff --git a/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp b/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp index 296060fc..2eb5cdf0 100644 --- a/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp +++ b/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp @@ -50,7 +50,8 @@ namespace std::vector& blocks, const block_t insertBlock, ILoadingStream& stream, - MemoryManager& memory) + MemoryManager& memory, + std::optional> progressCallback) : m_blocks(blocks), m_stream(stream), m_memory(memory), @@ -58,7 +59,11 @@ namespace m_block_mask((std::numeric_limits::max() >> (sizeof(uintptr_t) * 8 - blockBitCount)) << (pointerBitCount - blockBitCount)), m_block_shift(pointerBitCount - blockBitCount), m_offset_mask(std::numeric_limits::max() >> (sizeof(uintptr_t) * 8 - (pointerBitCount - blockBitCount))), - m_last_fill_size(0) + m_last_fill_size(0), + m_has_progress_callback(progressCallback.has_value()), + m_progress_callback(std::move(progressCallback).value_or(nullptr)), + m_progress_current_size(0uz), + m_progress_total_size(0uz) { assert(pointerBitCount % 8u == 0u); assert(insertBlock < static_cast(blocks.size())); @@ -68,6 +73,8 @@ namespace std::memset(m_block_offsets.get(), 0, sizeof(size_t) * blockCount); m_insert_block = blocks[insertBlock]; + + m_progress_total_size = CalculateTotalSize(); } [[nodiscard]] unsigned GetPointerBitCount() const override @@ -444,6 +451,12 @@ namespace void IncBlockPos(const XBlock& block, const size_t size) { m_block_offsets[block.m_index] += size; + + if (m_has_progress_callback) + { + m_progress_current_size += size; + m_progress_callback->OnProgress(m_progress_current_size, m_progress_total_size); + } } void Align(const XBlock& block, const unsigned align) @@ -455,6 +468,16 @@ namespace } } + [[nodiscard]] size_t CalculateTotalSize() const + { + size_t result = 0uz; + + for (const auto& block : m_blocks) + result += block->m_buffer_size; + + return result; + } + std::vector& m_blocks; std::unique_ptr m_block_offsets; @@ -475,6 +498,11 @@ namespace // These lookups map a block offset to a pointer in case of a platform mismatch std::unordered_map m_pointer_redirect_lookup; std::unordered_map m_alias_redirect_lookup; + + bool m_has_progress_callback; + std::unique_ptr m_progress_callback; + size_t m_progress_current_size; + size_t m_progress_total_size; }; } // namespace @@ -483,7 +511,8 @@ std::unique_ptr ZoneInputStream::Create(const unsigned pointerB std::vector& blocks, const block_t insertBlock, ILoadingStream& stream, - MemoryManager& memory) + MemoryManager& memory, + std::optional> progressCallback) { - return std::make_unique(pointerBitCount, blockBitCount, blocks, insertBlock, stream, memory); + return std::make_unique(pointerBitCount, blockBitCount, blocks, insertBlock, stream, memory, std::move(progressCallback)); } diff --git a/src/ZoneLoading/Zone/Stream/ZoneInputStream.h b/src/ZoneLoading/Zone/Stream/ZoneInputStream.h index f86c2e4c..2007d7fd 100644 --- a/src/ZoneLoading/Zone/Stream/ZoneInputStream.h +++ b/src/ZoneLoading/Zone/Stream/ZoneInputStream.h @@ -3,6 +3,7 @@ #include "Loading/Exception/InvalidLookupPositionException.h" #include "Loading/ILoadingStream.h" #include "Utils/MemoryManager.h" +#include "Utils/ProgressCallback.h" #include "Zone/Stream/IZoneStream.h" #include "Zone/XBlock.h" @@ -10,6 +11,7 @@ #include #include #include +#include #include #include @@ -238,6 +240,11 @@ public: virtual void DebugOffsets(size_t assetIndex) const = 0; #endif - static std::unique_ptr Create( - unsigned pointerBitCount, unsigned blockBitCount, std::vector& blocks, block_t insertBlock, ILoadingStream& stream, MemoryManager& memory); + static std::unique_ptr Create(unsigned pointerBitCount, + unsigned blockBitCount, + std::vector& blocks, + block_t insertBlock, + ILoadingStream& stream, + MemoryManager& memory, + std::optional> progressCallback); }; diff --git a/src/ZoneLoading/ZoneLoading.cpp b/src/ZoneLoading/ZoneLoading.cpp index 36e2a2ed..e9b2913c 100644 --- a/src/ZoneLoading/ZoneLoading.cpp +++ b/src/ZoneLoading/ZoneLoading.cpp @@ -7,11 +7,11 @@ #include #include #include -#include namespace fs = std::filesystem; -result::Expected, std::string> ZoneLoading::LoadZone(const std::string& path) +result::Expected, std::string> ZoneLoading::LoadZone(const std::string& path, + std::optional> progressCallback) { auto zoneName = fs::path(path).filename().replace_extension().string(); std::ifstream file(path, std::fstream::in | std::fstream::binary); @@ -28,7 +28,7 @@ result::Expected, std::string> ZoneLoading::LoadZone(const for (auto game = 0u; game < static_cast(GameId::COUNT); game++) { const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast(game)); - zoneLoader = factory->CreateLoaderForHeader(header, zoneName); + zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback)); if (zoneLoader) break; diff --git a/src/ZoneLoading/ZoneLoading.h b/src/ZoneLoading/ZoneLoading.h index 330b3421..3c86ac8e 100644 --- a/src/ZoneLoading/ZoneLoading.h +++ b/src/ZoneLoading/ZoneLoading.h @@ -1,5 +1,6 @@ #pragma once +#include "Utils/ProgressCallback.h" #include "Utils/Result.h" #include "Zone/Zone.h" @@ -8,5 +9,6 @@ class ZoneLoading { public: - static result::Expected, std::string> LoadZone(const std::string& path); + static result::Expected, std::string> LoadZone(const std::string& path, + std::optional> progressCallback); }; From 9fa41ca0d3786267c496f95a7fc69956a855bd3f Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 14 Oct 2025 17:59:34 +0100 Subject: [PATCH 2/6] feat: show loading progress in modman --- src/Common/Utils/ProgressCallback.h | 3 ++ src/ModMan/Context/FastFileContext.cpp | 36 ++++++++++++++- src/ModMan/Web/Binds/ZoneBinds.cpp | 18 ++++++++ src/ModMan/Web/Binds/ZoneBinds.h | 1 + src/ModManUi/src/App.vue | 44 ++++++++++++++++++- src/ModManUi/src/main.scss | 13 +++++- src/ModManUi/src/native/ZoneBinds.ts | 6 +++ .../Zone/Stream/ZoneInputStream.cpp | 18 +++++--- src/ZoneLoading/ZoneLoading.cpp | 7 +-- 9 files changed, 134 insertions(+), 12 deletions(-) diff --git a/src/Common/Utils/ProgressCallback.h b/src/Common/Utils/ProgressCallback.h index 8c7a23e2..d2222d84 100644 --- a/src/Common/Utils/ProgressCallback.h +++ b/src/Common/Utils/ProgressCallback.h @@ -5,5 +5,8 @@ class ProgressCallback { public: + ProgressCallback() = default; + virtual ~ProgressCallback() = default; + virtual void OnProgress(size_t current, size_t total) = 0; }; diff --git a/src/ModMan/Context/FastFileContext.cpp b/src/ModMan/Context/FastFileContext.cpp index d0fb2411..5392cdae 100644 --- a/src/ModMan/Context/FastFileContext.cpp +++ b/src/ModMan/Context/FastFileContext.cpp @@ -4,6 +4,40 @@ #include "Web/UiCommunication.h" #include "ZoneLoading.h" +#include + +namespace fs = std::filesystem; + +namespace +{ + constexpr double MIN_PROGRESS_TO_REPORT = 0.005; + + class EventProgressReporter : public ProgressCallback + { + public: + explicit EventProgressReporter(std::string zoneName) + : m_zone_name(std::move(zoneName)), + m_last_progress(0) + { + } + + void OnProgress(const size_t current, const size_t total) override + { + const double percentage = static_cast(current) / static_cast(total); + + if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT) + { + m_last_progress = percentage; + ui::NotifyZoneLoadProgress(m_zone_name, percentage); + } + } + + private: + std::string m_zone_name; + double m_last_progress; + }; +} // namespace + void FastFileContext::Destroy() { // Unload all zones @@ -12,7 +46,7 @@ void FastFileContext::Destroy() result::Expected FastFileContext::LoadFastFile(const std::string& path) { - auto zone = ZoneLoading::LoadZone(path, std::nullopt); + auto zone = ZoneLoading::LoadZone(path, std::make_unique(fs::path(path).filename().replace_extension().string())); if (!zone) return result::Unexpected(std::move(zone.error())); diff --git a/src/ModMan/Web/Binds/ZoneBinds.cpp b/src/ModMan/Web/Binds/ZoneBinds.cpp index 8882a7d0..fd538376 100644 --- a/src/ModMan/Web/Binds/ZoneBinds.cpp +++ b/src/ModMan/Web/Binds/ZoneBinds.cpp @@ -7,6 +7,15 @@ namespace { + class ZoneLoadProgressDto + { + public: + std::string zoneName; + double percentage; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadProgressDto, zoneName, percentage); + class ZoneLoadedDto { public: @@ -71,6 +80,15 @@ namespace namespace ui { + void NotifyZoneLoadProgress(std::string zoneName, const double percentage) + { + const ZoneLoadProgressDto dto{ + .zoneName = std::move(zoneName), + .percentage = percentage, + }; + Notify(*ModManContext::Get().m_main_webview, "zoneLoadProgress", dto); + } + void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath) { const ZoneLoadedDto dto{ diff --git a/src/ModMan/Web/Binds/ZoneBinds.h b/src/ModMan/Web/Binds/ZoneBinds.h index cf11f10d..142d5e5b 100644 --- a/src/ModMan/Web/Binds/ZoneBinds.h +++ b/src/ModMan/Web/Binds/ZoneBinds.h @@ -4,6 +4,7 @@ namespace ui { + void NotifyZoneLoadProgress(std::string zoneName, double percentage); void NotifyZoneLoaded(std::string zoneName, std::string fastFilePath); void NotifyZoneUnloaded(std::string zoneName); diff --git a/src/ModManUi/src/App.vue b/src/ModManUi/src/App.vue index 8c4a4dc1..c74edd92 100644 --- a/src/ModManUi/src/App.vue +++ b/src/ModManUi/src/App.vue @@ -1,14 +1,16 @@ - diff --git a/src/ModManUi/src/main.scss b/src/ModManUi/src/main.scss index 37c9c0b0..bfd40850 100644 --- a/src/ModManUi/src/main.scss +++ b/src/ModManUi/src/main.scss @@ -14,13 +14,24 @@ -webkit-text-size-adjust: 100%; } +* { + box-sizing: border-box; +} + +body { + margin: 0; +} + .container { margin: 0; padding-top: 10vh; display: flex; + position: relative; flex-direction: column; - justify-content: center; + justify-content: start; text-align: center; + + height: 100vh; } .logo { diff --git a/src/ModManUi/src/native/ZoneBinds.ts b/src/ModManUi/src/native/ZoneBinds.ts index e0636061..d775479b 100644 --- a/src/ModManUi/src/native/ZoneBinds.ts +++ b/src/ModManUi/src/native/ZoneBinds.ts @@ -1,3 +1,8 @@ +export interface ZoneLoadProgressDto { + zoneName: string; + percentage: number; +} + export interface ZoneLoadedDto { zoneName: string; filePath: string; @@ -13,6 +18,7 @@ export interface ZoneBinds { } export interface ZoneEventMap { + zoneLoadProgress: ZoneLoadProgressDto; zoneLoaded: ZoneLoadedDto; zoneUnloaded: ZoneUnloadedDto; } diff --git a/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp b/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp index 2eb5cdf0..5fb0586f 100644 --- a/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp +++ b/src/ZoneLoading/Zone/Stream/ZoneInputStream.cpp @@ -60,8 +60,6 @@ namespace m_block_shift(pointerBitCount - blockBitCount), m_offset_mask(std::numeric_limits::max() >> (sizeof(uintptr_t) * 8 - (pointerBitCount - blockBitCount))), m_last_fill_size(0), - m_has_progress_callback(progressCallback.has_value()), - m_progress_callback(std::move(progressCallback).value_or(nullptr)), m_progress_current_size(0uz), m_progress_total_size(0uz) { @@ -74,7 +72,12 @@ namespace m_insert_block = blocks[insertBlock]; - m_progress_total_size = CalculateTotalSize(); + if (progressCallback) + { + m_has_progress_callback = true; + m_progress_callback = *std::move(progressCallback); + m_progress_total_size = CalculateTotalSize(); + } } [[nodiscard]] unsigned GetPointerBitCount() const override @@ -452,7 +455,8 @@ namespace { m_block_offsets[block.m_index] += size; - if (m_has_progress_callback) + // We cannot know the full size of the temp block + if (m_has_progress_callback && block.m_type != XBlockType::BLOCK_TYPE_TEMP) { m_progress_current_size += size; m_progress_callback->OnProgress(m_progress_current_size, m_progress_total_size); @@ -473,7 +477,11 @@ namespace size_t result = 0uz; for (const auto& block : m_blocks) - result += block->m_buffer_size; + { + // We cannot know the full size of the temp block + if (block->m_type != XBlockType::BLOCK_TYPE_TEMP) + result += block->m_buffer_size; + } return result; } diff --git a/src/ZoneLoading/ZoneLoading.cpp b/src/ZoneLoading/ZoneLoading.cpp index e9b2913c..e7d4962b 100644 --- a/src/ZoneLoading/ZoneLoading.cpp +++ b/src/ZoneLoading/ZoneLoading.cpp @@ -28,10 +28,11 @@ result::Expected, std::string> ZoneLoading::LoadZone(const for (auto game = 0u; game < static_cast(GameId::COUNT); game++) { const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast(game)); - zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback)); - - if (zoneLoader) + if (factory->InspectZoneHeader(header)) + { + zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback)); break; + } } if (!zoneLoader) From c6e9cbeddac375e88fb106c6b8a9eb9e1e6969b0 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 14 Oct 2025 23:20:56 +0100 Subject: [PATCH 3/6] feat: report on unlinking progress --- src/ModMan/Context/FastFileContext.cpp | 6 +-- src/ModMan/Web/Binds/UnlinkingBinds.cpp | 45 +++++++++++++++---- src/ModMan/Web/Binds/UnlinkingBinds.h | 2 + src/ModManUi/src/App.vue | 6 ++- src/ModManUi/src/native/UnlinkingBinds.ts | 9 ++++ src/ModManUi/src/native/index.ts | 4 +- src/ObjWriting/Dumping/AbstractAssetDumper.h | 39 +++++++++------- .../Dumping/AssetDumpingContext.cpp | 33 ++++++++++++-- src/ObjWriting/Dumping/AssetDumpingContext.h | 14 +++++- src/ObjWriting/Dumping/IAssetDumper.h | 3 +- .../Game/IW3/Image/ImageDumperIW3.cpp | 11 ++--- .../Game/IW3/Image/ImageDumperIW3.h | 3 +- .../Game/IW3/Localize/LocalizeDumperIW3.cpp | 13 ++++-- .../Game/IW3/Localize/LocalizeDumperIW3.h | 3 +- .../Game/IW3/Maps/MapEntsDumperIW3.cpp | 11 ++--- .../Game/IW3/Maps/MapEntsDumperIW3.h | 3 +- src/ObjWriting/Game/IW3/ObjWriterIW3.cpp | 15 ++++++- .../Game/IW3/RawFile/RawFileDumperIW3.cpp | 11 ++--- .../Game/IW3/RawFile/RawFileDumperIW3.h | 3 +- .../Game/IW3/Sound/LoadedSoundDumperIW3.cpp | 11 ++--- .../Game/IW3/Sound/LoadedSoundDumperIW3.h | 3 +- .../IW3/StringTable/StringTableDumperIW3.cpp | 11 ++--- .../IW3/StringTable/StringTableDumperIW3.h | 3 +- .../Game/IW4/Image/ImageDumperIW4.cpp | 11 ++--- .../Game/IW4/Image/ImageDumperIW4.h | 3 +- .../Leaderboard/LeaderboardJsonDumperIW4.cpp | 11 ++--- .../Leaderboard/LeaderboardJsonDumperIW4.h | 3 +- .../Game/IW4/LightDef/LightDefDumperIW4.cpp | 13 ++---- .../Game/IW4/LightDef/LightDefDumperIW4.h | 3 +- .../Game/IW4/Localize/LocalizeDumperIW4.cpp | 14 ++++-- .../Game/IW4/Localize/LocalizeDumperIW4.h | 3 +- .../Game/IW4/Maps/AddonMapEntsDumperIW4.cpp | 11 ++--- .../Game/IW4/Maps/AddonMapEntsDumperIW4.h | 3 +- .../Material/MaterialDecompilingDumperIW4.cpp | 9 +--- .../Material/MaterialDecompilingDumperIW4.h | 3 +- .../Game/IW4/Menu/MenuDumperIW4.cpp | 17 +++---- src/ObjWriting/Game/IW4/Menu/MenuDumperIW4.h | 3 +- .../Game/IW4/Menu/MenuListDumperIW4.cpp | 15 +++---- .../Game/IW4/Menu/MenuListDumperIW4.h | 9 ++-- src/ObjWriting/Game/IW4/ObjWriterIW4.cpp | 15 ++++++- .../IW4/PhysCollmap/PhysCollmapDumperIW4.cpp | 11 ++--- .../IW4/PhysCollmap/PhysCollmapDumperIW4.h | 3 +- .../PhysPresetInfoStringDumperIW4.cpp | 21 ++++----- .../PhysPresetInfoStringDumperIW4.h | 3 +- .../Game/IW4/RawFile/RawFileDumperIW4.cpp | 11 ++--- .../Game/IW4/RawFile/RawFileDumperIW4.h | 3 +- .../Game/IW4/Shader/PixelShaderDumperIW4.cpp | 11 ++--- .../Game/IW4/Shader/PixelShaderDumperIW4.h | 3 +- .../Game/IW4/Shader/VertexShaderDumperIW4.cpp | 11 ++--- .../Game/IW4/Shader/VertexShaderDumperIW4.h | 3 +- .../Game/IW4/Sound/LoadedSoundDumperIW4.cpp | 11 ++--- .../Game/IW4/Sound/LoadedSoundDumperIW4.h | 3 +- .../Game/IW4/Sound/SndCurveDumperIW4.cpp | 11 +---- .../Game/IW4/Sound/SndCurveDumperIW4.h | 3 +- .../IW4/StringTable/StringTableDumperIW4.cpp | 11 ++--- .../IW4/StringTable/StringTableDumperIW4.h | 3 +- .../StructuredDataDefDumperIW4.cpp | 11 ++--- .../StructuredDataDefDumperIW4.h | 3 +- .../Game/IW4/Techset/TechsetDumperIW4.cpp | 9 +--- .../Game/IW4/Techset/TechsetDumperIW4.h | 3 +- .../Game/IW4/Tracer/TracerDumperIW4.cpp | 21 ++++----- .../Game/IW4/Tracer/TracerDumperIW4.h | 3 +- .../Game/IW4/Vehicle/VehicleDumperIW4.cpp | 21 ++++----- .../Game/IW4/Vehicle/VehicleDumperIW4.h | 3 +- .../Game/IW4/Weapon/WeaponDumperIW4.cpp | 25 +++++------ .../Game/IW4/Weapon/WeaponDumperIW4.h | 3 +- .../Game/IW5/Image/ImageDumperIW5.cpp | 11 ++--- .../Game/IW5/Image/ImageDumperIW5.h | 3 +- .../Leaderboard/LeaderboardJsonDumperIW5.cpp | 11 ++--- .../Leaderboard/LeaderboardJsonDumperIW5.h | 3 +- .../Game/IW5/Localize/LocalizeDumperIW5.cpp | 13 ++++-- .../Game/IW5/Localize/LocalizeDumperIW5.h | 3 +- .../Game/IW5/Maps/AddonMapEntsDumperIW5.cpp | 11 ++--- .../Game/IW5/Maps/AddonMapEntsDumperIW5.h | 3 +- .../Game/IW5/Menu/MenuDumperIW5.cpp | 21 ++++----- src/ObjWriting/Game/IW5/Menu/MenuDumperIW5.h | 3 +- .../Game/IW5/Menu/MenuListDumperIW5.cpp | 11 ++--- .../Game/IW5/Menu/MenuListDumperIW5.h | 3 +- src/ObjWriting/Game/IW5/ObjWriterIW5.cpp | 16 ++++++- .../Game/IW5/RawFile/RawFileDumperIW5.cpp | 11 ++--- .../Game/IW5/RawFile/RawFileDumperIW5.h | 3 +- .../Game/IW5/Script/ScriptDumperIW5.cpp | 19 +++----- .../Game/IW5/Script/ScriptDumperIW5.h | 3 +- .../Game/IW5/Sound/LoadedSoundDumperIW5.cpp | 11 ++--- .../Game/IW5/Sound/LoadedSoundDumperIW5.h | 3 +- .../IW5/StringTable/StringTableDumperIW5.cpp | 11 ++--- .../IW5/StringTable/StringTableDumperIW5.h | 3 +- .../IW5/Weapon/AttachmentJsonDumperIW5.cpp | 11 ++--- .../Game/IW5/Weapon/AttachmentJsonDumperIW5.h | 3 +- .../Game/IW5/Weapon/WeaponDumperIW5.cpp | 25 +++++------ .../Game/IW5/Weapon/WeaponDumperIW5.h | 3 +- .../Game/T5/Image/ImageDumperT5.cpp | 11 ++--- src/ObjWriting/Game/T5/Image/ImageDumperT5.h | 3 +- .../Game/T5/Localize/LocalizeDumperT5.cpp | 13 ++++-- .../Game/T5/Localize/LocalizeDumperT5.h | 3 +- src/ObjWriting/Game/T5/ObjWriterT5.cpp | 15 ++++++- .../Game/T5/RawFile/RawFileDumperT5.cpp | 15 +++---- .../Game/T5/RawFile/RawFileDumperT5.h | 3 +- .../T5/StringTable/StringTableDumperT5.cpp | 11 ++--- .../Game/T5/StringTable/StringTableDumperT5.h | 3 +- .../Game/T6/FontIcon/FontIconCsvDumperT6.cpp | 11 ++--- .../Game/T6/FontIcon/FontIconCsvDumperT6.h | 3 +- .../Game/T6/FontIcon/FontIconJsonDumperT6.cpp | 11 ++--- .../Game/T6/FontIcon/FontIconJsonDumperT6.h | 3 +- .../Game/T6/Image/ImageDumperT6.cpp | 11 ++--- src/ObjWriting/Game/T6/Image/ImageDumperT6.h | 3 +- .../Leaderboard/LeaderboardJsonDumperT6.cpp | 11 ++--- .../T6/Leaderboard/LeaderboardJsonDumperT6.h | 3 +- .../Game/T6/Localize/LocalizeDumperT6.cpp | 13 ++++-- .../Game/T6/Localize/LocalizeDumperT6.h | 3 +- .../Game/T6/Maps/MapEntsDumperT6.cpp | 9 +--- src/ObjWriting/Game/T6/Maps/MapEntsDumperT6.h | 3 +- src/ObjWriting/Game/T6/ObjWriterT6.cpp | 27 +++++++++-- .../PhysConstraintsInfoStringDumperT6.cpp | 23 ++++------ .../PhysConstraintsInfoStringDumperT6.h | 3 +- .../PhysPresetInfoStringDumperT6.cpp | 21 ++++----- .../PhysPreset/PhysPresetInfoStringDumperT6.h | 3 +- src/ObjWriting/Game/T6/Qdb/QdbDumperT6.cpp | 11 ++--- src/ObjWriting/Game/T6/Qdb/QdbDumperT6.h | 3 +- .../Game/T6/RawFile/RawFileDumperT6.cpp | 15 +++---- .../Game/T6/RawFile/RawFileDumperT6.h | 3 +- .../Game/T6/Script/ScriptDumperT6.cpp | 11 ++--- .../Game/T6/Script/ScriptDumperT6.h | 3 +- src/ObjWriting/Game/T6/Slug/SlugDumperT6.cpp | 11 ++--- src/ObjWriting/Game/T6/Slug/SlugDumperT6.h | 3 +- .../Game/T6/Sound/SndBankDumperT6.cpp | 11 ++++- .../Game/T6/Sound/SndBankDumperT6.h | 3 +- .../T6/Sound/SndDriverGlobalsDumperT6.cpp | 13 ++++-- .../Game/T6/Sound/SndDriverGlobalsDumperT6.h | 3 +- .../T6/StringTable/StringTableDumperT6.cpp | 11 ++--- .../Game/T6/StringTable/StringTableDumperT6.h | 3 +- .../Game/T6/Techset/TechsetDumperT6.cpp | 9 +--- .../Game/T6/Techset/TechsetDumperT6.h | 3 +- .../Game/T6/Tracer/TracerDumperT6.cpp | 21 ++++----- .../Game/T6/Tracer/TracerDumperT6.h | 3 +- .../Game/T6/Vehicle/VehicleDumperT6.cpp | 21 ++++----- .../Game/T6/Vehicle/VehicleDumperT6.h | 3 +- .../Game/T6/Weapon/AttachmentDumperT6.cpp | 21 ++++----- .../Game/T6/Weapon/AttachmentDumperT6.h | 3 +- .../T6/Weapon/AttachmentUniqueDumperT6.cpp | 21 ++++----- .../Game/T6/Weapon/AttachmentUniqueDumperT6.h | 3 +- .../Game/T6/Weapon/CamoJsonDumperT6.cpp | 11 ++--- .../Game/T6/Weapon/CamoJsonDumperT6.h | 3 +- .../Game/T6/Weapon/WeaponDumperT6.cpp | 25 +++++------ .../Game/T6/Weapon/WeaponDumperT6.h | 3 +- .../Game/T6/ZBarrier/ZBarrierDumperT6.cpp | 21 ++++----- .../Game/T6/ZBarrier/ZBarrierDumperT6.h | 3 +- .../Material/MaterialJsonDumper.cpp.template | 13 ++---- .../Material/MaterialJsonDumper.h.template | 5 +-- .../XModel/XModelDumper.cpp.template | 39 +++++++--------- src/ObjWriting/XModel/XModelDumper.h.template | 3 +- src/Unlinker/Unlinker.cpp | 2 +- src/ZoneCommon/Pool/AssetPool.h | 36 +++++++++++++++ .../Material/MaterialJsonDumperIW3Test.cpp | 4 +- .../Material/MaterialJsonDumperIW4Test.cpp | 4 +- .../Material/MaterialJsonDumperIW5Test.cpp | 4 +- .../T5/Material/MaterialJsonDumperT5Test.cpp | 4 +- .../T6/FontIcon/FontIconJsonDumperT6Test.cpp | 4 +- .../T6/Material/MaterialJsonDumperT6Test.cpp | 4 +- 159 files changed, 686 insertions(+), 802 deletions(-) diff --git a/src/ModMan/Context/FastFileContext.cpp b/src/ModMan/Context/FastFileContext.cpp index 5392cdae..2a5f8112 100644 --- a/src/ModMan/Context/FastFileContext.cpp +++ b/src/ModMan/Context/FastFileContext.cpp @@ -12,10 +12,10 @@ namespace { constexpr double MIN_PROGRESS_TO_REPORT = 0.005; - class EventProgressReporter : public ProgressCallback + class LoadingEventProgressReporter : public ProgressCallback { public: - explicit EventProgressReporter(std::string zoneName) + explicit LoadingEventProgressReporter(std::string zoneName) : m_zone_name(std::move(zoneName)), m_last_progress(0) { @@ -46,7 +46,7 @@ void FastFileContext::Destroy() result::Expected FastFileContext::LoadFastFile(const std::string& path) { - auto zone = ZoneLoading::LoadZone(path, std::make_unique(fs::path(path).filename().replace_extension().string())); + auto zone = ZoneLoading::LoadZone(path, std::make_unique(fs::path(path).filename().replace_extension().string())); if (!zone) return result::Unexpected(std::move(zone.error())); diff --git a/src/ModMan/Web/Binds/UnlinkingBinds.cpp b/src/ModMan/Web/Binds/UnlinkingBinds.cpp index bc281dcf..82d9d9b2 100644 --- a/src/ModMan/Web/Binds/UnlinkingBinds.cpp +++ b/src/ModMan/Web/Binds/UnlinkingBinds.cpp @@ -14,22 +14,41 @@ namespace fs = std::filesystem; namespace { - class ZoneLoadedDto + class ZoneUnlinkProgressDto { public: std::string zoneName; - std::string filePath; + double percentage; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zoneName, filePath); + NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnlinkProgressDto, zoneName, percentage); - class ZoneUnloadedDto + constexpr double MIN_PROGRESS_TO_REPORT = 0.005; + + class UnlinkingEventProgressReporter : public ProgressCallback { public: - std::string zoneName; - }; + explicit UnlinkingEventProgressReporter(std::string zoneName) + : m_zone_name(std::move(zoneName)), + m_last_progress(0) + { + } - NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName); + void OnProgress(const size_t current, const size_t total) override + { + const double percentage = static_cast(current) / static_cast(total); + + if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT) + { + m_last_progress = percentage; + ui::NotifyZoneUnlinkProgress(m_zone_name, percentage); + } + } + + private: + std::string m_zone_name; + double m_last_progress; + }; result::Expected UnlinkZoneInDbThread(const std::string& zoneName) { @@ -52,7 +71,8 @@ namespace OutputPathFilesystem outputFolderOutputPath(outputFolderPath); SearchPaths searchPaths; - AssetDumpingContext dumpingContext(zone, outputFolderPathStr, outputFolderOutputPath, searchPaths); + AssetDumpingContext dumpingContext( + zone, outputFolderPathStr, outputFolderOutputPath, searchPaths, std::make_unique(zoneName)); objWriter->DumpZone(dumpingContext); return NoResult(); @@ -81,6 +101,15 @@ namespace namespace ui { + void NotifyZoneUnlinkProgress(std::string zoneName, const double percentage) + { + const ZoneUnlinkProgressDto dto{ + .zoneName = std::move(zoneName), + .percentage = percentage, + }; + Notify(*ModManContext::Get().m_main_webview, "zoneUnlinkProgress", dto); + } + void RegisterUnlinkingBinds(webview::webview& wv) { BindAsync(wv, diff --git a/src/ModMan/Web/Binds/UnlinkingBinds.h b/src/ModMan/Web/Binds/UnlinkingBinds.h index 4169a780..6d2ac0ed 100644 --- a/src/ModMan/Web/Binds/UnlinkingBinds.h +++ b/src/ModMan/Web/Binds/UnlinkingBinds.h @@ -4,5 +4,7 @@ namespace ui { + void NotifyZoneUnlinkProgress(std::string zoneName, double percentage); + void RegisterUnlinkingBinds(webview::webview& wv); } // namespace ui diff --git a/src/ModManUi/src/App.vue b/src/ModManUi/src/App.vue index c74edd92..4d5255fe 100644 --- a/src/ModManUi/src/App.vue +++ b/src/ModManUi/src/App.vue @@ -76,9 +76,13 @@ function onUnloadClicked(zoneName: string) { } webviewAddEventListener("zoneLoadProgress", (dto) => { - console.log(dto); lastPercentage.value = dto.percentage; }); + +webviewAddEventListener("zoneUnlinkProgress", (dto) => { + lastPercentage.value = dto.percentage; + console.log("unlink", dto); +});