From 0c66d1838e3bdb8eea9a58defe70434f322e217e Mon Sep 17 00:00:00 2001 From: mo Date: Sat, 25 Jul 2026 23:40:02 +0100 Subject: [PATCH] feat: T4 add `StringTable` loader (#934) * feat: T4 add string table loader * chore: rename stringtable loaders to match style between games * fix: make sure all games correctly interpret failed stringtable loading --------- Co-authored-by: Jan Laupetin --- docs/SupportedAssetTypes.md | 2 +- src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 2 +- ...gTableIW3.cpp => LoaderStringTableIW3.cpp} | 4 +- ...tringTableIW3.h => LoaderStringTableIW3.h} | 0 .../IW5/StringTable/LoaderStringTableIW5.cpp | 6 +-- src/ObjLoading/Game/T4/ObjLoaderT4.cpp | 2 + .../T4/StringTable/LoaderStringTableT4.cpp | 45 ++++++++++++++++++ .../Game/T4/StringTable/LoaderStringTableT4.h | 13 +++++ .../T5/StringTable/LoaderStringTableT5.cpp | 4 +- .../T6/StringTable/LoaderStringTableT6.cpp | 4 +- .../AssetLoaderStringTableIW3Test.cpp | 3 +- .../StringTable/LoaderStringTableT4Test.cpp | 47 +++++++++++++++++++ 12 files changed, 117 insertions(+), 15 deletions(-) rename src/ObjLoading/Game/IW3/StringTable/{AssetLoaderStringTableIW3.cpp => LoaderStringTableIW3.cpp} (95%) rename src/ObjLoading/Game/IW3/StringTable/{AssetLoaderStringTableIW3.h => LoaderStringTableIW3.h} (100%) create mode 100644 src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.cpp create mode 100644 src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.h create mode 100644 test/ObjLoadingTests/Game/T4/StringTable/LoaderStringTableT4Test.cpp diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index e3bf0211..e268bdcd 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -153,7 +153,7 @@ using `Linker`): | FxEffectDef | ❌ | ❌ | | | FxImpactTable | ❌ | ❌ | | | RawFile | ✅ | ✅ | | -| StringTable | ✅ | ❌ | | +| StringTable | ✅ | ✅ | | | PackIndex | ❌ | ❌ | | ## T5 (Call of Duty: Black Ops) diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index d2fb4742..2a7fca49 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -20,7 +20,7 @@ #include "PhysPreset/RawLoaderPhysPresetIW3.h" #include "RawFile/AssetLoaderRawFileIW3.h" #include "Sound/LoaderSoundCurveIW3.h" -#include "StringTable/AssetLoaderStringTableIW3.h" +#include "StringTable/LoaderStringTableIW3.h" #include "Weapon/WeaponGdtLoaderIW3.h" #include "Weapon/WeaponRawLoaderIW3.h" diff --git a/src/ObjLoading/Game/IW3/StringTable/AssetLoaderStringTableIW3.cpp b/src/ObjLoading/Game/IW3/StringTable/LoaderStringTableIW3.cpp similarity index 95% rename from src/ObjLoading/Game/IW3/StringTable/AssetLoaderStringTableIW3.cpp rename to src/ObjLoading/Game/IW3/StringTable/LoaderStringTableIW3.cpp index e8146808..9d092ca6 100644 --- a/src/ObjLoading/Game/IW3/StringTable/AssetLoaderStringTableIW3.cpp +++ b/src/ObjLoading/Game/IW3/StringTable/LoaderStringTableIW3.cpp @@ -1,10 +1,8 @@ -#include "AssetLoaderStringTableIW3.h" +#include "LoaderStringTableIW3.h" #include "Game/IW3/IW3.h" #include "StringTable/StringTableLoader.h" -#include - using namespace IW3; namespace diff --git a/src/ObjLoading/Game/IW3/StringTable/AssetLoaderStringTableIW3.h b/src/ObjLoading/Game/IW3/StringTable/LoaderStringTableIW3.h similarity index 100% rename from src/ObjLoading/Game/IW3/StringTable/AssetLoaderStringTableIW3.h rename to src/ObjLoading/Game/IW3/StringTable/LoaderStringTableIW3.h diff --git a/src/ObjLoading/Game/IW5/StringTable/LoaderStringTableIW5.cpp b/src/ObjLoading/Game/IW5/StringTable/LoaderStringTableIW5.cpp index ba499cd1..487d19d0 100644 --- a/src/ObjLoading/Game/IW5/StringTable/LoaderStringTableIW5.cpp +++ b/src/ObjLoading/Game/IW5/StringTable/LoaderStringTableIW5.cpp @@ -3,12 +3,8 @@ #include "Csv/CsvStream.h" #include "Game/IW5/CommonIW5.h" #include "Game/IW5/IW5.h" -#include "ObjLoading.h" -#include "Pool/GlobalAssetPool.h" #include "StringTable/StringTableLoader.h" -#include - using namespace IW5; namespace @@ -30,6 +26,8 @@ namespace string_table::StringTableLoaderV2 loader; auto* stringTable = loader.LoadFromStream(assetName, m_memory, *file.m_stream); + if (!stringTable) + return AssetCreationResult::Failure(); return AssetCreationResult::Success(context.AddAsset(assetName, stringTable)); } diff --git a/src/ObjLoading/Game/T4/ObjLoaderT4.cpp b/src/ObjLoading/Game/T4/ObjLoaderT4.cpp index d666481b..2d64b6f0 100644 --- a/src/ObjLoading/Game/T4/ObjLoaderT4.cpp +++ b/src/ObjLoading/Game/T4/ObjLoaderT4.cpp @@ -14,6 +14,7 @@ #include "PhysPreset/GdtLoaderPhysPresetT4.h" #include "PhysPreset/RawLoaderPhysPresetT4.h" #include "RawFile/AssetLoaderRawFileT4.h" +#include "StringTable/LoaderStringTableT4.h" #include "Weapon/FlameTableLoaderT4.h" #include "Weapon/WeaponGdtLoaderT4.h" #include "Weapon/WeaponRawLoaderT4.h" @@ -108,6 +109,7 @@ namespace collection.AddAssetCreator(phys_preset::CreateRawLoaderT4(memory, searchPath, zone)); collection.AddAssetCreator(phys_preset::CreateGdtLoaderT4(memory, gdt, zone)); collection.AddAssetCreator(raw_file::CreateLoaderT4(memory, searchPath)); + collection.AddAssetCreator(string_table::CreateLoaderT4(memory, searchPath)); collection.AddAssetCreator(weapon::CreateRawLoaderT4(memory, searchPath, zone)); collection.AddAssetCreator(weapon::CreateGdtLoaderT4(memory, searchPath, gdt, zone)); diff --git a/src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.cpp b/src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.cpp new file mode 100644 index 00000000..ebbb961c --- /dev/null +++ b/src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.cpp @@ -0,0 +1,45 @@ +#include "LoaderStringTableT4.h" + +#include "Game/T4/T4.h" +#include "StringTable/StringTableLoader.h" + +using namespace T4; + +namespace +{ + class StringTableLoader final : public AssetCreator + { + public: + StringTableLoader(MemoryManager& memory, ISearchPath& searchPath) + : m_memory(memory), + m_search_path(searchPath) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + const auto file = m_search_path.Open(assetName); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + string_table::StringTableLoaderV1 loader; + auto* stringTable = loader.LoadFromStream(assetName, m_memory, *file.m_stream); + if (!stringTable) + return AssetCreationResult::Failure(); + + return AssetCreationResult::Success(context.AddAsset(assetName, stringTable)); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace string_table +{ + std::unique_ptr> CreateLoaderT4(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace string_table diff --git a/src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.h b/src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.h new file mode 100644 index 00000000..a915666b --- /dev/null +++ b/src/ObjLoading/Game/T4/StringTable/LoaderStringTableT4.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/T4/T4.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace string_table +{ + std::unique_ptr> CreateLoaderT4(MemoryManager& memory, ISearchPath& searchPath); +} // namespace string_table diff --git a/src/ObjLoading/Game/T5/StringTable/LoaderStringTableT5.cpp b/src/ObjLoading/Game/T5/StringTable/LoaderStringTableT5.cpp index b1085572..1f51a304 100644 --- a/src/ObjLoading/Game/T5/StringTable/LoaderStringTableT5.cpp +++ b/src/ObjLoading/Game/T5/StringTable/LoaderStringTableT5.cpp @@ -4,8 +4,6 @@ #include "Game/T5/T5.h" #include "StringTable/StringTableLoader.h" -#include - using namespace T5; namespace @@ -27,6 +25,8 @@ namespace string_table::StringTableLoaderV3 loader; auto* stringTable = loader.LoadFromStream(assetName, m_memory, *file.m_stream); + if (!stringTable) + return AssetCreationResult::Failure(); return AssetCreationResult::Success(context.AddAsset(assetName, stringTable)); } diff --git a/src/ObjLoading/Game/T6/StringTable/LoaderStringTableT6.cpp b/src/ObjLoading/Game/T6/StringTable/LoaderStringTableT6.cpp index d5e092d6..d35eeef0 100644 --- a/src/ObjLoading/Game/T6/StringTable/LoaderStringTableT6.cpp +++ b/src/ObjLoading/Game/T6/StringTable/LoaderStringTableT6.cpp @@ -5,8 +5,6 @@ #include "Game/T6/T6.h" #include "StringTable/StringTableLoader.h" -#include - using namespace T6; namespace @@ -28,6 +26,8 @@ namespace string_table::StringTableLoaderV3 loader; auto* stringTable = loader.LoadFromStream(assetName, m_memory, *file.m_stream); + if (!stringTable) + return AssetCreationResult::Failure(); return AssetCreationResult::Success(context.AddAsset(assetName, stringTable)); } diff --git a/test/ObjLoadingTests/Game/IW3/StringTable/AssetLoaderStringTableIW3Test.cpp b/test/ObjLoadingTests/Game/IW3/StringTable/AssetLoaderStringTableIW3Test.cpp index f90b8a59..16d2517a 100644 --- a/test/ObjLoadingTests/Game/IW3/StringTable/AssetLoaderStringTableIW3Test.cpp +++ b/test/ObjLoadingTests/Game/IW3/StringTable/AssetLoaderStringTableIW3Test.cpp @@ -1,6 +1,5 @@ -#include "Game/IW3/StringTable/AssetLoaderStringTableIW3.h" - #include "Game/IW3/GameIW3.h" +#include "Game/IW3/StringTable/LoaderStringTableIW3.h" #include "SearchPath/MockSearchPath.h" #include "Utils/MemoryManager.h" diff --git a/test/ObjLoadingTests/Game/T4/StringTable/LoaderStringTableT4Test.cpp b/test/ObjLoadingTests/Game/T4/StringTable/LoaderStringTableT4Test.cpp new file mode 100644 index 00000000..95c35eb6 --- /dev/null +++ b/test/ObjLoadingTests/Game/T4/StringTable/LoaderStringTableT4Test.cpp @@ -0,0 +1,47 @@ +#include "Game/T4/StringTable/LoaderStringTableT4.h" + +#include "Game/T4/GameT4.h" +#include "SearchPath/MockSearchPath.h" +#include "Utils/MemoryManager.h" + +#include +#include + +using namespace T4; +using namespace std::literals; + +namespace +{ + TEST_CASE("LoaderStringTable(T4): Can parse string table", "[t4][stringtable][assetloader]") + { + MockSearchPath searchPath; + searchPath.AddFileData("mp/cooltable.csv", + "test,data,lol\n" + "lorem,ipsum"); + + Zone zone("MockZone", 0, GameId::T4, GamePlatform::PC); + + MemoryManager memory; + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + auto loader = string_table::CreateLoaderT4(memory, searchPath); + auto result = loader->CreateAsset("mp/cooltable.csv", context); + REQUIRE(result.HasBeenSuccessful()); + + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* stringTable = assetInfo->Asset(); + + REQUIRE(stringTable->name == "mp/cooltable.csv"s); + REQUIRE(stringTable->columnCount == 3); + REQUIRE(stringTable->rowCount == 2); + + REQUIRE(stringTable->values[0] == "test"s); + REQUIRE(stringTable->values[1] == "data"s); + REQUIRE(stringTable->values[2] == "lol"s); + REQUIRE(stringTable->values[3] == "lorem"s); + REQUIRE(stringTable->values[4] == "ipsum"s); + REQUIRE(stringTable->values[5] == ""s); + } +} // namespace