From 3136a03754822ee1af7fc488dcf17ffff8957a8d Mon Sep 17 00:00:00 2001 From: njohnson Date: Tue, 28 Apr 2026 20:53:47 -0400 Subject: [PATCH 1/7] Add dumper and loader call to parents. --- src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 3 ++- src/ObjWriting/Game/IW3/ObjWriterIW3.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index b36c4e5a..f7532754 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -15,6 +15,7 @@ #include "PhysPreset/GdtLoaderPhysPresetIW3.h" #include "PhysPreset/RawLoaderPhysPresetIW3.h" #include "RawFile/AssetLoaderRawFileIW3.h" +#include "Sound/LoaderSoundCurveIW3.h" #include "StringTable/AssetLoaderStringTableIW3.h" #include @@ -104,7 +105,7 @@ namespace collection.AddAssetCreator(image::CreateLoaderEmbeddedIW3(memory, searchPath)); collection.AddAssetCreator(image::CreateLoaderExternalIW3(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(sound_curve::CreateLoaderIW3(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp index dfaa23c9..e81c2c37 100644 --- a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp +++ b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp @@ -9,6 +9,7 @@ #include "PhysPreset/PhysPresetInfoStringDumperIW3.h" #include "RawFile/RawFileDumperIW3.h" #include "Sound/LoadedSoundDumperIW3.h" +#include "Sound/SndCurveDumperIW3.h" #include "StringTable/StringTableDumperIW3.h" using namespace IW3; @@ -28,7 +29,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) )); RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumpersnd_alias_list_t) - // REGISTER_DUMPER(AssetDumperSndCurve) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperClipMap) // REGISTER_DUMPER(AssetDumperComWorld) From c1eaca67e5e712b1d7bfd0b870ec82a2253e3dd3 Mon Sep 17 00:00:00 2001 From: njohnson Date: Tue, 28 Apr 2026 20:54:11 -0400 Subject: [PATCH 2/7] Update docs. --- docs/SupportedAssetTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index 7bbf4154..0c0b5f54 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -18,7 +18,7 @@ The following section specify which assets are supported to be dumped to disk (u | MaterialTechniqueSet | ✅ | ✅ | For shaders: only dumps/loads shader bytecode. | | GfxImage | ✅ | ✅ | | | snd_alias_list_t | ❌ | ❌ | | -| SndCurve | ❌ | ❌ | | +| SndCurve | ✅ | ✅ | | | LoadedSound | ✅ | ❌ | | | clipMap_t | ❌ | ❌ | | | ComWorld | ❌ | ❌ | | From dec2ae96cbf67bd9e9ddb979e6b33a4e6624e5ca Mon Sep 17 00:00:00 2001 From: njohnson Date: Tue, 28 Apr 2026 20:54:37 -0400 Subject: [PATCH 3/7] Add loader logic from IW4. --- .../Game/IW3/Sound/LoaderSoundCurveIW3.cpp | 80 +++++++++++++++++++ .../Game/IW3/Sound/LoaderSoundCurveIW3.h | 13 +++ 2 files changed, 93 insertions(+) create mode 100644 src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.cpp create mode 100644 src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.h diff --git a/src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.cpp b/src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.cpp new file mode 100644 index 00000000..fabbd089 --- /dev/null +++ b/src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.cpp @@ -0,0 +1,80 @@ +#include "LoaderSoundCurveIW3.h" + +#include "Game/IW3/IW3.h" +#include "ObjLoading.h" +#include "Parsing/Graph2D/Graph2DReader.h" +#include "Pool/GlobalAssetPool.h" +#include "Sound/SoundCurveCommon.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include +#include + +using namespace IW3; + +namespace +{ + class LoaderSoundCurve final : public AssetCreator + { + public: + LoaderSoundCurve(MemoryManager& memory, ISearchPath& searchPath) + : m_memory(memory), + m_search_path(searchPath) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + const auto fileName = sound_curve::GetFileNameForAssetName(assetName); + const auto file = m_search_path.Open(fileName); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + const auto sndCurveData = graph2d::Read("sound curve", "SNDCURVE", *file.m_stream, fileName, assetName); + + if (!sndCurveData) + return AssetCreationResult::Failure(); + + if (sndCurveData->knots.size() > std::extent_v) + { + con::error("Failed to load SndCurve \"{}\": Too many knots ({})", assetName, sndCurveData->knots.size()); + return AssetCreationResult::Failure(); + } + + auto* sndCurve = m_memory.Alloc(); + sndCurve->filename = m_memory.Dup(assetName.c_str()); + sndCurve->knotCount = static_cast(sndCurveData->knots.size()); + + for (auto i = 0u; i < std::extent_v; i++) + { + if (i < sndCurveData->knots.size()) + { + const auto& [x, y] = sndCurveData->knots[i]; + sndCurve->knots[i][0] = static_cast(x); + sndCurve->knots[i][1] = static_cast(y); + } + else + { + sndCurve->knots[i][0] = 0; + sndCurve->knots[i][1] = 0; + } + } + + return AssetCreationResult::Success(context.AddAsset(assetName, sndCurve)); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace sound_curve +{ + std::unique_ptr> CreateLoaderIW3(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace sound_curve diff --git a/src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.h b/src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.h new file mode 100644 index 00000000..08dae2ad --- /dev/null +++ b/src/ObjLoading/Game/IW3/Sound/LoaderSoundCurveIW3.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/IW3/IW3.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace sound_curve +{ + std::unique_ptr> CreateLoaderIW3(MemoryManager& memory, ISearchPath& searchPath); +} // namespace sound_curve From 6400b57caf0fe3dbf01e8dabd5eabb670fd18034 Mon Sep 17 00:00:00 2001 From: njohnson Date: Tue, 28 Apr 2026 20:54:49 -0400 Subject: [PATCH 4/7] Add dumper logic from IW4. --- .../Game/IW3/Sound/SndCurveDumperIW3.cpp | 27 +++++++++++++++++++ .../Game/IW3/Sound/SndCurveDumperIW3.h | 13 +++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.cpp create mode 100644 src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.h diff --git a/src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.cpp b/src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.cpp new file mode 100644 index 00000000..d3ab138e --- /dev/null +++ b/src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.cpp @@ -0,0 +1,27 @@ +#include "SndCurveDumperIW3.h" + +#include "Sound/SndCurveDumper.h" +#include "Sound/SoundCurveCommon.h" + +using namespace IW3; + +namespace sound_curve +{ + void DumperIW3::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) + { + const auto* sndCurve = asset.Asset(); + + const auto assetFile = context.OpenAssetFile(GetFileNameForAssetName(sndCurve->filename)); + + if (!assetFile) + return; + + SndCurveDumper dumper(*assetFile); + + const auto knotCount = std::min(static_cast(sndCurve->knotCount), std::extent_v); + dumper.Init(knotCount); + + for (auto i = 0u; i < knotCount; i++) + dumper.WriteKnot(sndCurve->knots[i][0], sndCurve->knots[i][1]); + } +} // namespace sound_curve diff --git a/src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.h b/src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.h new file mode 100644 index 00000000..20ba622c --- /dev/null +++ b/src/ObjWriting/Game/IW3/Sound/SndCurveDumperIW3.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW3/IW3.h" + +namespace sound_curve +{ + class DumperIW3 final : public AbstractAssetDumper + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace sound_curve From 5cf3e90be26cd18a740311c1faf708bb720cde18 Mon Sep 17 00:00:00 2001 From: njohnson Date: Fri, 1 May 2026 16:32:30 -0400 Subject: [PATCH 5/7] Update docs. --- docs/SupportedAssetTypes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index 0c0b5f54..aa503afb 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -93,7 +93,7 @@ The following section specify which assets are supported to be dumped to disk (u | MaterialTechniqueSet | ❌ | ❌ | | | GfxImage | ✅ | ✅ | A few special image encodings are not yet supported. | | snd_alias_list_t | ❌ | ❌ | | -| SndCurve | ❌ | ❌ | | +| SndCurve | ✅ | ✅ | | | LoadedSound | ✅ | ❌ | | | clipMap_t | ❌ | ❌ | | | ComWorld | ❌ | ❌ | | From 0eb1f9f978dba6b41d5c500dc9469437b97e0118 Mon Sep 17 00:00:00 2001 From: njohnson Date: Fri, 1 May 2026 16:32:51 -0400 Subject: [PATCH 6/7] Add IW5 SndCurve dumper. --- src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 3 +- .../Game/IW5/Sound/LoaderSoundCurveIW5.cpp | 80 +++++++++++++++++++ .../Game/IW5/Sound/LoaderSoundCurveIW5.h | 13 +++ src/ObjWriting/Game/IW5/ObjWriterIW5.cpp | 3 +- .../Game/IW5/Sound/SndCurveDumperIW5.cpp | 27 +++++++ .../Game/IW5/Sound/SndCurveDumperIW5.h | 13 +++ 6 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.cpp create mode 100644 src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.h create mode 100644 src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.cpp create mode 100644 src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.h diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index 8023011f..60a79733 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -6,6 +6,7 @@ #include "Game/IW5/IW5.h" #include "Game/IW5/Image/ImageLoaderEmbeddedIW5.h" #include "Game/IW5/Image/ImageLoaderExternalIW5.h" +#include "Sound/LoaderSoundCurveIW5.h" #include "Game/IW5/Techset/PixelShaderLoaderIW5.h" #include "Game/IW5/Techset/VertexShaderLoaderIW5.h" #include "Game/IW5/XModel/LoaderXModelIW5.h" @@ -140,7 +141,7 @@ namespace collection.AddAssetCreator(image::CreateLoaderEmbeddedIW5(memory, searchPath)); collection.AddAssetCreator(image::CreateLoaderExternalIW5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(sound_curve::CreateLoaderIW5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); diff --git a/src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.cpp b/src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.cpp new file mode 100644 index 00000000..5b197935 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.cpp @@ -0,0 +1,80 @@ +#include "LoaderSoundCurveIW5.h" + +#include "Game/IW5/IW5.h" +#include "ObjLoading.h" +#include "Parsing/Graph2D/Graph2DReader.h" +#include "Pool/GlobalAssetPool.h" +#include "Sound/SoundCurveCommon.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include +#include + +using namespace IW5; + +namespace +{ + class LoaderSoundCurve final : public AssetCreator + { + public: + LoaderSoundCurve(MemoryManager& memory, ISearchPath& searchPath) + : m_memory(memory), + m_search_path(searchPath) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + const auto fileName = sound_curve::GetFileNameForAssetName(assetName); + const auto file = m_search_path.Open(fileName); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + const auto sndCurveData = graph2d::Read("sound curve", "SNDCURVE", *file.m_stream, fileName, assetName); + + if (!sndCurveData) + return AssetCreationResult::Failure(); + + if (sndCurveData->knots.size() > std::extent_v) + { + con::error("Failed to load SndCurve \"{}\": Too many knots ({})", assetName, sndCurveData->knots.size()); + return AssetCreationResult::Failure(); + } + + auto* sndCurve = m_memory.Alloc(); + sndCurve->filename = m_memory.Dup(assetName.c_str()); + sndCurve->knotCount = static_cast(sndCurveData->knots.size()); + + for (auto i = 0u; i < std::extent_v; i++) + { + if (i < sndCurveData->knots.size()) + { + const auto& [x, y] = sndCurveData->knots[i]; + sndCurve->knots[i][0] = static_cast(x); + sndCurve->knots[i][1] = static_cast(y); + } + else + { + sndCurve->knots[i][0] = 0; + sndCurve->knots[i][1] = 0; + } + } + + return AssetCreationResult::Success(context.AddAsset(assetName, sndCurve)); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace sound_curve +{ + std::unique_ptr> CreateLoaderIW5(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace sound_curve diff --git a/src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.h b/src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.h new file mode 100644 index 00000000..df7b320b --- /dev/null +++ b/src/ObjLoading/Game/IW5/Sound/LoaderSoundCurveIW5.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/IW5/IW5.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace sound_curve +{ + std::unique_ptr> CreateLoaderIW5(MemoryManager& memory, ISearchPath& searchPath); +} // namespace sound_curve diff --git a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp index d720cecd..ca09c0d8 100644 --- a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp +++ b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp @@ -6,6 +6,7 @@ #include "Game/IW5/Techset/VertexShaderDumperIW5.h" #include "Game/IW5/XModel/XModelDumperIW5.h" #include "Image/ImageDumperIW5.h" +#include "Sound/SndCurveDumperIW5.h" #include "Leaderboard/LeaderboardJsonDumperIW5.h" #include "Localize/LocalizeDumperIW5.h" #include "Maps/AddonMapEntsDumperIW5.h" @@ -40,7 +41,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) )); RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumpersnd_alias_list_t) - // REGISTER_DUMPER(AssetDumperSndCurve) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperclipMap_t) // REGISTER_DUMPER(AssetDumperComWorld) diff --git a/src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.cpp b/src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.cpp new file mode 100644 index 00000000..d192a84a --- /dev/null +++ b/src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.cpp @@ -0,0 +1,27 @@ +#include "SndCurveDumperIW5.h" + +#include "Sound/SndCurveDumper.h" +#include "Sound/SoundCurveCommon.h" + +using namespace IW5; + +namespace sound_curve +{ + void DumperIW5::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) + { + const auto* sndCurve = asset.Asset(); + + const auto assetFile = context.OpenAssetFile(GetFileNameForAssetName(sndCurve->filename)); + + if (!assetFile) + return; + + SndCurveDumper dumper(*assetFile); + + const auto knotCount = std::min(static_cast(sndCurve->knotCount), std::extent_v); + dumper.Init(knotCount); + + for (auto i = 0u; i < knotCount; i++) + dumper.WriteKnot(sndCurve->knots[i][0], sndCurve->knots[i][1]); + } +} // namespace sound_curve diff --git a/src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.h b/src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.h new file mode 100644 index 00000000..c0735968 --- /dev/null +++ b/src/ObjWriting/Game/IW5/Sound/SndCurveDumperIW5.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW5/IW5.h" + +namespace sound_curve +{ + class DumperIW5 final : public AbstractAssetDumper + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace sound_curve From 1d6c028454f1a265ef8db867dbd7c968b525dd5f Mon Sep 17 00:00:00 2001 From: njohnson Date: Fri, 1 May 2026 16:33:17 -0400 Subject: [PATCH 7/7] Clang format. --- src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 2 +- src/ObjWriting/Game/IW5/ObjWriterIW5.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index 60a79733..a015ee7d 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -6,7 +6,6 @@ #include "Game/IW5/IW5.h" #include "Game/IW5/Image/ImageLoaderEmbeddedIW5.h" #include "Game/IW5/Image/ImageLoaderExternalIW5.h" -#include "Sound/LoaderSoundCurveIW5.h" #include "Game/IW5/Techset/PixelShaderLoaderIW5.h" #include "Game/IW5/Techset/VertexShaderLoaderIW5.h" #include "Game/IW5/XModel/LoaderXModelIW5.h" @@ -19,6 +18,7 @@ #include "PhysPreset/RawLoaderPhysPresetIW5.h" #include "RawFile/LoaderRawFileIW5.h" #include "Script/LoaderScriptFileIW5.h" +#include "Sound/LoaderSoundCurveIW5.h" #include "StringTable/LoaderStringTableIW5.h" #include "Weapon/GdtLoaderWeaponIW5.h" #include "Weapon/LoaderAttachmentIW5.h" diff --git a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp index ca09c0d8..92e33973 100644 --- a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp +++ b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp @@ -6,7 +6,6 @@ #include "Game/IW5/Techset/VertexShaderDumperIW5.h" #include "Game/IW5/XModel/XModelDumperIW5.h" #include "Image/ImageDumperIW5.h" -#include "Sound/SndCurveDumperIW5.h" #include "Leaderboard/LeaderboardJsonDumperIW5.h" #include "Localize/LocalizeDumperIW5.h" #include "Maps/AddonMapEntsDumperIW5.h" @@ -16,6 +15,7 @@ #include "RawFile/RawFileDumperIW5.h" #include "Script/ScriptDumperIW5.h" #include "Sound/LoadedSoundDumperIW5.h" +#include "Sound/SndCurveDumperIW5.h" #include "StringTable/StringTableDumperIW5.h" #include "Weapon/AttachmentJsonDumperIW5.h" #include "Weapon/WeaponDumperIW5.h"