feat: QOS (007: Quantum of Solace) support (#894)

* feat: QOS (007: Quantum of Solace) support

* feat: add qos localize loader

* feat: add qos stringtable loader

* chore: comment about official zones

* fix: add qos gfximage runtime fields

* chore: remove useless dev time comments

* fix: remove all `static_asserts`s from asset headers causing build fail on x64

* chore: change union ordering of qos `entryInternalData`

* chore: move qos asset type enum to qos.h

* fix: qos and t4 xsurface index buffer

* chore: implement qos MaterialInfo struct

* chore: adjust some gfxworld struct members

* chore: implement qos PhysPreset and PhysConstraints

* chore: fix and adjust slight inconsistencies with qos zone code

* chore: reorder qos ZoneCode.lua entries in order of asset type enum

* chore: remove asset types that are not actual asset types in zones

* chore: check common asset arrays with static_assert

* chore: rename qos assets

* chore: reorder qos in templates to be in alphabetical order

* chore: use function for CopyMipLevel in Dx9TextureLoader

* chore: use templates for qos MapEntsDumper

* chore: add missing braces in XModelToCommonConverter

* chore: add generic way to peek at zone data

* Qos does not use a classic ZoneHeader so code needs to be more generic

* fix: do not use raw_byte and raw_uint typedefs for qos

* fix: not being able to write clipmapmp

* chore: additional research on qos vertex property and stuff

---------

Co-authored-by: Jan Laupetin <[email protected]>
This commit is contained in:
moandJan Laupetin authored and GitHub committed 2026-08-05 00:32:23 +02:00
1 parent 5c0a7257fd
commit 9f49e0e31c
142 files changed
+6000 -97

No files matched your search

@@ -124,8 +124,9 @@ namespace
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
const auto& header = filePeek.PeekStruct<ZoneHeader>();
if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC)
{
if (!memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
@@ -172,11 +173,11 @@ std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(c
return std::nullopt;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
const auto inspectResult = InspectZoneHeader(filePeek);
if (!inspectResult)
return nullptr;
@@ -190,6 +191,9 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeader)));
// Add steps for loading the auth header which also contain the signature of the zone if it is signed.
AddAuthHeaderSteps(*inspectResult, *zoneLoader, fileName);
@@ -9,8 +9,8 @@ namespace IW3
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
@@ -30,7 +30,6 @@
#include <cassert>
#include <cstring>
#include <filesystem>
#include <iostream>
#include <type_traits>
using namespace IW4;
@@ -44,8 +43,9 @@ namespace
bool m_is_iw4x;
};
std::optional<ZoneLoaderInspectionResultIW4> InspectZoneHeaderIw4(const ZoneHeader& header)
std::optional<ZoneLoaderInspectionResultIW4> InspectZoneHeaderIw4(ZoneDataPeeking& filePeek)
{
const auto& header = filePeek.PeekStruct<ZoneHeader>();
if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC)
{
if (!memcmp(header.m_magic, ZoneConstants::MAGIC_IW4X, std::char_traits<char>::length(ZoneConstants::MAGIC_IW4X)))
@@ -248,20 +248,20 @@ namespace
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
auto resultIw4 = InspectZoneHeaderIw4(header);
auto resultIw4 = InspectZoneHeaderIw4(filePeek);
if (!resultIw4)
return std::nullopt;
return resultIw4->m_generic_result;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeaderIw4(header);
const auto inspectResult = InspectZoneHeaderIw4(filePeek);
if (!inspectResult)
return nullptr;
@@ -275,6 +275,9 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeader)));
// Skip unknown 1 byte field that the game ignores as well
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(1));
@@ -9,8 +9,8 @@ namespace IW4
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
@@ -121,8 +121,9 @@ namespace
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
const auto& header = filePeek.PeekStruct<ZoneHeader>();
if (header.m_version != ZoneConstants::ZONE_VERSION)
return std::nullopt;
@@ -155,11 +156,11 @@ std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(c
return std::nullopt;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
const auto inspectResult = InspectZoneHeader(filePeek);
if (!inspectResult)
return nullptr;
@@ -173,6 +174,9 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeader)));
// Skip unknown 1 byte field that the game ignores as well
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(1));
@@ -9,8 +9,8 @@ namespace IW5
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
@@ -0,0 +1,166 @@
#include "ContentLoaderQOS.h"
#include "Game/QOS/AssetLoaderQOS.h"
#include "Loading/Exception/UnsupportedAssetTypeException.h"
#include <cassert>
using namespace QOS;
ContentLoader::ContentLoader(Zone& zone, ZoneInputStream& stream)
: ContentLoaderBase(zone, stream),
varXAssetList(nullptr),
varXAsset(nullptr),
varScriptStringList(nullptr)
{
}
void ContentLoader::LoadScriptStringList(const bool atStreamStart)
{
assert(!atStreamStart);
if (varScriptStringList->strings != nullptr)
{
assert(GetZonePointerType(varScriptStringList->strings) == ZonePointerType::FOLLOWING);
#ifdef ARCH_x86
varScriptStringList->strings = m_stream.Alloc<const char*>(4);
#else
varScriptStringList->strings = m_stream.AllocOutOfBlock<const char*>(4, varScriptStringList->count);
#endif
varXString = varScriptStringList->strings;
LoadXStringArray(true, varScriptStringList->count);
if (varScriptStringList->strings && varScriptStringList->count > 0)
m_zone.m_script_strings.InitializeForExistingZone(varScriptStringList->strings, static_cast<size_t>(varScriptStringList->count));
}
assert(m_zone.m_script_strings.Count() <= SCR_STRING_MAX + 1);
}
void ContentLoader::LoadXAsset(const bool atStreamStart) const
{
#define LOAD_ASSET(type_index, typeName, headerEntry) \
case type_index: \
{ \
Loader_##typeName loader(m_zone, m_stream); \
loader.Load(&varXAsset->header.headerEntry); \
break; \
}
assert(varXAsset != nullptr);
if (atStreamStart)
m_stream.Load<XAsset>(varXAsset);
switch (varXAsset->type)
{
LOAD_ASSET(ASSET_TYPE_PHYSPRESET, PhysPreset, physPreset)
LOAD_ASSET(ASSET_TYPE_PHYSCONSTRAINTS, PhysConstraints, physConstraints)
LOAD_ASSET(ASSET_TYPE_DESTRUCTIBLEDEF, DestructibleDef, destructibleDef)
LOAD_ASSET(ASSET_TYPE_XANIMPARTS, XAnimParts, parts)
LOAD_ASSET(ASSET_TYPE_XMODEL, XModel, model)
LOAD_ASSET(ASSET_TYPE_MATERIAL, Material, material)
LOAD_ASSET(ASSET_TYPE_TECHNIQUE_SET, MaterialTechniqueSet, techniqueSet)
LOAD_ASSET(ASSET_TYPE_IMAGE, GfxImage, image)
LOAD_ASSET(ASSET_TYPE_SOUND, snd_alias_list_t, sound)
LOAD_ASSET(ASSET_TYPE_SND_CURVE, SndCurve, sndCurve)
LOAD_ASSET(ASSET_TYPE_CLIPMAP_SP, clipMap_t, clipMap)
LOAD_ASSET(ASSET_TYPE_CLIPMAP_MP, clipMap_t, clipMap)
LOAD_ASSET(ASSET_TYPE_COMWORLD, ComWorld, comWorld)
LOAD_ASSET(ASSET_TYPE_GAMEWORLD_SP, GameWorldSp, gameWorldSp)
LOAD_ASSET(ASSET_TYPE_GAMEWORLD_MP, GameWorldMp, gameWorldMp)
LOAD_ASSET(ASSET_TYPE_MAP_ENTS, MapEnts, mapEnts)
LOAD_ASSET(ASSET_TYPE_GFXWORLD, GfxWorld, gfxWorld)
LOAD_ASSET(ASSET_TYPE_LIGHT_DEF, GfxLightDef, lightDef)
LOAD_ASSET(ASSET_TYPE_FONT, Font_s, font)
LOAD_ASSET(ASSET_TYPE_MENUFILE, MenuList, menuList)
LOAD_ASSET(ASSET_TYPE_MENU, menuDef_t, menu)
LOAD_ASSET(ASSET_TYPE_LOCALIZE_ENTRY, LocalizeEntry, localize)
LOAD_ASSET(ASSET_TYPE_WEAPON, WeaponDef, weapon)
LOAD_ASSET(ASSET_TYPE_FX, FxEffectDef, fx)
LOAD_ASSET(ASSET_TYPE_IMPACT_FX, FxImpactTable, impactFx)
LOAD_ASSET(ASSET_TYPE_RAWFILE, RawFile, rawfile)
LOAD_ASSET(ASSET_TYPE_STRINGTABLE, StringTable, stringTable)
LOAD_ASSET(ASSET_TYPE_XMLTREE, XmlTree, xmlTree)
LOAD_ASSET(ASSET_TYPE_SCENE_ANIMATION, SceneAnimation, sceneAnimation)
LOAD_ASSET(ASSET_TYPE_CUTSCENE, Cutscene, cutscene)
LOAD_ASSET(ASSET_TYPE_CUSTOM_CAMERA, CustomCamera, customCamera)
default:
{
throw UnsupportedAssetTypeException(varXAsset->type);
}
}
#undef LOAD_ASSET
}
void ContentLoader::LoadXAssetArray(const bool atStreamStart, const size_t count)
{
assert(varXAsset != nullptr);
if (atStreamStart)
{
#ifdef ARCH_x86
m_stream.Load<XAsset>(varXAsset, count);
#else
const auto fill = m_stream.LoadWithFill(8u * count);
for (size_t index = 0; index < count; index++)
{
fill.Fill(varXAsset[index].type, 8u * index);
fill.FillPtr(varXAsset[index].header.data, 8u * index + 4u);
m_stream.AddPointerLookup(&varXAsset[index].header.data, fill.BlockBuffer(8u * index + 4u));
}
#endif
}
for (size_t index = 0; index < count; index++)
{
LoadXAsset(false);
varXAsset++;
#ifdef DEBUG_OFFSETS
m_stream.DebugOffsets(index);
#endif
}
}
void ContentLoader::Load()
{
XAssetList assetList{};
varXAssetList = &assetList;
#ifdef ARCH_x86
m_stream.LoadDataRaw(&assetList, sizeof(assetList));
#else
const auto fillAccessor = m_stream.LoadWithFill(16u);
varScriptStringList = &varXAssetList->stringList;
fillAccessor.Fill(varScriptStringList->count, 0u);
fillAccessor.FillPtr(varScriptStringList->strings, 4u);
fillAccessor.Fill(varXAssetList->assetCount, 8u);
fillAccessor.FillPtr(varXAssetList->assets, 12u);
#endif
m_stream.PushBlock(XFILE_BLOCK_VIRTUAL);
varScriptStringList = &assetList.stringList;
LoadScriptStringList(false);
if (assetList.assets != nullptr)
{
assert(GetZonePointerType(assetList.assets) == ZonePointerType::FOLLOWING);
#ifdef ARCH_x86
assetList.assets = m_stream.Alloc<XAsset>(4);
#else
assetList.assets = m_stream.AllocOutOfBlock<XAsset>(4, assetList.assetCount);
#endif
varXAsset = assetList.assets;
LoadXAssetArray(true, assetList.assetCount);
}
m_stream.PopBlock();
}
@@ -0,0 +1,25 @@
#pragma once
#include "Game/QOS/QOS.h"
#include "Loading/ContentLoaderBase.h"
#include "Loading/IContentLoadingEntryPoint.h"
namespace QOS
{
class ContentLoader final : public ContentLoaderBase, public IContentLoadingEntryPoint
{
public:
ContentLoader(Zone& zone, ZoneInputStream& stream);
void Load() override;
private:
void LoadScriptStringList(bool atStreamStart);
void LoadXAsset(bool atStreamStart) const;
void LoadXAssetArray(bool atStreamStart, size_t count);
XAssetList* varXAssetList;
XAsset* varXAsset;
ScriptStringList* varScriptStringList;
};
} // namespace QOS
@@ -0,0 +1,23 @@
#include "gfximage_actions.h"
#include <cstring>
using namespace QOS;
Actions_GfxImage::Actions_GfxImage(Zone& zone)
: AssetLoadingActions(zone)
{
}
void Actions_GfxImage::OnImageLoaded(GfxImage* image) const
{
image->cardMemory.platform[0] = 0;
}
void Actions_GfxImage::LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const
{
const size_t loadDefSize = offsetof(GfxImageLoadDef, data) + loadDef->resourceSize;
image->texture.loadDef = static_cast<GfxImageLoadDef*>(m_zone.Memory().AllocRaw(loadDefSize));
memcpy(image->texture.loadDef, loadDef, loadDefSize);
}
@@ -0,0 +1,16 @@
#pragma once
#include "Game/QOS/QOS.h"
#include "Loading/AssetLoadingActions.h"
namespace QOS
{
class Actions_GfxImage final : public AssetLoadingActions
{
public:
explicit Actions_GfxImage(Zone& zone);
void OnImageLoaded(GfxImage* image) const;
void LoadImageData(GfxImageLoadDef* loadDef, GfxImage* image) const;
};
} // namespace QOS
@@ -0,0 +1,92 @@
#include "ZoneLoaderFactoryQOS.h"
#include "ContentLoaderQOS.h"
#include "Game/GameLanguage.h"
#include "Game/QOS/QOS.h"
#include "Game/QOS/ZoneConstantsQOS.h"
#include "Loading/Processor/ProcessorInflate.h"
#include "Loading/Steps/StepAddProcessor.h"
#include "Loading/Steps/StepAllocXBlocks.h"
#include "Loading/Steps/StepLoadZoneContent.h"
#include "Loading/Steps/StepSkipBytes.h"
#include "Utils/ClassUtils.h"
#include "Utils/Endianness.h"
using namespace QOS;
namespace
{
constexpr size_t REMAINING_FILE_HEADER_SIZE = ZoneConstants::FILE_HEADER_SIZE - sizeof(ZoneHeader);
static_assert(REMAINING_FILE_HEADER_SIZE == sizeof(uint32_t) * 4u);
void SetupBlock(ZoneLoader& zoneLoader)
{
#define XBLOCK_DEF(name, type) std::make_unique<XBlock>(STR(name), name, type)
zoneLoader.AddXBlock(XBLOCK_DEF(QOS::XFILE_BLOCK_TEMP, XBlockType::BLOCK_TYPE_TEMP));
zoneLoader.AddXBlock(XBLOCK_DEF(QOS::XFILE_BLOCK_RUNTIME, XBlockType::BLOCK_TYPE_RUNTIME));
zoneLoader.AddXBlock(XBLOCK_DEF(QOS::XFILE_BLOCK_VIRTUAL, XBlockType::BLOCK_TYPE_NORMAL));
zoneLoader.AddXBlock(XBLOCK_DEF(QOS::XFILE_BLOCK_LARGE, XBlockType::BLOCK_TYPE_NORMAL));
zoneLoader.AddXBlock(XBLOCK_DEF(QOS::XFILE_BLOCK_PHYSICAL, XBlockType::BLOCK_TYPE_NORMAL));
#undef XBLOCK_DEF
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
const auto& header = filePeek.PeekStruct<ZoneHeaderQos>();
if (endianness::FromLittleEndian(header.version) == ZoneConstants::ZONE_VERSION_PC)
{
return ZoneLoaderInspectionResult{
.m_game_id = GameId::QOS,
.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(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(filePeek);
if (!inspectResult)
return nullptr;
auto zone = std::make_unique<Zone>(fileName, 0, GameId::QOS, inspectResult->m_platform);
auto* zonePtr = zone.get();
zone->m_language = GameLanguage::LANGUAGE_NONE;
auto zoneLoader = std::make_unique<ZoneLoader>(std::move(zone));
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeaderQos)));
// Maybe external data size?
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(4));
zoneLoader->AddLoadingStep(step::CreateStepAllocXBlocks());
zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE)));
zoneLoader->AddLoadingStep(step::CreateStepLoadZoneContent(
[zonePtr](ZoneInputStream& stream)
{
return std::make_unique<ContentLoader>(*zonePtr, stream);
},
32u,
ZoneConstants::OFFSET_BLOCK_BIT_COUNT,
ZoneConstants::INSERT_BLOCK,
zonePtr->Memory(),
std::move(progressCallback)));
return zoneLoader;
}
@@ -0,0 +1,17 @@
#pragma once
#include "Loading/IZoneLoaderFactory.h"
#include <string>
namespace QOS
{
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
} // namespace QOS
@@ -9,6 +9,7 @@
#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 "Utils/Endianness.h"
@@ -34,8 +35,9 @@ namespace
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
const auto& header = filePeek.PeekStruct<ZoneHeader>();
if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC
&& !memcmp(header.m_magic, ZoneConstants::MAGIC_UNSIGNED, std::char_traits<char>::length(ZoneConstants::MAGIC_UNSIGNED)))
{
@@ -54,11 +56,11 @@ std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(c
return std::nullopt;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
const auto inspectResult = InspectZoneHeader(filePeek);
if (!inspectResult)
return nullptr;
@@ -71,6 +73,10 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH
auto zoneLoader = std::make_unique<ZoneLoader>(std::move(zone));
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeader)));
zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE)));
zoneLoader->AddLoadingStep(step::CreateStepLoadZoneSizes());
zoneLoader->AddLoadingStep(step::CreateStepAllocXBlocks());
@@ -9,8 +9,8 @@ namespace T4
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
@@ -10,9 +10,9 @@
#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>
#include <cstring>
#include <type_traits>
@@ -36,8 +36,9 @@ namespace
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
const auto& header = filePeek.PeekStruct<ZoneHeader>();
if (header.m_version != ZoneConstants::ZONE_VERSION)
return std::nullopt;
@@ -58,11 +59,11 @@ std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(c
return std::nullopt;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeader(header);
const auto inspectResult = InspectZoneHeader(filePeek);
if (!inspectResult)
return nullptr;
@@ -76,6 +77,9 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeader)));
zoneLoader->AddLoadingStep(step::CreateStepAddProcessor(processor::CreateProcessorInflate(ZoneConstants::AUTHED_CHUNK_SIZE)));
// Start of the XFile struct
@@ -9,8 +9,8 @@ namespace T5
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
@@ -47,8 +47,9 @@ namespace
ZoneCompressionTypeT6 m_compression_type;
};
std::optional<ZoneLoaderInspectionResultT6> InspectZoneHeaderT6(const ZoneHeader& header)
std::optional<ZoneLoaderInspectionResultT6> InspectZoneHeaderT6(ZoneDataPeeking& filePeek)
{
const auto& header = filePeek.PeekStruct<ZoneHeader>();
if (endianness::FromLittleEndian(header.m_version) == ZoneConstants::ZONE_VERSION_PC)
{
if (!memcmp(header.m_magic, ZoneConstants::MAGIC_SIGNED_TREYARCH, 8))
@@ -304,20 +305,20 @@ namespace
}
} // namespace
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(const ZoneHeader& header) const
std::optional<ZoneLoaderInspectionResult> ZoneLoaderFactory::InspectZoneHeader(ZoneDataPeeking& filePeek) const
{
auto resultT6 = InspectZoneHeaderT6(header);
auto resultT6 = InspectZoneHeaderT6(filePeek);
if (!resultT6)
return std::nullopt;
return resultT6->m_generic_result;
}
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneHeader& header,
std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const
{
const auto inspectResult = InspectZoneHeaderT6(header);
const auto inspectResult = InspectZoneHeaderT6(filePeek);
if (!inspectResult)
return nullptr;
@@ -331,6 +332,9 @@ std::unique_ptr<ZoneLoader> ZoneLoaderFactory::CreateLoaderForHeader(const ZoneH
SetupBlock(*zoneLoader);
// Skip the initial header that we peeked at before
zoneLoader->AddLoadingStep(step::CreateStepSkipBytes(sizeof(ZoneHeader)));
// If file is signed setup a RSA instance.
auto rsa = inspectResult->m_generic_result.m_is_signed ? SetupRsa(inspectResult->m_generic_result.m_is_official) : nullptr;
@@ -9,8 +9,8 @@ namespace T6
class ZoneLoaderFactory final : public IZoneLoaderFactory
{
public:
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const override;
[[nodiscard]] std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const override;
};
@@ -3,6 +3,7 @@
#include "Game/IW3/ZoneLoaderFactoryIW3.h"
#include "Game/IW4/ZoneLoaderFactoryIW4.h"
#include "Game/IW5/ZoneLoaderFactoryIW5.h"
#include "Game/QOS/ZoneLoaderFactoryQOS.h"
#include "Game/T4/ZoneLoaderFactoryT4.h"
#include "Game/T5/ZoneLoaderFactoryT5.h"
#include "Game/T6/ZoneLoaderFactoryT6.h"
@@ -15,6 +16,7 @@ const IZoneLoaderFactory* IZoneLoaderFactory::GetZoneLoaderFactoryForGame(GameId
new IW3::ZoneLoaderFactory(),
new IW4::ZoneLoaderFactory(),
new IW5::ZoneLoaderFactory(),
new QOS::ZoneLoaderFactory(),
new T4::ZoneLoaderFactory(),
new T5::ZoneLoaderFactory(),
new T6::ZoneLoaderFactory(),
+3 -2
View File
@@ -3,6 +3,7 @@
#include "Game/IGame.h"
#include "Utils/ProgressCallback.h"
#include "Zone/ZoneTypes.h"
#include "ZoneDataPeeking.h"
#include "ZoneLoader.h"
#include <memory>
@@ -36,8 +37,8 @@ public:
IZoneLoaderFactory& operator=(const IZoneLoaderFactory& other) = default;
IZoneLoaderFactory& operator=(IZoneLoaderFactory&& other) noexcept = default;
[[nodiscard]] virtual std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(const ZoneHeader& header) const = 0;
[[nodiscard]] virtual std::unique_ptr<ZoneLoader> CreateLoaderForHeader(const ZoneHeader& header,
[[nodiscard]] virtual std::optional<ZoneLoaderInspectionResult> InspectZoneHeader(ZoneDataPeeking& filePeek) const = 0;
[[nodiscard]] virtual std::unique_ptr<ZoneLoader> CreateLoaderForHeader(ZoneDataPeeking& filePeek,
const std::string& fileName,
std::optional<std::unique_ptr<ProgressCallback>> progressCallback) const = 0;
@@ -0,0 +1,16 @@
#include "ZoneDataPeeking.h"
ZoneDataPeeking::ZoneDataPeeking(std::istream& stream)
: m_stream(stream)
{
}
void ZoneDataPeeking::EnsureHasEnoughData(const size_t size)
{
const auto currentSize = m_data.size();
if (size > currentSize)
{
m_data.resize(size);
m_stream.read(&m_data[currentSize], size - currentSize);
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <cstring>
#include <istream>
#include <vector>
class ZoneDataPeeking
{
public:
explicit ZoneDataPeeking(std::istream& stream);
template<typename T> const T& PeekStruct()
{
EnsureHasEnoughData(sizeof(T));
return *reinterpret_cast<const T*>(m_data.data());
}
private:
void EnsureHasEnoughData(size_t size);
std::istream& m_stream;
std::vector<char> m_data;
};
+8 -7
View File
@@ -1,6 +1,7 @@
#include "ZoneLoading.h"
#include "Loading/IZoneLoaderFactory.h"
#include "Loading/ZoneDataPeeking.h"
#include "Loading/ZoneLoader.h"
#include <filesystem>
@@ -20,18 +21,14 @@ std::expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const st
if (!file.is_open())
return std::unexpected(std::format("Could not open file '{}'.", path));
ZoneHeader header{};
file.read(reinterpret_cast<char*>(&header), sizeof(header));
if (file.gcount() != sizeof(header))
return std::unexpected(std::format("Failed to read zone header from file '{}'.", path));
ZoneDataPeeking dataPeeking(file);
std::unique_ptr<ZoneLoader> zoneLoader;
for (auto game = 0u; game < static_cast<unsigned>(GameId::COUNT); game++)
{
const auto* factory = IZoneLoaderFactory::GetZoneLoaderFactoryForGame(static_cast<GameId>(game));
if (factory->InspectZoneHeader(header))
if (factory->InspectZoneHeader(dataPeeking))
{
zoneLoader = factory->CreateLoaderForHeader(header, zoneName, std::move(progressCallback));
zoneLoader = factory->CreateLoaderForHeader(dataPeeking, zoneName, std::move(progressCallback));
break;
}
}
@@ -39,6 +36,10 @@ std::expected<std::unique_ptr<Zone>, std::string> ZoneLoading::LoadZone(const st
if (!zoneLoader)
return std::unexpected(std::format("Could not create factory for zone '{}'.", zoneName));
// Revert to beginning to make sure no peeking has advanced the read position.
// (As different peeking logic may advance by different amounts of bytes).
file.seekg(0, std::ios::beg);
auto loadedZone = zoneLoader->LoadZone(file);
file.close();