using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace MSCO.Server; /// /// Letzte erfolgreiche Lizenzprüfung beim Master (data/license.json). Ist der Master mal nicht erreichbar oder lehnt /// den Schlüssel kurz ab (z.B. nach einem Umzug ohne keys.json), läuft bzw. startet der Server damit weiter - bis /// GraceHours ohne Bestätigung vergangen sind (fest, nicht einstellbar). Gespeichert wird nur ein Hash des Schlüssels. /// public static class LicenseCache { /// So lange startet ein Server ohne Bestätigung des Masters noch (ein laufender läuft bis zum Neustart weiter). public static readonly TimeSpan Grace = TimeSpan.FromHours(72); public sealed class Entry { public string KeyHash { get; set; } public string Owner { get; set; } public string Label { get; set; } public DateTime LastConfirmedUtc { get; set; } } static string PathOf(string dataDir) => Path.Combine(dataDir, "license.json"); static string Hash(string key) => Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(key ?? ""))).ToLowerInvariant(); /// Gemerkte Bestätigung für genau diesen Schlüssel (null = keine). public static Entry Load(string dataDir, string key) { try { string path = PathOf(dataDir); if (!File.Exists(path)) return null; var entry = JsonSerializer.Deserialize(File.ReadAllText(path)); return entry != null && entry.KeyHash == Hash(key) ? entry : null; } catch { return null; } } public static void Save(string dataDir, string key, string owner, string label, DateTime confirmedUtc) { try { var entry = new Entry { KeyHash = Hash(key), Owner = owner, Label = label, LastConfirmedUtc = confirmedUtc }; string path = PathOf(dataDir), tmp = path + ".tmp"; File.WriteAllText(tmp, JsonSerializer.Serialize(entry, new JsonSerializerOptions { WriteIndented = true })); File.Move(tmp, path, true); } catch (Exception e) { GameServer.Log("Could not remember the license check: " + e.Message); } } /// "3 h", "2 d 4 h" ... public static string Span(TimeSpan t) => t.TotalHours < 1 ? $"{Math.Max(1, (int)t.TotalMinutes)} min" : t.TotalDays < 1 ? $"{(int)t.TotalHours} h" : $"{(int)t.TotalDays} d {t.Hours} h"; }