2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-18 03:02:07 +00:00

chore: add possibility to provide loading progress callback when loading zones

This commit is contained in:
Jan Laupetin
2025-10-14 16:48:20 +01:00
parent fa7fd26db6
commit 5b3664ad8c
22 changed files with 451 additions and 246 deletions

View File

@@ -2,10 +2,11 @@
#include "GameLanguage.h" #include "GameLanguage.h"
#include <cstdint>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
enum class GameId enum class GameId : std::uint8_t
{ {
IW3, IW3,
IW4, IW4,
@@ -18,7 +19,7 @@ enum class GameId
// The full uppercase names are macros in the standard lib // The full uppercase names are macros in the standard lib
// So unfortunately not usable as values in the enum // So unfortunately not usable as values in the enum
enum class GameEndianness enum class GameEndianness : std::uint8_t
{ {
/* Little endian */ /* Little endian */
LE, LE,
@@ -26,12 +27,19 @@ enum class GameEndianness
BE BE
}; };
enum class GameWordSize enum class GameWordSize : std::uint8_t
{ {
ARCH_32, ARCH_32,
ARCH_64 ARCH_64
}; };
enum class GamePlatform : std::uint8_t
{
PC,
XBOX,
PS3
};
static constexpr const char* GameId_Names[]{ static constexpr const char* GameId_Names[]{
"IW3", "IW3",
"IW4", "IW4",

View File

@@ -0,0 +1,9 @@
#pragma once
#include <cstdlib>
class ProgressCallback
{
public:
virtual void OnProgress(size_t current, size_t total) = 0;
};

View File

@@ -364,7 +364,7 @@ class LinkerImpl final : public Linker
zoneDirectory = fs::current_path(); zoneDirectory = fs::current_path();
auto absoluteZoneDirectory = absolute(zoneDirectory).string(); auto absoluteZoneDirectory = absolute(zoneDirectory).string();
auto maybeZone = ZoneLoading::LoadZone(zonePath); auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
if (!maybeZone) if (!maybeZone)
{ {
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());

View File

@@ -12,7 +12,7 @@ void FastFileContext::Destroy()
result::Expected<Zone*, std::string> FastFileContext::LoadFastFile(const std::string& path) result::Expected<Zone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
{ {
auto zone = ZoneLoading::LoadZone(path); auto zone = ZoneLoading::LoadZone(path, std::nullopt);
if (!zone) if (!zone)
return result::Unexpected(std::move(zone.error())); return result::Unexpected(std::move(zone.error()));

View File

@@ -227,7 +227,7 @@ private:
auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string(); auto absoluteZoneDirectory = absolute(std::filesystem::path(zonePath).remove_filename()).string();
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
auto maybeZone = ZoneLoading::LoadZone(zonePath); auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
if (!maybeZone) if (!maybeZone)
{ {
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
@@ -289,7 +289,7 @@ private:
auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory); auto searchPathsForZone = paths.GetSearchPathsForZone(absoluteZoneDirectory);
auto maybeZone = ZoneLoading::LoadZone(zonePath); auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
if (!maybeZone) if (!maybeZone)
{ {
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error()); con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());

View File

@@ -11,7 +11,6 @@
#include "Loading/Steps/StepAllocXBlocks.h" #include "Loading/Steps/StepAllocXBlocks.h"
#include "Loading/Steps/StepLoadZoneContent.h" #include "Loading/Steps/StepLoadZoneContent.h"
#include "Loading/Steps/StepLoadZoneSizes.h" #include "Loading/Steps/StepLoadZoneSizes.h"
#include "Loading/Steps/StepSkipBytes.h"
#include "Utils/ClassUtils.h" #include "Utils/ClassUtils.h"
#include <cassert> #include <cassert>
@@ -22,24 +21,6 @@ using namespace IW3;
namespace 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<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
*isSecure = false;
*isOfficial = true;
return true;
}
return false;
}
void SetupBlock(ZoneLoader& zoneLoader) void SetupBlock(ZoneLoader& zoneLoader)
{ {
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type) #define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
@@ -58,13 +39,33 @@ namespace
} }
} // namespace } // namespace
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
{ {
bool isSecure; if (header.m_version != ZoneConstants::ZONE_VERSION)
bool isOfficial; return std::nullopt;
// Check if this file is a supported IW4 zone. if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
if (!CanLoad(header, &isSecure, &isOfficial)) {
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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
if (!inspectResult)
return nullptr; return nullptr;
// Create new zone // Create new zone
@@ -93,7 +94,8 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u, 32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK, ZoneConstants::INSERT_BLOCK,
zonePtr->Memory())); zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader; return zoneLoader;
} }

View File

@@ -9,6 +9,9 @@ namespace IW3
class ZoneLoaderFactory final : public IZoneLoaderFactory class ZoneLoaderFactory final : public IZoneLoaderFactory
{ {
public: public:
std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; [[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
}; };
} // namespace IW3 } // namespace IW3

View File

@@ -34,47 +34,75 @@ using namespace IW4;
namespace namespace
{ {
bool CanLoad(ZoneHeader& header, bool* isSecure, bool* isOfficial, bool* isIw4x) struct ZoneLoaderInspectionResultIW4
{ {
assert(isSecure != nullptr); ZoneLoaderInspectionResult m_generic_result;
assert(isOfficial != nullptr); bool m_is_iw4x;
assert(isIw4x != nullptr); };
if (header.m_version != ZoneConstants::ZONE_VERSION) std::optional<ZoneLoaderInspectionResultIW4> InspectZoneHeaderIw4(const ZoneHeader& header)
{ {
return false; if (header.m_version != ZoneConstants::ZONE_VERSION)
} return std::nullopt;
if (!memcmp(header.m_magic, ZoneConstants::MAGIC_IW4X, std::char_traits<char>::length(ZoneConstants::MAGIC_IW4X))) if (!memcmp(header.m_magic, ZoneConstants::MAGIC_IW4X, std::char_traits<char>::length(ZoneConstants::MAGIC_IW4X)))
{ {
if (*reinterpret_cast<uint32_t*>(&header.m_magic[std::char_traits<char>::length(ZoneConstants::MAGIC_IW4X)]) == ZoneConstants::IW4X_ZONE_VERSION) if (*reinterpret_cast<const uint32_t*>(&header.m_magic[std::char_traits<char>::length(ZoneConstants::MAGIC_IW4X)])
== ZoneConstants::IW4X_ZONE_VERSION)
{ {
*isSecure = false; return ZoneLoaderInspectionResultIW4{
*isOfficial = false; .m_generic_result =
*isIw4x = true; ZoneLoaderInspectionResult{
return true; .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<char>::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD))) if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits<char>::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD)))
{ {
*isSecure = true; return ZoneLoaderInspectionResultIW4{
*isOfficial = true; .m_generic_result =
*isIw4x = false; ZoneLoaderInspectionResult{
return true; .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<char>::length(ZoneConstants::MAGIC_UNSIGNED))) if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{ {
*isSecure = false; return ZoneLoaderInspectionResultIW4{
*isOfficial = true; .m_generic_result =
*isIw4x = false; ZoneLoaderInspectionResult{
return true; .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) 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 // Unsigned zones do not have an auth header
if (!isSecure) if (!inspectResult.m_generic_result.m_is_signed)
return; return;
// If file is signed setup a RSA instance. // 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::CreateStepVerifyMagic(ZoneConstants::MAGIC_AUTH_HEADER));
zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved
@@ -165,14 +193,21 @@ namespace
} }
} // namespace } // namespace
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
{ {
bool isSecure; auto resultIw4 = InspectZoneHeaderIw4(header);
bool isOfficial; if (!resultIw4)
bool isIw4x; return std::nullopt;
// Check if this file is a supported IW4 zone. return resultIw4->m_generic_result;
if (!CanLoad(header, &isSecure, &isOfficial, &isIw4x)) }
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeaderIw4(header);
if (!inspectResult)
return nullptr; return nullptr;
// Create new zone // Create new zone
@@ -193,11 +228,11 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8)); zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8));
// Add steps for loading the auth header which also contain the signature of the zone if it is signed. // 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))); 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::CreateStepAddProcessor(processor::CreateProcessorIW4xDecryption()));
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(1)); zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(1));
@@ -216,7 +251,8 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u, 32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK, ZoneConstants::INSERT_BLOCK,
zonePtr->Memory())); zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader; return zoneLoader;
} }

View File

@@ -9,6 +9,9 @@ namespace IW4
class ZoneLoaderFactory final : public IZoneLoaderFactory class ZoneLoaderFactory final : public IZoneLoaderFactory
{ {
public: public:
std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; [[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
}; };
} // namespace IW4 } // namespace IW4

View File

@@ -33,33 +33,6 @@ using namespace IW5;
namespace 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<char>::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD)))
{
*isSecure = true;
*isOfficial = true;
return true;
}
if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
*isSecure = false;
*isOfficial = true;
return true;
}
return false;
}
void SetupBlock(ZoneLoader& zoneLoader) void SetupBlock(ZoneLoader& zoneLoader)
{ {
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type) #define XBLOCK_DEF(name, type) std::make_unique<XBlock>(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 // Unsigned zones do not have an auth header
if (!isSecure) if (!inspectResult.m_is_signed)
return; return;
// If file is signed setup a RSA instance. // 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::CreateStepVerifyMagic(ZoneConstants::MAGIC_AUTH_HEADER));
zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved zoneLoader.AddLoadingStep(step::CreateStepSkipBytes(4)); // Skip reserved
@@ -149,13 +122,46 @@ namespace
} }
} // namespace } // namespace
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
{ {
bool isSecure; if (header.m_version != ZoneConstants::ZONE_VERSION)
bool isOfficial; return std::nullopt;
// Check if this file is a supported IW4 zone. if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_INFINITY_WARD, std::char_traits<char>::length(ZoneConstants::MAGIC_SIGNED_INFINITY_WARD)))
if (!CanLoad(header, &isSecure, &isOfficial)) {
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<char>::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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
if (!inspectResult)
return nullptr; return nullptr;
// Create new zone // Create new zone
@@ -176,7 +182,7 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8)); zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(8));
// Add steps for loading the auth header which also contain the signature of the zone if it is signed. // 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))); zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE)));
@@ -193,7 +199,8 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u, 32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK, ZoneConstants::INSERT_BLOCK,
zonePtr->Memory())); zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader; return zoneLoader;
} }

View File

@@ -9,6 +9,9 @@ namespace IW5
class ZoneLoaderFactory final : public IZoneLoaderFactory class ZoneLoaderFactory final : public IZoneLoaderFactory
{ {
public: public:
std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; [[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
}; };
} // namespace IW5 } // namespace IW5

View File

@@ -11,7 +11,6 @@
#include "Loading/Steps/StepAllocXBlocks.h" #include "Loading/Steps/StepAllocXBlocks.h"
#include "Loading/Steps/StepLoadZoneContent.h" #include "Loading/Steps/StepLoadZoneContent.h"
#include "Loading/Steps/StepLoadZoneSizes.h" #include "Loading/Steps/StepLoadZoneSizes.h"
#include "Loading/Steps/StepSkipBytes.h"
#include "Utils/ClassUtils.h" #include "Utils/ClassUtils.h"
#include <cassert> #include <cassert>
@@ -22,26 +21,6 @@ using namespace T5;
namespace 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<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
*isSecure = false;
*isOfficial = true;
return true;
}
return false;
}
void SetupBlock(ZoneLoader& zoneLoader) void SetupBlock(ZoneLoader& zoneLoader)
{ {
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type) #define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
@@ -58,13 +37,34 @@ namespace
} }
} // namespace } // namespace
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
{ {
bool isSecure; if (header.m_version != ZoneConstants::ZONE_VERSION)
bool isOfficial; return std::nullopt;
// Check if this file is a supported IW4 zone. if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
if (!CanLoad(header, &isSecure, &isOfficial)) {
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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
if (!inspectResult)
return nullptr; return nullptr;
// Create new zone // Create new zone
@@ -93,7 +93,8 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u, 32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK, ZoneConstants::INSERT_BLOCK,
zonePtr->Memory())); zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader; return zoneLoader;
} }

View File

@@ -9,6 +9,9 @@ namespace T5
class ZoneLoaderFactory final : public IZoneLoaderFactory class ZoneLoaderFactory final : public IZoneLoaderFactory
{ {
public: public:
std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; [[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
}; };
} // namespace T5 } // namespace T5

View File

@@ -24,11 +24,10 @@
#include "Zone/XChunk/XChunkProcessorSalsa20Decryption.h" #include "Zone/XChunk/XChunkProcessorSalsa20Decryption.h"
#include <cassert> #include <cassert>
#include <cstdio> #include <cstdint>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
#include <format> #include <format>
#include <iostream>
#include <memory> #include <memory>
using namespace T6; using namespace T6;
@@ -36,6 +35,130 @@ namespace fs = std::filesystem;
namespace namespace
{ {
enum class ZoneCompressionTypeT6 : std::uint8_t
{
DEFLATE,
LZX
};
struct ZoneLoaderInspectionResultT6
{
ZoneLoaderInspectionResult m_generic_result;
ZoneCompressionTypeT6 m_compression_type;
};
std::optional<ZoneLoaderInspectionResultT6> 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) GameLanguage GetZoneLanguage(const std::string& zoneName)
{ {
const auto& languagePrefixes = IGame::GetGameById(GameId::T6)->GetLanguagePrefixes(); const auto& languagePrefixes = IGame::GetGameById(GameId::T6)->GetLanguagePrefixes();
@@ -51,68 +174,6 @@ namespace
return GameLanguage::LANGUAGE_NONE; 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) void SetupBlock(ZoneLoader& zoneLoader)
{ {
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type) #define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
@@ -169,26 +230,16 @@ namespace
return signatureLoadStepPtr; return signatureLoadStepPtr;
} }
ICapturedDataProvider* ICapturedDataProvider* AddXChunkProcessor(const ZoneLoaderInspectionResultT6& inspectResult, ZoneLoader& zoneLoader, const std::string& fileName)
AddXChunkProcessor(const bool isBigEndian, const bool isEncrypted, const bool isLzxCompressed, ZoneLoader& zoneLoader, std::string& fileName)
{ {
ICapturedDataProvider* result = nullptr; ICapturedDataProvider* result = nullptr;
std::unique_ptr<processor::IProcessorXChunks> xChunkProcessor; auto xChunkProcessor = processor::CreateProcessorXChunks(
ZoneConstants::STREAM_COUNT, ZoneConstants::XCHUNK_SIZE, inspectResult.m_generic_result.m_endianness, ZoneConstants::VANILLA_BUFFER_SIZE);
if (isBigEndian) const uint8_t (&salsa20Key)[32] = inspectResult.m_generic_result.m_platform == GamePlatform::XBOX ? ZoneConstants::SALSA20_KEY_TREYARCH_XENON
{ : ZoneConstants::SALSA20_KEY_TREYARCH_PC;
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] = isBigEndian ? ZoneConstants::SALSA20_KEY_TREYARCH_XENON : ZoneConstants::SALSA20_KEY_TREYARCH_PC; if (inspectResult.m_generic_result.m_is_encrypted)
if (isEncrypted)
{ {
// If zone is encrypted, the decryption is applied before the decompression. T6 Zones always use Salsa20. // If zone is encrypted, the decryption is applied before the decompression. T6 Zones always use Salsa20.
auto chunkProcessorSalsa20 = auto chunkProcessorSalsa20 =
@@ -197,7 +248,7 @@ namespace
xChunkProcessor->AddChunkProcessor(std::move(chunkProcessorSalsa20)); xChunkProcessor->AddChunkProcessor(std::move(chunkProcessorSalsa20));
} }
if (isLzxCompressed) if (inspectResult.m_compression_type == ZoneCompressionTypeT6::LZX)
{ {
// Decompress the chunks using lzx // Decompress the chunks using lzx
xChunkProcessor->AddChunkProcessor(std::make_unique<XChunkProcessorLzxDecompress>(ZoneConstants::STREAM_COUNT)); xChunkProcessor->AddChunkProcessor(std::make_unique<XChunkProcessorLzxDecompress>(ZoneConstants::STREAM_COUNT));
@@ -215,12 +266,21 @@ namespace
} }
} // namespace } // namespace
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const std::optional<ZoneLoaderInspectionResult> 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. return resultT6->m_generic_result;
if (!CanLoad(header, isBigEndian, isSecure, isOfficial, isEncrypted, isLzxCompressed)) }
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeaderT6(header);
if (!inspectResult)
return nullptr; return nullptr;
// Create new zone // Create new zone
@@ -235,15 +295,15 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
SetupBlock(*zoneLoader); SetupBlock(*zoneLoader);
// If file is signed setup a RSA instance. // 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. // 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. // 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 // Start of the XFile struct
zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes()); zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes());
@@ -258,13 +318,12 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u, 32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT, ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK, 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)); zoneLoader->AddLoadingStep(step::CreateStepVerifySignature(std::move(rsa), signatureProvider, signatureDataProvider));
} }
}
else else
{ {
fs::path dumpFileNamePath = fs::path(fileName).filename(); fs::path dumpFileNamePath = fs::path(fileName).filename();

View File

@@ -9,6 +9,9 @@ namespace T6
class ZoneLoaderFactory final : public IZoneLoaderFactory class ZoneLoaderFactory final : public IZoneLoaderFactory
{ {
public: public:
std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const override; [[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
}; };
} // namespace T6 } // namespace T6

View File

@@ -1,9 +1,30 @@
#pragma once #pragma once
#include "Game/IGame.h"
#include "Utils/ProgressCallback.h"
#include "Zone/ZoneTypes.h" #include "Zone/ZoneTypes.h"
#include "ZoneLoader.h" #include "ZoneLoader.h"
#include <memory> #include <memory>
#include <optional>
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 class IZoneLoaderFactory
{ {
@@ -15,7 +36,10 @@ public:
IZoneLoaderFactory& operator=(const IZoneLoaderFactory& other) = default; IZoneLoaderFactory& operator=(const IZoneLoaderFactory& other) = default;
IZoneLoaderFactory& operator=(IZoneLoaderFactory&& other) noexcept = default; IZoneLoaderFactory& operator=(IZoneLoaderFactory&& other) noexcept = default;
virtual std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const = 0; [[nodiscard]] virtual std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const = 0;
[[nodiscard]] virtual std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const = 0;
static const IZoneLoaderFactory* GetZoneLoaderFactoryForGame(GameId game); static const IZoneLoaderFactory* GetZoneLoaderFactoryForGame(GameId game);
}; };

View File

@@ -11,19 +11,21 @@ namespace
const unsigned pointerBitCount, const unsigned pointerBitCount,
const unsigned offsetBlockBitCount, const unsigned offsetBlockBitCount,
const block_t insertBlock, const block_t insertBlock,
MemoryManager& memory) MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback)
: m_entry_point_factory(std::move(entryPointFactory)), : m_entry_point_factory(std::move(entryPointFactory)),
m_pointer_bit_count(pointerBitCount), m_pointer_bit_count(pointerBitCount),
m_offset_block_bit_count(offsetBlockBitCount), m_offset_block_bit_count(offsetBlockBitCount),
m_insert_block(insertBlock), m_insert_block(insertBlock),
m_memory(memory) m_memory(memory),
m_progress_callback(std::move(progressCallback))
{ {
} }
void PerformStep(ZoneLoader& zoneLoader, ILoadingStream& stream) override void PerformStep(ZoneLoader& zoneLoader, ILoadingStream& stream) override
{ {
const auto inputStream = const auto inputStream = ZoneInputStream::Create(
ZoneInputStream::Create(m_pointer_bit_count, m_offset_block_bit_count, zoneLoader.m_blocks, m_insert_block, stream, m_memory); 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); const auto entryPoint = m_entry_point_factory(*inputStream);
assert(entryPoint); assert(entryPoint);
@@ -37,6 +39,7 @@ namespace
unsigned m_offset_block_bit_count; unsigned m_offset_block_bit_count;
block_t m_insert_block; block_t m_insert_block;
MemoryManager& m_memory; MemoryManager& m_memory;
std::optional<std::unique_ptr<ProgressCallback>> m_progress_callback;
}; };
} // namespace } // namespace
@@ -46,8 +49,10 @@ namespace step
const unsigned pointerBitCount, const unsigned pointerBitCount,
const unsigned offsetBlockBitCount, const unsigned offsetBlockBitCount,
const block_t insertBlock, const block_t insertBlock,
MemoryManager& memory) MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback)
{ {
return std::make_unique<StepLoadZoneContent>(std::move(entryPointFactory), pointerBitCount, offsetBlockBitCount, insertBlock, memory); return std::make_unique<StepLoadZoneContent>(
std::move(entryPointFactory), pointerBitCount, offsetBlockBitCount, insertBlock, memory, std::move(progressCallback));
} }
} // namespace step } // namespace step

View File

@@ -13,5 +13,6 @@ namespace step
unsigned pointerBitCount, unsigned pointerBitCount,
unsigned offsetBlockBitCount, unsigned offsetBlockBitCount,
block_t insertBlock, block_t insertBlock,
MemoryManager& memory); MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback);
} }

View File

@@ -50,7 +50,8 @@ namespace
std::vector<XBlock*>& blocks, std::vector<XBlock*>& blocks,
const block_t insertBlock, const block_t insertBlock,
ILoadingStream& stream, ILoadingStream& stream,
MemoryManager& memory) MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback)
: m_blocks(blocks), : m_blocks(blocks),
m_stream(stream), m_stream(stream),
m_memory(memory), m_memory(memory),
@@ -58,7 +59,11 @@ namespace
m_block_mask((std::numeric_limits<uintptr_t>::max() >> (sizeof(uintptr_t) * 8 - blockBitCount)) << (pointerBitCount - blockBitCount)), m_block_mask((std::numeric_limits<uintptr_t>::max() >> (sizeof(uintptr_t) * 8 - blockBitCount)) << (pointerBitCount - blockBitCount)),
m_block_shift(pointerBitCount - blockBitCount), m_block_shift(pointerBitCount - blockBitCount),
m_offset_mask(std::numeric_limits<uintptr_t>::max() >> (sizeof(uintptr_t) * 8 - (pointerBitCount - blockBitCount))), m_offset_mask(std::numeric_limits<uintptr_t>::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(pointerBitCount % 8u == 0u);
assert(insertBlock < static_cast<block_t>(blocks.size())); assert(insertBlock < static_cast<block_t>(blocks.size()));
@@ -68,6 +73,8 @@ namespace
std::memset(m_block_offsets.get(), 0, sizeof(size_t) * blockCount); std::memset(m_block_offsets.get(), 0, sizeof(size_t) * blockCount);
m_insert_block = blocks[insertBlock]; m_insert_block = blocks[insertBlock];
m_progress_total_size = CalculateTotalSize();
} }
[[nodiscard]] unsigned GetPointerBitCount() const override [[nodiscard]] unsigned GetPointerBitCount() const override
@@ -444,6 +451,12 @@ namespace
void IncBlockPos(const XBlock& block, const size_t size) void IncBlockPos(const XBlock& block, const size_t size)
{ {
m_block_offsets[block.m_index] += 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) 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<XBlock*>& m_blocks; std::vector<XBlock*>& m_blocks;
std::unique_ptr<size_t[]> m_block_offsets; std::unique_ptr<size_t[]> m_block_offsets;
@@ -475,6 +498,11 @@ namespace
// These lookups map a block offset to a pointer in case of a platform mismatch // These lookups map a block offset to a pointer in case of a platform mismatch
std::unordered_map<uintptr_t, void*> m_pointer_redirect_lookup; std::unordered_map<uintptr_t, void*> m_pointer_redirect_lookup;
std::unordered_map<uintptr_t, void*> m_alias_redirect_lookup; std::unordered_map<uintptr_t, void*> m_alias_redirect_lookup;
bool m_has_progress_callback;
std::unique_ptr<ProgressCallback> m_progress_callback;
size_t m_progress_current_size;
size_t m_progress_total_size;
}; };
} // namespace } // namespace
@@ -483,7 +511,8 @@ std::unique_ptr<ZoneInputStream> ZoneInputStream::Create(const unsigned pointerB
std::vector<XBlock*>& blocks, std::vector<XBlock*>& blocks,
const block_t insertBlock, const block_t insertBlock,
ILoadingStream& stream, ILoadingStream& stream,
MemoryManager& memory) MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback)
{ {
return std::make_unique<XBlockInputStream>(pointerBitCount, blockBitCount, blocks, insertBlock, stream, memory); return std::make_unique<XBlockInputStream>(pointerBitCount, blockBitCount, blocks, insertBlock, stream, memory, std::move(progressCallback));
} }

View File

@@ -3,6 +3,7 @@
#include "Loading/Exception/InvalidLookupPositionException.h" #include "Loading/Exception/InvalidLookupPositionException.h"
#include "Loading/ILoadingStream.h" #include "Loading/ILoadingStream.h"
#include "Utils/MemoryManager.h" #include "Utils/MemoryManager.h"
#include "Utils/ProgressCallback.h"
#include "Zone/Stream/IZoneStream.h" #include "Zone/Stream/IZoneStream.h"
#include "Zone/XBlock.h" #include "Zone/XBlock.h"
@@ -10,6 +11,7 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <optional>
#include <type_traits> #include <type_traits>
#include <vector> #include <vector>
@@ -238,6 +240,11 @@ public:
virtual void DebugOffsets(size_t assetIndex) const = 0; virtual void DebugOffsets(size_t assetIndex) const = 0;
#endif #endif
static std::unique_ptr<ZoneInputStream> Create( static std::unique_ptr<ZoneInputStream> Create(unsigned pointerBitCount,
unsigned pointerBitCount, unsigned blockBitCount, std::vector<XBlock*>& blocks, block_t insertBlock, ILoadingStream& stream, MemoryManager& memory); unsigned blockBitCount,
std::vector<XBlock*>& blocks,
block_t insertBlock,
ILoadingStream& stream,
MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback);
}; };

View File

@@ -7,11 +7,11 @@
#include <filesystem> #include <filesystem>
#include <format> #include <format>
#include <fstream> #include <fstream>
#include <iostream>
namespace fs = std::filesystem; namespace fs = std::filesystem;
result::Expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const std::string& path) result::Expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const std::string& path,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback)
{ {
auto zoneName = fs::path(path).filename().replace_extension().string(); auto zoneName = fs::path(path).filename().replace_extension().string();
std::ifstream file(path, std::fstream::in | std::fstream::binary); std::ifstream file(path, std::fstream::in | std::fstream::binary);
@@ -28,7 +28,7 @@ result::Expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const
for (auto game = 0u; game < static_cast<unsigned>(GameId::COUNT); game++) for (auto game = 0u; game < static_cast<unsigned>(GameId::COUNT); game++)
{ {
const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast<GameId>(game)); const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast<GameId>(game));
zoneLoader = factory->CreateLoaderForHeader(header, zoneName); zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback));
if (zoneLoader) if (zoneLoader)
break; break;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "Utils/ProgressCallback.h"
#include "Utils/Result.h" #include "Utils/Result.h"
#include "Zone/Zone.h" #include "Zone/Zone.h"
@@ -8,5 +9,6 @@
class ZoneLoading class ZoneLoading
{ {
public: public:
static result::Expected<std::unique_ptr<Zone>, std::string> LoadZone(const std::string& path); static result::Expected<std::unique_ptr<Zone>, std::string> LoadZone(const std::string& path,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback);
}; };