From 645a1629a6b46a5974af9c7be16160bf4d171182 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Fri, 5 Jun 2026 22:36:07 +0200 Subject: [PATCH] feat: load binary t5 xanims --- src/ObjLoading/Game/T5/ObjLoaderT5.cpp | 3 +- src/ObjLoading/XAnim/CompiledXAnimLoader.cpp | 65 ++++++++++++++++-- src/ObjLoading/XAnim/XAnimLoader.cpp.template | 21 +++++- src/ObjLoading/XAnim/XAnimLoader.h.template | 2 +- test/SystemTests/Game/T5/XAnim/test_anim | Bin 0 -> 16143 bytes test/SystemTests/Game/T5/XAnimT5.cpp | 62 +++++++++++++++++ 6 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 test/SystemTests/Game/T5/XAnim/test_anim create mode 100644 test/SystemTests/Game/T5/XAnimT5.cpp diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp index e39247d5..31dbaec0 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp @@ -8,6 +8,7 @@ #include "Game/T5/T5.h" #include "Game/T5/Techset/PixelShaderLoaderT5.h" #include "Game/T5/Techset/VertexShaderLoaderT5.h" +#include "Game/T5/XAnim/XAnimLoaderT5.h" #include "Game/T5/XModel/LoaderXModelT5.h" #include "LightDef/LightDefLoaderT5.h" #include "Localize/LoaderLocalizeT5.h" @@ -112,7 +113,7 @@ namespace collection.AddAssetCreator(phys_preset::CreateGdtLoaderT5(memory, gdt, zone)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(xanim::CreateLoaderT5(memory, searchPath, zone)); collection.AddAssetCreator(xmodel::CreateLoaderT5(memory, searchPath, zone)); collection.AddAssetCreator(material::CreateLoaderT5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/XAnim/CompiledXAnimLoader.cpp b/src/ObjLoading/XAnim/CompiledXAnimLoader.cpp index 822adb79..ea823eba 100644 --- a/src/ObjLoading/XAnim/CompiledXAnimLoader.cpp +++ b/src/ObjLoading/XAnim/CompiledXAnimLoader.cpp @@ -14,9 +14,6 @@ using namespace xanim; namespace { - constexpr uint8_t FLAG_LOOPED = 1u; - constexpr uint8_t FLAG_DELTA = 2u; - // The linker decodes raw trans size[] with these exact float literals. // They correspond to 1.0f / 255.0f and 1.0f / 65535.0f, but we keep the // decompiled values to preserve binary-stable round trips. @@ -30,6 +27,8 @@ namespace { case CompiledXAnimVersion::VERSION_17: return CompiledXAnimVersion::VERSION_17; + case CompiledXAnimVersion::VERSION_19: + return CompiledXAnimVersion::VERSION_19; default: return std::unexpected(std::format("Version {} is not supported", fileVersion)); } @@ -347,6 +346,54 @@ namespace // This notify is always automatically added parts.m_notifies.emplace_back("end", 1.0f); } + + bool IsLooped(const uint8_t flags, const CompiledXAnimVersion version) + { + switch (version) + { + case CompiledXAnimVersion::VERSION_17: + return (flags & binary17::FLAG_LOOPED) > 0; + case CompiledXAnimVersion::VERSION_19: + return (flags & binary19::FLAG_LOOPED) > 0; + } + + return false; + } + + bool HasDelta(const uint8_t flags, const CompiledXAnimVersion version) + { + switch (version) + { + case CompiledXAnimVersion::VERSION_17: + return (flags & binary17::FLAG_DELTA) > 0; + case CompiledXAnimVersion::VERSION_19: + return (flags & binary19::FLAG_DELTA) > 0; + } + + return false; + } + + bool IsLeftHandGripIk(const uint8_t flags, const CompiledXAnimVersion version) + { + switch (version) + { + case CompiledXAnimVersion::VERSION_19: + return (flags & binary19::FLAG_LEFT_HAND_GRIP_IK) > 0; + default: + return false; + } + } + + bool IsStreamable(const uint8_t flags, const CompiledXAnimVersion version) + { + switch (version) + { + case CompiledXAnimVersion::VERSION_19: + return (flags & binary19::FLAG_STREAMABLE) > 0; + default: + return false; + } + } } // namespace namespace xanim @@ -360,7 +407,6 @@ namespace xanim auto parts = std::make_unique(); const auto version = maybeVersion.value(); - assert(version == CompiledXAnimVersion::VERSION_17); const auto numFrames = stream::ReadValue(stream); const auto boneCount = stream::ReadValue(stream); @@ -370,15 +416,22 @@ namespace xanim if (stream.fail()) return std::unexpected("Truncated file"); - const bool isLooped = flags & FLAG_LOOPED; - const bool hasDelta = flags & FLAG_DELTA; + const bool isLooped = IsLooped(flags, version); + const bool hasDelta = HasDelta(flags, version); + const bool leftHandGripIk = IsLeftHandGripIk(flags, version); + const bool streamable = IsStreamable(flags, version); const uint16_t numLoopFrames = isLooped ? numFrames + 1u : numFrames; parts->m_num_frames = numLoopFrames - 1; parts->m_looped = isLooped; + parts->m_left_hand_grip_ik = leftHandGripIk; + parts->m_streamable = streamable; parts->m_asset_type = assetType; parts->m_frame_rate = static_cast(framerate); + if (version == CompiledXAnimVersion::VERSION_19 && streamable) + parts->m_primed_length = stream::ReadValue(stream); + const auto useByteIndices = parts->m_num_frames < 256; if (hasDelta) diff --git a/src/ObjLoading/XAnim/XAnimLoader.cpp.template b/src/ObjLoading/XAnim/XAnimLoader.cpp.template index 76ddc0ea..463e2071 100644 --- a/src/ObjLoading/XAnim/XAnimLoader.cpp.template +++ b/src/ObjLoading/XAnim/XAnimLoader.cpp.template @@ -1,4 +1,4 @@ -#options GAME(IW3) +#options GAME(IW3, T5) #filename "Game/" + GAME + "/XAnim/XAnimLoader" + GAME + ".cpp" @@ -70,6 +70,18 @@ namespace notify.time = commonNotify.m_time; } + +#ifdef FEATURE_T5 + const auto loopBegin = std::ranges::find_if(commonParts.m_notifies, [](const xanim::CommonXAnimNotifyInfo& notify) + { + return notify.m_name == "loop_begin"; + }); + + if (loopBegin != commonParts.m_notifies.end()) + parts.loopEntryTime = loopBegin->m_time; + else + parts.loopEntryTime = 0; +#endif } template void ConvertIndices(T& indices, const std::vector& commonIndices, const bool useByteIndices) @@ -279,9 +291,16 @@ namespace { parts.numframes = static_cast(commonParts.m_num_frames); parts.bLoop = commonParts.m_looped; +#ifdef FEATURE_T5 + parts.bLeftHandGripIK = commonParts.m_left_hand_grip_ik; + parts.bStreamable = commonParts.m_streamable; +#endif parts.assetType = commonParts.m_asset_type; parts.framerate = commonParts.m_frame_rate; parts.frequency = parts.numframes > 0 ? parts.framerate / static_cast(parts.numframes) : 0; +#ifdef FEATURE_T5 + parts.primedLength = commonParts.m_primed_length; +#endif const auto useByteIndices = parts.numframes < 256; diff --git a/src/ObjLoading/XAnim/XAnimLoader.h.template b/src/ObjLoading/XAnim/XAnimLoader.h.template index 27df1bb4..79c6230e 100644 --- a/src/ObjLoading/XAnim/XAnimLoader.h.template +++ b/src/ObjLoading/XAnim/XAnimLoader.h.template @@ -1,4 +1,4 @@ -#options GAME(IW3) +#options GAME(IW3, T5) #filename "Game/" + GAME + "/XAnim/XAnimLoader" + GAME + ".h" diff --git a/test/SystemTests/Game/T5/XAnim/test_anim b/test/SystemTests/Game/T5/XAnim/test_anim new file mode 100644 index 0000000000000000000000000000000000000000..01c5b3e322e7a6bfd08369ba4fb0377249330aa1 GIT binary patch literal 16143 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 H{PuqUgRq$U literal 0 HcmV?d00001 diff --git a/test/SystemTests/Game/T5/XAnimT5.cpp b/test/SystemTests/Game/T5/XAnimT5.cpp new file mode 100644 index 00000000..2f5c5763 --- /dev/null +++ b/test/SystemTests/Game/T5/XAnimT5.cpp @@ -0,0 +1,62 @@ +#include "Game/T5/XAnim/XAnimDumperT5.h" +#include "Game/T5/XAnim/XAnimLoaderT5.h" +#include "OatTestPaths.h" +#include "SearchPath/MockOutputPath.h" +#include "SearchPath/MockSearchPath.h" +#include "ZoneLoading.h" + +#include +#include +#include +#include +#include + +using namespace std::literals; +namespace fs = std::filesystem; + +namespace +{ + TEST_CASE("XAnim Loading/Dumping (T5)", "[t5][system]") + { + MockSearchPath searchPath; + + const auto filePath = oat::paths::GetTestDirectory() / "SystemTests/Game/T5/XAnim/test_anim"; + const auto fileSize = static_cast(fs::file_size(filePath)); + + std::ifstream file(filePath, std::ios::binary); + REQUIRE(file.is_open()); + + const auto data = std::make_unique(fileSize); + file.read(data.get(), fileSize); + + searchPath.AddFileData("xanim/test_anim", std::string(data.get(), fileSize)); + + Zone zone("MockZone", 0, GameId::IW3, GamePlatform::PC); + AssetCreatorCollection creatorCollection(zone); + IgnoredAssetLookup ignoredAssetLookup; + MemoryManager memoryManager; + const auto loader = xanim::CreateLoaderT5(memoryManager, searchPath, zone); + AssetCreationContext context(zone, &creatorCollection, &ignoredAssetLookup); + + const auto result = loader->CreateAsset("test_anim", context); + + REQUIRE(result.HasBeenSuccessful()); + const auto* assetInfo = reinterpret_cast*>(result.GetAssetInfo()); + const auto* parts = assetInfo->Asset(); + + REQUIRE(parts->name == "test_anim"s); + REQUIRE(parts->numframes > 0); + + MockSearchPath mockObjPath; + MockOutputPath mockOutput; + xanim::DumperT5 dumper; + AssetDumpingContext dumpingContext(zone, "", mockOutput, mockObjPath, std::nullopt); + dumper.Dump(dumpingContext); + + const auto* outAnimFile = mockOutput.GetMockedFile("xanim/test_anim"); + REQUIRE(outAnimFile != nullptr); + + REQUIRE(outAnimFile->m_data.size() == fileSize); + REQUIRE(memcmp(outAnimFile->m_data.data(), data.get(), fileSize) == 0); + } +} // namespace