From 398157b42fa14ec081c9e3797906ec90c5c6cd26 Mon Sep 17 00:00:00 2001 From: MrIkso Date: Tue, 14 Apr 2026 02:12:27 +0300 Subject: [PATCH 1/2] feat(iw3): add font dumper and loader --- .../Game/IW3/Font/AssetLoaderFontW3.cpp | 134 ++++++++++++++++++ .../Game/IW3/Font/AssetLoaderFontW3.h | 13 ++ src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 3 +- .../Game/IW3/Font/FontFileDumperIW3.cpp | 70 +++++++++ .../Game/IW3/Font/FontFileDumperIW3.h | 13 ++ src/ObjWriting/Game/IW3/ObjWriterIW3.cpp | 3 +- 6 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp create mode 100644 src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.h create mode 100644 src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.cpp create mode 100644 src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.h diff --git a/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp new file mode 100644 index 000000000..c13f08ee3 --- /dev/null +++ b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp @@ -0,0 +1,134 @@ +#include "AssetLoaderFontW3.h" +#include "Game/IW3/IW3.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include + +using namespace IW3; + +namespace +{ + class FontLoader final : public AssetCreator + { + public: + FontLoader(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(std::format("{}.json", assetName)); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + auto* font = m_memory.Alloc(); + std::memset(font, 0, sizeof(Font_s)); + + AssetRegistration registration(assetName, font); + + if (!LoadFromJson(*file.m_stream, *font, context, registration)) + { + con::error("Failed to load font \"{}\"", assetName); + return AssetCreationResult::Failure(); + } + + return AssetCreationResult::Success(context.AddAsset(std::move(registration))); + } + + private: + bool LoadFromJson(std::istream& jsonStream, Font_s& font, AssetCreationContext& context, AssetRegistration& registration) + { + try + { + const auto jRoot = nlohmann::json::parse(jsonStream); + + std::string type; + jRoot.at("_type").get_to(type); + + if (type != "font") + { + con::error("Tried to load font but did not find expected type 'font'"); + return false; + } + + font.pixelHeight = jRoot.value("pixelHeight", 0); + font.glyphCount = jRoot.value("glyphCount", 0); + + std::string fontName = jRoot.value("name", ""); + font.fontName = m_memory.Dup(fontName.c_str()); + + if (jRoot.contains("material") && !jRoot["material"].is_null()) + { + std::string matName = jRoot["material"]; + auto* matInfo = context.LoadDependency(matName); + if (!matInfo) + { + con::error("Font \"{}\" missing dependency material \"{}\"", font.fontName, matName); + return false; + } + registration.AddDependency(matInfo); + font.material = matInfo->Asset(); + } + + if (jRoot.contains("glowMaterial") && !jRoot["glowMaterial"].is_null()) + { + std::string glowMatName = jRoot["glowMaterial"]; + auto* glowMatInfo = context.LoadDependency(glowMatName); + if (glowMatInfo) + { + registration.AddDependency(glowMatInfo); + font.glowMaterial = glowMatInfo->Asset(); + } + } + + if (font.glyphCount > 0 && jRoot.contains("glyphs")) + { + const auto& jGlyphs = jRoot["glyphs"]; + font.glyphs = m_memory.Alloc(font.glyphCount); + + for (int i = 0; i < font.glyphCount; ++i) + { + const auto& jG = jGlyphs[i]; + auto& g = font.glyphs[i]; + + g.letter = jG.value("letter", 0); + + g.x0 = static_cast(jG.value("x0", 0)); + g.y0 = static_cast(jG.value("y0", 0)); + g.dx = static_cast(jG.value("dx", 0)); + g.pixelWidth = static_cast(jG.value("width", 0)); + g.pixelHeight = static_cast(jG.value("height", 0)); + + g.s0 = jG.value("s0", 0.0f); + g.t0 = jG.value("t0", 0.0f); + g.s1 = jG.value("s1", 0.0f); + g.t1 = jG.value("t1", 0.0f); + } + } + + return true; + } + catch (const nlohmann::json::exception& e) + { + con::error("Failed to parse json of font: {}", e.what()); + } + + return false; + } + + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace font +{ + std::unique_ptr> CreateLoaderIW3(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace font diff --git a/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.h b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.h new file mode 100644 index 000000000..e2aad0733 --- /dev/null +++ b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.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 font +{ + std::unique_ptr> CreateLoaderIW3(MemoryManager& memory, ISearchPath& searchPath); +} // namespace font diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index d8656beb6..f6fda8401 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -14,6 +14,7 @@ #include "ObjLoading.h" #include "RawFile/AssetLoaderRawFileIW3.h" #include "StringTable/AssetLoaderStringTableIW3.h" +#include "Font/AssetLoaderFontW3.h" #include @@ -110,7 +111,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderIW3(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderIW3(memory, searchPath, zone)); diff --git a/src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.cpp b/src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.cpp new file mode 100644 index 000000000..dad91c33a --- /dev/null +++ b/src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.cpp @@ -0,0 +1,70 @@ +#include "FontFileDumperIW3.h" +#include + +using ordered_json = nlohmann::ordered_json; +using namespace nlohmann; +using namespace IW3; + +namespace font +{ + void DumperIW3::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) + { + const auto* fontFile = asset.Asset(); + const auto assetFile = context.OpenAssetFile(std::format("{}.json", asset.m_name)); + + if (!assetFile) + return; + + ordered_json jRoot; + jRoot["$schema"] = "http://openassettools.dev/schema/font.v1.json"; + jRoot["_type"] = "font"; + jRoot["_game"] = "iw3"; + + jRoot["name"] = fontFile->fontName ? fontFile->fontName : ""; + jRoot["pixelHeight"] = fontFile->pixelHeight; + jRoot["glyphCount"] = fontFile->glyphCount; + + if (fontFile->material && fontFile->material->info.name) + { + jRoot["material"] = fontFile->material->info.name; + } + else + { + jRoot["material"] = nullptr; + } + + if (fontFile->glowMaterial && fontFile->glowMaterial->info.name) + { + jRoot["glowMaterial"] = fontFile->glowMaterial->info.name; + } + + jRoot["glyphs"] = json::array(); + + if (fontFile->glyphs && fontFile->glyphCount > 0) + { + for (int i = 0; i < fontFile->glyphCount; ++i) + { + const auto& g = fontFile->glyphs[i]; + + ordered_json glyphJson; + glyphJson["letter"] = g.letter; + + glyphJson["x0"] = static_cast(g.x0); + glyphJson["y0"] = static_cast(g.y0); + glyphJson["dx"] = static_cast(g.dx); + glyphJson["width"] = static_cast(g.pixelWidth); + glyphJson["height"] = static_cast(g.pixelHeight); + + glyphJson["s0"] = g.s0; + glyphJson["t0"] = g.t0; + glyphJson["s1"] = g.s1; + glyphJson["t1"] = g.t1; + + jRoot["glyphs"].push_back(glyphJson); + } + } + + auto& stream = *assetFile; + stream << jRoot.dump(4) << std::endl; + } +} // namespace font diff --git a/src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.h b/src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.h new file mode 100644 index 000000000..8ff9a9996 --- /dev/null +++ b/src/ObjWriting/Game/IW3/Font/FontFileDumperIW3.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW3/IW3.h" + +namespace font +{ + class DumperIW3 final : public AbstractAssetDumper + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace font diff --git a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp index 0f3f9e2ef..f4bd87f7a 100644 --- a/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp +++ b/src/ObjWriting/Game/IW3/ObjWriterIW3.cpp @@ -9,6 +9,7 @@ #include "RawFile/RawFileDumperIW3.h" #include "Sound/LoadedSoundDumperIW3.h" #include "StringTable/StringTableDumperIW3.h" +#include "Font/FontFileDumperIW3.h" using namespace IW3; @@ -36,7 +37,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperGfxWorld) // REGISTER_DUMPER(AssetDumperGfxLightDef) - // REGISTER_DUMPER(AssetDumperFont_s) + RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperMenuList) // REGISTER_DUMPER(AssetDumpermenuDef_t) RegisterAssetDumper(std::make_unique()); From 49a31fa8243a4e086038ba9e42fa12b2b69cb823 Mon Sep 17 00:00:00 2001 From: MrIkso Date: Tue, 14 Apr 2026 02:42:54 +0300 Subject: [PATCH 2/2] feat: add iw4, iw5, t5 font dumper and loader --- docs/SupportedAssetTypes.md | 8 +- ...oaderFontW3.cpp => AssetLoaderFontIW3.cpp} | 2 +- ...setLoaderFontW3.h => AssetLoaderFontIW3.h} | 0 src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp | 2 +- .../Game/IW4/Font/AssetLoaderFontIW4.cpp | 134 ++++++++++++++++++ .../Game/IW4/Font/AssetLoaderFontIW4.h | 13 ++ src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp | 3 +- .../Game/IW5/Font/AssetLoaderFontIW5.cpp | 134 ++++++++++++++++++ .../Game/IW5/Font/AssetLoaderFontIW5.h | 13 ++ src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp | 3 +- .../Game/T5/Font/AssetLoaderFontT5.cpp | 134 ++++++++++++++++++ .../Game/T5/Font/AssetLoaderFontT5.h | 13 ++ src/ObjLoading/Game/T5/ObjLoaderT5.cpp | 3 +- .../Game/IW4/Font/FontFileDumperIW4.cpp | 70 +++++++++ .../Game/IW4/Font/FontFileDumperIW4.h | 13 ++ src/ObjWriting/Game/IW4/ObjWriterIW4.cpp | 3 +- .../Game/IW5/Font/FontFileDumperIW5.cpp | 70 +++++++++ .../Game/IW5/Font/FontFileDumperIW5.h | 13 ++ src/ObjWriting/Game/IW5/ObjWriterIW5.cpp | 3 +- .../Game/T5/Font/FontFileDumperT5.cpp | 70 +++++++++ .../Game/T5/Font/FontFileDumperT5.h | 13 ++ src/ObjWriting/Game/T5/ObjWriterT5.cpp | 3 +- 22 files changed, 708 insertions(+), 12 deletions(-) rename src/ObjLoading/Game/IW3/Font/{AssetLoaderFontW3.cpp => AssetLoaderFontIW3.cpp} (99%) rename src/ObjLoading/Game/IW3/Font/{AssetLoaderFontW3.h => AssetLoaderFontIW3.h} (100%) create mode 100644 src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.cpp create mode 100644 src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.h create mode 100644 src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.cpp create mode 100644 src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.h create mode 100644 src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.cpp create mode 100644 src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.h create mode 100644 src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.cpp create mode 100644 src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.h create mode 100644 src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.cpp create mode 100644 src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.h create mode 100644 src/ObjWriting/Game/T5/Font/FontFileDumperT5.cpp create mode 100644 src/ObjWriting/Game/T5/Font/FontFileDumperT5.h diff --git a/docs/SupportedAssetTypes.md b/docs/SupportedAssetTypes.md index 0918fd447..a09f0b5ae 100644 --- a/docs/SupportedAssetTypes.md +++ b/docs/SupportedAssetTypes.md @@ -27,7 +27,7 @@ The following section specify which assets are supported to be dumped to disk (u | MapEnts | ✅ | ❌ | | | GfxWorld | ❌ | ❌ | | | GfxLightDef | ❌ | ❌ | | -| Font_s | ❌ | ❌ | | +| Font_s | ✅ | ✅ | | | MenuList | ❌ | ❌ | | | menuDef_t | ❌ | ❌ | | | LocalizeEntry | ✅ | ✅ | | @@ -62,7 +62,7 @@ The following section specify which assets are supported to be dumped to disk (u | FxWorld | ❌ | ❌ | | | GfxWorld | ❌ | ❌ | | | GfxLightDef | ✅ | ✅ | | -| Font_s | ❌ | ❌ | | +| Font_s | ✅ | ✅ | | | MenuList | ✅ | ✅ | The output is decompiled. The result will not be the same as the input. | | menuDef_t | ✅ | ✅ | See menulist. | | LocalizeEntry | ✅ | ✅ | | @@ -104,7 +104,7 @@ The following section specify which assets are supported to be dumped to disk (u | FxWorld | ❌ | ❌ | | | GfxWorld | ❌ | ❌ | | | GfxLightDef | ❌ | ❌ | | -| Font_s | ❌ | ❌ | | +| Font_s | ✅ | ✅ | | | MenuList | ✅ | ✅ | The output is decompiled. The result will not be the same as the input. | | menuDef_t | ✅ | ✅ | See menulist. | | LocalizeEntry | ✅ | ✅ | | @@ -143,7 +143,7 @@ The following section specify which assets are supported to be dumped to disk (u | MapEnts | ❌ | ❌ | | | GfxWorld | ❌ | ❌ | | | GfxLightDef | ❌ | ❌ | | -| Font_s | ❌ | ❌ | | +| Font_s | ✅ | ✅ | | | MenuList | ❌ | ❌ | | | menuDef_t | ❌ | ❌ | | | LocalizeEntry | ✅ | ✅ | | diff --git a/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontIW3.cpp similarity index 99% rename from src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp rename to src/ObjLoading/Game/IW3/Font/AssetLoaderFontIW3.cpp index c13f08ee3..2dc35c6f8 100644 --- a/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.cpp +++ b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontIW3.cpp @@ -1,4 +1,4 @@ -#include "AssetLoaderFontW3.h" +#include "AssetLoaderFontIW3.h" #include "Game/IW3/IW3.h" #include "Utils/Logging/Log.h" diff --git a/src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.h b/src/ObjLoading/Game/IW3/Font/AssetLoaderFontIW3.h similarity index 100% rename from src/ObjLoading/Game/IW3/Font/AssetLoaderFontW3.h rename to src/ObjLoading/Game/IW3/Font/AssetLoaderFontIW3.h diff --git a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp index f6fda8401..119c62a0b 100644 --- a/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp +++ b/src/ObjLoading/Game/IW3/ObjLoaderIW3.cpp @@ -14,7 +14,7 @@ #include "ObjLoading.h" #include "RawFile/AssetLoaderRawFileIW3.h" #include "StringTable/AssetLoaderStringTableIW3.h" -#include "Font/AssetLoaderFontW3.h" +#include "Font/AssetLoaderFontIW3.h" #include diff --git a/src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.cpp b/src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.cpp new file mode 100644 index 000000000..23dd276d5 --- /dev/null +++ b/src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.cpp @@ -0,0 +1,134 @@ +#include "AssetLoaderFontIW4.h" +#include "Game/IW4/IW4.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include + +using namespace IW4; + +namespace +{ + class FontLoader final : public AssetCreator + { + public: + FontLoader(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(std::format("{}.json", assetName)); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + auto* font = m_memory.Alloc(); + std::memset(font, 0, sizeof(Font_s)); + + AssetRegistration registration(assetName, font); + + if (!LoadFromJson(*file.m_stream, *font, context, registration)) + { + con::error("Failed to load font \"{}\"", assetName); + return AssetCreationResult::Failure(); + } + + return AssetCreationResult::Success(context.AddAsset(std::move(registration))); + } + + private: + bool LoadFromJson(std::istream& jsonStream, Font_s& font, AssetCreationContext& context, AssetRegistration& registration) + { + try + { + const auto jRoot = nlohmann::json::parse(jsonStream); + + std::string type; + jRoot.at("_type").get_to(type); + + if (type != "font") + { + con::error("Tried to load font but did not find expected type 'font'"); + return false; + } + + font.pixelHeight = jRoot.value("pixelHeight", 0); + font.glyphCount = jRoot.value("glyphCount", 0); + + std::string fontName = jRoot.value("name", ""); + font.fontName = m_memory.Dup(fontName.c_str()); + + if (jRoot.contains("material") && !jRoot["material"].is_null()) + { + std::string matName = jRoot["material"]; + auto* matInfo = context.LoadDependency(matName); + if (!matInfo) + { + con::error("Font \"{}\" missing dependency material \"{}\"", font.fontName, matName); + return false; + } + registration.AddDependency(matInfo); + font.material = matInfo->Asset(); + } + + if (jRoot.contains("glowMaterial") && !jRoot["glowMaterial"].is_null()) + { + std::string glowMatName = jRoot["glowMaterial"]; + auto* glowMatInfo = context.LoadDependency(glowMatName); + if (glowMatInfo) + { + registration.AddDependency(glowMatInfo); + font.glowMaterial = glowMatInfo->Asset(); + } + } + + if (font.glyphCount > 0 && jRoot.contains("glyphs")) + { + const auto& jGlyphs = jRoot["glyphs"]; + font.glyphs = m_memory.Alloc(font.glyphCount); + + for (int i = 0; i < font.glyphCount; ++i) + { + const auto& jG = jGlyphs[i]; + auto& g = font.glyphs[i]; + + g.letter = jG.value("letter", 0); + + g.x0 = static_cast(jG.value("x0", 0)); + g.y0 = static_cast(jG.value("y0", 0)); + g.dx = static_cast(jG.value("dx", 0)); + g.pixelWidth = static_cast(jG.value("width", 0)); + g.pixelHeight = static_cast(jG.value("height", 0)); + + g.s0 = jG.value("s0", 0.0f); + g.t0 = jG.value("t0", 0.0f); + g.s1 = jG.value("s1", 0.0f); + g.t1 = jG.value("t1", 0.0f); + } + } + + return true; + } + catch (const nlohmann::json::exception& e) + { + con::error("Failed to parse json of font: {}", e.what()); + } + + return false; + } + + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace font +{ + std::unique_ptr> CreateLoaderIW4(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace font diff --git a/src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.h b/src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.h new file mode 100644 index 000000000..8b42168b1 --- /dev/null +++ b/src/ObjLoading/Game/IW4/Font/AssetLoaderFontIW4.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/IW4/IW4.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace font +{ + std::unique_ptr> CreateLoaderIW4(MemoryManager& memory, ISearchPath& searchPath); +} // namespace font diff --git a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp index 1bc7f9130..d64f96eb5 100644 --- a/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp +++ b/src/ObjLoading/Game/IW4/ObjLoaderIW4.cpp @@ -23,6 +23,7 @@ #include "StructuredDataDef/LoaderStructuredDataDefIW4.h" #include "Weapon/GdtLoaderWeaponIW4.h" #include "Weapon/RawLoaderWeaponIW4.h" +#include "Font/AssetLoaderFontIW4.h" #include @@ -145,7 +146,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(light_def::CreateLoaderIW4(memory, searchPath)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderIW4(memory, searchPath)); collection.AddAssetCreator(menu::CreateMenuListLoaderIW4(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderIW4(memory, searchPath, zone)); diff --git a/src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.cpp b/src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.cpp new file mode 100644 index 000000000..427ba3850 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.cpp @@ -0,0 +1,134 @@ +#include "AssetLoaderFontIW5.h" +#include "Game/IW5/IW5.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include + +using namespace IW5; + +namespace +{ + class FontLoader final : public AssetCreator + { + public: + FontLoader(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(std::format("{}.json", assetName)); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + auto* font = m_memory.Alloc(); + std::memset(font, 0, sizeof(Font_s)); + + AssetRegistration registration(assetName, font); + + if (!LoadFromJson(*file.m_stream, *font, context, registration)) + { + con::error("Failed to load font \"{}\"", assetName); + return AssetCreationResult::Failure(); + } + + return AssetCreationResult::Success(context.AddAsset(std::move(registration))); + } + + private: + bool LoadFromJson(std::istream& jsonStream, Font_s& font, AssetCreationContext& context, AssetRegistration& registration) + { + try + { + const auto jRoot = nlohmann::json::parse(jsonStream); + + std::string type; + jRoot.at("_type").get_to(type); + + if (type != "font") + { + con::error("Tried to load font but did not find expected type 'font'"); + return false; + } + + font.pixelHeight = jRoot.value("pixelHeight", 0); + font.glyphCount = jRoot.value("glyphCount", 0); + + std::string fontName = jRoot.value("name", ""); + font.fontName = m_memory.Dup(fontName.c_str()); + + if (jRoot.contains("material") && !jRoot["material"].is_null()) + { + std::string matName = jRoot["material"]; + auto* matInfo = context.LoadDependency(matName); + if (!matInfo) + { + con::error("Font \"{}\" missing dependency material \"{}\"", font.fontName, matName); + return false; + } + registration.AddDependency(matInfo); + font.material = matInfo->Asset(); + } + + if (jRoot.contains("glowMaterial") && !jRoot["glowMaterial"].is_null()) + { + std::string glowMatName = jRoot["glowMaterial"]; + auto* glowMatInfo = context.LoadDependency(glowMatName); + if (glowMatInfo) + { + registration.AddDependency(glowMatInfo); + font.glowMaterial = glowMatInfo->Asset(); + } + } + + if (font.glyphCount > 0 && jRoot.contains("glyphs")) + { + const auto& jGlyphs = jRoot["glyphs"]; + font.glyphs = m_memory.Alloc(font.glyphCount); + + for (int i = 0; i < font.glyphCount; ++i) + { + const auto& jG = jGlyphs[i]; + auto& g = font.glyphs[i]; + + g.letter = jG.value("letter", 0); + + g.x0 = static_cast(jG.value("x0", 0)); + g.y0 = static_cast(jG.value("y0", 0)); + g.dx = static_cast(jG.value("dx", 0)); + g.pixelWidth = static_cast(jG.value("width", 0)); + g.pixelHeight = static_cast(jG.value("height", 0)); + + g.s0 = jG.value("s0", 0.0f); + g.t0 = jG.value("t0", 0.0f); + g.s1 = jG.value("s1", 0.0f); + g.t1 = jG.value("t1", 0.0f); + } + } + + return true; + } + catch (const nlohmann::json::exception& e) + { + con::error("Failed to parse json of font: {}", e.what()); + } + + return false; + } + + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace font +{ + std::unique_ptr> CreateLoaderIW5(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace font diff --git a/src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.h b/src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.h new file mode 100644 index 000000000..4e2fff624 --- /dev/null +++ b/src/ObjLoading/Game/IW5/Font/AssetLoaderFontIW5.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 font +{ + std::unique_ptr> CreateLoaderIW5(MemoryManager& memory, ISearchPath& searchPath); +} // namespace font diff --git a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp index 59a936c82..8bebe2810 100644 --- a/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp +++ b/src/ObjLoading/Game/IW5/ObjLoaderIW5.cpp @@ -20,6 +20,7 @@ #include "Weapon/GdtLoaderWeaponIW5.h" #include "Weapon/LoaderAttachmentIW5.h" #include "Weapon/RawLoaderWeaponIW5.h" +#include "Font/AssetLoaderFontIW5.h" #include @@ -148,7 +149,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderIW5(memory, searchPath)); collection.AddAssetCreator(menu::CreateMenuListLoaderIW5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderIW5(memory, searchPath, zone)); diff --git a/src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.cpp b/src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.cpp new file mode 100644 index 000000000..2ec1c2e27 --- /dev/null +++ b/src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.cpp @@ -0,0 +1,134 @@ +#include "AssetLoaderFontT5.h" +#include "Game/T5/T5.h" +#include "Utils/Logging/Log.h" + +#include +#include +#include + +using namespace T5; + +namespace +{ + class FontLoader final : public AssetCreator + { + public: + FontLoader(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(std::format("{}.json", assetName)); + if (!file.IsOpen()) + return AssetCreationResult::NoAction(); + + auto* font = m_memory.Alloc(); + std::memset(font, 0, sizeof(Font_s)); + + AssetRegistration registration(assetName, font); + + if (!LoadFromJson(*file.m_stream, *font, context, registration)) + { + con::error("Failed to load font \"{}\"", assetName); + return AssetCreationResult::Failure(); + } + + return AssetCreationResult::Success(context.AddAsset(std::move(registration))); + } + + private: + bool LoadFromJson(std::istream& jsonStream, Font_s& font, AssetCreationContext& context, AssetRegistration& registration) + { + try + { + const auto jRoot = nlohmann::json::parse(jsonStream); + + std::string type; + jRoot.at("_type").get_to(type); + + if (type != "font") + { + con::error("Tried to load font but did not find expected type 'font'"); + return false; + } + + font.pixelHeight = jRoot.value("pixelHeight", 0); + font.glyphCount = jRoot.value("glyphCount", 0); + + std::string fontName = jRoot.value("name", ""); + font.fontName = m_memory.Dup(fontName.c_str()); + + if (jRoot.contains("material") && !jRoot["material"].is_null()) + { + std::string matName = jRoot["material"]; + auto* matInfo = context.LoadDependency(matName); + if (!matInfo) + { + con::error("Font \"{}\" missing dependency material \"{}\"", font.fontName, matName); + return false; + } + registration.AddDependency(matInfo); + font.material = matInfo->Asset(); + } + + if (jRoot.contains("glowMaterial") && !jRoot["glowMaterial"].is_null()) + { + std::string glowMatName = jRoot["glowMaterial"]; + auto* glowMatInfo = context.LoadDependency(glowMatName); + if (glowMatInfo) + { + registration.AddDependency(glowMatInfo); + font.glowMaterial = glowMatInfo->Asset(); + } + } + + if (font.glyphCount > 0 && jRoot.contains("glyphs")) + { + const auto& jGlyphs = jRoot["glyphs"]; + font.glyphs = m_memory.Alloc(font.glyphCount); + + for (int i = 0; i < font.glyphCount; ++i) + { + const auto& jG = jGlyphs[i]; + auto& g = font.glyphs[i]; + + g.letter = jG.value("letter", 0); + + g.x0 = static_cast(jG.value("x0", 0)); + g.y0 = static_cast(jG.value("y0", 0)); + g.dx = static_cast(jG.value("dx", 0)); + g.pixelWidth = static_cast(jG.value("width", 0)); + g.pixelHeight = static_cast(jG.value("height", 0)); + + g.s0 = jG.value("s0", 0.0f); + g.t0 = jG.value("t0", 0.0f); + g.s1 = jG.value("s1", 0.0f); + g.t1 = jG.value("t1", 0.0f); + } + } + + return true; + } + catch (const nlohmann::json::exception& e) + { + con::error("Failed to parse json of font: {}", e.what()); + } + + return false; + } + + MemoryManager& m_memory; + ISearchPath& m_search_path; + }; +} // namespace + +namespace font +{ + std::unique_ptr> CreateLoaderT5(MemoryManager& memory, ISearchPath& searchPath) + { + return std::make_unique(memory, searchPath); + } +} // namespace font diff --git a/src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.h b/src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.h new file mode 100644 index 000000000..391589e2b --- /dev/null +++ b/src/ObjLoading/Game/T5/Font/AssetLoaderFontT5.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/T5/T5.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace font +{ + std::unique_ptr> CreateLoaderT5(MemoryManager& memory, ISearchPath& searchPath); +} // namespace font diff --git a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp index b50a9a53e..86fa134d7 100644 --- a/src/ObjLoading/Game/T5/ObjLoaderT5.cpp +++ b/src/ObjLoading/Game/T5/ObjLoaderT5.cpp @@ -14,6 +14,7 @@ #include "ObjLoading.h" #include "RawFile/LoaderRawFileT5.h" #include "StringTable/LoaderStringTableT5.h" +#include "Font/AssetLoaderFontT5.h" #include @@ -123,7 +124,7 @@ namespace // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); - // collection.AddAssetCreator(std::make_unique(memory)); + collection.AddAssetCreator(font::CreateLoaderT5(memory, searchPath)); // collection.AddAssetCreator(std::make_unique(memory)); // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(localize::CreateLoaderT5(memory, searchPath, zone)); diff --git a/src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.cpp b/src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.cpp new file mode 100644 index 000000000..cd369d13c --- /dev/null +++ b/src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.cpp @@ -0,0 +1,70 @@ +#include "FontFileDumperIW4.h" +#include + +using ordered_json = nlohmann::ordered_json; +using namespace nlohmann; +using namespace IW4; + +namespace font +{ + void DumperIW4::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) + { + const auto* fontFile = asset.Asset(); + const auto assetFile = context.OpenAssetFile(std::format("{}.json", asset.m_name)); + + if (!assetFile) + return; + + ordered_json jRoot; + jRoot["$schema"] = "http://openassettools.dev/schema/font.v1.json"; + jRoot["_type"] = "font"; + jRoot["_game"] = "iw4"; + + jRoot["name"] = fontFile->fontName ? fontFile->fontName : ""; + jRoot["pixelHeight"] = fontFile->pixelHeight; + jRoot["glyphCount"] = fontFile->glyphCount; + + if (fontFile->material && fontFile->material->info.name) + { + jRoot["material"] = fontFile->material->info.name; + } + else + { + jRoot["material"] = nullptr; + } + + if (fontFile->glowMaterial && fontFile->glowMaterial->info.name) + { + jRoot["glowMaterial"] = fontFile->glowMaterial->info.name; + } + + jRoot["glyphs"] = json::array(); + + if (fontFile->glyphs && fontFile->glyphCount > 0) + { + for (int i = 0; i < fontFile->glyphCount; ++i) + { + const auto& g = fontFile->glyphs[i]; + + ordered_json glyphJson; + glyphJson["letter"] = g.letter; + + glyphJson["x0"] = static_cast(g.x0); + glyphJson["y0"] = static_cast(g.y0); + glyphJson["dx"] = static_cast(g.dx); + glyphJson["width"] = static_cast(g.pixelWidth); + glyphJson["height"] = static_cast(g.pixelHeight); + + glyphJson["s0"] = g.s0; + glyphJson["t0"] = g.t0; + glyphJson["s1"] = g.s1; + glyphJson["t1"] = g.t1; + + jRoot["glyphs"].push_back(glyphJson); + } + } + + auto& stream = *assetFile; + stream << jRoot.dump(4) << std::endl; + } +} // namespace font diff --git a/src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.h b/src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.h new file mode 100644 index 000000000..4e6b4442e --- /dev/null +++ b/src/ObjWriting/Game/IW4/Font/FontFileDumperIW4.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW4/IW4.h" + +namespace font +{ + class DumperIW4 final : public AbstractAssetDumper + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace font diff --git a/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp b/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp index 6004d5eaf..218744a45 100644 --- a/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp +++ b/src/ObjWriting/Game/IW4/ObjWriterIW4.cpp @@ -23,6 +23,7 @@ #include "Tracer/TracerDumperIW4.h" #include "Vehicle/VehicleDumperIW4.h" #include "Weapon/WeaponDumperIW4.h" +#include "Font/FontFileDumperIW4.h" using namespace IW4; @@ -57,7 +58,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) // REGISTER_DUMPER(AssetDumperFxWorld) // REGISTER_DUMPER(AssetDumperGfxWorld) RegisterAssetDumper(std::make_unique()); - // REGISTER_DUMPER(AssetDumperFont_s) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.cpp b/src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.cpp new file mode 100644 index 000000000..ce0d2127e --- /dev/null +++ b/src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.cpp @@ -0,0 +1,70 @@ +#include "FontFileDumperIW5.h" +#include + +using ordered_json = nlohmann::ordered_json; +using namespace nlohmann; +using namespace IW5; + +namespace font +{ + void DumperIW5::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) + { + const auto* fontFile = asset.Asset(); + const auto assetFile = context.OpenAssetFile(std::format("{}.json", asset.m_name)); + + if (!assetFile) + return; + + ordered_json jRoot; + jRoot["$schema"] = "http://openassettools.dev/schema/font.v1.json"; + jRoot["_type"] = "font"; + jRoot["_game"] = "iw5"; + + jRoot["name"] = fontFile->fontName ? fontFile->fontName : ""; + jRoot["pixelHeight"] = fontFile->pixelHeight; + jRoot["glyphCount"] = fontFile->glyphCount; + + if (fontFile->material && fontFile->material->info.name) + { + jRoot["material"] = fontFile->material->info.name; + } + else + { + jRoot["material"] = nullptr; + } + + if (fontFile->glowMaterial && fontFile->glowMaterial->info.name) + { + jRoot["glowMaterial"] = fontFile->glowMaterial->info.name; + } + + jRoot["glyphs"] = json::array(); + + if (fontFile->glyphs && fontFile->glyphCount > 0) + { + for (int i = 0; i < fontFile->glyphCount; ++i) + { + const auto& g = fontFile->glyphs[i]; + + ordered_json glyphJson; + glyphJson["letter"] = g.letter; + + glyphJson["x0"] = static_cast(g.x0); + glyphJson["y0"] = static_cast(g.y0); + glyphJson["dx"] = static_cast(g.dx); + glyphJson["width"] = static_cast(g.pixelWidth); + glyphJson["height"] = static_cast(g.pixelHeight); + + glyphJson["s0"] = g.s0; + glyphJson["t0"] = g.t0; + glyphJson["s1"] = g.s1; + glyphJson["t1"] = g.t1; + + jRoot["glyphs"].push_back(glyphJson); + } + } + + auto& stream = *assetFile; + stream << jRoot.dump(4) << std::endl; + } +} // namespace font diff --git a/src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.h b/src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.h new file mode 100644 index 000000000..d7afd0afa --- /dev/null +++ b/src/ObjWriting/Game/IW5/Font/FontFileDumperIW5.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/IW5/IW5.h" + +namespace font +{ + class DumperIW5 final : public AbstractAssetDumper + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace font diff --git a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp index 74a3bcdd5..62bac6493 100644 --- a/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp +++ b/src/ObjWriting/Game/IW5/ObjWriterIW5.cpp @@ -17,6 +17,7 @@ #include "StringTable/StringTableDumperIW5.h" #include "Weapon/AttachmentJsonDumperIW5.h" #include "Weapon/WeaponDumperIW5.h" +#include "Font/FontFileDumperIW5.h" using namespace IW5; @@ -50,7 +51,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) // REGISTER_DUMPER(AssetDumperFxWorld) // REGISTER_DUMPER(AssetDumperGfxWorld) // REGISTER_DUMPER(AssetDumperGfxLightDef) - // REGISTER_DUMPER(AssetDumperFont_s) + RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); RegisterAssetDumper(std::make_unique()); diff --git a/src/ObjWriting/Game/T5/Font/FontFileDumperT5.cpp b/src/ObjWriting/Game/T5/Font/FontFileDumperT5.cpp new file mode 100644 index 000000000..3caa30382 --- /dev/null +++ b/src/ObjWriting/Game/T5/Font/FontFileDumperT5.cpp @@ -0,0 +1,70 @@ +#include "FontFileDumperT5.h" +#include + +using ordered_json = nlohmann::ordered_json; +using namespace nlohmann; +using namespace T5; + +namespace font +{ + void DumperT5::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) + { + const auto* fontFile = asset.Asset(); + const auto assetFile = context.OpenAssetFile(std::format("{}.json", asset.m_name)); + + if (!assetFile) + return; + + ordered_json jRoot; + jRoot["$schema"] = "http://openassettools.dev/schema/font.v1.json"; + jRoot["_type"] = "font"; + jRoot["_game"] = "t5"; + + jRoot["name"] = fontFile->fontName ? fontFile->fontName : ""; + jRoot["pixelHeight"] = fontFile->pixelHeight; + jRoot["glyphCount"] = fontFile->glyphCount; + + if (fontFile->material && fontFile->material->info.name) + { + jRoot["material"] = fontFile->material->info.name; + } + else + { + jRoot["material"] = nullptr; + } + + if (fontFile->glowMaterial && fontFile->glowMaterial->info.name) + { + jRoot["glowMaterial"] = fontFile->glowMaterial->info.name; + } + + jRoot["glyphs"] = json::array(); + + if (fontFile->glyphs && fontFile->glyphCount > 0) + { + for (int i = 0; i < fontFile->glyphCount; ++i) + { + const auto& g = fontFile->glyphs[i]; + + ordered_json glyphJson; + glyphJson["letter"] = g.letter; + + glyphJson["x0"] = static_cast(g.x0); + glyphJson["y0"] = static_cast(g.y0); + glyphJson["dx"] = static_cast(g.dx); + glyphJson["width"] = static_cast(g.pixelWidth); + glyphJson["height"] = static_cast(g.pixelHeight); + + glyphJson["s0"] = g.s0; + glyphJson["t0"] = g.t0; + glyphJson["s1"] = g.s1; + glyphJson["t1"] = g.t1; + + jRoot["glyphs"].push_back(glyphJson); + } + } + + auto& stream = *assetFile; + stream << jRoot.dump(4) << std::endl; + } +} // namespace font diff --git a/src/ObjWriting/Game/T5/Font/FontFileDumperT5.h b/src/ObjWriting/Game/T5/Font/FontFileDumperT5.h new file mode 100644 index 000000000..21b2ab191 --- /dev/null +++ b/src/ObjWriting/Game/T5/Font/FontFileDumperT5.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Dumping/AbstractAssetDumper.h" +#include "Game/T5/T5.h" + +namespace font +{ + class DumperT5 final : public AbstractAssetDumper + { + protected: + void DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) override; + }; +} // namespace font diff --git a/src/ObjWriting/Game/T5/ObjWriterT5.cpp b/src/ObjWriting/Game/T5/ObjWriterT5.cpp index 19c5e2770..aa2c97bbc 100644 --- a/src/ObjWriting/Game/T5/ObjWriterT5.cpp +++ b/src/ObjWriting/Game/T5/ObjWriterT5.cpp @@ -7,6 +7,7 @@ #include "Localize/LocalizeDumperT5.h" #include "RawFile/RawFileDumperT5.h" #include "StringTable/StringTableDumperT5.h" +#include "Font/FontFileDumperT5.h" using namespace T5; @@ -35,7 +36,7 @@ void ObjWriter::RegisterAssetDumpers(AssetDumpingContext& context) // REGISTER_DUMPER(AssetDumperMapEnts, m_map_ents) // REGISTER_DUMPER(AssetDumperGfxWorld, m_gfx_world) // REGISTER_DUMPER(AssetDumperGfxLightDef, m_gfx_light_def) - // REGISTER_DUMPER(AssetDumperFont, m_font) + RegisterAssetDumper(std::make_unique()); // REGISTER_DUMPER(AssetDumperMenuList, m_menu_list) // REGISTER_DUMPER(AssetDumperMenuDef, m_menu_def) RegisterAssetDumper(std::make_unique());