2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-17 18:52:06 +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 <cstdint>
#include <type_traits>
#include <vector>
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",

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();
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());

View File

@@ -12,7 +12,7 @@ void FastFileContext::Destroy()
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)
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 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());

View File

@@ -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 <cassert>
@@ -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<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
*isSecure = false;
*isOfficial = true;
return true;
}
return false;
}
void SetupBlock(ZoneLoader& zoneLoader)
{
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
@@ -58,13 +39,33 @@ 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;
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<char>::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<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;
// Create new zone
@@ -93,7 +94,8 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK,
zonePtr->Memory()));
zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader;
}

View File

@@ -9,6 +9,9 @@ namespace IW3
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
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

View File

@@ -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<ZoneLoaderInspectionResultIW4> 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<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;
*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<char>::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<char>::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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const
std::optional<ZoneLoaderInspectionResult> 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<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;
// Create new zone
@@ -193,11 +228,11 @@ std::unique_ptr<ZoneLoader> 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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK,
zonePtr->Memory()));
zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader;
}

View File

@@ -9,6 +9,9 @@ namespace IW4
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
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

View File

@@ -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<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)
{
#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
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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader& header, std::string& fileName) const
std::optional<ZoneLoaderInspectionResult> 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<char>::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<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;
// Create new zone
@@ -176,7 +182,7 @@ std::unique_ptr<ZoneLoader> 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<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK,
zonePtr->Memory()));
zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader;
}

View File

@@ -9,6 +9,9 @@ namespace IW5
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
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

View File

@@ -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 <cassert>
@@ -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<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
*isSecure = false;
*isOfficial = true;
return true;
}
return false;
}
void SetupBlock(ZoneLoader& zoneLoader)
{
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
@@ -58,13 +37,34 @@ 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;
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<char>::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<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;
// Create new zone
@@ -93,7 +93,8 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneHeader&
32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK,
zonePtr->Memory()));
zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader;
}

View File

@@ -9,6 +9,9 @@ namespace T5
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
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

View File

@@ -24,11 +24,10 @@
#include "Zone/XChunk/XChunkProcessorSalsa20Decryption.h"
#include <cassert>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <format>
#include <iostream>
#include <memory>
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<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)
{
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<XBlock>(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<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)
{
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<XChunkProcessorLzxDecompress>(ZoneConstants::STREAM_COUNT));
@@ -215,12 +266,21 @@ 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.
if (!CanLoad(header, isBigEndian, isSecure, isOfficial, isEncrypted, isLzxCompressed))
return resultT6->m_generic_result;
}
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;
// Create new zone
@@ -235,15 +295,15 @@ std::unique_ptr<ZoneLoader> 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<ZoneLoader> 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
{

View File

@@ -9,6 +9,9 @@ namespace T6
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
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

View File

@@ -1,9 +1,30 @@
#pragma once
#include "Game/IGame.h"
#include "Utils/ProgressCallback.h"
#include "Zone/ZoneTypes.h"
#include "ZoneLoader.h"
#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
{
@@ -15,7 +36,10 @@ public:
IZoneLoaderFactory& operator=(const IZoneLoaderFactory& other) = 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);
};

View File

@@ -11,19 +11,21 @@ namespace
const unsigned pointerBitCount,
const unsigned offsetBlockBitCount,
const block_t insertBlock,
MemoryManager& memory)
MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> 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<std::unique_ptr<ProgressCallback>> 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<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

View File

@@ -13,5 +13,6 @@ namespace step
unsigned pointerBitCount,
unsigned offsetBlockBitCount,
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,
const block_t insertBlock,
ILoadingStream& stream,
MemoryManager& memory)
MemoryManager& memory,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback)
: m_blocks(blocks),
m_stream(stream),
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_shift(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(insertBlock < static_cast<block_t>(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<XBlock*>& m_blocks;
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
std::unordered_map<uintptr_t, void*> m_pointer_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
@@ -483,7 +511,8 @@ std::unique_ptr<ZoneInputStream> ZoneInputStream::Create(const unsigned pointerB
std::vector<XBlock*>& blocks,
const block_t insertBlock,
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/ILoadingStream.h"
#include "Utils/MemoryManager.h"
#include "Utils/ProgressCallback.h"
#include "Zone/Stream/IZoneStream.h"
#include "Zone/XBlock.h"
@@ -10,6 +11,7 @@
#include <cstdlib>
#include <cstring>
#include <memory>
#include <optional>
#include <type_traits>
#include <vector>
@@ -238,6 +240,11 @@ public:
virtual void DebugOffsets(size_t assetIndex) const = 0;
#endif
static std::unique_ptr<ZoneInputStream> Create(
unsigned pointerBitCount, unsigned blockBitCount, std::vector<XBlock*>& blocks, block_t insertBlock, ILoadingStream& stream, MemoryManager& memory);
static std::unique_ptr<ZoneInputStream> Create(unsigned pointerBitCount,
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 <format>
#include <fstream>
#include <iostream>
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();
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++)
{
const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast<GameId>(game));
zoneLoader = factory->CreateLoaderForHeader(header, zoneName);
zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback));
if (zoneLoader)
break;

View File

@@ -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::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);
};