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.
DorkNet Launcher
Single-download desktop launcher for self-hosting / joining DorkNet servers. PhotinoNET-wrapped WebView2 with the orchestration logic in C#.
This README is for developers of the launcher. End-users see
the main README and download a prebuilt .exe from
Releases.
What it does
- Host mode: fetches the matching server binary from GitHub Releases for the user's picked Rec Room version, runs it as a subprocess, opens a Cloudflare quick-tunnel, patches the user's Rec Room install, hands the user a shareable join code.
- Join mode: parses the join code, fetches the matching client patcher from GitHub Releases, patches the user's Rec Room install to point at the host.
How it's wired
launcher/
├── DorkNet.Launcher.csproj .NET 9 single-file self-contained exe (Photino.NET dep)
├── Program.cs PhotinoNET window + message-bridge wiring
├── app.manifest Windows DPI-awareness manifest
├── Backend/ Orchestration (C#)
│ ├── AppPaths.cs %APPDATA%\DorkNet layout
│ ├── AppState.cs Persisted user choices (mode, paths, Photon AppId)
│ ├── VersionsManifest.cs Fetches versions.json from main
│ ├── ReleaseDownloader.cs GitHub Releases API + artifact unpack
│ ├── ServerProcess.cs Spawns the downloaded server binary
│ ├── ClientPatcher.cs Invokes install-plugin.ps1 from the unpacked patcher zip
│ ├── Tunnel.cs Wraps `cloudflared tunnel --url …`
│ ├── RecRoomPicker.cs Windows folder dialog
│ ├── JoinCode.cs base64url-encoded {host, version, photonAppId, …}
│ └── MessageBridge.cs JSON envelope router between JS ↔ C#
└── ui/ Single-file frontend (no build step)
├── index.html
├── style.css
└── app.js
Message protocol: both directions use
{ "type": "command-or-event-name", "payload": { ... } }. New
commands go in MessageBridge.HandleAsync's switch; new outbound
events use bridge.SendEvent(name, payload).
Build + run
cd launcher
dotnet run
Watch the launcher window open. C# stderr lands in the console; the WebView2 devtools open with F12 (PhotinoNET passes them through).
Publish a self-contained .exe
dotnet publish -c Release
# Output: bin\Release\net9.0-windows\win-x64\publish\dorknet.exe (~80 MB)
The published exe is what ships in GitHub Releases on main (use the
Easy launcher release workflow when it exists).
Dependencies on per-version branches
The launcher fetches release artifacts from GitHub Releases tagged on the per-version branches. See RELEASES.md for the exact naming convention each branch must follow.
Adding a new command
- Add a new
caseinMessageBridge.HandleAsync. - Implement the work in a
Backend/*class. - In
ui/app.js, callbridge.send({ type: 'your-command', payload: {...} })from wherever the user triggers it. - If the command needs to push events back, call
bridge.SendEvent('your-event-name', { ... })from the handler and add a case inapp.js'shandleEventswitch.
Keep the bridge handlers tight — they're routing + state mutation;
long work goes in Backend/*.