Plugins: mods.content and read_content read the server's Mods on a dedicated server

A server plugin with mods.read could call reskate.mods.content, but the
server's game had no content of its own, so it got nil and "not
available". A mode that keeps its data in mods (a race reading start
and finish from a park mod) could only read it in the players' games.

The server now answers from every mod folder in its Mods, by name, as it
finds its maps there; it has no load order or mods.json. A hidden folder
(.reskate_plugin_data) and names a plugin could not ask for are left out,
and a mod's title is its manifest.json's "name".

The listing and reading moved from plugin_game_runtime.cpp into
plugin_content.cpp, which the game and the server share, so both keep
the same limits: the folders the manifest's "content" names, 1000 files,
four folders deep, 1 MB a file, nothing outside the mod's folder. The
game's side is unchanged apart from calling it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SYabE7FCPgj3SaWeF92EML
This commit is contained in:
Robin MoellerandClaude Opus 5.5 committed 2026-10-23 15:50:55 +02:00
1 parent f763792732
commit 0706730e79
8 files changed
+246 -54

No files matched your search

+21
View File
@@ -1,5 +1,6 @@
#include "server_plugins.h"
#include "server_host.h"
#include "Extension/Plugins/plugin_content.h"
#include "Extension/Plugins/plugin_host.h"
#include "Extension/Plugins/plugin_manifest.h"
#include "Extension/Plugins/plugin_wire.h"
@@ -101,6 +102,15 @@ class ServerGame final : public Game {
}
return result;
}
// mods.content: every mod folder in the server's Mods, as a game reads its enabled mods. The
// server has no load order or mods.json of its own; the folders come by name, as for its maps.
std::string content_list(std::string_view folder, std::vector<ContentFile> &files) override {
content::list(mod_folders(), folder, files);
return {};
}
std::string content_read(std::string_view mod, std::string_view folder, std::string_view path, std::string &text) override {
return content::read(mod_folders(), mod, folder, path, text);
}
// Through the console's own ban and unban, so they keep its checks and save the bans file.
std::string ban(std::uint64_t player) override {
const auto answer = host_.command("ban " + std::to_string(player));
@@ -116,6 +126,15 @@ class ServerGame final : public Game {
}
private:
// Read again at most every 2 s: a plugin may list them every tick.
const std::vector<content::Mod> &mod_folders() {
const auto now = std::chrono::steady_clock::now();
if (!mods_read_ || now - *mods_read_ >= std::chrono::seconds(2)) {
mods_read_ = now;
mods_ = content::mod_folders(host_.config().mods);
}
return mods_;
}
bool present(std::uint64_t player) const {
for (const auto &p : host_.player_list())
if (p.id == player) return true;
@@ -123,6 +142,8 @@ class ServerGame final : public Game {
}
Host &host_;
ServerPlugins::Log log_;
std::vector<content::Mod> mods_;
std::optional<std::chrono::steady_clock::time_point> mods_read_;
};
class ServerEnvironment final : public Environment {