From 5b3664ad8c9ec2abd3f0640ee33f7d797014cfa5 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Tue, 14 Oct 2025 16:48:20 +0100 Subject: [PATCH] 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); };