using System.IO; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; using System.Text.Json.Serialization; namespace DorkNet.Launcher.Backend; /// Fetches the canonical branch-to-build mapping from /// versions.json on the public DorkNet repo's main /// branch. Falls back to the locally-cached copy when offline, so the /// launcher opens even with no internet (host mode just can't fetch /// new server builds). /// /// Source URL is hardcoded — it's the entire point of the /// launcher knowing where to look. To run against a fork's manifest, /// edit . public sealed class VersionsManifest { public const string ManifestUrl = "https://raw.githubusercontent.com/DorkSquadRR/DorkNet/main/versions.json"; private static readonly HttpClient Http = new() { Timeout = TimeSpan.FromSeconds(15), }; [JsonPropertyName("$schema_version")] public int SchemaVersion { get; set; } [JsonPropertyName("branches")] public List Branches { get; set; } = new(); public async Task FetchAsync(CancellationToken ct = default) { // Dev-time override: read manifest from a local file instead of // GitHub. Used for testing branch changes before they're public. var localPath = LocalOverrides.GetLocalManifestPath(); if (localPath is not null) { try { var json = await File.ReadAllTextAsync(localPath, ct); return JsonSerializer.Deserialize(json); } catch { // Bad local file — fall through to the network so the // user gets a real error instead of silent emptiness. } } try { var m = await Http.GetFromJsonAsync(ManifestUrl, ct); if (m is not null) { File.WriteAllText(AppPaths.ManifestCacheFile, JsonSerializer.Serialize(m)); } return m; } catch { // Offline / 404 / parse failure — fall back to last-seen // cache. Returns null only when there's no cache either // (first launch + no internet — the launcher reports the // failure to the user in that case). return LoadCached(); } } public static VersionsManifest? LoadCached() { if (!File.Exists(AppPaths.ManifestCacheFile)) return null; try { var json = File.ReadAllText(AppPaths.ManifestCacheFile); return JsonSerializer.Deserialize(json); } catch { return null; } } } public sealed class VersionEntry { [JsonPropertyName("branch")] public string Branch { get; set; } = ""; [JsonPropertyName("client_build")] public string ClientBuild { get; set; } = ""; [JsonPropertyName("alt_builds")] public List AltBuilds { get; set; } = new(); [JsonPropertyName("version_key")] public string VersionKey { get; set; } = ""; [JsonPropertyName("release_tag_prefix")] public string ReleaseTagPrefix { get; set; } = ""; [JsonPropertyName("supported")] public bool Supported { get; set; } }