Server 2.10.0: distribute MSCO mods from data/mods

- ModStore: mods in data/mods (id, name, version, SHA-256), console "mods [reload]"
- ModList is sent before Welcome, files on request in 60 KB parts (once per connection)
- ModMessage relayed between players (max 64 KB)
- Heartbeat reports the mods to the keymaster (automatic "modded" tag)
- Web panel page "Mods": upload, remove, reload (DE/EN)
- Tests for list, download, checksum, relay and limits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 01:00:23 +02:00
co-authored by Claude Opus 5.5
parent 5e07fe1001
commit 3a79c928d1
10 changed files with 355 additions and 8 deletions
+6 -3
View File
@@ -22,7 +22,10 @@ public sealed class MasterClient
public bool Unreachable { get; init; }
}
record HeartbeatBody(string Key, string Name, string Description, string Address, int Port, int Players, int MaxPlayers,
string Version, bool Public, bool RequireSteam, List<string> Tags);
string Version, bool Public, bool RequireSteam, List<string> Tags, List<ModInfo> Mods);
/// <summary>Mod des Servers für die Serverliste (die Dateien selbst bekommen Spieler direkt vom Server).</summary>
public sealed record ModInfo(string Id, string Name, string Version);
readonly HttpClient http = new() { Timeout = TimeSpan.FromSeconds(10) };
readonly string baseUrl;
@@ -67,13 +70,13 @@ public sealed class MasterClient
catch { }
}
public async Task<HeartbeatResult> HeartbeatAsync(ServerConfig config, int players, string version)
public async Task<HeartbeatResult> HeartbeatAsync(ServerConfig config, int players, string version, List<ModInfo> mods = null)
{
try
{
var body = new HeartbeatBody(config.LicenseKey, config.Name, config.Description, config.PublicAddress, config.Port,
players, config.MaxPlayers, version, config.Public, config.RequireSteam,
ServerConfig.NormalizeTags(config.Tags, out _));
ServerConfig.NormalizeTags(config.Tags, out _), mods ?? new List<ModInfo>());
var response = await http.PostAsJsonAsync(baseUrl + "/api/servers/heartbeat", body);
int status = (int)response.StatusCode;
if (!response.IsSuccessStatusCode) return new HeartbeatResult(status, null, false, null, 0);