Files
ReSkate/Engine/Vfs/Test/mod_build_stamp_tests.cpp
2026-10-02 14:40:41 -05:00

82 lines
4.5 KiB
C++

#include "Engine/Vfs/mod_catalog.h"
#include "Engine/Game/Build/supported_build.h"
#include <Windows.h>
#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
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::Mod> &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<char>(std::toupper(static_cast<unsigned char>(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";
}