mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
df9dac053d
Single-download .NET 9 self-contained launcher for hosting / joining
DorkNet servers. Reads versions.json from main to discover supported
client builds, fetches the matching server binary + client patcher
from GitHub Releases per branch, manages the server subprocess +
Cloudflare quick-tunnel, and wraps it all in a PhotinoNET window.
Code layout:
- Program.cs PhotinoNET window + message bridge bootstrap
- Backend/ AppPaths, AppState, VersionsManifest,
ReleaseDownloader, ServerProcess, ClientPatcher,
Tunnel, RecRoomPicker, JoinCode, MessageBridge
- ui/ index.html + style.css + app.js (no build step)
- RELEASES.md GitHub Releases artifact contract per-version
branches must publish to
Per-version branches need to ship two release assets per tag:
- dorknet-server-{version_key}-win-x64.zip
- dorknet-clientpatch-{version_key}.zip
(see launcher/RELEASES.md for the full naming + arg contract)
Photon AppIds, JWT secret, and the picked Rec Room path stay
per-machine; the launcher never bundles them.
89 lines
3.3 KiB
C#
89 lines
3.3 KiB
C#
using System.Diagnostics;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace DorkNet.Launcher.Backend;
|
|
|
|
/// <summary>Wraps Cloudflare's <c>cloudflared</c> quick-tunnel so the
|
|
/// host doesn't need to open ports / configure DNS. Each launch gets
|
|
/// a fresh random <c>*.trycloudflare.com</c> URL.
|
|
///
|
|
/// <para>Requires <c>cloudflared.exe</c> on PATH or alongside the
|
|
/// launcher binary. If absent, returns a clear error and the host
|
|
/// can fall back to LAN-only play.</para></summary>
|
|
public sealed class Tunnel : IAsyncDisposable
|
|
{
|
|
private Process? _process;
|
|
private TaskCompletionSource<string>? _urlReady;
|
|
public string? PublicUrl { get; private set; }
|
|
|
|
public async Task<string> StartAsync(int localPort, CancellationToken ct = default)
|
|
{
|
|
var exe = FindCloudflared() ??
|
|
throw new FileNotFoundException(
|
|
"cloudflared not found. Install from " +
|
|
"https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/ " +
|
|
"and put cloudflared.exe on PATH (or next to the DorkNet launcher).");
|
|
|
|
_urlReady = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = exe,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
};
|
|
psi.ArgumentList.Add("tunnel");
|
|
psi.ArgumentList.Add("--no-autoupdate");
|
|
psi.ArgumentList.Add("--url");
|
|
psi.ArgumentList.Add($"http://127.0.0.1:{localPort}");
|
|
|
|
_process = Process.Start(psi) ??
|
|
throw new InvalidOperationException("Failed to spawn cloudflared.exe");
|
|
_process.OutputDataReceived += (_, e) => ParseLine(e.Data);
|
|
_process.ErrorDataReceived += (_, e) => ParseLine(e.Data);
|
|
_process.BeginOutputReadLine();
|
|
_process.BeginErrorReadLine();
|
|
|
|
using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
|
timeoutCts.CancelAfter(TimeSpan.FromSeconds(30));
|
|
PublicUrl = await _urlReady.Task.WaitAsync(timeoutCts.Token);
|
|
return PublicUrl;
|
|
}
|
|
|
|
private static readonly Regex UrlRegex = new(
|
|
@"https://[a-z0-9\-]+\.trycloudflare\.com",
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
private void ParseLine(string? line)
|
|
{
|
|
if (line is null || _urlReady is null || _urlReady.Task.IsCompleted) return;
|
|
var match = UrlRegex.Match(line);
|
|
if (match.Success) _urlReady.TrySetResult(match.Value);
|
|
}
|
|
|
|
private static string? FindCloudflared()
|
|
{
|
|
var sideBySide = Path.Combine(AppContext.BaseDirectory, "cloudflared.exe");
|
|
if (File.Exists(sideBySide)) return sideBySide;
|
|
var onPath = Environment.GetEnvironmentVariable("PATH")?
|
|
.Split(';')
|
|
.Select(p => Path.Combine(p, "cloudflared.exe"))
|
|
.FirstOrDefault(File.Exists);
|
|
return onPath;
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_process is null) return;
|
|
try
|
|
{
|
|
if (!_process.HasExited) _process.Kill(entireProcessTree: true);
|
|
await _process.WaitForExitAsync();
|
|
}
|
|
catch { }
|
|
_process.Dispose();
|
|
}
|
|
}
|