#include "Engine/Vfs/mod_catalog.h" #include "Engine/Game/Build/supported_build.h" #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace dingosdk; namespace { void check(bool ok, const std::string &message) { if (ok) return; std::cerr << message << '\n'; std::exit(1); } void write(const fs::path &path, const std::string &text) { fs::create_directories(path.parent_path()); std::ofstream(path, std::ios::binary) << text; } const mods::Mod *find(const mods::ModList &list, std::string_view name) { for (const auto &entry : list.entries) if (entry.mod.name == name) return &entry.mod; return nullptr; } bool listed(const std::vector &mods, std::string_view name) { return std::ranges::any_of(mods, [&](const mods::Mod &mod) { return mod.name == name; }); } } // namespace int main() { wchar_t temp[MAX_PATH]{}; GetTempPathW(MAX_PATH, temp); const auto root = fs::path(temp) / ("reskate-mod-stamp-" + std::to_string(GetCurrentProcessId())); fs::remove_all(root); const auto mods_root = root / "Mods"; const std::string current(supported_build::game_sha256); const std::string other = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; // Built by ReSkate Studio for this game (CRLF and an upper-case hash are fine). write(mods_root / "Current" / "layout.toc", "x"); std::string upper = current; for (auto &c : upper) c = static_cast(std::toupper(static_cast(c))); write(mods_root / "Current" / ".reskate-studio-patch", "ReSkate Studio native Patch v1\r\nskate_sha256=" + upper + "\r\n"); // An older Studio: the stamp without a game version. write(mods_root / "OldStudio" / "layout.toc", "x"); write(mods_root / "OldStudio" / ".reskate-studio-patch", "ReSkate Studio native Patch v1\n"); // Built for another Skate.exe. write(mods_root / "OtherBuild" / "layout.toc", "x"); write(mods_root / "OtherBuild" / ".reskate-studio-patch", "ReSkate Studio native Patch v1\nskate_sha256=" + other + "\n"); // Game data with no stamp at all. write(mods_root / "Unstamped" / "layout.toc", "x"); // A park mod ships no game data: no stamp needed. write(mods_root / "Park" / "parks" / "sanvan.park.json", "{}"); // Disabled and built for another game: still left out of the merge. write(mods_root / "OffOld" / "layout.toc", "x"); write(mods_root / "OffOld" / ".reskate-studio-patch", "ReSkate Studio native Patch v1\nskate_sha256=" + other + "\n"); write(mods_root / "mods.json", R"({"schema":1,"mods":[{"name":"OffOld","enabled":false}]})"); const auto list = mods::scan_mods(root); check(list.issue.empty(), "The Mods folder was not read: " + list.issue); check(find(list, "Current") && find(list, "Current")->outdated.empty() && find(list, "Current")->skate_sha256 == current, "A mod built for this game was marked outdated"); check(find(list, "OldStudio") && !find(list, "OldStudio")->outdated.empty(), "A stamp without a game version was accepted"); check(find(list, "OtherBuild") && !find(list, "OtherBuild")->outdated.empty(), "A mod for another Skate.exe was accepted"); check(find(list, "Unstamped") && !find(list, "Unstamped")->outdated.empty(), "Unstamped game data was accepted"); check(find(list, "Park") && find(list, "Park")->outdated.empty(), "A park mod needed a game version stamp"); const auto catalog = mods::load_catalog(root, {}, false); check(catalog.issue.empty(), "The catalog was rejected: " + catalog.issue); check(listed(catalog.mods, "Current") && listed(catalog.mods, "Park"), "A current mod was left out"); for (const auto *name : {"OldStudio", "OtherBuild", "Unstamped"}) check(!listed(catalog.mods, name) && listed(catalog.excluded, name) && listed(catalog.outdated, name), std::string("Outdated mod ") + name + " was not left out"); check(listed(catalog.outdated, "OffOld") && !listed(catalog.inactive, "OffOld"), "A disabled outdated mod stayed in the merge"); check(std::ranges::any_of(catalog.warnings, [](const std::string &w) { return w.find("OtherBuild") != std::string::npos && w.find("outdated") != std::string::npos; }), "No warning names the outdated mod"); fs::remove_all(root); std::cout << "Mod build stamps: current, older Studio, other build, unstamped, park-only and disabled mods passed.\n"; }