From 4b928274b82cafdc4dcb1c7b850a3497fe7194fc Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 19:29:18 -0700 Subject: [PATCH] feat(editor): add --rename-by-magic extension recovery flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reads the 4-byte magic of a file, looks it up in the shared format table, and renames the file to use the correct .w* extension. Useful when files have lost their extensions (downloaded as 'data.bin', extracted from a tarball with mangled metadata, or copied via a tool that strips suffixes). Safe by default — refuses to overwrite an existing target; pass --force to allow overwrite. --dry-run prints the planned move without touching the filesystem. Files that already have the correct extension are a no-op. Unrecognized magic exits 1 with the bytes printed for diagnostic context. Reuses cli_format_table.cpp so any future format addition is picked up automatically. --- CMakeLists.txt | 1 + tools/editor/cli_arg_required.cpp | 2 +- tools/editor/cli_dispatch.cpp | 2 + tools/editor/cli_help.cpp | 2 + tools/editor/cli_rename_magic.cpp | 101 ++++++++++++++++++++++++++++++ tools/editor/cli_rename_magic.hpp | 11 ++++ 6 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tools/editor/cli_rename_magic.cpp create mode 100644 tools/editor/cli_rename_magic.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 08a03173..4902c595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1420,6 +1420,7 @@ add_executable(wowee_editor tools/editor/cli_spell_visuals_catalog.cpp tools/editor/cli_format_table.cpp tools/editor/cli_summary_dir.cpp + tools/editor/cli_rename_magic.cpp tools/editor/cli_quest_objective.cpp tools/editor/cli_quest_reward.cpp tools/editor/cli_clone.cpp diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 416b7400..6197446f 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -134,7 +134,7 @@ const char* const kArgRequired[] = { "--gen-liquids", "--gen-liquids-magical", "--gen-liquids-hazardous", "--info-wliq", "--validate-wliq", "--export-wliq-json", "--import-wliq-json", - "--info-magic", "--summary-dir", + "--info-magic", "--summary-dir", "--rename-by-magic", "--gen-animations", "--gen-animations-combat", "--gen-animations-movement", "--info-wani", "--validate-wani", "--export-wani-json", "--import-wani-json", diff --git a/tools/editor/cli_dispatch.cpp b/tools/editor/cli_dispatch.cpp index 8acf429e..09e2c7b5 100644 --- a/tools/editor/cli_dispatch.cpp +++ b/tools/editor/cli_dispatch.cpp @@ -78,6 +78,7 @@ #include "cli_animations_catalog.hpp" #include "cli_spell_visuals_catalog.hpp" #include "cli_summary_dir.hpp" +#include "cli_rename_magic.hpp" #include "cli_quest_objective.hpp" #include "cli_quest_reward.hpp" #include "cli_clone.hpp" @@ -197,6 +198,7 @@ constexpr DispatchFn kDispatchTable[] = { handleAnimationsCatalog, handleSpellVisualsCatalog, handleSummaryDir, + handleRenameMagic, handleQuestObjective, handleQuestReward, handleClone, diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index d94988e2..f103945e 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -1353,6 +1353,8 @@ void printUsage(const char* argv0) { std::printf(" Auto-detect any .w* file by 4-byte magic; report format / version / catalog name / entry count + suggest --info-* flag\n"); std::printf(" --summary-dir [--json]\n"); std::printf(" Recursively walk a directory; report per-format file count, total entries, and bytes for every Wowee open format found\n"); + std::printf(" --rename-by-magic [--dry-run] [--force]\n"); + std::printf(" Recover the correct .w* extension on a file by reading its 4-byte magic. --dry-run prints the planned move; --force overwrites\n"); std::printf(" --gen-animations [name]\n"); std::printf(" Emit .wani starter: 5 essential animations (Stand / Walk / Run / Death / AttackUnarmed) with fallback chains\n"); std::printf(" --gen-animations-combat [name]\n"); diff --git a/tools/editor/cli_rename_magic.cpp b/tools/editor/cli_rename_magic.cpp new file mode 100644 index 00000000..0a8e03a4 --- /dev/null +++ b/tools/editor/cli_rename_magic.cpp @@ -0,0 +1,101 @@ +#include "cli_rename_magic.hpp" +#include "cli_arg_parse.hpp" +#include "cli_format_table.hpp" + +#include +#include +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +namespace { + +namespace fs = std::filesystem; + +bool readMagic(const fs::path& path, char magic[4]) { + std::ifstream is(path, std::ios::binary); + if (!is) return false; + is.read(magic, 4); + return is.gcount() == 4; +} + +int handleRename(int& i, int argc, char** argv) { + std::string filePath = argv[++i]; + bool dryRun = false; + bool force = false; + while (i + 1 < argc) { + std::string a = argv[i + 1]; + if (a == "--dry-run") { dryRun = true; ++i; } + else if (a == "--force") { force = true; ++i; } + else break; + } + fs::path src = filePath; + if (!fs::exists(src) || !fs::is_regular_file(src)) { + std::fprintf(stderr, + "rename-by-magic: not a file: %s\n", filePath.c_str()); + return 1; + } + char magic[4]; + if (!readMagic(src, magic)) { + std::fprintf(stderr, + "rename-by-magic: cannot read 4-byte magic: %s\n", + filePath.c_str()); + return 1; + } + const FormatMagicEntry* fmt = findFormatByMagic(magic); + if (!fmt) { + char magicStr[5] = {magic[0], magic[1], magic[2], magic[3], 0}; + std::fprintf(stderr, + "rename-by-magic: unrecognized magic '%s' in %s\n", + magicStr, filePath.c_str()); + return 1; + } + fs::path dst = src; + dst.replace_extension(fmt->extension); + if (src == dst) { + std::printf("rename-by-magic: %s already has correct " + "extension (%s) — no change\n", + filePath.c_str(), fmt->extension); + return 0; + } + if (fs::exists(dst) && !force) { + std::fprintf(stderr, + "rename-by-magic: target %s already exists " + "(pass --force to overwrite)\n", dst.string().c_str()); + return 1; + } + if (dryRun) { + std::printf("rename-by-magic (dry-run): %s -> %s\n", + filePath.c_str(), dst.string().c_str()); + return 0; + } + std::error_code ec; + fs::rename(src, dst, ec); + if (ec) { + std::fprintf(stderr, + "rename-by-magic: rename failed: %s\n", + ec.message().c_str()); + return 1; + } + std::printf("rename-by-magic: %s -> %s\n", + filePath.c_str(), dst.string().c_str()); + return 0; +} + +} // namespace + +bool handleRenameMagic(int& i, int argc, char** argv, int& outRc) { + if (std::strcmp(argv[i], "--rename-by-magic") == 0 && i + 1 < argc) { + outRc = handleRename(i, argc, argv); return true; + } + return false; +} + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_rename_magic.hpp b/tools/editor/cli_rename_magic.hpp new file mode 100644 index 00000000..fb3119c8 --- /dev/null +++ b/tools/editor/cli_rename_magic.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace wowee { +namespace editor { +namespace cli { + +bool handleRenameMagic(int& i, int argc, char** argv, int& outRc); + +} // namespace cli +} // namespace editor +} // namespace wowee