diff --git a/launcher/Backend/LocalOverrides.cs b/launcher/Backend/LocalOverrides.cs new file mode 100644 index 0000000..dd4aa05 --- /dev/null +++ b/launcher/Backend/LocalOverrides.cs @@ -0,0 +1,80 @@ +using System.IO; + +namespace DorkNet.Launcher.Backend; + +/// Local dev-time overrides that bypass the GitHub-backed +/// manifest + releases fetches. Set via environment variables before +/// launching dorknet.exe: +/// +/// +/// DORKNET_LOCAL_MANIFEST = full path to a +/// versions.json file. When set + exists, +/// reads it from disk and +/// skips the GitHub round-trip. +/// DORKNET_LOCAL_RELEASES = path to a directory whose +/// children are {version_key}/server/ and +/// {version_key}/patcher/. When the launcher would normally +/// download a server / patcher zip, it points at these in-place +/// instead — no copying, no markers, just read the dir. +/// +/// +/// Both overrides are no-ops when the env var is unset or the +/// path doesn't exist on disk, so they're safe to leave set +/// permanently in a dev profile. +public static class LocalOverrides +{ + public const string ManifestEnvVar = "DORKNET_LOCAL_MANIFEST"; + public const string ReleasesEnvVar = "DORKNET_LOCAL_RELEASES"; + + /// Returns the configured manifest path if the env var is + /// set AND the file exists; null otherwise. Caller decides whether + /// to fall back to the network. + public static string? GetLocalManifestPath() + { + var path = Environment.GetEnvironmentVariable(ManifestEnvVar); + if (string.IsNullOrEmpty(path)) return null; + return File.Exists(path) ? path : null; + } + + /// Returns the configured releases-root if the env var is + /// set AND the directory exists; null otherwise. + public static string? GetLocalReleasesRoot() + { + var path = Environment.GetEnvironmentVariable(ReleasesEnvVar); + if (string.IsNullOrEmpty(path)) return null; + return Directory.Exists(path) ? path : null; + } + + /// Returns the local server-dir for + /// if the releases-root override is set and the subfolder exists. + /// Convention: {root}/{version_key}/server/. + public static string? GetLocalServerDir(string versionKey) + { + var root = GetLocalReleasesRoot(); + if (root is null) return null; + var dir = Path.Combine(root, versionKey, "server"); + return Directory.Exists(dir) ? dir : null; + } + + /// Same as but for the + /// client patcher. Convention: {root}/{version_key}/patcher/. + public static string? GetLocalPatcherDir(string versionKey) + { + var root = GetLocalReleasesRoot(); + if (root is null) return null; + var dir = Path.Combine(root, versionKey, "patcher"); + return Directory.Exists(dir) ? dir : null; + } + + /// Single-line description of what overrides are active, + /// for surfacing in the Settings view. Empty string if none. + public static string DescribeActive() + { + var parts = new List(); + var m = GetLocalManifestPath(); + if (m is not null) parts.Add($"manifest -> {m}"); + var r = GetLocalReleasesRoot(); + if (r is not null) parts.Add($"releases -> {r}"); + return string.Join("\n", parts); + } +} diff --git a/launcher/Backend/ReleaseDownloader.cs b/launcher/Backend/ReleaseDownloader.cs index a84d6fc..57679a1 100644 --- a/launcher/Backend/ReleaseDownloader.cs +++ b/launcher/Backend/ReleaseDownloader.cs @@ -44,6 +44,11 @@ public sealed class ReleaseDownloader IProgress? progress, CancellationToken ct = default) { + // Dev-time override — use a local server build in-place rather + // than downloading from GitHub. Skips cache entirely. + var local = LocalOverrides.GetLocalServerDir(version.VersionKey); + if (local is not null) return local; + var dir = Path.Combine(AppPaths.ServersRoot, version.VersionKey); var marker = Path.Combine(dir, ".dorknet-version"); // If the marker file exists, the server is already unpacked @@ -73,6 +78,11 @@ public sealed class ReleaseDownloader IProgress? progress, CancellationToken ct = default) { + // Dev-time override — same as EnsureServerAsync but for the + // client-patcher dir. + var local = LocalOverrides.GetLocalPatcherDir(version.VersionKey); + if (local is not null) return local; + var dir = Path.Combine(AppPaths.PatchersRoot, version.VersionKey); var marker = Path.Combine(dir, ".dorknet-version"); if (File.Exists(marker)) return dir; diff --git a/launcher/Backend/VersionsManifest.cs b/launcher/Backend/VersionsManifest.cs index 27aa9ee..66899d0 100644 --- a/launcher/Backend/VersionsManifest.cs +++ b/launcher/Backend/VersionsManifest.cs @@ -30,6 +30,23 @@ public sealed class VersionsManifest 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); diff --git a/launcher/MainWindow.xaml b/launcher/MainWindow.xaml index 9c2500d..97b7a3d 100644 --- a/launcher/MainWindow.xaml +++ b/launcher/MainWindow.xaml @@ -661,6 +661,24 @@ + + + + + + + + + diff --git a/launcher/MainWindow.xaml.cs b/launcher/MainWindow.xaml.cs index 0aafcca..9b245c2 100644 --- a/launcher/MainWindow.xaml.cs +++ b/launcher/MainWindow.xaml.cs @@ -166,6 +166,17 @@ public partial class MainWindow : Window $"Patchers: {AppPaths.PatchersRoot}\n" + $"Logs: {AppPaths.LogsRoot}"; + var overrides = LocalOverrides.DescribeActive(); + if (!string.IsNullOrEmpty(overrides)) + { + LocalOverridesPanel.Visibility = Visibility.Visible; + LocalOverridesText.Text = overrides; + } + else + { + LocalOverridesPanel.Visibility = Visibility.Collapsed; + } + RefreshJoinReadiness(); ShowView(PickRoutingTarget()); }