feat(launcher): native Windows support

Port the egui launcher to run natively on Windows (no Wine/Proton). The GUI,
launch state machine, config, health/account monitors, and openfut.cfg writing
are unchanged and cross-platform; only the effect layer is branched:

- game_launch: cfg(windows) launch spawns the game executable directly with its
  working dir (the version.dll hijack loads from the game dir; no WINEDLLOVERRIDES,
  Wine prefix, or licence regen). Requires the launcher to run elevated so the
  child inherits admin. Linux Proton path gated cfg(unix).
- arm: cfg(windows) is a no-op (routing is openfut.cfg, written by the client-files
  step; no ptrace_scope/DNAT/hosts). Linux arming gated cfg(unix).
- local_services: on Windows LSX/autopatch are in-process (stp-origin_emu.dll +
  version.dll hook), so ensure_running reports ready without spawning. Gated the
  unix-only CommandExt/process_group.
- preflight: cfg(windows) run() keeps only backend-reachable + hook-config checks.
- config: GameProfile configured()/validate() accept a runner-less Windows profile.

theme: fix a latent cross-platform panic — egui 0.29 keeps a Style per theme, so
set_style only reached the active one and TextStyle::resolve("Hero") panicked when
the other theme rendered. Install the full style into both themes and pin Dark.

Cross-built for x86_64-pc-windows-gnu; Linux build + 75 tests unchanged.
This commit is contained in:
funman300
2026-08-20 19:21:01 +00:00
parent 8d5bb6202a
commit 057cf92c3b
6 changed files with 158 additions and 23 deletions
+14 -1
View File
@@ -25,6 +25,7 @@ use crate::config::LauncherConfig;
/// Accept only hostname/IP characters. These values come from config fields that
/// are ever only IPs or hostnames, so a surprising character is a bug — reject it
/// rather than try to escape it into an elevated shell command.
#[cfg(unix)]
fn safe_host(s: &str) -> anyhow::Result<&str> {
let t = s.trim();
if t.is_empty() {
@@ -41,6 +42,7 @@ fn safe_host(s: &str) -> anyhow::Result<&str> {
/// Build the privileged arming script. Pure and unit-tested; the effectful part
/// ([`arm`]) only validates config and hands this to the elevated runner.
#[cfg(unix)]
pub(crate) fn arming_script(
server: &str,
redirector_port: u16,
@@ -84,6 +86,7 @@ pub(crate) fn arming_script(
/// Human-readable list of what [`arm`] changed, in the order the script applies
/// it. Logged by the UI so the user sees exactly what was set — not just that
/// "something" ran under `pkexec`.
#[cfg(unix)]
pub(crate) fn arming_summary(
server: &str,
redirector_port: u16,
@@ -103,6 +106,16 @@ pub(crate) fn arming_summary(
/// Arm the client from config, under one elevated prompt. Requires the same
/// fields preflight reads; a missing one is a clear error, never a silent
/// loopback fallback. Returns the applied changes for the UI to surface.
/// On native Windows there is nothing to arm: routing is the `openfut.cfg` the
/// client-files step writes into the game directory (read by the version.dll
/// hook), and there is no `ptrace_scope`, DNAT, or `/etc/hosts` to set. Returns
/// no changes so the launch sequence treats client preparation as satisfied.
#[cfg(windows)]
pub fn arm(_cfg: &LauncherConfig) -> anyhow::Result<Vec<String>> {
Ok(Vec::new())
}
#[cfg(unix)]
pub fn arm(cfg: &LauncherConfig) -> anyhow::Result<Vec<String>> {
let server = cfg.openfut_server_host.trim();
if server.is_empty() {
@@ -128,7 +141,7 @@ pub fn arm(cfg: &LauncherConfig) -> anyhow::Result<Vec<String>> {
))
}
#[cfg(test)]
#[cfg(all(test, unix))]
mod tests {
use super::*;