From e3a4cd109269bd3ea695a1197762deb943fb1c96 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sun, 1 Feb 2026 17:40:27 -0500 Subject: [PATCH] Fix ss13 Playercounts --- .../ServerStatus/ClassicServerListCache.cs | 136 +++--------------- 1 file changed, 22 insertions(+), 114 deletions(-) diff --git a/SS14.Launcher/Models/ServerStatus/ClassicServerListCache.cs b/SS14.Launcher/Models/ServerStatus/ClassicServerListCache.cs index 47a3dc4..57f090b 100644 --- a/SS14.Launcher/Models/ServerStatus/ClassicServerListCache.cs +++ b/SS14.Launcher/Models/ServerStatus/ClassicServerListCache.cs @@ -5,6 +5,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using System.Net.Http.Json; +using System.Text.Json.Serialization; using Serilog; using Splat; using SS14.Launcher.Utility; @@ -28,8 +30,10 @@ public async Task Refresh() { try { - var response = await _http.GetStringAsync("http://www.byond.com/games/exadv1/spacestation13?format=text"); - var servers = ParseByondResponse(response); + var hubResponse = await _http.GetFromJsonAsync("https://node.goonhub.com/hub"); + if (hubResponse == null) return; + + var servers = ParseGoonhubResponse(hubResponse); Avalonia.Threading.Dispatcher.UIThread.Post(() => { @@ -46,101 +50,17 @@ public async Task Refresh() } } - private List ParseByondResponse(string response) + private List ParseGoonhubResponse(GoonhubHubResponse hubResponse) { var list = new List(); - using var reader = new StringReader(response); - - string? line; - string? currentName = null; - string? currentUrl = null; - string? currentStatus = null; - int currentPlayers = 0; - - // Simple state machine to parse the text format - // The format uses 'world/ID' blocks for servers. - - bool inServerBlock = false; - - while ((line = reader.ReadLine()) != null) - { - var trimmed = line.Trim(); - if (string.IsNullOrWhiteSpace(trimmed)) continue; - - if (trimmed.StartsWith("world/")) - { - // If we were parsing a server, save it - if (inServerBlock && currentUrl != null) - { - // Name might be missing, try to extract from status or use URL - var name = currentName ?? ExtractNameFromStatus(currentStatus) ?? "Unknown Server"; - var roundTime = ExtractRoundTimeFromStatus(currentStatus); - list.Add(new ClassicServerStatusData(name, currentUrl, currentPlayers, CleanStatus(currentStatus, name) ?? "", roundTime ?? "In-Lobby")); - } - - // Reset for new server - inServerBlock = true; - currentName = null; - currentUrl = null; - currentStatus = null; - currentPlayers = 0; - } - else if (inServerBlock) - { - if (trimmed.StartsWith("name =")) - { - currentName = ParseStringValue(trimmed); - } - else if (trimmed.StartsWith("url =")) - { - currentUrl = ParseStringValue(trimmed); - } - else if (trimmed.StartsWith("status =")) - { - currentStatus = ParseStringValue(trimmed); - } - else if (trimmed.StartsWith("players = list(")) - { - // "players = list("Bob","Alice")" - // Just count the commas + 1, correcting for empty list "list()" - var content = trimmed.Substring("players = list(".Length); - if (content.EndsWith(")")) - { - content = content.Substring(0, content.Length - 1); - if (string.IsNullOrWhiteSpace(content)) - { - currentPlayers = 0; - } - else - { - // A simple Count(',') + 1 is risky if names contain commas, but usually they are quoted. - // However, parsing full CSV is safer but 'Splitting by ",' might be enough? - // Let's iterate and count quoted segments. - // Or simpler: Splitting by ',' is mostly fine for SS13 ckeys. - currentPlayers = content.Split(',').Length; - } - } - } - else if (trimmed.StartsWith("players =")) - { - // Fallback for simple number if ever used - var parts = trimmed.Split('='); - if (parts.Length > 1 && int.TryParse(parts[1].Trim(), out var p)) - { - currentPlayers = p; - } - } - } - } - - // Add the last one if exists - if (inServerBlock && currentUrl != null) + foreach (var server in hubResponse.Response) { - var name = currentName ?? ExtractNameFromStatus(currentStatus) ?? "Unknown Server"; - var roundTime = ExtractRoundTimeFromStatus(currentStatus); - list.Add(new ClassicServerStatusData(name, currentUrl, currentPlayers, CleanStatus(currentStatus, name) ?? "", roundTime ?? "In-Lobby")); + var name = ExtractNameFromStatus(server.Status) ?? "Unknown Server"; + var roundTime = ExtractRoundTimeFromStatus(server.Status) ?? "In-Lobby"; + var address = $"byond://BYOND.world.{server.UrlId}"; + + list.Add(new ClassicServerStatusData(name, address, server.Players, CleanStatus(server.Status, name) ?? "", roundTime)); } - return list; } @@ -199,27 +119,15 @@ private List ParseByondResponse(string response) return s; } - private string ParseStringValue(string line) + private sealed class GoonhubHubResponse + { + [JsonPropertyName("response")] public List Response { get; set; } = new(); + } + + private sealed class GoonhubServerEntry { - // format: key = "value" - var idx = line.IndexOf('"'); - if (idx == -1) return string.Empty; - var lastIdx = line.LastIndexOf('"'); - if (lastIdx <= idx) return string.Empty; - - // Extract content inside quotes - var inner = line.Substring(idx + 1, lastIdx - idx - 1); - - // Unescape BYOND/C string escapes - // \" -> " - // \n -> newline - // \\ -> \ - // The most critical one is \n showing up as literal \n in UI. - - // Simple manual unescape for common sequences - return inner.Replace("\\\"", "\"") - .Replace("\\n", "\n") - .Replace("\\\\", "\\") - .Replace("\\t", "\t"); + [JsonPropertyName("urlId")] public string UrlId { get; set; } = ""; + [JsonPropertyName("players")] public int Players { get; set; } + [JsonPropertyName("status")] public string Status { get; set; } = ""; } }