Files
MrMahoandClaude Opus 5.5 4e333c8250 0.3.0: Serverliste und BSO-Konten
- Heartbeat an die Website, Abmelden beim Beenden (--private, --public-address, --description, --master)
- Spiel-Tickets pruefen (Signatur der Website), Name aus dem Konto, --no-auth fuer LAN
- IPv4 und IPv6, Protokoll 2

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:10:51 +01:00

63 lines
2.8 KiB
C#

using System;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
namespace BSO.Net
{
/// <summary>
/// Spiel-Ticket der BSO-Website: base64url(JSON {u, id, iat, exp}) + "." + base64url(RSA-SHA256-Signatur). Der Launcher
/// holt es beim Spielen und schreibt es in BSO.json, die Mod schickt es im Hello mit. Die Mod liest es nur (Name, Ablauf),
/// pruefen kann es nur der Server mit dem oeffentlichen Schluessel der Website (Verify) - wer die Mod veraendert, kommt so
/// trotzdem nicht ohne gueltiges Konto auf einen Server.
/// </summary>
public sealed class Ticket
{
public string Username { get; private set; }
public string AccountId { get; private set; }
public DateTime Expires { get; private set; }
public bool Expired => DateTime.UtcNow >= Expires;
/// <summary>Inhalt lesen, ohne die Signatur zu pruefen. null = kein Ticket / kaputt.</summary>
public static Ticket Read(string ticket)
{
try
{
if (string.IsNullOrEmpty(ticket) || ticket.Length > 4096) return null;
string[] parts = ticket.Split('.');
if (parts.Length != 2) return null;
using var doc = JsonDocument.Parse(FromB64(parts[0]));
var r = doc.RootElement;
return new Ticket
{
Username = r.GetProperty("u").GetString(),
AccountId = r.GetProperty("id").GetString(),
Expires = DateTimeOffset.FromUnixTimeSeconds(r.GetProperty("exp").GetInt64()).UtcDateTime
};
}
catch { return null; }
}
/// <summary>Signatur pruefen (Server). Gueltig und nicht abgelaufen -> Ticket, sonst null mit Grund.</summary>
public static Ticket Verify(string ticket, RSA publicKey, out string error)
{
error = null;
Ticket t = Read(ticket);
if (t == null) { error = "Kein gueltiges BSO-Konto - bitte ueber den BSO Launcher starten"; return null; }
string[] parts = ticket.Split('.');
bool ok;
try { ok = publicKey.VerifyData(Encoding.ASCII.GetBytes(parts[0]), FromB64(parts[1]), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); }
catch { ok = false; }
if (!ok) { error = "Ungueltiges Spiel-Ticket - bitte ueber den BSO Launcher starten"; return null; }
if (t.Expired) { error = "Spiel-Ticket abgelaufen - bitte im BSO Launcher neu auf SPIELEN druecken"; return null; }
return t;
}
static byte[] FromB64(string s)
{
s = s.Replace('-', '+').Replace('_', '/');
return Convert.FromBase64String(s.PadRight(s.Length + (4 - s.Length % 4) % 4, '='));
}
}
}