mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-10 02:53:51 +00:00
refactor(editor): extract --migrate-* handlers into cli_migrate.cpp
Moves the four schema-migration handlers (--migrate-wom,
--migrate-zone, --migrate-project, --migrate-jsondbc) out of
main.cpp into a new cli_migrate.{hpp,cpp} module. Same
behavior — they're idempotent in-place upgraders for older
WOM revisions and JSON DBC sidecar schemas. main.cpp shrinks
by 282 lines (11,261 → 10,979).
This commit is contained in:
parent
8fd6df6113
commit
f0bbc228ef
4 changed files with 359 additions and 286 deletions
|
|
@ -1323,6 +1323,7 @@ add_executable(wowee_editor
|
|||
tools/editor/cli_extract_info.cpp
|
||||
tools/editor/cli_export.cpp
|
||||
tools/editor/cli_bake.cpp
|
||||
tools/editor/cli_migrate.cpp
|
||||
tools/editor/editor_app.cpp
|
||||
tools/editor/editor_camera.cpp
|
||||
tools/editor/editor_viewport.cpp
|
||||
|
|
|
|||
333
tools/editor/cli_migrate.cpp
Normal file
333
tools/editor/cli_migrate.cpp
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
#include "cli_migrate.hpp"
|
||||
|
||||
#include "pipeline/wowee_model.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
namespace {
|
||||
|
||||
int handleMigrateWom(int& i, int argc, char** argv) {
|
||||
// Upgrade an older WOM (v1=static, v2=animated) to WOM3 by
|
||||
// adding a default single-batch entry that covers the whole
|
||||
// mesh. WOM3 is a strict superset; tooling that consumes
|
||||
// batches (--info-batches, --export-glb per-primitive split,
|
||||
// material-aware renderers) becomes useful on previously-
|
||||
// batchless content. The save() function picks WOM3 magic
|
||||
// automatically once batches.size() > 0.
|
||||
std::string base = argv[++i];
|
||||
std::string outBase;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') outBase = argv[++i];
|
||||
if (base.size() >= 4 && base.substr(base.size() - 4) == ".wom")
|
||||
base = base.substr(0, base.size() - 4);
|
||||
if (!wowee::pipeline::WoweeModelLoader::exists(base)) {
|
||||
std::fprintf(stderr, "WOM not found: %s.wom\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (outBase.empty()) outBase = base;
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) {
|
||||
std::fprintf(stderr, "migrate-wom: %s.wom has no geometry\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
int oldVersion = wom.version;
|
||||
int batchesAdded = 0;
|
||||
if (wom.batches.empty()) {
|
||||
// Single batch covering the entire index range with the
|
||||
// first texture (or 0 if no textures exist). Opaque
|
||||
// blend mode + no flags — safe defaults that match how
|
||||
// the renderer was treating the whole mesh implicitly.
|
||||
wowee::pipeline::WoweeModel::Batch b;
|
||||
b.indexStart = 0;
|
||||
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
||||
b.textureIndex = wom.texturePaths.empty() ? 0 : 0;
|
||||
b.blendMode = 0;
|
||||
b.flags = 0;
|
||||
wom.batches.push_back(b);
|
||||
batchesAdded = 1;
|
||||
}
|
||||
// version field is recomputed inside save() based on
|
||||
// hasBatches/hasAnimation, so we don't need to set it here.
|
||||
if (!wowee::pipeline::WoweeModelLoader::save(wom, outBase)) {
|
||||
std::fprintf(stderr, "migrate-wom: failed to write %s.wom\n",
|
||||
outBase.c_str());
|
||||
return 1;
|
||||
}
|
||||
// Re-load to verify the new version flag landed correctly.
|
||||
auto check = wowee::pipeline::WoweeModelLoader::load(outBase);
|
||||
std::printf("Migrated %s.wom -> %s.wom\n", base.c_str(), outBase.c_str());
|
||||
std::printf(" version: %d -> %u batches: %zu -> %zu (added %d)\n",
|
||||
oldVersion, check.version,
|
||||
size_t(0), check.batches.size(), batchesAdded);
|
||||
if (batchesAdded == 0) {
|
||||
std::printf(" (already had batches; no schema change)\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int handleMigrateZone(int& i, int argc, char** argv) {
|
||||
// Batch-runs --migrate-wom in-place on every .wom under
|
||||
// a zone directory. Idempotent (already-migrated files
|
||||
// become no-ops). Useful when wowee_editor adds a new
|
||||
// WOM3-only feature and you want to upgrade legacy zones
|
||||
// in one shot.
|
||||
std::string zoneDir = argv[++i];
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(zoneDir) || !fs::is_directory(zoneDir)) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-zone: %s is not a directory\n", zoneDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
int scanned = 0, upgraded = 0, alreadyV3 = 0, failed = 0;
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
std::string ext = e.path().extension().string();
|
||||
if (ext != ".wom") continue;
|
||||
scanned++;
|
||||
std::string base = e.path().string();
|
||||
if (base.size() >= 4) base = base.substr(0, base.size() - 4);
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) { failed++; continue; }
|
||||
if (!wom.batches.empty()) { alreadyV3++; continue; }
|
||||
wowee::pipeline::WoweeModel::Batch b;
|
||||
b.indexStart = 0;
|
||||
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
||||
b.textureIndex = 0;
|
||||
b.blendMode = 0;
|
||||
b.flags = 0;
|
||||
wom.batches.push_back(b);
|
||||
if (wowee::pipeline::WoweeModelLoader::save(wom, base)) {
|
||||
upgraded++;
|
||||
std::printf(" upgraded: %s.wom\n", base.c_str());
|
||||
} else {
|
||||
failed++;
|
||||
std::fprintf(stderr, " FAILED: %s.wom\n", base.c_str());
|
||||
}
|
||||
}
|
||||
std::printf("\nmigrate-zone: %s\n", zoneDir.c_str());
|
||||
std::printf(" scanned : %d WOM file(s)\n", scanned);
|
||||
std::printf(" upgraded : %d (added single-batch entry)\n", upgraded);
|
||||
std::printf(" already v3: %d (no change needed)\n", alreadyV3);
|
||||
if (failed > 0) {
|
||||
std::printf(" FAILED : %d (see stderr)\n", failed);
|
||||
}
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int handleMigrateProject(int& i, int argc, char** argv) {
|
||||
// Project-level wrapper around --migrate-zone. Walks every
|
||||
// zone in <projectDir> and upgrades legacy WOMs in-place.
|
||||
// Idempotent — already-migrated files become no-ops, safe to
|
||||
// run repeatedly.
|
||||
(void)argc;
|
||||
std::string projectDir = argv[++i];
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-project: %s is not a directory\n",
|
||||
projectDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> zones;
|
||||
for (const auto& entry : fs::directory_iterator(projectDir)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
if (!fs::exists(entry.path() / "zone.json")) continue;
|
||||
zones.push_back(entry.path().string());
|
||||
}
|
||||
std::sort(zones.begin(), zones.end());
|
||||
int totalScanned = 0, totalUpgraded = 0, totalAlreadyV3 = 0, totalFailed = 0;
|
||||
// Per-zone breakdown for the summary table.
|
||||
struct ZRow { std::string name; int scanned, upgraded, alreadyV3, failed; };
|
||||
std::vector<ZRow> rows;
|
||||
for (const auto& zoneDir : zones) {
|
||||
ZRow r{fs::path(zoneDir).filename().string(), 0, 0, 0, 0};
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
if (e.path().extension() != ".wom") continue;
|
||||
r.scanned++;
|
||||
std::string base = e.path().string();
|
||||
if (base.size() >= 4) base = base.substr(0, base.size() - 4);
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) { r.failed++; continue; }
|
||||
if (!wom.batches.empty()) { r.alreadyV3++; continue; }
|
||||
wowee::pipeline::WoweeModel::Batch b;
|
||||
b.indexStart = 0;
|
||||
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
||||
b.textureIndex = 0;
|
||||
b.blendMode = 0;
|
||||
b.flags = 0;
|
||||
wom.batches.push_back(b);
|
||||
if (wowee::pipeline::WoweeModelLoader::save(wom, base)) {
|
||||
r.upgraded++;
|
||||
} else {
|
||||
r.failed++;
|
||||
}
|
||||
}
|
||||
totalScanned += r.scanned;
|
||||
totalUpgraded += r.upgraded;
|
||||
totalAlreadyV3 += r.alreadyV3;
|
||||
totalFailed += r.failed;
|
||||
rows.push_back(r);
|
||||
}
|
||||
std::printf("migrate-project: %s\n", projectDir.c_str());
|
||||
std::printf(" zones : %zu\n", zones.size());
|
||||
std::printf(" totals : %d scanned, %d upgraded, %d already-v3, %d failed\n",
|
||||
totalScanned, totalUpgraded, totalAlreadyV3, totalFailed);
|
||||
if (!rows.empty()) {
|
||||
std::printf("\n zone scan upgrade v3 failed\n");
|
||||
for (const auto& r : rows) {
|
||||
std::printf(" %-26s %4d %5d %3d %5d\n",
|
||||
r.name.substr(0, 26).c_str(),
|
||||
r.scanned, r.upgraded, r.alreadyV3, r.failed);
|
||||
}
|
||||
}
|
||||
return totalFailed == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
int handleMigrateJsondbc(int& i, int argc, char** argv) {
|
||||
// Auto-fix common schema problems in JSON DBC sidecars so they
|
||||
// pass --validate-jsondbc cleanly. Designed for upgrading
|
||||
// sidecars produced by older asset_extract versions or from
|
||||
// third-party tools that omit fields the runtime now expects:
|
||||
// - missing 'format' tag → add 'wowee-dbc-json-1.0'
|
||||
// - missing 'source' field → derive from filename
|
||||
// - missing 'fieldCount' → infer from first row
|
||||
// - recordCount mismatch → recompute from actual records[]
|
||||
// Wrong-width rows are not silently fixed (data loss risk);
|
||||
// they're surfaced as warnings so the user can decide.
|
||||
std::string path = argv[++i];
|
||||
std::string outPath;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i];
|
||||
if (outPath.empty()) outPath = path; // in-place
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: cannot open %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
nlohmann::json doc;
|
||||
try { in >> doc; }
|
||||
catch (const std::exception& e) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: bad JSON in %s (%s)\n",
|
||||
path.c_str(), e.what());
|
||||
return 1;
|
||||
}
|
||||
in.close();
|
||||
if (!doc.is_object()) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: top-level value is not an object\n");
|
||||
return 1;
|
||||
}
|
||||
int fixes = 0;
|
||||
if (!doc.contains("format") || !doc["format"].is_string()) {
|
||||
doc["format"] = "wowee-dbc-json-1.0";
|
||||
fixes++;
|
||||
std::printf(" added: format = 'wowee-dbc-json-1.0'\n");
|
||||
} else if (doc["format"] != "wowee-dbc-json-1.0") {
|
||||
std::printf(" retained existing format: '%s' (not changed)\n",
|
||||
doc["format"].get<std::string>().c_str());
|
||||
}
|
||||
if (!doc.contains("source") || !doc["source"].is_string() ||
|
||||
doc["source"].get<std::string>().empty()) {
|
||||
// Derive from input path's stem + .dbc — best-effort
|
||||
// matching the convention asset_extract uses.
|
||||
std::string stem = std::filesystem::path(path).stem().string();
|
||||
doc["source"] = stem + ".dbc";
|
||||
fixes++;
|
||||
std::printf(" added: source = '%s'\n",
|
||||
doc["source"].get<std::string>().c_str());
|
||||
}
|
||||
// recordCount + fieldCount are non-negotiable for re-import.
|
||||
if (!doc.contains("records") || !doc["records"].is_array()) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: 'records' missing or not an array — cannot fix\n");
|
||||
return 1;
|
||||
}
|
||||
const auto& records = doc["records"];
|
||||
uint32_t actualCount = static_cast<uint32_t>(records.size());
|
||||
uint32_t headerCount = doc.value("recordCount", 0u);
|
||||
if (headerCount != actualCount) {
|
||||
doc["recordCount"] = actualCount;
|
||||
fixes++;
|
||||
std::printf(" fixed: recordCount %u -> %u (matches actual)\n",
|
||||
headerCount, actualCount);
|
||||
}
|
||||
// Infer fieldCount from first row if missing.
|
||||
if (!doc.contains("fieldCount") ||
|
||||
!doc["fieldCount"].is_number_integer()) {
|
||||
if (!records.empty() && records[0].is_array()) {
|
||||
uint32_t inferred = static_cast<uint32_t>(records[0].size());
|
||||
doc["fieldCount"] = inferred;
|
||||
fixes++;
|
||||
std::printf(" inferred: fieldCount = %u (from first row)\n",
|
||||
inferred);
|
||||
}
|
||||
}
|
||||
// Surface wrong-width rows as warnings (no auto-fix).
|
||||
uint32_t fc = doc.value("fieldCount", 0u);
|
||||
int badRows = 0;
|
||||
for (size_t r = 0; r < records.size(); ++r) {
|
||||
if (records[r].is_array() && records[r].size() != fc) {
|
||||
if (++badRows <= 3) {
|
||||
std::printf(" WARN: row %zu has %zu cells, expected %u\n",
|
||||
r, records[r].size(), fc);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (badRows > 3) {
|
||||
std::printf(" WARN: ... and %d more wrong-width rows\n",
|
||||
badRows - 3);
|
||||
}
|
||||
std::ofstream out(outPath);
|
||||
if (!out) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: cannot write %s\n", outPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
out << doc.dump(2) << "\n";
|
||||
out.close();
|
||||
std::printf("Migrated %s -> %s\n", path.c_str(), outPath.c_str());
|
||||
std::printf(" fixes applied: %d\n", fixes);
|
||||
if (badRows > 0) {
|
||||
std::printf(" warnings : %d wrong-width rows (NOT auto-fixed)\n",
|
||||
badRows);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool handleMigrate(int& i, int argc, char** argv, int& outRc) {
|
||||
if (std::strcmp(argv[i], "--migrate-wom") == 0 && i + 1 < argc) {
|
||||
outRc = handleMigrateWom(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-zone") == 0 && i + 1 < argc) {
|
||||
outRc = handleMigrateZone(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-project") == 0 && i + 1 < argc) {
|
||||
outRc = handleMigrateProject(i, argc, argv); return true;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-jsondbc") == 0 && i + 1 < argc) {
|
||||
outRc = handleMigrateJsondbc(i, argc, argv); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
21
tools/editor/cli_migrate.hpp
Normal file
21
tools/editor/cli_migrate.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
// Dispatch the schema-migration handlers — these upgrade older
|
||||
// on-disk asset versions in-place so newer tooling stops rejecting
|
||||
// or silently misreading them. All migrators are idempotent:
|
||||
// already-modern files become no-ops.
|
||||
// --migrate-wom single WOM v1/v2 → WOM3 (single-batch)
|
||||
// --migrate-zone every WOM in a zone directory
|
||||
// --migrate-project every zone in a project directory
|
||||
// --migrate-jsondbc JSON DBC sidecar schema fixes
|
||||
//
|
||||
// Returns true if matched; outRc holds the exit code.
|
||||
bool handleMigrate(int& i, int argc, char** argv, int& outRc);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
#include "cli_extract_info.hpp"
|
||||
#include "cli_export.hpp"
|
||||
#include "cli_bake.hpp"
|
||||
#include "cli_migrate.hpp"
|
||||
#include "content_pack.hpp"
|
||||
#include "npc_spawner.hpp"
|
||||
#include "object_placer.hpp"
|
||||
|
|
@ -423,6 +424,9 @@ int main(int argc, char* argv[]) {
|
|||
if (wowee::editor::cli::handleBake(i, argc, argv, outRc)) {
|
||||
return outRc;
|
||||
}
|
||||
if (wowee::editor::cli::handleMigrate(i, argc, argv, outRc)) {
|
||||
return outRc;
|
||||
}
|
||||
}
|
||||
if (std::strcmp(argv[i], "--data") == 0 && i + 1 < argc) {
|
||||
dataPath = argv[++i];
|
||||
|
|
@ -10951,292 +10955,6 @@ int main(int argc, char* argv[]) {
|
|||
img.width, img.height, img.data.size());
|
||||
return 0;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-wom") == 0 && i + 1 < argc) {
|
||||
// Upgrade an older WOM (v1=static, v2=animated) to WOM3 by
|
||||
// adding a default single-batch entry that covers the whole
|
||||
// mesh. WOM3 is a strict superset; tooling that consumes
|
||||
// batches (--info-batches, --export-glb per-primitive split,
|
||||
// material-aware renderers) becomes useful on previously-
|
||||
// batchless content. The save() function picks WOM3 magic
|
||||
// automatically once batches.size() > 0.
|
||||
std::string base = argv[++i];
|
||||
std::string outBase;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') outBase = argv[++i];
|
||||
if (base.size() >= 4 && base.substr(base.size() - 4) == ".wom")
|
||||
base = base.substr(0, base.size() - 4);
|
||||
if (!wowee::pipeline::WoweeModelLoader::exists(base)) {
|
||||
std::fprintf(stderr, "WOM not found: %s.wom\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
if (outBase.empty()) outBase = base;
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) {
|
||||
std::fprintf(stderr, "migrate-wom: %s.wom has no geometry\n", base.c_str());
|
||||
return 1;
|
||||
}
|
||||
int oldVersion = wom.version;
|
||||
int batchesAdded = 0;
|
||||
if (wom.batches.empty()) {
|
||||
// Single batch covering the entire index range with the
|
||||
// first texture (or 0 if no textures exist). Opaque
|
||||
// blend mode + no flags — safe defaults that match how
|
||||
// the renderer was treating the whole mesh implicitly.
|
||||
wowee::pipeline::WoweeModel::Batch b;
|
||||
b.indexStart = 0;
|
||||
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
||||
b.textureIndex = wom.texturePaths.empty() ? 0 : 0;
|
||||
b.blendMode = 0;
|
||||
b.flags = 0;
|
||||
wom.batches.push_back(b);
|
||||
batchesAdded = 1;
|
||||
}
|
||||
// version field is recomputed inside save() based on
|
||||
// hasBatches/hasAnimation, so we don't need to set it here.
|
||||
if (!wowee::pipeline::WoweeModelLoader::save(wom, outBase)) {
|
||||
std::fprintf(stderr, "migrate-wom: failed to write %s.wom\n",
|
||||
outBase.c_str());
|
||||
return 1;
|
||||
}
|
||||
// Re-load to verify the new version flag landed correctly.
|
||||
auto check = wowee::pipeline::WoweeModelLoader::load(outBase);
|
||||
std::printf("Migrated %s.wom -> %s.wom\n", base.c_str(), outBase.c_str());
|
||||
std::printf(" version: %d -> %u batches: %zu -> %zu (added %d)\n",
|
||||
oldVersion, check.version,
|
||||
size_t(0), check.batches.size(), batchesAdded);
|
||||
if (batchesAdded == 0) {
|
||||
std::printf(" (already had batches; no schema change)\n");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-zone") == 0 && i + 1 < argc) {
|
||||
// Batch-runs --migrate-wom in-place on every .wom under
|
||||
// a zone directory. Idempotent (already-migrated files
|
||||
// become no-ops). Useful when wowee_editor adds a new
|
||||
// WOM3-only feature and you want to upgrade legacy zones
|
||||
// in one shot.
|
||||
std::string zoneDir = argv[++i];
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(zoneDir) || !fs::is_directory(zoneDir)) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-zone: %s is not a directory\n", zoneDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
int scanned = 0, upgraded = 0, alreadyV3 = 0, failed = 0;
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
std::string ext = e.path().extension().string();
|
||||
if (ext != ".wom") continue;
|
||||
scanned++;
|
||||
std::string base = e.path().string();
|
||||
if (base.size() >= 4) base = base.substr(0, base.size() - 4);
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) { failed++; continue; }
|
||||
if (!wom.batches.empty()) { alreadyV3++; continue; }
|
||||
wowee::pipeline::WoweeModel::Batch b;
|
||||
b.indexStart = 0;
|
||||
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
||||
b.textureIndex = 0;
|
||||
b.blendMode = 0;
|
||||
b.flags = 0;
|
||||
wom.batches.push_back(b);
|
||||
if (wowee::pipeline::WoweeModelLoader::save(wom, base)) {
|
||||
upgraded++;
|
||||
std::printf(" upgraded: %s.wom\n", base.c_str());
|
||||
} else {
|
||||
failed++;
|
||||
std::fprintf(stderr, " FAILED: %s.wom\n", base.c_str());
|
||||
}
|
||||
}
|
||||
std::printf("\nmigrate-zone: %s\n", zoneDir.c_str());
|
||||
std::printf(" scanned : %d WOM file(s)\n", scanned);
|
||||
std::printf(" upgraded : %d (added single-batch entry)\n", upgraded);
|
||||
std::printf(" already v3: %d (no change needed)\n", alreadyV3);
|
||||
if (failed > 0) {
|
||||
std::printf(" FAILED : %d (see stderr)\n", failed);
|
||||
}
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-project") == 0 && i + 1 < argc) {
|
||||
// Project-level wrapper around --migrate-zone. Walks every
|
||||
// zone in <projectDir> and upgrades legacy WOMs in-place.
|
||||
// Idempotent — already-migrated files become no-ops, safe to
|
||||
// run repeatedly.
|
||||
std::string projectDir = argv[++i];
|
||||
namespace fs = std::filesystem;
|
||||
if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-project: %s is not a directory\n",
|
||||
projectDir.c_str());
|
||||
return 1;
|
||||
}
|
||||
std::vector<std::string> zones;
|
||||
for (const auto& entry : fs::directory_iterator(projectDir)) {
|
||||
if (!entry.is_directory()) continue;
|
||||
if (!fs::exists(entry.path() / "zone.json")) continue;
|
||||
zones.push_back(entry.path().string());
|
||||
}
|
||||
std::sort(zones.begin(), zones.end());
|
||||
int totalScanned = 0, totalUpgraded = 0, totalAlreadyV3 = 0, totalFailed = 0;
|
||||
// Per-zone breakdown for the summary table.
|
||||
struct ZRow { std::string name; int scanned, upgraded, alreadyV3, failed; };
|
||||
std::vector<ZRow> rows;
|
||||
for (const auto& zoneDir : zones) {
|
||||
ZRow r{fs::path(zoneDir).filename().string(), 0, 0, 0, 0};
|
||||
std::error_code ec;
|
||||
for (const auto& e : fs::recursive_directory_iterator(zoneDir, ec)) {
|
||||
if (!e.is_regular_file()) continue;
|
||||
if (e.path().extension() != ".wom") continue;
|
||||
r.scanned++;
|
||||
std::string base = e.path().string();
|
||||
if (base.size() >= 4) base = base.substr(0, base.size() - 4);
|
||||
auto wom = wowee::pipeline::WoweeModelLoader::load(base);
|
||||
if (!wom.isValid()) { r.failed++; continue; }
|
||||
if (!wom.batches.empty()) { r.alreadyV3++; continue; }
|
||||
wowee::pipeline::WoweeModel::Batch b;
|
||||
b.indexStart = 0;
|
||||
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
||||
b.textureIndex = 0;
|
||||
b.blendMode = 0;
|
||||
b.flags = 0;
|
||||
wom.batches.push_back(b);
|
||||
if (wowee::pipeline::WoweeModelLoader::save(wom, base)) {
|
||||
r.upgraded++;
|
||||
} else {
|
||||
r.failed++;
|
||||
}
|
||||
}
|
||||
totalScanned += r.scanned;
|
||||
totalUpgraded += r.upgraded;
|
||||
totalAlreadyV3 += r.alreadyV3;
|
||||
totalFailed += r.failed;
|
||||
rows.push_back(r);
|
||||
}
|
||||
std::printf("migrate-project: %s\n", projectDir.c_str());
|
||||
std::printf(" zones : %zu\n", zones.size());
|
||||
std::printf(" totals : %d scanned, %d upgraded, %d already-v3, %d failed\n",
|
||||
totalScanned, totalUpgraded, totalAlreadyV3, totalFailed);
|
||||
if (!rows.empty()) {
|
||||
std::printf("\n zone scan upgrade v3 failed\n");
|
||||
for (const auto& r : rows) {
|
||||
std::printf(" %-26s %4d %5d %3d %5d\n",
|
||||
r.name.substr(0, 26).c_str(),
|
||||
r.scanned, r.upgraded, r.alreadyV3, r.failed);
|
||||
}
|
||||
}
|
||||
return totalFailed == 0 ? 0 : 1;
|
||||
}
|
||||
if (std::strcmp(argv[i], "--migrate-jsondbc") == 0 && i + 1 < argc) {
|
||||
// Auto-fix common schema problems in JSON DBC sidecars so they
|
||||
// pass --validate-jsondbc cleanly. Designed for upgrading
|
||||
// sidecars produced by older asset_extract versions or from
|
||||
// third-party tools that omit fields the runtime now expects:
|
||||
// - missing 'format' tag → add 'wowee-dbc-json-1.0'
|
||||
// - missing 'source' field → derive from filename
|
||||
// - missing 'fieldCount' → infer from first row
|
||||
// - recordCount mismatch → recompute from actual records[]
|
||||
// Wrong-width rows are not silently fixed (data loss risk);
|
||||
// they're surfaced as warnings so the user can decide.
|
||||
std::string path = argv[++i];
|
||||
std::string outPath;
|
||||
if (i + 1 < argc && argv[i + 1][0] != '-') outPath = argv[++i];
|
||||
if (outPath.empty()) outPath = path; // in-place
|
||||
std::ifstream in(path);
|
||||
if (!in) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: cannot open %s\n", path.c_str());
|
||||
return 1;
|
||||
}
|
||||
nlohmann::json doc;
|
||||
try { in >> doc; }
|
||||
catch (const std::exception& e) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: bad JSON in %s (%s)\n",
|
||||
path.c_str(), e.what());
|
||||
return 1;
|
||||
}
|
||||
in.close();
|
||||
if (!doc.is_object()) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: top-level value is not an object\n");
|
||||
return 1;
|
||||
}
|
||||
int fixes = 0;
|
||||
if (!doc.contains("format") || !doc["format"].is_string()) {
|
||||
doc["format"] = "wowee-dbc-json-1.0";
|
||||
fixes++;
|
||||
std::printf(" added: format = 'wowee-dbc-json-1.0'\n");
|
||||
} else if (doc["format"] != "wowee-dbc-json-1.0") {
|
||||
std::printf(" retained existing format: '%s' (not changed)\n",
|
||||
doc["format"].get<std::string>().c_str());
|
||||
}
|
||||
if (!doc.contains("source") || !doc["source"].is_string() ||
|
||||
doc["source"].get<std::string>().empty()) {
|
||||
// Derive from input path's stem + .dbc — best-effort
|
||||
// matching the convention asset_extract uses.
|
||||
std::string stem = std::filesystem::path(path).stem().string();
|
||||
doc["source"] = stem + ".dbc";
|
||||
fixes++;
|
||||
std::printf(" added: source = '%s'\n",
|
||||
doc["source"].get<std::string>().c_str());
|
||||
}
|
||||
// recordCount + fieldCount are non-negotiable for re-import.
|
||||
if (!doc.contains("records") || !doc["records"].is_array()) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: 'records' missing or not an array — cannot fix\n");
|
||||
return 1;
|
||||
}
|
||||
const auto& records = doc["records"];
|
||||
uint32_t actualCount = static_cast<uint32_t>(records.size());
|
||||
uint32_t headerCount = doc.value("recordCount", 0u);
|
||||
if (headerCount != actualCount) {
|
||||
doc["recordCount"] = actualCount;
|
||||
fixes++;
|
||||
std::printf(" fixed: recordCount %u -> %u (matches actual)\n",
|
||||
headerCount, actualCount);
|
||||
}
|
||||
// Infer fieldCount from first row if missing.
|
||||
if (!doc.contains("fieldCount") ||
|
||||
!doc["fieldCount"].is_number_integer()) {
|
||||
if (!records.empty() && records[0].is_array()) {
|
||||
uint32_t inferred = static_cast<uint32_t>(records[0].size());
|
||||
doc["fieldCount"] = inferred;
|
||||
fixes++;
|
||||
std::printf(" inferred: fieldCount = %u (from first row)\n",
|
||||
inferred);
|
||||
}
|
||||
}
|
||||
// Surface wrong-width rows as warnings (no auto-fix).
|
||||
uint32_t fc = doc.value("fieldCount", 0u);
|
||||
int badRows = 0;
|
||||
for (size_t r = 0; r < records.size(); ++r) {
|
||||
if (records[r].is_array() && records[r].size() != fc) {
|
||||
if (++badRows <= 3) {
|
||||
std::printf(" WARN: row %zu has %zu cells, expected %u\n",
|
||||
r, records[r].size(), fc);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (badRows > 3) {
|
||||
std::printf(" WARN: ... and %d more wrong-width rows\n",
|
||||
badRows - 3);
|
||||
}
|
||||
std::ofstream out(outPath);
|
||||
if (!out) {
|
||||
std::fprintf(stderr,
|
||||
"migrate-jsondbc: cannot write %s\n", outPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
out << doc.dump(2) << "\n";
|
||||
out.close();
|
||||
std::printf("Migrated %s -> %s\n", path.c_str(), outPath.c_str());
|
||||
std::printf(" fixes applied: %d\n", fixes);
|
||||
if (badRows > 0) {
|
||||
std::printf(" warnings : %d wrong-width rows (NOT auto-fixed)\n",
|
||||
badRows);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataPath.empty()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue