From 5a816f104c5f04c42ce69c3fb32cba6366cec7ef Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 10:50:17 -0700 Subject: [PATCH] feat(editor): add --info-extract CLI for extraction coverage report Walks an extracted asset tree and reports per-format counts plus how many proprietary files have a wowee open-format sidecar. Lets users (or CI) see at a glance whether asset_extract was run with --emit-open and how complete the open-format coverage is: BLP textures : 12340 (12340 PNG sidecar = 100.0% open) DBC tables : 240 (240 JSON sidecar = 100.0% open) M2 models : 8500 (0 WOM sidecar = 0.0% open) ... overall open-format coverage: 41.2% (run `asset_extract --emit-open` to fill missing sidecars) Skips _NNN group sub-files when counting WMOs (only the root WMO ships with a WOB sidecar). The headless CLI is now at 22 commands. --- tools/editor/main.cpp | 69 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index ec7bceae..61b28aa3 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -41,6 +41,7 @@ static void printUsage(const char* argv0) { std::printf(" --info-wob Print WOB building metadata (groups, portals, doodads) and exit\n"); std::printf(" --info-woc Print WOC collision metadata (triangle counts, bounds) and exit\n"); std::printf(" --info-wot Print WOT/WHM terrain metadata (tile, chunks, height range) and exit\n"); + std::printf(" --info-extract Walk extracted asset tree and report open-format coverage and exit\n"); std::printf(" --info-zone Print zone.json fields (manifest, tiles, audio, flags) and exit\n"); std::printf(" --info-creatures

Print creatures.json summary (counts, behaviors) and exit\n"); std::printf(" --info-objects

Print objects.json summary (counts, types, scale range) and exit\n"); @@ -65,7 +66,8 @@ int main(int argc, char* argv[]) { static const char* kArgRequired[] = { "--data", "--info", "--info-wob", "--info-woc", "--info-wot", "--info-creatures", "--info-objects", "--info-quests", - "--info-zone", "--info-wcp", "--list-wcp", "--unpack-wcp", "--pack-wcp", + "--info-extract", "--info-zone", "--info-wcp", "--list-wcp", + "--unpack-wcp", "--pack-wcp", "--validate", "--zone-summary", "--scaffold-zone", "--build-woc", "--regen-collision", "--fix-zone", "--export-png", @@ -210,6 +212,71 @@ int main(int argc, char* argv[]) { std::printf(" scale range : [%.2f, %.2f]\n", minScale, maxScale); } return 0; + } else if (std::strcmp(argv[i], "--info-extract") == 0 && i + 1 < argc) { + // Walk an extracted-asset directory and report counts by + // extension + open-format coverage. Useful for seeing whether + // a user ran asset_extract with --emit-open. + std::string dataDir = argv[++i]; + namespace fs = std::filesystem; + if (!fs::exists(dataDir)) { + std::fprintf(stderr, "info-extract: %s does not exist\n", dataDir.c_str()); + return 1; + } + // Per-format counts. Pair proprietary with open-format sidecar + // so the report can show coverage percentages. + uint64_t blpCount = 0, pngSidecar = 0; + uint64_t dbcCount = 0, jsonSidecar = 0; + uint64_t m2Count = 0, womSidecar = 0; + uint64_t wmoCount = 0, wobSidecar = 0; + uint64_t adtCount = 0, whmSidecar = 0; + uint64_t totalBytes = 0; + for (auto& entry : fs::recursive_directory_iterator(dataDir)) { + if (!entry.is_regular_file()) continue; + totalBytes += entry.file_size(); + std::string ext = entry.path().extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::string base = entry.path().string(); + if (base.size() > ext.size()) base = base.substr(0, base.size() - ext.size()); + auto sidecarExists = [&](const char* sidecarExt) { + return fs::exists(base + sidecarExt); + }; + if (ext == ".blp") { blpCount++; if (sidecarExists(".png")) pngSidecar++; } + else if (ext == ".dbc") { dbcCount++; if (sidecarExists(".json")) jsonSidecar++; } + else if (ext == ".m2") { m2Count++; if (sidecarExists(".wom")) womSidecar++; } + else if (ext == ".wmo") { + // Skip group sub-files (_NNN.wmo) so coverage + // counts only root WMOs against the WOB output. + std::string fname = entry.path().filename().string(); + auto under = fname.rfind('_'); + bool isGroup = (under != std::string::npos && + fname.size() - under == 8); + if (!isGroup) { + wmoCount++; if (sidecarExists(".wob")) wobSidecar++; + } + } + else if (ext == ".adt") { adtCount++; if (sidecarExists(".whm")) whmSidecar++; } + } + auto pct = [](uint64_t x, uint64_t total) { + return total == 0 ? 0.0 : (100.0 * x) / total; + }; + std::printf("Extracted asset tree: %s\n", dataDir.c_str()); + std::printf(" total bytes : %.2f GB\n", totalBytes / (1024.0 * 1024.0 * 1024.0)); + std::printf(" BLP textures : %lu (%lu PNG sidecar = %.1f%% open)\n", + blpCount, pngSidecar, pct(pngSidecar, blpCount)); + std::printf(" DBC tables : %lu (%lu JSON sidecar = %.1f%% open)\n", + dbcCount, jsonSidecar, pct(jsonSidecar, dbcCount)); + std::printf(" M2 models : %lu (%lu WOM sidecar = %.1f%% open)\n", + m2Count, womSidecar, pct(womSidecar, m2Count)); + std::printf(" WMO buildings: %lu (%lu WOB sidecar = %.1f%% open)\n", + wmoCount, wobSidecar, pct(wobSidecar, wmoCount)); + std::printf(" ADT terrain : %lu (%lu WHM sidecar = %.1f%% open)\n", + adtCount, whmSidecar, pct(whmSidecar, adtCount)); + uint64_t openTotal = pngSidecar + jsonSidecar + womSidecar + wobSidecar + whmSidecar; + uint64_t propTotal = blpCount + dbcCount + m2Count + wmoCount + adtCount; + std::printf(" overall open-format coverage: %.1f%%\n", pct(openTotal, propTotal)); + std::printf(" (run `asset_extract --emit-open` to fill missing sidecars)\n"); + return 0; } else if (std::strcmp(argv[i], "--info-zone") == 0 && i + 1 < argc) { // Parse a zone.json and print every manifest field. Useful when // diffing two zones or auditing the audio/flag setup before