Server 2.10.3: browse the MSCO mod list in the web panel
Mods -> MSCO mod list: search the approved mods from msco.mbm.wtf/mods and add or update one with a click. The server lists and downloads only from the official keymaster (never from a URL in the answer), checks the SHA-256 and stores it in data/mods like an uploaded mod. Admins only. 6 new tests (154 pass). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MSCO.Server;
|
||||
|
||||
/// <summary>
|
||||
/// Mod-Liste des Keymasters (msco.mbm.wtf/mods: Mods von Spielern, von MSCO-Admins geprüft) im Web-Panel durchsuchen
|
||||
/// und mit einem Klick als Server-Mod übernehmen. Geladen wird nur vom offiziellen Master, die SHA-256 wird geprüft;
|
||||
/// danach liegt die Mod wie eine hochgeladene in data/mods (Datei <id>.dll).
|
||||
/// </summary>
|
||||
public static class ModCatalog
|
||||
{
|
||||
public sealed record Entry(
|
||||
[property: JsonPropertyName("id")] string Id,
|
||||
[property: JsonPropertyName("name")] string Name,
|
||||
[property: JsonPropertyName("description")] string Description,
|
||||
[property: JsonPropertyName("author")] string Author,
|
||||
[property: JsonPropertyName("version")] string Version,
|
||||
[property: JsonPropertyName("sha256")] string Sha256,
|
||||
[property: JsonPropertyName("size")] long Size,
|
||||
[property: JsonPropertyName("changelog")] string Changelog,
|
||||
[property: JsonPropertyName("updatedUtc")] string UpdatedUtc,
|
||||
[property: JsonPropertyName("downloads")] int Downloads);
|
||||
|
||||
record ListResponse([property: JsonPropertyName("mods")] List<Entry> Mods);
|
||||
|
||||
static readonly HttpClient http = CreateHttp();
|
||||
|
||||
/// <summary>Adresse der Mod-Liste: der offizielle Master (nur die Tests setzen hier einen eigenen).</summary>
|
||||
public static string BaseUrl { get; set; } = MasterServer.Url;
|
||||
|
||||
static HttpClient CreateHttp()
|
||||
{
|
||||
var c = new HttpClient { Timeout = TimeSpan.FromSeconds(20) };
|
||||
c.DefaultRequestHeaders.UserAgent.ParseAdd("MSCO-Server");
|
||||
return c;
|
||||
}
|
||||
|
||||
/// <summary>Freigegebene Mods vom Keymaster. Wirft bei Netzwerkfehlern.</summary>
|
||||
public static async Task<List<Entry>> ListAsync()
|
||||
{
|
||||
var res = await http.GetFromJsonAsync<ListResponse>(BaseUrl + "/api/mods");
|
||||
return res?.Mods ?? new List<Entry>();
|
||||
}
|
||||
|
||||
/// <summary>Mod aus der Liste in data/mods übernehmen (bzw. aktualisieren). Fehlertext oder null.</summary>
|
||||
public static async Task<string> InstallAsync(string dataDir, string id)
|
||||
{
|
||||
List<Entry> list;
|
||||
try { list = await ListAsync(); }
|
||||
catch (Exception e) { return "The MSCO mod list is not reachable (" + e.Message + ")"; }
|
||||
var entry = list.FirstOrDefault(m => m.Id == id);
|
||||
if (entry == null) return "This mod is not in the MSCO mod list (any more)";
|
||||
if (entry.Size > ModStore.MaxModBytes) return "This mod is too large";
|
||||
|
||||
// Immer vom offiziellen Master laden - nie von einer Adresse aus der Antwort
|
||||
string url = BaseUrl + "/mods/download/" + Uri.EscapeDataString(entry.Id) + "/" + Uri.EscapeDataString(entry.Version);
|
||||
byte[] data;
|
||||
try
|
||||
{
|
||||
using var res = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
|
||||
if (!res.IsSuccessStatusCode) return "Download failed (HTTP " + (int)res.StatusCode + ")";
|
||||
await using var stream = await res.Content.ReadAsStreamAsync();
|
||||
using var ms = new MemoryStream();
|
||||
var chunk = new byte[81920];
|
||||
int read;
|
||||
while ((read = await stream.ReadAsync(chunk)) > 0)
|
||||
{
|
||||
ms.Write(chunk, 0, read);
|
||||
if (ms.Length > ModStore.MaxModBytes) return "This mod is too large";
|
||||
}
|
||||
data = ms.ToArray();
|
||||
}
|
||||
catch (Exception e) { return "Download failed (" + e.Message + ")"; }
|
||||
|
||||
string sha = Convert.ToHexString(SHA256.HashData(data)).ToLowerInvariant();
|
||||
if (!string.Equals(sha, entry.Sha256, StringComparison.OrdinalIgnoreCase)) return "The download is damaged (checksum mismatch) - try again";
|
||||
return ModStore.Add(dataDir, entry.Id + ".dll", data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user