From bc9033eb432d6556a980bcb9050aa00b2cbeb8c9 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 19:17:42 -0700 Subject: [PATCH] feat(editor): add --info-pack-tree for WCP directory hierarchy view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tree view of a WCP archive's contents with per-file byte sizes. --list-wcp shows the flat sorted file list; this gives the hierarchical view that's easier to read for archives with subdirectories: wowee_editor --info-pack-tree custom_zones/MyZone.wcp custom_zones/MyZone.wcp (47 files, 2348.21 KB) ├─ Forest_28_30.whm (150540 bytes) ├─ Forest_28_30.wot (26685 bytes) ├─ buildings/ │ ├─ inn.wob (45120 bytes) │ └─ tavern.wob (38104 bytes) ├─ creatures.json (694 bytes) ├─ data/ │ ├─ Spell.json (15032 bytes) │ └─ Item.json (8194 bytes) ├─ objects.json (234 bytes) └─ zone.json (500 bytes) Recursive renderer with UTF-8 box-drawing connectors. Files show their byte size; directories show the subtree subtotal aggregated from children. Children sorted alphabetically (std::map). Pairs with --info-pack-budget (per-extension byte breakdown) and --list-wcp (flat sorted list) — three lenses on the same archive: hierarchy / extension cost / flat search. Verified on a 6-file mvp-zone WCP: tree correctly shows top-level files (no subdirs in mvp-zone output) with byte sizes and total 175 KB summary. --- tools/editor/main.cpp | 83 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index b8c1ab83..2af4cbab 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include "stb_image_write.h" @@ -767,6 +769,8 @@ static void printUsage(const char* argv0) { std::printf(" Print WCP archive metadata (name, files) and exit\n"); std::printf(" --info-pack-budget [--json]\n"); std::printf(" Per-extension byte breakdown of a WCP archive (sized largest-first)\n"); + std::printf(" --info-pack-tree \n"); + std::printf(" Render a tree view of a WCP's directory structure with byte sizes\n"); std::printf(" --list-wcp Print every file inside a WCP archive (sorted by path) and exit\n"); std::printf(" --diff-wcp [--json]\n"); std::printf(" Compare two WCPs file-by-file; exit 0 if identical, 1 otherwise\n"); @@ -820,6 +824,7 @@ int main(int argc, char* argv[]) { "--info-extract", "--info-extract-tree", "--info-extract-budget", "--list-missing-sidecars", "--info-png", "--info-jsondbc", "--info-blp", "--info-pack-budget", + "--info-pack-tree", "--info-m2", "--info-wmo", "--info-adt", "--info-zone", "--info-wcp", "--list-wcp", "--list-creatures", "--list-objects", "--list-quests", @@ -4614,6 +4619,84 @@ int main(int argc, char* argv[]) { cb.second / 1024.0, pct); } return 0; + } else if (std::strcmp(argv[i], "--info-pack-tree") == 0 && i + 1 < argc) { + // Tree view of a WCP's directory layout with per-file byte + // sizes. --list-wcp shows the flat sorted file list; + // --info-pack-tree gives the hierarchical view that's + // easier to read for archives with subdirectories (textures + // under data/, models under buildings/, etc.). + std::string path = argv[++i]; + wowee::editor::ContentPackInfo info; + if (!wowee::editor::ContentPacker::readInfo(path, info)) { + std::fprintf(stderr, + "info-pack-tree: failed to read %s\n", path.c_str()); + return 1; + } + // Build a directory tree from flat file paths. Sub-tree + // children are sorted alphabetically with files before dirs + // (by-convention filesystem-tree look). + struct Node { + std::map> children; + bool isFile = false; + uint64_t bytes = 0; + }; + auto root = std::make_shared(); + auto split = [](const std::string& p) { + std::vector parts; + std::string cur; + for (char c : p) { + if (c == '/' || c == '\\') { + if (!cur.empty()) { parts.push_back(cur); cur.clear(); } + } else cur += c; + } + if (!cur.empty()) parts.push_back(cur); + return parts; + }; + uint64_t totalBytes = 0; + for (const auto& f : info.files) { + auto parts = split(f.path); + if (parts.empty()) continue; + Node* cur = root.get(); + for (size_t k = 0; k < parts.size(); ++k) { + auto& child = cur->children[parts[k]]; + if (!child) child = std::make_shared(); + if (k == parts.size() - 1) { + child->isFile = true; + child->bytes = f.size; + } + cur = child.get(); + } + totalBytes += f.size; + } + // Recursive renderer with box-drawing connectors. Aggregates + // child bytes up so directories show their subtotal. + std::function render = + [&](const Node* n, const std::string& prefix) -> uint64_t { + size_t i = 0; + size_t total = n->children.size(); + uint64_t subtotal = 0; + for (const auto& [name, child] : n->children) { + bool last = (++i == total); + const char* branch = last ? "└─ " : "├─ "; + const char* cont = last ? " " : "│ "; + if (child->isFile) { + std::printf("%s%s%s (%llu bytes)\n", + prefix.c_str(), branch, name.c_str(), + static_cast(child->bytes)); + subtotal += child->bytes; + } else { + // Directory — recurse, then print header with subtotal. + std::printf("%s%s%s/\n", + prefix.c_str(), branch, name.c_str()); + subtotal += render(child.get(), prefix + cont); + } + } + return subtotal; + }; + std::printf("%s (%zu files, %.2f KB)\n", + path.c_str(), info.files.size(), totalBytes / 1024.0); + render(root.get(), ""); + return 0; } else if (std::strcmp(argv[i], "--info-wot") == 0 && i + 1 < argc) { std::string base = argv[++i]; bool jsonOut = (i + 1 < argc &&