diff --git a/launcher/Backend/LocalOverrides.cs b/launcher/Backend/LocalOverrides.cs
index b627557..6775714 100644
--- a/launcher/Backend/LocalOverrides.cs
+++ b/launcher/Backend/LocalOverrides.cs
@@ -26,14 +26,21 @@ 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.
+ /// Returns a local manifest path to use instead of the
+ /// GitHub fetch. Prefers DORKNET_LOCAL_MANIFEST when set; if
+ /// that's unset, falls back to a side-by-side versions.json
+ /// next to dorknet.exe (so dev checkouts against a private
+ /// repo work without env-var wiring — the launcher's csproj copies
+ /// the repo-root versions.json to its bin output on build).
public static string? GetLocalManifestPath()
{
var path = Environment.GetEnvironmentVariable(ManifestEnvVar);
- if (string.IsNullOrEmpty(path)) return null;
- return File.Exists(path) ? path : null;
+ if (!string.IsNullOrEmpty(path) && File.Exists(path)) return path;
+
+ var sideBySide = Path.Combine(AppContext.BaseDirectory, "versions.json");
+ if (File.Exists(sideBySide)) return sideBySide;
+
+ return null;
}
/// Returns the configured releases-root if the env var is
@@ -87,4 +94,23 @@ public static class LocalOverrides
? $"{envVar} -> {raw} [active]"
: $"{envVar} -> {raw} [SET BUT NOT FOUND on disk]");
}
+
+ /// Best-effort description of where the manifest is
+ /// actually coming from this session. Mainly for the Settings UI
+ /// so the user can tell at a glance "side-by-side dev manifest"
+ /// vs "live GitHub fetch" vs "broken env-var path".
+ public static string DescribeManifestSource()
+ {
+ var env = Environment.GetEnvironmentVariable(ManifestEnvVar);
+ if (!string.IsNullOrEmpty(env))
+ {
+ return File.Exists(env)
+ ? $"manifest source: {env} (env var)"
+ : $"manifest source: {env} (env var, NOT FOUND — falling back)";
+ }
+ var sideBySide = Path.Combine(AppContext.BaseDirectory, "versions.json");
+ if (File.Exists(sideBySide))
+ return $"manifest source: {sideBySide} (side-by-side fallback)";
+ return "manifest source: live GitHub fetch (no local override)";
+ }
}
diff --git a/launcher/DorkNet.Launcher.csproj b/launcher/DorkNet.Launcher.csproj
index 2eaf223..5b09e99 100644
--- a/launcher/DorkNet.Launcher.csproj
+++ b/launcher/DorkNet.Launcher.csproj
@@ -27,4 +27,17 @@
+
+
+
+ versions.json
+ PreserveNewest
+
+
+
diff --git a/launcher/MainWindow.xaml.cs b/launcher/MainWindow.xaml.cs
index 9b245c2..b081528 100644
--- a/launcher/MainWindow.xaml.cs
+++ b/launcher/MainWindow.xaml.cs
@@ -184,13 +184,25 @@ public partial class MainWindow : Window
private async Task RefreshManifestAsync()
{
_manifest = await new VersionsManifest().FetchAsync();
+ VersionSelect.Items.Clear();
if (_manifest is null)
{
- HostStatusText.Text = "Couldn't load versions.json from GitHub. " +
- "Check internet, then reopen.";
+ // Empty placeholder so the user sees *something* in the
+ // dropdown instead of a silent blank — helps when the public
+ // repo is unreachable (e.g. private repo + no env override).
+ var placeholder = new ComboBoxItem
+ {
+ Content = "(no versions — check internet or set DORKNET_LOCAL_MANIFEST)",
+ IsEnabled = false,
+ Tag = null,
+ };
+ VersionSelect.Items.Add(placeholder);
+ placeholder.IsSelected = true;
+ HostStatusText.Text = "Couldn't load versions.json. " +
+ "Either the repo isn't reachable, or DORKNET_LOCAL_MANIFEST " +
+ "points at a missing/invalid file — check Settings.";
return;
}
- VersionSelect.Items.Clear();
foreach (var v in _manifest.Branches.Where(b => b.Supported))
{
var item = new ComboBoxItem