From a4e135f2e09ee9ed2493d4aa19a44a3dd4430098 Mon Sep 17 00:00:00 2001 From: z3nnik Date: Tue, 7 Jul 2026 16:32:30 +0300 Subject: [PATCH] fix(module_metadata): guard orphan cleanup when output dir is missing On Linux, Dota linuxsteamrt64 libraries stub ExtractModuleMetadata (always returns null), so no kv3 files are written. Orphan cleanup then iterated a non-existent module_metadata/ directory and aborted the dump early, skipping .stringsignore and the final success log. - Guard directory_iterator when outputPath does not exist - Skip modules with missing/null ExtractModuleMetadata gracefully - Log loaded module count and per-module outcomes Ref: ValveResourceFormat/DumpSource2#10 --- src/main/appframework.cpp | 1 + .../module_metadata/module_metadata.cpp | 128 +++++++++++++----- 2 files changed, 97 insertions(+), 32 deletions(-) diff --git a/src/main/appframework.cpp b/src/main/appframework.cpp index 78ac6c3..b341d86 100644 --- a/src/main/appframework.cpp +++ b/src/main/appframework.cpp @@ -185,5 +185,6 @@ void InitializeAppSystems() } Modules::allModules.emplace_back(std::move(module)); + spdlog::debug("Registered module for metadata dump: {} (total {})", appSystem.moduleName, Modules::allModules.size()); } } \ No newline at end of file diff --git a/src/main/dumpers/module_metadata/module_metadata.cpp b/src/main/dumpers/module_metadata/module_metadata.cpp index 7ead774..15e71b2 100644 --- a/src/main/dumpers/module_metadata/module_metadata.cpp +++ b/src/main/dumpers/module_metadata/module_metadata.cpp @@ -24,71 +24,135 @@ #include "utils/common.h" #include "globalvariables.h" #include "keyvalues3.h" +#include +#include +#include #include namespace Dumpers::ModuleMetadata { -void GetModuleMetadata(const CModule& module, SimpleCUtlString& err, SimpleCUtlString& buf) +namespace { - spdlog::info("Dumping metadata for {}", module.m_pszModule); - - typedef void* (*ExtractModuleMetadata)(SimpleCUtlString& str); - auto extractModuleMetadataFn = module.GetSymbol("ExtractModuleMetadata"); - SimpleCUtlString additional_info; - auto kv3 = extractModuleMetadataFn(additional_info); +typedef void* (*ExtractModuleMetadataFn)(SimpleCUtlString& str); +typedef int (*SaveKV3Text_ToStringFn)(KV3ID_t const&, void* kv3, SimpleCUtlString& err, SimpleCUtlString& str); - typedef int (*SaveKV3Text_ToString)(KV3ID_t const&, void* kv3, SimpleCUtlString& err, SimpleCUtlString& str); +SaveKV3Text_ToStringFn GetSaveKV3TextToString() +{ #ifdef WIN32 - static auto saveKV3Text_ToStringFn = Modules::tier0->GetSymbol("?SaveKV3Text_ToString@@YA_NAEBUKV3ID_t@@PEBVKeyValues3@@PEAVCUtlString@@2I@Z"); + return Modules::tier0->GetSymbol("?SaveKV3Text_ToString@@YA_NAEBUKV3ID_t@@PEBVKeyValues3@@PEAVCUtlString@@2I@Z"); #else - static auto saveKV3Text_ToStringFn = Modules::tier0->GetSymbol("_Z20SaveKV3Text_ToStringRK7KV3ID_tPK10KeyValues3P10CUtlStringS6_j"); + return Modules::tier0->GetSymbol("_Z20SaveKV3Text_ToStringRK7KV3ID_tPK10KeyValues3P10CUtlStringS6_j"); #endif +} + +ExtractModuleMetadataFn TryGetExtractModuleMetadata(const CModule& module) +{ + return reinterpret_cast(dlsym(module.m_hModule, "ExtractModuleMetadata")); +} + +} // namespace + +bool GetModuleMetadata(const CModule& module, SimpleCUtlString& err, SimpleCUtlString& buf) +{ + spdlog::info("Dumping metadata for {}", module.m_pszModule); + + auto extractModuleMetadataFn = TryGetExtractModuleMetadata(module); + if (!extractModuleMetadataFn) + { + spdlog::warn("{} has no ExtractModuleMetadata export ({})", module.m_pszModule, dlerror()); + return false; + } + + SimpleCUtlString additional_info; + void* kv3 = nullptr; + try + { + kv3 = extractModuleMetadataFn(additional_info); + } + catch (...) + { + spdlog::error("{} ExtractModuleMetadata threw an exception", module.m_pszModule); + return false; + } + + if (!kv3) + { + spdlog::warn("{} ExtractModuleMetadata returned null", module.m_pszModule); + return false; + } + + static auto saveKV3Text_ToStringFn = GetSaveKV3TextToString(); + if (!saveKV3Text_ToStringFn) + { + spdlog::error("SaveKV3Text_ToString not found in tier0"); + return false; + } + + const auto saved = saveKV3Text_ToStringFn(g_KV3Encoding_Text, kv3, err, buf); + if (!saved) + { + if (err.Get()) + spdlog::warn("{} SaveKV3Text_ToString failed: {}", module.m_pszModule, err.Get()); + else + spdlog::warn("{} SaveKV3Text_ToString failed", module.m_pszModule); + return false; + } + + if (!buf.Get() || !buf.Get()[0]) + { + spdlog::warn("{} produced empty module metadata buffer", module.m_pszModule); + return false; + } - saveKV3Text_ToStringFn(g_KV3Encoding_Text, kv3, err, buf); + spdlog::info("{} metadata: {} bytes", module.m_pszModule, std::strlen(buf.Get())); if (additional_info.Get()) spdlog::warn("{} has additional_info {}", module.m_pszModule, additional_info.Get()); + + return true; } void Dump() { - spdlog::info("Dumping module metadata"); + spdlog::info("Dumping module metadata ({} loaded module(s))", Modules::allModules.size()); std::unordered_set foundModules; const auto outputPath = Globals::outputPath / "module_metadata"; for (const auto& module : Modules::allModules) { SimpleCUtlString err, buf; - GetModuleMetadata(module, err, buf); - - if (buf.Get()) - { - auto sanitizedModuleName = std::string(module.m_pszModule); - std::replace(sanitizedModuleName.begin(), sanitizedModuleName.end(), '/', '_'); - foundModules.insert(sanitizedModuleName); + if (!GetModuleMetadata(module, err, buf)) + continue; + auto sanitizedModuleName = std::string(module.m_pszModule); + std::replace(sanitizedModuleName.begin(), sanitizedModuleName.end(), '/', '_'); + foundModules.insert(sanitizedModuleName); - if (!std::filesystem::is_directory(outputPath) && !std::filesystem::create_directory(outputPath)) - { - spdlog::error("Failed to create module_metadata directory"); - return; - } - - std::ofstream output((outputPath / sanitizedModuleName).replace_extension(".kv3")); - output << buf.Get() << std::endl; + if (!std::filesystem::is_directory(outputPath) && !std::filesystem::create_directories(outputPath)) + { + spdlog::error("Failed to create module_metadata directory"); + return; } + + std::ofstream output((outputPath / sanitizedModuleName).replace_extension(".kv3")); + output << buf.Get() << std::endl; } - for (const auto& typeScopePath : std::filesystem::directory_iterator(outputPath)) + if (std::filesystem::exists(outputPath)) { - if (foundModules.find(typeScopePath.path().stem().string()) == foundModules.end()) + for (const auto& typeScopePath : std::filesystem::directory_iterator(outputPath)) { - spdlog::info("Removing orphan metadata file {}", typeScopePath.path().generic_string()); - std::filesystem::remove(typeScopePath.path()); + if (foundModules.find(typeScopePath.path().stem().string()) == foundModules.end()) + { + spdlog::info("Removing orphan metadata file {}", typeScopePath.path().generic_string()); + std::filesystem::remove(typeScopePath.path()); + } } } + + spdlog::info("Module metadata dump complete: {} kv3 file(s)", foundModules.size()); } -} // namespace Dumpers::ModuleMetadata \ No newline at end of file +} // namespace Dumpers::ModuleMetadata