Merge pull request #330 from Laupetin/test/obj-compiling-tests

Add tests for ObjCompiling module
This commit is contained in:
Jan 2025-01-08 22:55:18 +01:00 committed by GitHub
commit 98288f5d47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 1842 additions and 426 deletions

View File

@ -49,6 +49,7 @@ jobs:
working-directory: ${{ github.workspace }}/build/lib/Release_x86/tests
run: |
./ObjCommonTests
./ObjCompilingTests
./ObjLoadingTests
./ParserTests
./ZoneCodeGeneratorLibTests
@ -86,6 +87,8 @@ jobs:
$combinedExitCode = 0
./ObjCommonTests
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./ObjCompilingTests
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./ObjLoadingTests
$combinedExitCode = [System.Math]::max($combinedExitCode, $LASTEXITCODE)
./ParserTests

View File

@ -170,8 +170,10 @@ group ""
-- ========================
-- Tests
-- ========================
include "test/Catch2Common.lua"
include "test/ObjCommonTestUtils.lua"
include "test/ObjCommonTests.lua"
include "test/ObjCompilingTests.lua"
include "test/ObjLoadingTests.lua"
include "test/ParserTestUtils.lua"
include "test/ParserTests.lua"
@ -180,8 +182,10 @@ include "test/ZoneCommonTests.lua"
-- Tests group: Unit test and other tests projects
group "Tests"
Catch2Common:project()
ObjCommonTestUtils:project()
ObjCommonTests:project()
ObjCompilingTests:project()
ObjLoadingTests:project()
ParserTestUtils:project()
ParserTests:project()

View File

@ -3,6 +3,7 @@
#include "Gdt/GdtLookup.h"
#include "IObjCompiler.h"
#include "IObjLoader.h"
#include "SearchPath/OutputPathFilesystem.h"
#include <cassert>
@ -67,8 +68,10 @@ namespace zone_creator
ZoneDefinitionContext zoneDefinitionContext(*context.m_definition);
AssetCreationContext creationContext(*zone, &creatorCollection, &ignoredAssetLookup);
OutputPathFilesystem outDir(context.m_out_dir);
OutputPathFilesystem cacheDir(context.m_cache_dir);
objCompiler->ConfigureCreatorCollection(
creatorCollection, *zone, zoneDefinitionContext, *context.m_asset_search_path, lookup, creationContext, context.m_out_dir, context.m_cache_dir);
creatorCollection, *zone, zoneDefinitionContext, *context.m_asset_search_path, lookup, creationContext, outDir, cacheDir);
objLoader->ConfigureCreatorCollection(creatorCollection, *zone, *context.m_asset_search_path, lookup);
for (const auto& assetEntry : context.m_definition->m_assets)

View File

@ -0,0 +1,18 @@
#pragma once
#include <memory>
#include <ostream>
#include <string>
class IOutputPath
{
public:
IOutputPath() = default;
virtual ~IOutputPath() = default;
IOutputPath(const IOutputPath& other) = default;
IOutputPath(IOutputPath&& other) noexcept = default;
IOutputPath& operator=(const IOutputPath& other) = default;
IOutputPath& operator=(IOutputPath&& other) noexcept = default;
virtual std::unique_ptr<std::ostream> Open(const std::string& fileName) = 0;
};

View File

@ -0,0 +1,27 @@
#include "OutputPathFilesystem.h"
#include <fstream>
namespace fs = std::filesystem;
OutputPathFilesystem::OutputPathFilesystem(const fs::path& path)
: m_path(fs::weakly_canonical(path))
{
}
std::unique_ptr<std::ostream> OutputPathFilesystem::Open(const std::string& fileName)
{
const auto fullNewPath = fs::weakly_canonical(m_path / fileName);
if (!fullNewPath.string().starts_with(m_path.string()))
return nullptr;
const auto containingDirectory = fullNewPath.parent_path();
fs::create_directories(containingDirectory);
std::ofstream stream(fullNewPath, std::ios::binary | std::ios::out);
if (!stream.is_open())
return nullptr;
return std::make_unique<std::ofstream>(std::move(stream));
}

View File

@ -0,0 +1,16 @@
#pragma once
#include "IOutputPath.h"
#include <filesystem>
class OutputPathFilesystem final : public IOutputPath
{
public:
explicit OutputPathFilesystem(const std::filesystem::path& path);
std::unique_ptr<std::ostream> Open(const std::string& fileName) override;
private:
std::filesystem::path m_path;
};

View File

@ -6,7 +6,6 @@
#include <memory>
using namespace IW3;
namespace fs = std::filesystem;
namespace
{
@ -22,7 +21,7 @@ namespace
const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir)
IOutputPath& outDir)
{
auto& memory = *zone.GetMemory();
@ -37,8 +36,8 @@ void ObjCompiler::ConfigureCreatorCollection(AssetCreatorCollection& collection,
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir,
const fs::path& cacheDir) const
IOutputPath& outDir,
IOutputPath& cacheDir) const
{
ConfigurePostProcessors(collection, zone, zoneDefinition, searchPath, zoneStates, outDir);
}

View File

@ -13,7 +13,7 @@ namespace IW3
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir,
const std::filesystem::path& cacheDir) const override;
IOutputPath& outDir,
IOutputPath& cacheDir) const override;
};
} // namespace IW3

View File

@ -6,7 +6,6 @@
#include <memory>
using namespace IW4;
namespace fs = std::filesystem;
namespace
{
@ -22,7 +21,7 @@ namespace
const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir)
IOutputPath& outDir)
{
auto& memory = *zone.GetMemory();
@ -37,8 +36,8 @@ void ObjCompiler::ConfigureCreatorCollection(AssetCreatorCollection& collection,
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir,
const fs::path& cacheDir) const
IOutputPath& outDir,
IOutputPath& cacheDir) const
{
ConfigurePostProcessors(collection, zone, zoneDefinition, searchPath, zoneStates, outDir);
}

View File

@ -13,7 +13,7 @@ namespace IW4
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir,
const std::filesystem::path& cacheDir) const override;
IOutputPath& outDir,
IOutputPath& cacheDir) const override;
};
} // namespace IW4

View File

@ -6,7 +6,6 @@
#include <memory>
using namespace IW5;
namespace fs = std::filesystem;
namespace
{
@ -22,7 +21,7 @@ namespace
const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir)
IOutputPath& outDir)
{
auto& memory = *zone.GetMemory();
@ -37,8 +36,8 @@ void ObjCompiler::ConfigureCreatorCollection(AssetCreatorCollection& collection,
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir,
const fs::path& cacheDir) const
IOutputPath& outDir,
IOutputPath& cacheDir) const
{
ConfigurePostProcessors(collection, zone, zoneDefinition, searchPath, zoneStates, outDir);
}

View File

@ -13,7 +13,7 @@ namespace IW5
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir,
const std::filesystem::path& cacheDir) const override;
IOutputPath& outDir,
IOutputPath& cacheDir) const override;
};
} // namespace IW5

View File

@ -6,7 +6,6 @@
#include <memory>
using namespace T5;
namespace fs = std::filesystem;
namespace
{
@ -22,7 +21,7 @@ namespace
const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir)
IOutputPath& outDir)
{
auto& memory = *zone.GetMemory();
@ -37,8 +36,8 @@ void ObjCompiler::ConfigureCreatorCollection(AssetCreatorCollection& collection,
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir,
const fs::path& cacheDir) const
IOutputPath& outDir,
IOutputPath& cacheDir) const
{
ConfigurePostProcessors(collection, zone, zoneDefinition, searchPath, zoneStates, outDir);
}

View File

@ -13,7 +13,7 @@ namespace T5
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir,
const std::filesystem::path& cacheDir) const override;
IOutputPath& outDir,
IOutputPath& cacheDir) const override;
};
} // namespace T5

View File

@ -2,6 +2,7 @@
#include "Game/T6/CommonT6.h"
#include "Game/T6/T6.h"
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include <cassert>
#include <format>
@ -9,54 +10,73 @@
using namespace T6;
KeyValuePairsCompiler::KeyValuePairsCompiler(MemoryManager& memory,
const Zone& zone,
const ZoneDefinition& zoneDefinition,
ZoneAssetCreationStateContainer& zoneStates)
: m_memory(memory),
m_zone(zone),
m_zone_definition(zoneDefinition),
m_kvp_creator(zoneStates.GetZoneAssetCreationState<KeyValuePairsCreator>())
namespace
{
}
std::optional<asset_type_t> KeyValuePairsCompiler::GetHandlingAssetType() const
{
return std::nullopt;
}
AssetCreationResult KeyValuePairsCompiler::CreateAsset(const std::string& assetName, AssetCreationContext& context)
{
return AssetCreationResult::NoAction();
}
void KeyValuePairsCompiler::FinalizeZone(AssetCreationContext& context)
{
m_kvp_creator.Finalize(m_zone_definition);
const auto commonKvps = m_kvp_creator.GetFinalKeyValuePairs();
if (commonKvps.empty())
return;
auto* gameKvps = m_memory.Alloc<KeyValuePairs>();
gameKvps->name = m_memory.Dup(m_zone.m_name.c_str());
gameKvps->numVariables = commonKvps.size();
gameKvps->keyValuePairs = m_memory.Alloc<KeyValuePair>(commonKvps.size());
const auto namespaceHash = Common::Com_HashKey(m_zone.m_name.c_str(), 64);
for (auto kvpIndex = 0u; kvpIndex < gameKvps->numVariables; kvpIndex++)
class KeyValuePairsCompiler final : public IAssetCreator
{
const auto& commonKvp = commonKvps[kvpIndex];
auto& gameKvp = gameKvps->keyValuePairs[kvpIndex];
public:
KeyValuePairsCompiler(MemoryManager& memory, const Zone& zone, const ZoneDefinition& zoneDefinition, ZoneAssetCreationStateContainer& zoneStates)
: m_memory(memory),
m_zone(zone),
m_zone_definition(zoneDefinition),
m_kvp_creator(zoneStates.GetZoneAssetCreationState<KeyValuePairsCreator>())
{
}
assert(commonKvp.m_key_str || commonKvp.m_key_hash);
if (commonKvp.m_key_str)
gameKvp.keyHash = Common::Com_HashKey(commonKvp.m_key_str->c_str(), 64);
else
gameKvp.keyHash = *commonKvp.m_key_hash;
[[nodiscard]] std::optional<asset_type_t> GetHandlingAssetType() const override
{
return std::nullopt;
}
gameKvp.namespaceHash = namespaceHash;
gameKvp.value = m_memory.Dup(commonKvp.m_value.c_str());
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override
{
return AssetCreationResult::NoAction();
}
void FinalizeZone(AssetCreationContext& context) override
{
m_kvp_creator.Finalize(m_zone_definition);
const auto commonKvps = m_kvp_creator.GetFinalKeyValuePairs();
if (commonKvps.empty())
return;
auto* gameKvps = m_memory.Alloc<KeyValuePairs>();
gameKvps->name = m_memory.Dup(m_zone.m_name.c_str());
gameKvps->numVariables = commonKvps.size();
gameKvps->keyValuePairs = m_memory.Alloc<KeyValuePair>(commonKvps.size());
const auto namespaceHash = Common::Com_HashKey(m_zone.m_name.c_str(), 64);
for (auto kvpIndex = 0u; kvpIndex < gameKvps->numVariables; kvpIndex++)
{
const auto& commonKvp = commonKvps[kvpIndex];
auto& gameKvp = gameKvps->keyValuePairs[kvpIndex];
assert(commonKvp.m_key_str || commonKvp.m_key_hash);
if (commonKvp.m_key_str)
gameKvp.keyHash = Common::Com_HashKey(commonKvp.m_key_str->c_str(), 64);
else
gameKvp.keyHash = *commonKvp.m_key_hash;
gameKvp.namespaceHash = namespaceHash;
gameKvp.value = m_memory.Dup(commonKvp.m_value.c_str());
}
context.AddAsset(AssetRegistration<AssetKeyValuePairs>(m_zone.m_name, gameKvps));
}
private:
MemoryManager& m_memory;
const Zone& m_zone;
const ZoneDefinition& m_zone_definition;
KeyValuePairsCreator& m_kvp_creator;
};
} // namespace
namespace T6
{
std::unique_ptr<IAssetCreator>
CreateKeyValuePairsCompiler(MemoryManager& memory, const Zone& zone, const ZoneDefinition& zoneDefinition, ZoneAssetCreationStateContainer& zoneStates)
{
return std::make_unique<KeyValuePairsCompiler>(memory, zone, zoneDefinition, zoneStates);
}
context.AddAsset(AssetRegistration<AssetKeyValuePairs>(m_zone.m_name, gameKvps));
}
} // namespace T6

View File

@ -1,27 +1,16 @@
#pragma once
#include "Asset/IAssetCreator.h"
#include "Game/T6/T6.h"
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include "Asset/IZoneAssetCreationState.h"
#include "SearchPath/ISearchPath.h"
#include "Utils/MemoryManager.h"
#include "Zone/Definition/ZoneDefinition.h"
#include "Zone/Zone.h"
#include <memory>
namespace T6
{
class KeyValuePairsCompiler final : public IAssetCreator
{
public:
KeyValuePairsCompiler(MemoryManager& memory, const Zone& zone, const ZoneDefinition& zoneDefinition, ZoneAssetCreationStateContainer& zoneStates);
[[nodiscard]] std::optional<asset_type_t> GetHandlingAssetType() const override;
AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override;
void FinalizeZone(AssetCreationContext& context) override;
private:
MemoryManager& m_memory;
const Zone& m_zone;
const ZoneDefinition& m_zone_definition;
KeyValuePairsCreator& m_kvp_creator;
};
std::unique_ptr<IAssetCreator>
CreateKeyValuePairsCompiler(MemoryManager& memory, const Zone& zone, const ZoneDefinition& zoneDefinition, ZoneAssetCreationStateContainer& zoneStates);
} // namespace T6

View File

@ -8,7 +8,6 @@
#include <memory>
using namespace T6;
namespace fs = std::filesystem;
namespace
{
@ -20,7 +19,7 @@ namespace
{
auto& memory = *zone.GetMemory();
collection.AddAssetCreator(std::make_unique<KeyValuePairsCompiler>(memory, zone, zoneDefinition.m_zone_definition, zoneStates));
collection.AddAssetCreator(CreateKeyValuePairsCompiler(memory, zone, zoneDefinition.m_zone_definition, zoneStates));
}
void ConfigurePostProcessors(AssetCreatorCollection& collection,
@ -28,7 +27,7 @@ namespace
const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir)
IOutputPath& outDir)
{
auto& memory = *zone.GetMemory();
@ -46,8 +45,8 @@ void ObjCompiler::ConfigureCreatorCollection(AssetCreatorCollection& collection,
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const fs::path& outDir,
const fs::path& cacheDir) const
IOutputPath& outDir,
IOutputPath& cacheDir) const
{
ConfigureCompilers(collection, zone, zoneDefinition, searchPath, zoneStates);
ConfigurePostProcessors(collection, zone, zoneDefinition, searchPath, zoneStates, outDir);

View File

@ -13,7 +13,7 @@ namespace T6
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir,
const std::filesystem::path& cacheDir) const override;
IOutputPath& outDir,
IOutputPath& cacheDir) const override;
};
} // namespace T6

View File

@ -4,14 +4,10 @@
#include "Asset/IZoneAssetCreationState.h"
#include "Asset/ZoneDefinitionContext.h"
#include "Gdt/IGdtQueryable.h"
#include "SearchPath/IOutputPath.h"
#include "SearchPath/ISearchPath.h"
#include "Zone/Definition/ZoneDefinition.h"
#include "Zone/Zone.h"
#include <cstdint>
#include <filesystem>
#include <string>
class IObjCompiler
{
public:
@ -28,8 +24,8 @@ public:
ISearchPath& searchPath,
IGdtQueryable& gdt,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir,
const std::filesystem::path& cacheDir) const = 0;
IOutputPath& outDir,
IOutputPath& cacheDir) const = 0;
static const IObjCompiler* GetObjCompilerForGame(GameId game);
};

View File

@ -379,22 +379,26 @@ void IPakToCreate::AddImage(std::string imageName)
m_image_names.emplace_back(std::move(imageName));
}
void IPakToCreate::Build(ISearchPath& searchPath, const std::filesystem::path& outPath)
void IPakToCreate::Build(ISearchPath& searchPath, IOutputPath& outPath)
{
auto filePath = outPath / std::format("{}.ipak", m_name);
std::ofstream file(filePath, std::ios::out | std::ios::binary);
if (!file.is_open())
const auto file = outPath.Open(std::format("{}.ipak", m_name));
if (!file)
{
std::cerr << std::format("Failed to open file for ipak {}\n", m_name);
return;
}
IPakWriter writer(file, searchPath, m_image_names);
IPakWriter writer(*file, searchPath, m_image_names);
writer.Write();
std::cout << std::format("Created ipak {} with {} entries\n", m_name, m_image_names.size());
}
const std::vector<std::string>& IPakToCreate::GetImageNames() const
{
return m_image_names;
}
IPakCreator::IPakCreator()
: m_kvp_creator(nullptr)
{
@ -413,6 +417,7 @@ IPakToCreate* IPakCreator::GetOrAddIPak(const std::string& ipakName)
auto newIPak = std::make_unique<IPakToCreate>(ipakName);
auto* result = newIPak.get();
m_ipak_lookup.emplace(ipakName, result);
m_ipaks.emplace_back(std::move(newIPak));
assert(m_kvp_creator);
@ -421,7 +426,7 @@ IPakToCreate* IPakCreator::GetOrAddIPak(const std::string& ipakName)
return result;
}
void IPakCreator::Finalize(ISearchPath& searchPath, const std::filesystem::path& outPath)
void IPakCreator::Finalize(ISearchPath& searchPath, IOutputPath& outPath)
{
for (const auto& ipakToCreate : m_ipaks)
ipakToCreate->Build(searchPath, outPath);

View File

@ -2,9 +2,9 @@
#include "Asset/IZoneAssetCreationState.h"
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include "SearchPath/IOutputPath.h"
#include "SearchPath/ISearchPath.h"
#include <filesystem>
#include <memory>
#include <string>
#include <unordered_map>
@ -15,14 +15,15 @@ public:
explicit IPakToCreate(std::string name);
void AddImage(std::string imageName);
void Build(ISearchPath& searchPath, const std::filesystem::path& outPath);
void Build(ISearchPath& searchPath, IOutputPath& outPath);
[[nodiscard]] const std::vector<std::string>& GetImageNames() const;
private:
std::string m_name;
std::vector<std::string> m_image_names;
};
class IPakCreator : public IZoneAssetCreationState
class IPakCreator final : public IZoneAssetCreationState
{
public:
IPakCreator();
@ -30,7 +31,7 @@ public:
void Inject(ZoneAssetCreationInjection& inject) override;
IPakToCreate* GetOrAddIPak(const std::string& ipakName);
void Finalize(ISearchPath& searchPath, const std::filesystem::path& outPath);
void Finalize(ISearchPath& searchPath, IOutputPath& outPath);
private:
KeyValuePairsCreator* m_kvp_creator;

View File

@ -2,36 +2,34 @@
#include "IPak/IPakCreator.h"
#include <format>
#include <algorithm>
AbstractImageIPakPostProcessor::AbstractImageIPakPostProcessor(const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir)
IOutputPath& outDir)
: m_zone_definition(zoneDefinition),
m_search_path(searchPath),
m_ipak_creator(zoneStates.GetZoneAssetCreationState<IPakCreator>()),
m_out_dir(outDir),
m_initialized(false),
m_obj_container_index(0u),
m_current_ipak(nullptr),
m_current_ipak_start_index(0u),
m_current_ipak_end_index(0u)
{
FindNextObjContainer();
}
bool AbstractImageIPakPostProcessor::AppliesToZoneDefinition(const ZoneDefinitionContext& zoneDefinition)
{
for (const auto& objContainer : zoneDefinition.m_zone_definition.m_obj_containers)
{
if (objContainer.m_type == ZoneDefinitionObjContainerType::IPAK)
return true;
}
return false;
return std::ranges::any_of(zoneDefinition.m_zone_definition.m_obj_containers,
[](const ZoneDefinitionObjContainer& objContainer)
{
return objContainer.m_type == ZoneDefinitionObjContainerType::IPAK;
});
}
void AbstractImageIPakPostProcessor::FindNextObjContainer(AssetCreationContext& context)
void AbstractImageIPakPostProcessor::FindNextObjContainer()
{
const auto objContainerCount = m_zone_definition.m_zone_definition.m_obj_containers.size();
while (m_obj_container_index < objContainerCount)
@ -55,17 +53,10 @@ void AbstractImageIPakPostProcessor::PostProcessAsset(XAssetInfoGeneric& assetIn
if (assetInfo.m_name.empty() || assetInfo.m_name[0] == ',')
return;
// Initialize on first image occurance
if (!m_initialized)
{
FindNextObjContainer(context);
m_initialized = true;
}
while (m_current_ipak && m_zone_definition.m_asset_index_in_definition >= m_current_ipak_end_index)
FindNextObjContainer(context);
FindNextObjContainer();
if (m_current_ipak && m_zone_definition.m_asset_index_in_definition <= m_current_ipak_start_index)
if (m_current_ipak && m_zone_definition.m_asset_index_in_definition >= m_current_ipak_start_index)
m_current_ipak->AddImage(assetInfo.m_name);
}

View File

@ -3,8 +3,7 @@
#include "Asset/IAssetPostProcessor.h"
#include "Asset/ZoneDefinitionContext.h"
#include "Image/IPak/IPakCreator.h"
#include <filesystem>
#include "SearchPath/IOutputPath.h"
class AbstractImageIPakPostProcessor : public IAssetPostProcessor
{
@ -12,7 +11,7 @@ public:
AbstractImageIPakPostProcessor(const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir);
IOutputPath& outDir);
static bool AppliesToZoneDefinition(const ZoneDefinitionContext& zoneDefinition);
@ -20,14 +19,13 @@ public:
void FinalizeZone(AssetCreationContext& context) override;
private:
void FindNextObjContainer(AssetCreationContext& context);
void FindNextObjContainer();
const ZoneDefinitionContext& m_zone_definition;
ISearchPath& m_search_path;
IPakCreator& m_ipak_creator;
const std::filesystem::path& m_out_dir;
IOutputPath& m_out_dir;
bool m_initialized;
unsigned m_obj_container_index;
IPakToCreate* m_current_ipak;
unsigned m_current_ipak_start_index;
@ -42,7 +40,7 @@ public:
ImageIPakPostProcessor(const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir)
IOutputPath& outDir)
: AbstractImageIPakPostProcessor(zoneDefinition, searchPath, zoneStates, outDir)
{
}
@ -50,5 +48,5 @@ public:
[[nodiscard]] asset_type_t GetHandlingAssetType() const override
{
return AssetType::EnumEntry;
};
}
};

View File

@ -2,37 +2,35 @@
#include "Iwd/IwdCreator.h"
#include <algorithm>
#include <format>
#include <iostream>
AbstractImageIwdPostProcessor::AbstractImageIwdPostProcessor(const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir)
IOutputPath& outDir)
: m_zone_definition(zoneDefinition),
m_search_path(searchPath),
m_iwd_creator(zoneStates.GetZoneAssetCreationState<IwdCreator>()),
m_out_dir(outDir),
m_initialized(false),
m_obj_container_index(0u),
m_current_iwd(nullptr),
m_current_iwd_start_index(0u),
m_current_iwd_end_index(0u)
{
FindNextObjContainer();
}
bool AbstractImageIwdPostProcessor::AppliesToZoneDefinition(const ZoneDefinitionContext& zoneDefinition)
{
for (const auto& objContainer : zoneDefinition.m_zone_definition.m_obj_containers)
{
if (objContainer.m_type == ZoneDefinitionObjContainerType::IWD)
return true;
}
return false;
return std::ranges::any_of(zoneDefinition.m_zone_definition.m_obj_containers,
[](const ZoneDefinitionObjContainer& objContainer)
{
return objContainer.m_type == ZoneDefinitionObjContainerType::IWD;
});
}
void AbstractImageIwdPostProcessor::FindNextObjContainer(AssetCreationContext& context)
void AbstractImageIwdPostProcessor::FindNextObjContainer()
{
const auto objContainerCount = m_zone_definition.m_zone_definition.m_obj_containers.size();
while (m_obj_container_index < objContainerCount)
@ -56,17 +54,10 @@ void AbstractImageIwdPostProcessor::PostProcessAsset(XAssetInfoGeneric& assetInf
if (assetInfo.m_name.empty() || assetInfo.m_name[0] == ',')
return;
// Initialize on first image occurance
if (!m_initialized)
{
FindNextObjContainer(context);
m_initialized = true;
}
while (m_current_iwd && m_zone_definition.m_asset_index_in_definition >= m_current_iwd_end_index)
FindNextObjContainer(context);
FindNextObjContainer();
if (m_current_iwd && m_zone_definition.m_asset_index_in_definition <= m_current_iwd_start_index)
if (m_current_iwd && m_zone_definition.m_asset_index_in_definition >= m_current_iwd_start_index)
m_current_iwd->AddFile(std::format("images/{}.iwi", assetInfo.m_name));
}

View File

@ -3,8 +3,7 @@
#include "Asset/IAssetPostProcessor.h"
#include "Asset/ZoneDefinitionContext.h"
#include "Iwd/IwdCreator.h"
#include <filesystem>
#include "SearchPath/IOutputPath.h"
class AbstractImageIwdPostProcessor : public IAssetPostProcessor
{
@ -12,7 +11,7 @@ public:
AbstractImageIwdPostProcessor(const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir);
IOutputPath& outDir);
static bool AppliesToZoneDefinition(const ZoneDefinitionContext& zoneDefinition);
@ -20,14 +19,13 @@ public:
void FinalizeZone(AssetCreationContext& context) override;
private:
void FindNextObjContainer(AssetCreationContext& context);
void FindNextObjContainer();
const ZoneDefinitionContext& m_zone_definition;
ISearchPath& m_search_path;
IwdCreator& m_iwd_creator;
const std::filesystem::path& m_out_dir;
IOutputPath& m_out_dir;
bool m_initialized;
unsigned m_obj_container_index;
IwdToCreate* m_current_iwd;
unsigned m_current_iwd_start_index;
@ -42,7 +40,7 @@ public:
ImageIwdPostProcessor(const ZoneDefinitionContext& zoneDefinition,
ISearchPath& searchPath,
ZoneAssetCreationStateContainer& zoneStates,
const std::filesystem::path& outDir)
IOutputPath& outDir)
: AbstractImageIwdPostProcessor(zoneDefinition, searchPath, zoneStates, outDir)
{
}
@ -50,5 +48,5 @@ public:
[[nodiscard]] asset_type_t GetHandlingAssetType() const override
{
return AssetType::EnumEntry;
};
}
};

View File

@ -18,19 +18,19 @@ void IwdToCreate::AddFile(std::string filePath)
m_file_paths.emplace_back(std::move(filePath));
}
void IwdToCreate::Build(ISearchPath& searchPath, const std::filesystem::path& outPath)
void IwdToCreate::Build(ISearchPath& searchPath, IOutputPath& outPath)
{
auto filePath = outPath / std::format("{}.iwd", m_name);
std::ofstream file(filePath, std::ios::out | std::ios::binary);
if (!file.is_open())
const auto fileName = std::format("{}.iwd", m_name);
const auto file = outPath.Open(fileName);
if (!file)
{
std::cerr << std::format("Failed to open file for iwd {}\n", m_name);
return;
}
auto functions = FileToZlibWrapper::CreateFunctions32ForFile(&file);
auto functions = FileToZlibWrapper::CreateFunctions32ForFile(file.get());
auto zipFile = zipOpen2(filePath.string().c_str(), APPEND_STATUS_CREATE, nullptr, &functions);
const auto zipFile = zipOpen2(fileName.c_str(), APPEND_STATUS_CREATE, nullptr, &functions);
if (!zipFile)
{
std::cerr << std::format("Failed to open file as zip for iwd {}\n", m_name);
@ -79,6 +79,11 @@ void IwdToCreate::Build(ISearchPath& searchPath, const std::filesystem::path& ou
std::cout << std::format("Created iwd {} with {} entries\n", m_name, m_file_paths.size());
}
const std::vector<std::string>& IwdToCreate::GetFilePaths() const
{
return m_file_paths;
}
IwdToCreate* IwdCreator::GetOrAddIwd(const std::string& iwdName)
{
const auto existingIwd = m_iwd_lookup.find(iwdName);
@ -87,12 +92,13 @@ IwdToCreate* IwdCreator::GetOrAddIwd(const std::string& iwdName)
auto newIwd = std::make_unique<IwdToCreate>(iwdName);
auto* result = newIwd.get();
m_iwd_lookup.emplace(iwdName, result);
m_iwds.emplace_back(std::move(newIwd));
return result;
}
void IwdCreator::Finalize(ISearchPath& searchPath, const std::filesystem::path& outPath)
void IwdCreator::Finalize(ISearchPath& searchPath, IOutputPath& outPath)
{
std::cout << std::format("Writing {} iwd files to disk\n", m_iwds.size());
for (const auto& iwdToCreate : m_iwds)

View File

@ -1,9 +1,9 @@
#pragma once
#include "Asset/IZoneAssetCreationState.h"
#include "SearchPath/IOutputPath.h"
#include "SearchPath/ISearchPath.h"
#include <filesystem>
#include <memory>
#include <string>
#include <unordered_map>
@ -14,18 +14,19 @@ public:
explicit IwdToCreate(std::string name);
void AddFile(std::string filePath);
void Build(ISearchPath& searchPath, const std::filesystem::path& outPath);
void Build(ISearchPath& searchPath, IOutputPath& outPath);
[[nodiscard]] const std::vector<std::string>& GetFilePaths() const;
private:
std::string m_name;
std::vector<std::string> m_file_paths;
};
class IwdCreator : public IZoneAssetCreationState
class IwdCreator final : public IZoneAssetCreationState
{
public:
IwdToCreate* GetOrAddIwd(const std::string& iwdName);
void Finalize(ISearchPath& searchPath, const std::filesystem::path& outPath);
void Finalize(ISearchPath& searchPath, IOutputPath& outPath);
private:
std::unordered_map<std::string, IwdToCreate*> m_iwd_lookup;

View File

@ -1,7 +1,9 @@
#include "KeyValuePairsCreator.h"
#include <algorithm>
#include <format>
#include <iostream>
#include <ranges>
CommonKeyValuePair::CommonKeyValuePair(std::string keyStr, std::string value)
: m_key_str(std::move(keyStr)),
@ -49,6 +51,23 @@ void KeyValuePairsCreator::Finalize(const ZoneDefinition& zoneDefinition)
}
}
}
std::ranges::sort(m_key_value_pairs,
[](const CommonKeyValuePair& v0, const CommonKeyValuePair& v1)
{
if (v0.m_key_str.has_value())
{
if (!v1.m_key_str.has_value())
return true;
return *v0.m_key_str < *v1.m_key_str;
}
if (!v1.m_key_hash.has_value())
return false;
return *v0.m_key_hash < *v1.m_key_hash;
});
}
std::vector<CommonKeyValuePair> KeyValuePairsCreator::GetFinalKeyValuePairs()

View File

@ -18,7 +18,7 @@ public:
std::string m_value;
};
class KeyValuePairsCreator : public IZoneAssetCreationState
class KeyValuePairsCreator final : public IZoneAssetCreationState
{
public:
void AddKeyValuePair(CommonKeyValuePair keyValuePair);

View File

@ -165,13 +165,13 @@ namespace T6
if (ObjLoading::Configuration.Verbose)
std::cout << std::format("Trying to load ipak '{}' for zone '{}'\n", ipakName, zone.m_name);
auto* existingIPak = IPak::Repository.GetContainerByName(ipakName);
auto* existingIPak = IIPak::Repository.GetContainerByName(ipakName);
if (existingIPak != nullptr)
{
if (ObjLoading::Configuration.Verbose)
std::cout << std::format("Referencing loaded ipak '{}'.\n", ipakName);
IPak::Repository.AddContainerReference(existingIPak, &zone);
IIPak::Repository.AddContainerReference(existingIPak, &zone);
return;
}
@ -180,11 +180,11 @@ namespace T6
auto file = searchPath.Open(ipakFilename);
if (file.IsOpen())
{
auto ipak = std::make_unique<IPak>(ipakFilename, std::move(file.m_stream));
auto ipak = IIPak::Create(ipakFilename, std::move(file.m_stream));
if (ipak->Initialize())
{
IPak::Repository.AddContainer(std::move(ipak), &zone);
IIPak::Repository.AddContainer(std::move(ipak), &zone);
if (ObjLoading::Configuration.Verbose)
std::cout << std::format("Found and loaded ipak '{}'.\n", ipakFilename);
@ -277,7 +277,7 @@ namespace T6
void ObjLoader::UnloadContainersOfZone(Zone& zone) const
{
IPak::Repository.RemoveContainerReferences(&zone);
IIPak::Repository.RemoveContainerReferences(&zone);
}
namespace

View File

@ -1,35 +1,22 @@
#include "IPak.h"
#include "Exception/IPakLoadException.h"
#include "IPakStreamManager.h"
#include "ObjContainer/IPak/IPakTypes.h"
#include "Utils/FileUtils.h"
#include "zlib.h"
#include <filesystem>
#include <format>
#include <iostream>
#include <memory>
#include <sstream>
#include <vector>
namespace fs = std::filesystem;
ObjContainerRepository<IPak, Zone> IPak::Repository;
ObjContainerRepository<IIPak, Zone> IIPak::Repository;
class IPak::Impl : public ObjContainerReferenceable
namespace
{
std::string m_path;
std::unique_ptr<std::istream> m_stream;
bool m_initialized;
std::unique_ptr<IPakSection> m_index_section;
std::unique_ptr<IPakSection> m_data_section;
std::vector<IPakIndexEntry> m_index_entries;
IPakStreamManager m_stream_manager;
static uint32_t R_HashString(const char* str, uint32_t hash)
std::uint32_t R_HashString(const char* str, std::uint32_t hash)
{
for (const auto* pos = str; *pos; pos++)
{
@ -38,203 +25,189 @@ class IPak::Impl : public ObjContainerReferenceable
return hash;
}
} // namespace
bool ReadIndexSection()
namespace
{
class IPak final : public IIPak
{
m_stream->seekg(m_index_section->offset);
IPakIndexEntry indexEntry{};
for (unsigned itemIndex = 0; itemIndex < m_index_section->itemCount; itemIndex++)
public:
IPak(std::string path, std::unique_ptr<std::istream> stream)
: m_path(std::move(path)),
m_stream(std::move(stream)),
m_initialized(false),
m_index_section(nullptr),
m_data_section(nullptr),
m_stream_manager(*m_stream)
{
m_stream->read(reinterpret_cast<char*>(&indexEntry), sizeof(indexEntry));
if (m_stream->gcount() != sizeof(indexEntry))
{
printf("Unexpected eof when trying to load index entry %u.\n", itemIndex);
}
bool Initialize() override
{
if (m_initialized)
return true;
if (!ReadHeader())
return false;
}
m_index_entries.push_back(indexEntry);
}
std::ranges::sort(m_index_entries,
[](const IPakIndexEntry& entry1, const IPakIndexEntry& entry2)
{
return entry1.key.combinedKey < entry2.key.combinedKey;
});
return true;
}
bool ReadSection()
{
IPakSection section{};
m_stream->read(reinterpret_cast<char*>(&section), sizeof(section));
if (m_stream->gcount() != sizeof(section))
{
printf("Unexpected eof when trying to load section.\n");
return false;
}
switch (section.type)
{
case ipak_consts::IPAK_INDEX_SECTION:
m_index_section = std::make_unique<IPakSection>(section);
break;
case ipak_consts::IPAK_DATA_SECTION:
m_data_section = std::make_unique<IPakSection>(section);
break;
default:
break;
}
return true;
}
bool ReadHeader()
{
IPakHeader header{};
m_stream->read(reinterpret_cast<char*>(&header), sizeof(header));
if (m_stream->gcount() != sizeof(header))
{
printf("Unexpected eof when trying to load header.\n");
return false;
}
if (header.magic != ipak_consts::IPAK_MAGIC)
{
printf("Invalid ipak magic '0x%x'.\n", header.magic);
return false;
}
if (header.version != ipak_consts::IPAK_VERSION)
{
printf("Unsupported ipak version '%u'.\n", header.version);
return false;
}
for (unsigned section = 0; section < header.sectionCount; section++)
{
if (!ReadSection())
return false;
}
if (m_index_section == nullptr)
{
printf("IPak does not contain an index section.\n");
return false;
}
if (m_data_section == nullptr)
{
printf("IPak does not contain a data section.\n");
return false;
}
if (!ReadIndexSection())
return false;
return true;
}
public:
Impl(std::string path, std::unique_ptr<std::istream> stream)
: m_path(std::move(path)),
m_stream(std::move(stream)),
m_initialized(false),
m_index_section(nullptr),
m_data_section(nullptr),
m_stream_manager(*m_stream)
{
}
~Impl() override = default;
std::string GetName() override
{
return fs::path(m_path).filename().replace_extension("").string();
}
bool Initialize()
{
if (m_initialized)
m_initialized = true;
return true;
if (!ReadHeader())
return false;
m_initialized = true;
return true;
}
std::unique_ptr<iobjstream> GetEntryData(const Hash nameHash, const Hash dataHash)
{
IPakIndexEntryKey wantedKey{};
wantedKey.nameHash = nameHash;
wantedKey.dataHash = dataHash;
for (auto& entry : m_index_entries)
{
if (entry.key.combinedKey == wantedKey.combinedKey)
{
return m_stream_manager.OpenStream(static_cast<int64_t>(m_data_section->offset) + entry.offset, entry.size);
}
else if (entry.key.combinedKey > wantedKey.combinedKey)
{
// The index entries are sorted so if the current entry is higher than the wanted entry we can cancel here
return nullptr;
}
}
return nullptr;
}
[[nodiscard]] std::unique_ptr<iobjstream> GetEntryStream(const Hash nameHash, const Hash dataHash) const override
{
IPakIndexEntryKey wantedKey{};
wantedKey.nameHash = nameHash;
wantedKey.dataHash = dataHash;
static Hash HashString(const std::string& str)
{
return R_HashString(str.c_str(), 0);
}
for (auto& entry : m_index_entries)
{
if (entry.key.combinedKey == wantedKey.combinedKey)
{
return m_stream_manager.OpenStream(static_cast<int64_t>(m_data_section->offset) + entry.offset, entry.size);
}
else if (entry.key.combinedKey > wantedKey.combinedKey)
{
// The index entries are sorted so if the current entry is higher than the wanted entry we can cancel here
return nullptr;
}
}
static Hash HashData(const void* data, const size_t dataSize)
{
return crc32(0, static_cast<const Bytef*>(data), dataSize);
}
};
return nullptr;
}
IPak::IPak(std::string path, std::unique_ptr<std::istream> stream)
std::string GetName() override
{
return fs::path(m_path).filename().replace_extension("").string();
}
private:
bool ReadIndexSection()
{
m_stream->seekg(m_index_section->offset);
IPakIndexEntry indexEntry{};
for (unsigned itemIndex = 0; itemIndex < m_index_section->itemCount; itemIndex++)
{
m_stream->read(reinterpret_cast<char*>(&indexEntry), sizeof(indexEntry));
if (m_stream->gcount() != sizeof(indexEntry))
{
std::cerr << std::format("Unexpected eof when trying to load index entry {}.\n", itemIndex);
return false;
}
m_index_entries.push_back(indexEntry);
}
std::ranges::sort(m_index_entries,
[](const IPakIndexEntry& entry1, const IPakIndexEntry& entry2)
{
return entry1.key.combinedKey < entry2.key.combinedKey;
});
return true;
}
bool ReadSection()
{
IPakSection section{};
m_stream->read(reinterpret_cast<char*>(&section), sizeof(section));
if (m_stream->gcount() != sizeof(section))
{
std::cerr << "Unexpected eof when trying to load section.\n";
return false;
}
switch (section.type)
{
case ipak_consts::IPAK_INDEX_SECTION:
m_index_section = std::make_unique<IPakSection>(section);
break;
case ipak_consts::IPAK_DATA_SECTION:
m_data_section = std::make_unique<IPakSection>(section);
break;
default:
break;
}
return true;
}
bool ReadHeader()
{
IPakHeader header{};
m_stream->read(reinterpret_cast<char*>(&header), sizeof(header));
if (m_stream->gcount() != sizeof(header))
{
std::cerr << "Unexpected eof when trying to load header.\n";
return false;
}
if (header.magic != ipak_consts::IPAK_MAGIC)
{
std::cerr << std::format("Invalid ipak magic '{:#x}'.\n", header.magic);
return false;
}
if (header.version != ipak_consts::IPAK_VERSION)
{
std::cerr << std::format("Unsupported ipak version '{}'.\n", header.version);
return false;
}
for (unsigned section = 0; section < header.sectionCount; section++)
{
if (!ReadSection())
return false;
}
if (m_index_section == nullptr)
{
std::cerr << "IPak does not contain an index section.\n";
return false;
}
if (m_data_section == nullptr)
{
std::cerr << "IPak does not contain a data section.\n";
return false;
}
if (!ReadIndexSection())
return false;
return true;
}
std::string m_path;
std::unique_ptr<std::istream> m_stream;
bool m_initialized;
std::unique_ptr<IPakSection> m_index_section;
std::unique_ptr<IPakSection> m_data_section;
std::vector<IPakIndexEntry> m_index_entries;
IPakStreamManager m_stream_manager;
};
} // namespace
std::unique_ptr<IIPak> IIPak::Create(std::string path, std::unique_ptr<std::istream> stream)
{
m_impl = new Impl(std::move(path), std::move(stream));
return std::make_unique<IPak>(std::move(path), std::move(stream));
}
IPak::~IPak()
IIPak::Hash IIPak::HashString(const std::string& str)
{
delete m_impl;
m_impl = nullptr;
return R_HashString(str.c_str(), 0);
}
std::string IPak::GetName()
IIPak::Hash IIPak::HashData(const void* data, const size_t dataSize)
{
return m_impl->GetName();
}
bool IPak::Initialize()
{
return m_impl->Initialize();
}
std::unique_ptr<iobjstream> IPak::GetEntryStream(const Hash nameHash, const Hash dataHash) const
{
return m_impl->GetEntryData(nameHash, dataHash);
}
IPak::Hash IPak::HashString(const std::string& str)
{
return Impl::HashString(str);
}
IPak::Hash IPak::HashData(const void* data, const size_t dataSize)
{
return Impl::HashData(data, dataSize);
return crc32(0, static_cast<const Bytef*>(data), dataSize);
}

View File

@ -2,30 +2,31 @@
#include "ObjContainer/ObjContainerReferenceable.h"
#include "ObjContainer/ObjContainerRepository.h"
#include "Utils/ClassUtils.h"
#include "Utils/ObjStream.h"
#include "Zone/Zone.h"
#include <cstdint>
#include <istream>
#include <memory>
#include <string>
class IPak final : public ObjContainerReferenceable
class IIPak : public ObjContainerReferenceable
{
class Impl;
Impl* m_impl;
public:
typedef uint32_t Hash;
static ObjContainerRepository<IIPak, Zone> Repository;
typedef std::uint32_t Hash;
static ObjContainerRepository<IPak, Zone> Repository;
IIPak() = default;
virtual ~IIPak() = default;
IIPak(const IIPak& other) = default;
IIPak(IIPak&& other) noexcept = default;
IIPak& operator=(const IIPak& other) = default;
IIPak& operator=(IIPak&& other) noexcept = default;
IPak(std::string path, std::unique_ptr<std::istream> stream);
~IPak() override;
std::string GetName() override;
bool Initialize();
_NODISCARD std::unique_ptr<iobjstream> GetEntryStream(Hash nameHash, Hash dataHash) const;
virtual bool Initialize() = 0;
[[nodiscard]] virtual std::unique_ptr<iobjstream> GetEntryStream(Hash nameHash, Hash dataHash) const = 0;
static std::unique_ptr<IIPak> Create(std::string path, std::unique_ptr<std::istream> stream);
static Hash HashString(const std::string& str);
static Hash HashData(const void* data, size_t dataSize);
};

View File

@ -38,7 +38,7 @@ namespace
{
if (image->streamedPartCount > 0)
{
for (auto* ipak : IPak::Repository)
for (auto* ipak : IIPak::Repository)
{
auto ipakStream = ipak->GetEntryStream(image->hash, image->streamedParts[0].hash);

View File

@ -6,6 +6,33 @@
class MemoryManager
{
public:
MemoryManager();
virtual ~MemoryManager();
MemoryManager(const MemoryManager& other) = delete;
MemoryManager(MemoryManager&& other) noexcept = default;
MemoryManager& operator=(const MemoryManager& other) = delete;
MemoryManager& operator=(MemoryManager&& other) noexcept = default;
void* AllocRaw(size_t size);
char* Dup(const char* str);
template<typename T> std::add_pointer_t<T> Alloc(const size_t count = 1u)
{
return static_cast<std::add_pointer_t<T>>(AllocRaw(sizeof(T) * count));
}
template<class T, class... ValType> std::add_pointer_t<T> Create(ValType&&... val)
{
Allocation<T>* allocation = new Allocation<T>(std::forward<ValType>(val)...);
m_destructible.emplace_back(allocation, &allocation->m_entry);
return &allocation->m_entry;
}
void Free(const void* data);
void Delete(const void* data);
protected:
class IDestructible
{
public:
@ -47,30 +74,4 @@ class MemoryManager
std::vector<void*> m_allocations;
std::vector<AllocationInfo> m_destructible;
public:
MemoryManager();
virtual ~MemoryManager();
MemoryManager(const MemoryManager& other) = delete;
MemoryManager(MemoryManager&& other) noexcept = default;
MemoryManager& operator=(const MemoryManager& other) = delete;
MemoryManager& operator=(MemoryManager&& other) noexcept = default;
void* AllocRaw(size_t size);
char* Dup(const char* str);
template<typename T> std::add_pointer_t<T> Alloc(const size_t count = 1u)
{
return static_cast<std::add_pointer_t<T>>(AllocRaw(sizeof(T) * count));
}
template<class T, class... ValType> std::add_pointer_t<T> Create(ValType&&... val)
{
Allocation<T>* allocation = new Allocation<T>(std::forward<ValType>(val)...);
m_destructible.emplace_back(allocation, &allocation->m_entry);
return &allocation->m_entry;
}
void Free(const void* data);
void Delete(const void* data);
};

View File

@ -1,10 +1,15 @@
#include "ZoneDefinition.h"
ZoneDefinitionObjContainer::ZoneDefinitionObjContainer(std::string name, const ZoneDefinitionObjContainerType type, const unsigned start)
: ZoneDefinitionObjContainer(std::move(name), type, start, 0u)
{
}
ZoneDefinitionObjContainer::ZoneDefinitionObjContainer(std::string name, const ZoneDefinitionObjContainerType type, const unsigned start, const unsigned end)
: m_name(std::move(name)),
m_type(type),
m_asset_start(start),
m_asset_end(0u)
m_asset_end(end)
{
}

View File

@ -32,6 +32,7 @@ public:
unsigned m_asset_end;
ZoneDefinitionObjContainer(std::string name, ZoneDefinitionObjContainerType type, unsigned start);
ZoneDefinitionObjContainer(std::string name, ZoneDefinitionObjContainerType type, unsigned start, unsigned end);
};
class ZoneDefinitionAsset

51
test/Catch2Common.lua Normal file
View File

@ -0,0 +1,51 @@
Catch2Common = {}
function Catch2Common:include(includes)
if includes:handle(self:name()) then
includedirs {
path.join(TestFolder(), "Catch2Common")
}
end
end
function Catch2Common:link(links)
links:add(self:name())
links:linkto(catch2)
end
function Catch2Common:use()
end
function Catch2Common:name()
return "Catch2Common"
end
function Catch2Common:project()
local folder = TestFolder()
local includes = Includes:create()
local links = Links:create()
project(self:name())
targetdir(TargetDirectoryTest)
location "%{wks.location}/test/%{prj.name}"
kind "StaticLib"
language "C++"
files {
path.join(folder, "Catch2Common/**.h"),
path.join(folder, "Catch2Common/**.cpp")
}
vpaths {
["*"] = {
path.join(folder, "Catch2Common")
}
}
self:include(includes)
catch2:include(includes)
links:linkto(catch2)
links:linkall()
end

View File

@ -0,0 +1,27 @@
#include "OatTestPaths.h"
#include <filesystem>
namespace fs = std::filesystem;
namespace oat::paths
{
std::filesystem::path GetSourceDirectory()
{
return fs::current_path() / "src";
}
std::filesystem::path GetTestDirectory()
{
return fs::current_path() / "test";
}
std::filesystem::path GetTempDirectory()
{
auto result = fs::current_path() / "build" / ".tmp";
if (!fs::is_directory(result))
fs::create_directories(result);
return result;
}
} // namespace oat::paths

View File

@ -0,0 +1,10 @@
#pragma once
#include <filesystem>
namespace oat::paths
{
std::filesystem::path GetSourceDirectory();
std::filesystem::path GetTestDirectory();
std::filesystem::path GetTempDirectory();
} // namespace oat::paths

View File

@ -0,0 +1,48 @@
#include <catch2/catch_session.hpp>
#include <filesystem>
#include <format>
#include <iostream>
namespace fs = std::filesystem;
int main(const int argc, char* argv[])
{
const fs::path absoluteBinDir(fs::canonical(argv[0]).parent_path());
const auto expectedLibDir = absoluteBinDir.parent_path().parent_path();
const auto expectedBuildDir = expectedLibDir.parent_path();
const auto expectedRootDir = expectedBuildDir.parent_path();
if (absoluteBinDir.filename() != "tests" || expectedLibDir.filename() != "lib" || expectedBuildDir.filename() != "build")
{
std::cerr << std::format("Expected test binary to be in the folder it was compiled into (build/lib/?/tests) but was {}\n", absoluteBinDir.string());
std::cerr << "Please do not move test executable out of compilation folder\n";
return 1;
}
const auto expectedSrcDir = expectedRootDir / "src";
if (!fs::is_directory(expectedSrcDir))
{
std::cerr << std::format("Expected source directory to exist in {}, but it did not\n", expectedSrcDir.string());
std::cerr << "Please do not move test executable out of compilation folder\n";
return 1;
}
const auto expectedTestDir = expectedRootDir / "test";
if (!fs::is_directory(expectedTestDir))
{
std::cerr << std::format("Expected test directory to exist in {}, but it did not\n", expectedTestDir.string());
std::cerr << "Please do not move test executable out of compilation folder\n";
return 1;
}
fs::current_path(expectedRootDir);
const auto result = Catch::Session().run(argc, argv);
const auto tempDir = expectedBuildDir / ".tmp";
if (fs::is_directory(tempDir))
fs::remove_all(tempDir);
return result;
}

View File

@ -0,0 +1,135 @@
#include "MockOutputPath.h"
#include "Utils/ObjStream.h"
#include <cstring>
namespace
{
class MockFileBuffer final : public std::streambuf
{
public:
MockFileBuffer()
: m_pos(0u)
{
}
std::vector<std::uint8_t> data()
{
return std::move(m_data);
}
protected:
int_type overflow(const int_type b) override
{
if (!std::char_traits<char>::eq_int_type(b, std::char_traits<char>::eof()))
{
m_data.insert(m_data.begin() + static_cast<decltype(m_data)::difference_type>(m_pos), static_cast<std::uint8_t>(b));
++m_pos;
}
return b;
}
std::streamsize xsputn(const char* ptr, const std::streamsize count) override
{
const auto curSize = m_data.size();
const auto overrideCount = m_pos < curSize ? std::min(curSize - m_pos, static_cast<size_t>(count)) : 0u;
const auto insertCount = count - overrideCount;
if (overrideCount > 0)
{
std::memcpy(&m_data[m_pos], ptr, overrideCount);
m_pos += overrideCount;
ptr += overrideCount;
}
if (insertCount > 0)
{
m_data.insert(m_data.begin() + static_cast<decltype(m_data)::difference_type>(m_pos), ptr, ptr + insertCount);
m_pos += static_cast<size_t>(insertCount);
}
return count;
}
pos_type seekoff(const off_type off, const std::ios_base::seekdir dir, const std::ios_base::openmode mode) override
{
if (dir == std::ios::beg)
return seekpos(off, mode);
if (dir == std::ios::cur)
return seekpos(static_cast<pos_type>(m_pos) + off, mode);
if (off < static_cast<off_type>(m_data.size()))
return seekpos(static_cast<off_type>(m_data.size()) - off, mode);
return std::char_traits<char>::eof();
}
pos_type seekpos(const pos_type pos, std::ios_base::openmode) override
{
if (pos > m_data.size())
m_data.resize(static_cast<decltype(m_data)::size_type>(pos));
m_pos = static_cast<size_t>(pos);
return pos;
}
private:
std::vector<std::uint8_t> m_data;
std::size_t m_pos;
};
class MockFileWrapper final : public std::ostream
{
public:
MockFileWrapper(std::string name, std::vector<MockOutputFile>& files)
: std::ostream(&m_buf),
m_name(std::move(name)),
m_files(files)
{
}
~MockFileWrapper() override
{
m_files.emplace_back(std::move(m_name), m_buf.data());
}
private:
MockFileBuffer m_buf;
std::string m_name;
std::vector<MockOutputFile>& m_files;
};
} // namespace
MockOutputFile::MockOutputFile() = default;
MockOutputFile::MockOutputFile(std::string name, std::vector<std::uint8_t> data)
: m_name(std::move(name)),
m_data(std::move(data))
{
}
std::string MockOutputFile::AsString() const
{
return std::string(reinterpret_cast<const char*>(m_data.data()), m_data.size());
}
std::unique_ptr<std::ostream> MockOutputPath::Open(const std::string& fileName)
{
return std::make_unique<MockFileWrapper>(fileName, m_files);
}
const MockOutputFile* MockOutputPath::GetMockedFile(const std::string& name) const
{
for (const auto& file : m_files)
{
if (file.m_name == name)
return &file;
}
return nullptr;
}
const std::vector<MockOutputFile>& MockOutputPath::GetMockedFileList() const
{
return m_files;
}

View File

@ -0,0 +1,32 @@
#pragma once
#include "SearchPath/IOutputPath.h"
#include <cstdint>
#include <sstream>
#include <string>
#include <vector>
class MockOutputFile
{
public:
std::string m_name;
std::vector<std::uint8_t> m_data;
MockOutputFile();
MockOutputFile(std::string name, std::vector<std::uint8_t> data);
[[nodiscard]] std::string AsString() const;
};
class MockOutputPath final : public IOutputPath
{
public:
std::unique_ptr<std::ostream> Open(const std::string& fileName) override;
[[nodiscard]] const MockOutputFile* GetMockedFile(const std::string& name) const;
[[nodiscard]] const std::vector<MockOutputFile>& GetMockedFileList() const;
private:
std::vector<MockOutputFile> m_files;
};

View File

@ -0,0 +1,14 @@
#pragma once
#include "Utils/MemoryManager.h"
#include <cstdlib>
class TestMemoryManager : public MemoryManager
{
public:
[[nodiscard]] size_t GetAllocationCount() const
{
return m_allocations.size() + m_destructible.size();
}
};

View File

@ -44,6 +44,7 @@ function ObjCommonTests:project()
}
self:include(includes)
Catch2Common:include(includes)
ObjCommon:include(includes)
ObjImage:include(includes)
catch2:include(includes)
@ -51,5 +52,6 @@ function ObjCommonTests:project()
links:linkto(ObjCommon)
links:linkto(ObjImage)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkall()
end

View File

@ -0,0 +1,60 @@
ObjCompilingTests = {}
function ObjCompilingTests:include(includes)
if includes:handle(self:name()) then
includedirs {
path.join(TestFolder(), "ObjCompilingTests")
}
end
end
function ObjCompilingTests:link(links)
end
function ObjCompilingTests:use()
end
function ObjCompilingTests:name()
return "ObjCompilingTests"
end
function ObjCompilingTests:project()
local folder = TestFolder()
local includes = Includes:create()
local links = Links:create()
project(self:name())
targetdir(TargetDirectoryTest)
location "%{wks.location}/test/%{prj.name}"
kind "ConsoleApp"
language "C++"
files {
path.join(folder, "ObjCompilingTests/**.h"),
path.join(folder, "ObjCompilingTests/**.cpp")
}
vpaths {
["*"] = {
path.join(folder, "ObjCompilingTests")
}
}
self:include(includes)
Catch2Common:include(includes)
ObjCommonTestUtils:include(includes)
ParserTestUtils:include(includes)
ObjLoading:include(includes)
ObjCompiling:include(includes)
catch2:include(includes)
links:linkto(ObjCommonTestUtils)
links:linkto(ParserTestUtils)
links:linkto(ObjLoading)
links:linkto(ObjCompiling)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkall()
end

View File

@ -0,0 +1,130 @@
#include "Game/T6/KeyValuePairs/KeyValuePairsCompilerT6.h"
#include "Game/T6/CommonT6.h"
#include "Game/T6/GameAssetPoolT6.h"
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include "Utils/TestMemoryManager.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <memory>
using namespace T6;
using namespace std::string_literals;
namespace
{
class TestContext
{
public:
TestContext()
: m_memory(),
m_zone("test", 0, IGame::GetGameById(GameId::T6)),
m_zone_definition(),
m_zone_states(m_zone),
m_creators(m_zone),
m_ignored_assets(),
m_context(m_zone, &m_creators, &m_ignored_assets),
m_kvp_creator(m_zone_states.GetZoneAssetCreationState<KeyValuePairsCreator>())
{
}
std::unique_ptr<IAssetCreator> CreateSut()
{
return CreateKeyValuePairsCompiler(m_memory, m_zone, m_zone_definition, m_zone_states);
}
TestMemoryManager m_memory;
Zone m_zone;
ZoneDefinition m_zone_definition;
ZoneAssetCreationStateContainer m_zone_states;
AssetCreatorCollection m_creators;
IgnoredAssetLookup m_ignored_assets;
AssetCreationContext m_context;
KeyValuePairsCreator& m_kvp_creator;
};
} // namespace
namespace test::game::t6::keyvaluepairs
{
TEST_CASE("KeyValuePairsCompilerT6: Does not handle any asset type", "[keyvaluepairs][t6]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
REQUIRE(!sut->GetHandlingAssetType().has_value());
}
TEST_CASE("KeyValuePairsCompilerT6: Does not take any action", "[keyvaluepairs][t6]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
REQUIRE(sut->CreateAsset("anyAsset", testContext.m_context).HasTakenAction() == false);
}
TEST_CASE("KeyValuePairsCompilerT6: Does nothing without any KeyValuePairs", "[keyvaluepairs][t6]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
sut->FinalizeZone(testContext.m_context);
REQUIRE(testContext.m_memory.GetAllocationCount() == 0u);
REQUIRE(testContext.m_zone.m_pools->GetTotalAssetCount() == 0u);
}
TEST_CASE("KeyValuePairsCompilerT6: Creates KeyValuePairs asset with identical name to the zone", "[keyvaluepairs][t6]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
testContext.m_kvp_creator.AddKeyValuePair(CommonKeyValuePair("ipak_read", "test_ipak"));
sut->FinalizeZone(testContext.m_context);
REQUIRE(testContext.m_memory.GetAllocationCount() > 0u);
REQUIRE(testContext.m_zone.m_pools->GetTotalAssetCount() == 1u);
XAssetInfo<KeyValuePairs>* assetInfo = *dynamic_cast<GameAssetPoolT6*>(testContext.m_zone.m_pools.get())->m_key_value_pairs->begin();
REQUIRE(assetInfo);
REQUIRE(assetInfo->m_name == "test");
auto* asset = assetInfo->Asset();
REQUIRE(asset->name == "test"s);
REQUIRE(asset->numVariables == 1u);
REQUIRE(asset->keyValuePairs != nullptr);
REQUIRE(asset->keyValuePairs[0].keyHash == 0x0001bdc1);
REQUIRE(asset->keyValuePairs[0].namespaceHash == 0x0000d2d3);
REQUIRE(asset->keyValuePairs[0].value == "test_ipak"s);
}
TEST_CASE("KeyValuePairsCompilerT6: Creates KeyValuePairs asset with predefined hash", "[keyvaluepairs][t6]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
testContext.m_kvp_creator.AddKeyValuePair(CommonKeyValuePair(0xDDEEFFAA, "hello_there"));
sut->FinalizeZone(testContext.m_context);
REQUIRE(testContext.m_memory.GetAllocationCount() > 0u);
REQUIRE(testContext.m_zone.m_pools->GetTotalAssetCount() == 1u);
XAssetInfo<KeyValuePairs>* assetInfo = *dynamic_cast<GameAssetPoolT6*>(testContext.m_zone.m_pools.get())->m_key_value_pairs->begin();
REQUIRE(assetInfo);
REQUIRE(assetInfo->m_name == "test");
auto* asset = assetInfo->Asset();
REQUIRE(asset->name == "test"s);
REQUIRE(asset->numVariables == 1u);
REQUIRE(asset->keyValuePairs != nullptr);
REQUIRE(asset->keyValuePairs[0].keyHash == 0xDDEEFFAA);
REQUIRE(asset->keyValuePairs[0].namespaceHash == 0x0000d2d3);
REQUIRE(asset->keyValuePairs[0].value == "hello_there"s);
}
} // namespace test::game::t6::keyvaluepairs

View File

@ -0,0 +1,86 @@
#include "Image/IPak/IPakCreator.h"
#include "Asset/AssetCreatorCollection.h"
#include "ObjContainer/IPak/IPak.h"
#include "SearchPath/MockOutputPath.h"
#include "SearchPath/MockSearchPath.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cstring>
#include <filesystem>
#include <memory>
using namespace std::string_literals;
namespace
{
class TestContext
{
public:
TestContext()
: m_zone("test", 0, IGame::GetGameById(GameId::T6)),
m_zone_states(m_zone),
m_out_dir()
{
}
IPakCreator& CreateSut()
{
return m_zone_states.GetZoneAssetCreationState<IPakCreator>();
}
Zone m_zone;
MockSearchPath m_search_path;
ZoneAssetCreationStateContainer m_zone_states;
MockOutputPath m_out_dir;
};
} // namespace
namespace test::image::ipak
{
TEST_CASE("IPakCreator: Does nothing if no ipak was defined", "[image]")
{
TestContext testContext;
auto& sut = testContext.CreateSut();
sut.Finalize(testContext.m_search_path, testContext.m_out_dir);
REQUIRE(testContext.m_out_dir.GetMockedFileList().empty());
}
TEST_CASE("IPakCreator: Writes IPak file", "[image]")
{
TestContext testContext;
auto& sut = testContext.CreateSut();
auto* ipak = sut.GetOrAddIPak("amazing");
ipak->AddImage("random");
constexpr auto iwiData = "hello world";
testContext.m_search_path.AddFileData("images/random.iwi", iwiData);
sut.Finalize(testContext.m_search_path, testContext.m_out_dir);
const auto* file = testContext.m_out_dir.GetMockedFile("amazing.ipak");
REQUIRE(file);
const auto* data = file->m_data.data();
REQUIRE(data[0] == 'K');
REQUIRE(data[1] == 'A');
REQUIRE(data[2] == 'P');
REQUIRE(data[3] == 'I');
auto readIpak = IIPak::Create("amazing.ipak", std::make_unique<std::istringstream>(file->AsString()));
REQUIRE(readIpak->Initialize());
auto entry = readIpak->GetEntryStream(IIPak::HashString("random"), IIPak::HashData(iwiData, std::char_traits<char>::length(iwiData)));
REQUIRE(entry);
char readBuffer[std::char_traits<char>::length(iwiData) + 10];
entry->read(readBuffer, sizeof(readBuffer));
REQUIRE(entry->gcount() == std::char_traits<char>::length(iwiData));
REQUIRE(std::strncmp(iwiData, readBuffer, std::char_traits<char>::length(iwiData)) == 0);
}
} // namespace test::image::ipak

View File

@ -0,0 +1,278 @@
#include "Image/ImageIPakPostProcessor.h"
#include "Game/T6/T6.h"
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include "SearchPath/MockOutputPath.h"
#include "SearchPath/MockSearchPath.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <filesystem>
#include <memory>
using namespace T6;
using namespace std::string_literals;
namespace
{
class TestContext
{
public:
TestContext()
: m_zone("test", 0, IGame::GetGameById(GameId::T6)),
m_zone_definition(),
m_zone_definition_context(m_zone_definition),
m_zone_states(m_zone),
m_creators(m_zone),
m_ignored_assets(),
m_out_dir(),
m_context(m_zone, &m_creators, &m_ignored_assets),
m_ipak_creator(m_zone_states.GetZoneAssetCreationState<IPakCreator>())
{
}
std::unique_ptr<IAssetPostProcessor> CreateSut()
{
return std::make_unique<ImageIPakPostProcessor<AssetImage>>(m_zone_definition_context, m_search_path, m_zone_states, m_out_dir);
}
Zone m_zone;
ZoneDefinition m_zone_definition;
ZoneDefinitionContext m_zone_definition_context;
MockSearchPath m_search_path;
ZoneAssetCreationStateContainer m_zone_states;
AssetCreatorCollection m_creators;
IgnoredAssetLookup m_ignored_assets;
MockOutputPath m_out_dir;
AssetCreationContext m_context;
IPakCreator& m_ipak_creator;
};
} // namespace
namespace test::image
{
TEST_CASE("ImageIPakPostProcessor: Handles asset type of specified asset", "[image]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
REQUIRE(sut->GetHandlingAssetType() == ASSET_TYPE_IMAGE);
}
TEST_CASE("ImageIPakPostProcessor: Adds images to ipak when obj container is specified", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 0, 2);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage0");
REQUIRE(imageNames[1] == "testImage1");
}
TEST_CASE("ImageIPakPostProcessor: Respects lower obj container boundary when adding images to ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 1, 3);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage1");
REQUIRE(imageNames[1] == "testImage2");
}
TEST_CASE("ImageIPakPostProcessor: Respects upper obj container boundary when adding images to ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 0, 2);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage0");
REQUIRE(imageNames[1] == "testImage1");
}
TEST_CASE("ImageIPakPostProcessor: Respects upper and lower obj container boundary when adding images to ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 1, 3);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage1");
REQUIRE(imageNames[1] == "testImage2");
}
TEST_CASE("ImageIPakPostProcessor: Can add images to multiple ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak0", ZoneDefinitionObjContainerType::IPAK, 0, 2);
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak1", ZoneDefinitionObjContainerType::IPAK, 2, 4);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
auto result0 = testContext.m_ipak_creator.GetOrAddIPak("testIpak0");
REQUIRE(result0);
const auto& imageNames0 = result0->GetImageNames();
REQUIRE(imageNames0.size() == 2u);
REQUIRE(imageNames0[0] == "testImage0");
REQUIRE(imageNames0[1] == "testImage1");
auto result1 = testContext.m_ipak_creator.GetOrAddIPak("testIpak1");
REQUIRE(result1);
const auto& imageNames1 = result1->GetImageNames();
REQUIRE(imageNames1.size() == 2u);
REQUIRE(imageNames1[0] == "testImage2");
REQUIRE(imageNames1[1] == "testImage3");
}
TEST_CASE("ImageIPakPostProcessor: Can add images to multiple ipak with gap between", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak0", ZoneDefinitionObjContainerType::IPAK, 0, 2);
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak1", ZoneDefinitionObjContainerType::IPAK, 3, 5);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset4(ASSET_TYPE_IMAGE, "testImage4", nullptr);
sut->PostProcessAsset(imageAsset4, testContext.m_context);
auto result0 = testContext.m_ipak_creator.GetOrAddIPak("testIpak0");
REQUIRE(result0);
const auto& imageNames0 = result0->GetImageNames();
REQUIRE(imageNames0.size() == 2u);
REQUIRE(imageNames0[0] == "testImage0");
REQUIRE(imageNames0[1] == "testImage1");
auto result1 = testContext.m_ipak_creator.GetOrAddIPak("testIpak1");
REQUIRE(result1);
const auto& imageNames1 = result1->GetImageNames();
REQUIRE(imageNames1.size() == 2u);
REQUIRE(imageNames1[0] == "testImage3");
REQUIRE(imageNames1[1] == "testImage4");
}
TEST_CASE("ImageIPakPostProcessor: Writes IPak when finalizing", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 0, 2);
testContext.m_search_path.AddFileData("images/testImage0.iwi", "asdf0");
testContext.m_search_path.AddFileData("images/testImage1.iwi", "asdf1");
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
sut->FinalizeZone(testContext.m_context);
const auto* mockFile = testContext.m_out_dir.GetMockedFile("testIpak.ipak");
REQUIRE(mockFile);
}
} // namespace test::image

View File

@ -0,0 +1,277 @@
#include "Image/ImageIwdPostProcessor.h"
#include "Game/T6/T6.h"
#include "SearchPath/MockOutputPath.h"
#include "SearchPath/MockSearchPath.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <filesystem>
#include <memory>
using namespace T6;
using namespace std::string_literals;
namespace
{
class TestContext
{
public:
TestContext()
: m_zone("test", 0, IGame::GetGameById(GameId::T6)),
m_zone_definition(),
m_zone_definition_context(m_zone_definition),
m_zone_states(m_zone),
m_creators(m_zone),
m_ignored_assets(),
m_out_dir(),
m_context(m_zone, &m_creators, &m_ignored_assets),
m_iwd_creator(m_zone_states.GetZoneAssetCreationState<IwdCreator>())
{
}
std::unique_ptr<IAssetPostProcessor> CreateSut()
{
return std::make_unique<ImageIwdPostProcessor<AssetImage>>(m_zone_definition_context, m_search_path, m_zone_states, m_out_dir);
}
Zone m_zone;
ZoneDefinition m_zone_definition;
ZoneDefinitionContext m_zone_definition_context;
MockSearchPath m_search_path;
ZoneAssetCreationStateContainer m_zone_states;
AssetCreatorCollection m_creators;
IgnoredAssetLookup m_ignored_assets;
MockOutputPath m_out_dir;
AssetCreationContext m_context;
IwdCreator& m_iwd_creator;
};
} // namespace
namespace test::image
{
TEST_CASE("ImageIwdPostProcessor: Handles asset type of specified asset", "[image]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
REQUIRE(sut->GetHandlingAssetType() == ASSET_TYPE_IMAGE);
}
TEST_CASE("ImageIwdPostProcessor: Adds images to iwd when obj container is specified", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd", ZoneDefinitionObjContainerType::IWD, 0, 2);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
auto result = testContext.m_iwd_creator.GetOrAddIwd("testIwd");
REQUIRE(result);
const auto& filePaths = result->GetFilePaths();
REQUIRE(filePaths.size() == 2u);
REQUIRE(filePaths[0] == "images/testImage0.iwi");
REQUIRE(filePaths[1] == "images/testImage1.iwi");
}
TEST_CASE("ImageIwdPostProcessor: Respects lower obj container boundary when adding images to iwd", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd", ZoneDefinitionObjContainerType::IWD, 1, 3);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
auto result = testContext.m_iwd_creator.GetOrAddIwd("testIwd");
REQUIRE(result);
const auto& filePaths = result->GetFilePaths();
REQUIRE(filePaths.size() == 2u);
REQUIRE(filePaths[0] == "images/testImage1.iwi");
REQUIRE(filePaths[1] == "images/testImage2.iwi");
}
TEST_CASE("ImageIwdPostProcessor: Respects upper obj container boundary when adding images to iwd", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd", ZoneDefinitionObjContainerType::IWD, 0, 2);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
auto result = testContext.m_iwd_creator.GetOrAddIwd("testIwd");
REQUIRE(result);
const auto& filePaths = result->GetFilePaths();
REQUIRE(filePaths.size() == 2u);
REQUIRE(filePaths[0] == "images/testImage0.iwi");
REQUIRE(filePaths[1] == "images/testImage1.iwi");
}
TEST_CASE("ImageIwdPostProcessor: Respects upper and lower obj container boundary when adding images to iwd", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd", ZoneDefinitionObjContainerType::IWD, 1, 3);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
auto result = testContext.m_iwd_creator.GetOrAddIwd("testIwd");
REQUIRE(result);
const auto& filePaths = result->GetFilePaths();
REQUIRE(filePaths.size() == 2u);
REQUIRE(filePaths[0] == "images/testImage1.iwi");
REQUIRE(filePaths[1] == "images/testImage2.iwi");
}
TEST_CASE("ImageIwdPostProcessor: Can add images to multiple iwd", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd0", ZoneDefinitionObjContainerType::IWD, 0, 2);
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd1", ZoneDefinitionObjContainerType::IWD, 2, 4);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
auto result0 = testContext.m_iwd_creator.GetOrAddIwd("testIwd0");
REQUIRE(result0);
const auto& filePaths0 = result0->GetFilePaths();
REQUIRE(filePaths0.size() == 2u);
REQUIRE(filePaths0[0] == "images/testImage0.iwi");
REQUIRE(filePaths0[1] == "images/testImage1.iwi");
auto result1 = testContext.m_iwd_creator.GetOrAddIwd("testIwd1");
REQUIRE(result1);
const auto& filePaths1 = result1->GetFilePaths();
REQUIRE(filePaths1.size() == 2u);
REQUIRE(filePaths1[0] == "images/testImage2.iwi");
REQUIRE(filePaths1[1] == "images/testImage3.iwi");
}
TEST_CASE("ImageIwdPostProcessor: Can add images to multiple iwd with gap between", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd0", ZoneDefinitionObjContainerType::IWD, 0, 2);
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd1", ZoneDefinitionObjContainerType::IWD, 3, 5);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset4(ASSET_TYPE_IMAGE, "testImage4", nullptr);
sut->PostProcessAsset(imageAsset4, testContext.m_context);
auto result0 = testContext.m_iwd_creator.GetOrAddIwd("testIwd0");
REQUIRE(result0);
const auto& filePaths0 = result0->GetFilePaths();
REQUIRE(filePaths0.size() == 2u);
REQUIRE(filePaths0[0] == "images/testImage0.iwi");
REQUIRE(filePaths0[1] == "images/testImage1.iwi");
auto result1 = testContext.m_iwd_creator.GetOrAddIwd("testIwd1");
REQUIRE(result1);
const auto& filePaths1 = result1->GetFilePaths();
REQUIRE(filePaths1.size() == 2u);
REQUIRE(filePaths1[0] == "images/testImage3.iwi");
REQUIRE(filePaths1[1] == "images/testImage4.iwi");
}
TEST_CASE("ImageIwdPostProcessor: Writes Iwd when finalizing", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIwd", ZoneDefinitionObjContainerType::IWD, 0, 2);
testContext.m_search_path.AddFileData("images/testImage0.iwi", "asdf0");
testContext.m_search_path.AddFileData("images/testImage1.iwi", "asdf1");
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
sut->FinalizeZone(testContext.m_context);
const auto* mockFile = testContext.m_out_dir.GetMockedFile("testIwd.iwd");
REQUIRE(mockFile);
}
} // namespace test::image

View File

@ -0,0 +1,94 @@
#include "Iwd/IwdCreator.h"
#include "Asset/AssetCreatorCollection.h"
#include "Game/T6/T6.h"
#include "SearchPath/MockOutputPath.h"
#include "SearchPath/MockSearchPath.h"
#include "Utils/FileToZlibWrapper.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <cstring>
#include <filesystem>
#include <memory>
#include <unzip.h>
using namespace T6;
using namespace std::string_literals;
namespace
{
class TestContext
{
public:
TestContext()
: m_zone("test", 0, IGame::GetGameById(GameId::T6)),
m_zone_states(m_zone),
m_out_dir()
{
}
IwdCreator& CreateSut()
{
return m_zone_states.GetZoneAssetCreationState<IwdCreator>();
}
Zone m_zone;
MockSearchPath m_search_path;
ZoneAssetCreationStateContainer m_zone_states;
MockOutputPath m_out_dir;
};
} // namespace
namespace test::iwd
{
TEST_CASE("IwdCreator: Does nothing if no iwd was defined", "[image]")
{
TestContext testContext;
auto& sut = testContext.CreateSut();
sut.Finalize(testContext.m_search_path, testContext.m_out_dir);
REQUIRE(testContext.m_out_dir.GetMockedFileList().empty());
}
TEST_CASE("IwdCreator: Writes Iwd file", "[image]")
{
TestContext testContext;
auto& sut = testContext.CreateSut();
auto* ipak = sut.GetOrAddIwd("amazing");
ipak->AddFile("images/random.iwi");
constexpr auto iwiData = "hello world";
testContext.m_search_path.AddFileData("images/random.iwi", iwiData);
sut.Finalize(testContext.m_search_path, testContext.m_out_dir);
const auto* file = testContext.m_out_dir.GetMockedFile("amazing.iwd");
REQUIRE(file);
const auto* data = file->m_data.data();
REQUIRE(data[0] == 'P');
REQUIRE(data[1] == 'K');
std::istringstream ss(file->AsString());
auto zlibFunctions = FileToZlibWrapper::CreateFunctions32ForFile(&ss);
auto zip = unzOpen2("amazing.iwd", &zlibFunctions);
REQUIRE(zip);
REQUIRE(unzGoToFirstFile(zip) == UNZ_OK);
unz_file_info fileInfo;
char fileNameBuffer[64];
char readBuffer[std::char_traits<char>::length(iwiData) + 10];
REQUIRE(unzGetCurrentFileInfo(zip, &fileInfo, fileNameBuffer, sizeof(fileNameBuffer), nullptr, 0, nullptr, 0) == UNZ_OK);
REQUIRE("images/random.iwi"s == fileNameBuffer);
REQUIRE(fileInfo.uncompressed_size == std::char_traits<char>::length(iwiData));
REQUIRE(unzOpenCurrentFile(zip) == UNZ_OK);
REQUIRE(unzReadCurrentFile(zip, readBuffer, sizeof(readBuffer)) == std::char_traits<char>::length(iwiData));
REQUIRE(std::strncmp(iwiData, readBuffer, std::char_traits<char>::length(iwiData)) == 0);
}
} // namespace test::iwd

View File

@ -0,0 +1,101 @@
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include "Utils/TestMemoryManager.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <memory>
using namespace std::string_literals;
namespace test::keyvaluepairs
{
TEST_CASE("KeyValuePairsCreator: ZoneDefinition with no properties produces no KeyValuePairs", "[keyvaluepairs]")
{
KeyValuePairsCreator sut;
ZoneDefinition zoneDefinition;
sut.Finalize(zoneDefinition);
const auto kvps = sut.GetFinalKeyValuePairs();
REQUIRE(kvps.empty());
}
TEST_CASE("KeyValuePairsCreator: ZoneDefinition with unrelated properties produce no KeyValuePairs", "[keyvaluepairs]")
{
KeyValuePairsCreator sut;
ZoneDefinition zoneDefinition;
zoneDefinition.m_properties.AddProperty("linker.test", "yes");
sut.Finalize(zoneDefinition);
const auto kvps = sut.GetFinalKeyValuePairs();
REQUIRE(kvps.empty());
}
TEST_CASE("KeyValuePairsCreator: ZoneDefinition with level properties produce KeyValuePairs", "[keyvaluepairs]")
{
KeyValuePairsCreator sut;
ZoneDefinition zoneDefinition;
zoneDefinition.m_properties.AddProperty("linker.test", "yes");
zoneDefinition.m_properties.AddProperty("level.random", "lemao");
sut.Finalize(zoneDefinition);
const auto kvps = sut.GetFinalKeyValuePairs();
REQUIRE(kvps.size() == 1);
REQUIRE(kvps[0].m_key_str);
REQUIRE(!kvps[0].m_key_hash);
REQUIRE(*kvps[0].m_key_str == "random");
REQUIRE(kvps[0].m_value == "lemao");
}
TEST_CASE("KeyValuePairsCreator: ZoneDefinition can have level properties with hash", "[keyvaluepairs]")
{
KeyValuePairsCreator sut;
ZoneDefinition zoneDefinition;
zoneDefinition.m_properties.AddProperty("level.@D34DB33F", "yes");
zoneDefinition.m_properties.AddProperty("level.@112233", "no");
zoneDefinition.m_properties.AddProperty("level.@0", "maybe");
sut.Finalize(zoneDefinition);
const auto kvps = sut.GetFinalKeyValuePairs();
REQUIRE(kvps.size() == 3);
REQUIRE(*kvps[0].m_key_hash == 0);
REQUIRE(kvps[0].m_value == "maybe");
REQUIRE(*kvps[1].m_key_hash == 0x112233);
REQUIRE(kvps[1].m_value == "no");
REQUIRE(*kvps[2].m_key_hash == 0xD34DB33F);
REQUIRE(kvps[2].m_value == "yes");
}
TEST_CASE("KeyValuePairsCreator: ZoneDefinition can have level properties with name and/or hash", "[keyvaluepairs]")
{
KeyValuePairsCreator sut;
ZoneDefinition zoneDefinition;
zoneDefinition.m_properties.AddProperty("level.ipak_read", "asdf");
zoneDefinition.m_properties.AddProperty("level.hello_world", "foo");
zoneDefinition.m_properties.AddProperty("level.@D34DB33F", "yes");
zoneDefinition.m_properties.AddProperty("level.@112233", "no");
sut.Finalize(zoneDefinition);
const auto kvps = sut.GetFinalKeyValuePairs();
REQUIRE(kvps.size() == 4);
REQUIRE(*kvps[0].m_key_str == "hello_world");
REQUIRE(kvps[0].m_value == "foo");
REQUIRE(*kvps[1].m_key_str == "ipak_read");
REQUIRE(kvps[1].m_value == "asdf");
REQUIRE(*kvps[2].m_key_hash == 0x112233);
REQUIRE(kvps[2].m_value == "no");
REQUIRE(*kvps[3].m_key_hash == 0xD34DB33F);
REQUIRE(kvps[3].m_value == "yes");
}
} // namespace test::keyvaluepairs

View File

@ -43,6 +43,7 @@ function ObjLoadingTests:project()
}
self:include(includes)
Catch2Common:include(includes)
ObjCommonTestUtils:include(includes)
ParserTestUtils:include(includes)
ObjLoading:include(includes)
@ -52,5 +53,6 @@ function ObjLoadingTests:project()
links:linkto(ParserTestUtils)
links:linkto(ObjLoading)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkall()
end

View File

@ -43,6 +43,7 @@ function ParserTests:project()
}
self:include(includes)
Catch2Common:include(includes)
ParserTestUtils:include(includes)
Parser:include(includes)
catch2:include(includes)
@ -50,5 +51,6 @@ function ParserTests:project()
links:linkto(ParserTestUtils)
links:linkto(Parser)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkall()
end

View File

@ -43,6 +43,7 @@ function ZoneCodeGeneratorLibTests:project()
}
self:include(includes)
Catch2Common:include(includes)
ZoneCodeGeneratorLib:include(includes)
ParserTestUtils:include(includes)
catch2:include(includes)
@ -50,5 +51,6 @@ function ZoneCodeGeneratorLibTests:project()
links:linkto(ZoneCodeGeneratorLib)
links:linkto(ParserTestUtils)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkall()
end

View File

@ -45,6 +45,7 @@ function ZoneCommonTests:project()
}
self:include(includes)
Catch2Common:include(includes)
ObjCommonTestUtils:include(includes)
ZoneCommon:include(includes)
catch2:include(includes)
@ -52,6 +53,7 @@ function ZoneCommonTests:project()
links:linkto(ObjCommonTestUtils)
links:linkto(ZoneCommon)
links:linkto(catch2)
links:linkto(Catch2Common)
links:linkall()
ZoneCode:use()

View File

@ -49,6 +49,7 @@ function catch2:project()
defines {
"DO_NOT_USE_WMAIN",
"CATCH_AMALGAMATED_CUSTOM_MAIN",
"_CRT_SECURE_NO_WARNINGS"
}