#pragma once #include #include #include #include #include #include // The Mods folder as a list: which mod folders exist, what they say about // themselves, and the order and enabled state mods.json gives them. No merge // happens here, so the launcher's mod manager and the runtime share it. namespace dingosdk::mods { // There is no limit on how many mods are listed or loaded: nothing in the merge needs one // (archives are numbered with 16 bits). There was one of 64 once, which players reached: past // it the launcher could not save the list at all, and the game skipped the later mods. inline constexpr std::size_t maximum_mod_name = 64; // Generated state the SDK owns. Folders beginning with a dot are never mods. inline constexpr char generated_folder[] = ".reskate"; inline constexpr char mods_folder[] = "Mods"; inline constexpr char order_file[] = "mods.json"; // The mod's own description, written by its author or by the park editor: // {"name":"Display name","author":"...","version_number":"1.0.0", // "description":"..."}. Read loosely; other fields are kept when rewritten. inline constexpr char manifest_file[] = "manifest.json"; // The older name for the same information ("version" instead of // "version_number"), still read when a mod has no manifest.json. inline constexpr char info_file[] = "reskate-mod.json"; // ReSkate Studio's stamp in every mod it builds: "ReSkate Studio native Patch v1", then // key=value lines, of which skate_sha256= decides whether the // mod loads: game data built for another Skate.exe points into that build's archives. inline constexpr char studio_marker_file[] = ".reskate-studio-patch"; struct Mod { std::string name; // folder name directly under Mods/ std::filesystem::path directory; bool provides_layout{}; // has layout.toc, so it becomes a layout layer bool provides_levels{}; // has reskate-levels.json // Base maps it ships a park for: parks/.park.json (park editor mods). std::vector park_maps; // Presentation, from manifest.json (or reskate-mod.json). `title` falls back to the folder // name and `version` to the one reskate-build.json records. std::string title, author, version, description; // What built the mod and when, from reskate-build.json; empty when the mod // predates that file or was made by another tool. std::string tool, built; // The level assets its reskate-levels.json registers, read loosely for // logging; the strict reading that feeds the level list happens later. std::vector levels; // What the merge could not use from this mod. Empty once it merged cleanly. std::vector problems; // From .reskate-studio-patch: whether the mod has one, and the Skate.exe SHA-256 it records. bool studio_marker{}; std::string skate_sha256; // Why the mod cannot load on this game build (it must be updated), or empty. Such a mod is left // out of every merge whatever mods.json says: a mod with no build stamp that ships game data, // or a stamp for another Skate.exe. std::string outdated; }; // Mods left out of the merge because they could not be merged cleanly, kept // in /.reskate-excluded.json so later launches skip them without merging // twice. An entry holds only while the mod's files are unchanged. inline constexpr char exclusions_file[] = ".reskate-excluded.json"; struct Exclusion { std::string fingerprint; // mod_fingerprint() when it was left out std::string sdk; // the ReSkate.dll that left it out; a new one retries std::vector problems; // why, most important first // Left out for a conflict with this mod, higher in the load order, as its files were then // (its mod_fingerprint()). It stays out only while that mod still loads above it unchanged. std::string conflictWith; std::string conflictFingerprint; }; // Names, sizes and write times of every file in the mod folder. std::string mod_fingerprint(const std::filesystem::path& directory); std::map> read_exclusions(const std::filesystem::path& mods_root) noexcept; // Replaces the file; an empty map removes it. void write_exclusions(const std::filesystem::path& mods_root, const std::map>& exclusions) noexcept; struct ModEntry { Mod mod; bool enabled{true}; }; struct ModList { std::filesystem::path root; // /Mods bool present{}; // the Mods folder exists // Every mod folder on disk, highest priority first: mods.json's rows in // their order (enabled or not), then unlisted folders by name, enabled. std::vector entries; // mods.json rows naming folders that are not present. std::vector missing; // Mods the game currently leaves out because they could not be merged, // with the reasons (only while their files are unchanged). std::map, std::less<>> excluded; // Non-empty when mods.json is malformed; the runtime then loads no mod. std::string issue; // Advisory messages worth logging, such as unreadable info files. std::vector notes; }; // Folder names travel into engine paths, so only plain ASCII is accepted: // letters, digits, space, '_', '-' and '.', not starting with '.'. bool valid_mod_name(std::string_view name); // `path` in generic form ('/' separators) as ASCII text, or empty when it holds any other // character. Every name the game or a mod list uses is ASCII, and path::string() throws on // characters outside the ANSI code page, so one oddly named file must not stop a scan. std::string ascii_path(const std::filesystem::path& path); // Reads /Mods and its mods.json. Never throws. When mods.json is // malformed, `issue` says why and `entries` still lists the folders (all // shown as enabled) so a manager can offer to rewrite it. ModList scan_mods(const std::filesystem::path& data_root) noexcept; // One mod folder as scan_mods reads it: its layout, levels, parks, info files and build stamp. // `name` is how it is known (for a folder under Mods/, the folder's name). Throws on I/O errors. Mod describe_mod(const std::filesystem::path& directory, std::string name, std::vector& notes); // Writes mods.json for `entries` in order, replacing the file atomically. // Throws with a readable message on failure. void save_mod_order(const std::filesystem::path& mods_root, const std::vector& entries); // Whether mods.json lists the mod folder `name` switched off. A folder it does not list loads. // Reads mods.json alone, not the mods. Never throws: false when it cannot be read. bool mod_disabled(const std::filesystem::path& mods_root, std::string_view name) noexcept; // Switches the mod folder `name` on or off in mods.json, every other row as it was; one it does // not list yet is added at its end, the bottom of the load order. False when mods.json could not // be read (malformed: left for the player to see) or written. Never throws. bool set_mod_enabled(const std::filesystem::path& mods_root, std::string_view name, bool enabled) noexcept; } // namespace dingosdk::mods