3b965f1f3f
Adds Step 2 in the Setup tab: grants cap_net_bind_service to the bridge binary via pkexec/sudo so it can bind port 443 directly. On success, automatically updates bridge_listen_addr to 0.0.0.0:443 — no iptables rules required. Step re-detects cap state on each frame so it stays accurate after a rebuild clears the capability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
139 lines
5.0 KiB
Rust
139 lines
5.0 KiB
Rust
use std::{path::{Path, PathBuf}, process::Command};
|
|
|
|
// ── Port 443 capability ───────────────────────────────────────────────────────
|
|
|
|
/// Check whether the bridge binary already has cap_net_bind_service set.
|
|
pub fn bridge_has_cap443(binary: &Path) -> bool {
|
|
std::process::Command::new("getcap")
|
|
.arg(binary)
|
|
.output()
|
|
.map(|o| String::from_utf8_lossy(&o.stdout).contains("cap_net_bind_service"))
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
/// Grant cap_net_bind_service to the bridge binary so it can bind port 443
|
|
/// without running as root. Uses pkexec (or sudo as fallback).
|
|
pub fn setcap_bridge_443(binary: &Path) -> anyhow::Result<()> {
|
|
let script = format!(
|
|
"setcap cap_net_bind_service=+ep '{}'",
|
|
binary.to_string_lossy()
|
|
);
|
|
run_elevated(&script)
|
|
}
|
|
|
|
// ── Cert installation ─────────────────────────────────────────────────────────
|
|
|
|
/// Find the bridge cert in the given captures dir.
|
|
pub fn find_bridge_cert(captures_dir: &str) -> Option<PathBuf> {
|
|
let p = Path::new(captures_dir).join("bridge_cert.pem");
|
|
p.exists().then_some(p)
|
|
}
|
|
|
|
/// Install the bridge cert into the Wine/Proton prefix cert store so the game
|
|
/// trusts TLS connections to the bridge. Falls back to the system CA store.
|
|
pub fn install_cert(cert_src: &Path) -> anyhow::Result<()> {
|
|
// Try certutil inside the Proton prefix first (Wine cert store)
|
|
let wine_result = try_wine_certutil(cert_src);
|
|
if wine_result.is_ok() {
|
|
return wine_result;
|
|
}
|
|
|
|
// Fallback: Linux system CA store via pkexec/sudo
|
|
let dest = Path::new("/usr/local/share/ca-certificates/openfut-bridge.crt");
|
|
let script = format!(
|
|
"cp '{}' '{}' && update-ca-certificates",
|
|
cert_src.display(),
|
|
dest.display()
|
|
);
|
|
run_elevated(&script)
|
|
}
|
|
|
|
fn try_wine_certutil(cert_src: &Path) -> anyhow::Result<()> {
|
|
// certutil is available inside a Wine prefix via winetricks or natively
|
|
let status = Command::new("wine")
|
|
.args([
|
|
"certutil",
|
|
"-addstore",
|
|
"-user",
|
|
"Root",
|
|
&cert_src.to_string_lossy(),
|
|
])
|
|
.status();
|
|
|
|
match status {
|
|
Ok(s) if s.success() => Ok(()),
|
|
Ok(s) => anyhow::bail!("wine certutil exited {s}"),
|
|
Err(e) => anyhow::bail!("wine certutil not available: {e}"),
|
|
}
|
|
}
|
|
|
|
fn run_elevated(script: &str) -> anyhow::Result<()> {
|
|
let status = Command::new("pkexec")
|
|
.args(["sh", "-c", script])
|
|
.status();
|
|
|
|
match status {
|
|
Ok(s) if s.success() => Ok(()),
|
|
_ => {
|
|
let s = Command::new("sudo")
|
|
.args(["sh", "-c", script])
|
|
.status()?;
|
|
if s.success() {
|
|
Ok(())
|
|
} else {
|
|
anyhow::bail!("elevated command failed")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── DLL hook deployment ───────────────────────────────────────────────────────
|
|
|
|
/// Deploy openfut_hook.dll into the FIFA 23 game directory and write
|
|
/// openfut.cfg with the redirect IP the hook will use.
|
|
/// Uses `version.dll` as the hijack name — FIFA 23 loads it but defers to
|
|
/// the system copy, so Proton picks up our local one first.
|
|
pub fn deploy_hook_dll(dll_src: &Path, game_dir: &Path, redirect_ip: &str) -> anyhow::Result<()> {
|
|
if !dll_src.exists() {
|
|
anyhow::bail!(
|
|
"Hook DLL not found at {}. Build it first with:\n\
|
|
cargo build --release --target x86_64-pc-windows-gnu\n\
|
|
(inside openfut-hook/)",
|
|
dll_src.display()
|
|
);
|
|
}
|
|
std::fs::create_dir_all(game_dir)?;
|
|
std::fs::copy(dll_src, game_dir.join("version.dll"))?;
|
|
std::fs::write(game_dir.join("openfut.cfg"), redirect_ip)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Update only openfut.cfg without redeploying the DLL.
|
|
pub fn update_hook_config(game_dir: &Path, redirect_ip: &str) -> anyhow::Result<()> {
|
|
let cfg = game_dir.join("openfut.cfg");
|
|
if !cfg.exists() {
|
|
anyhow::bail!("Hook DLL not deployed yet — deploy first.");
|
|
}
|
|
std::fs::write(cfg, redirect_ip)?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Remove the deployed hook DLL from the FIFA game directory.
|
|
pub fn remove_hook_dll(game_dir: &Path) -> anyhow::Result<()> {
|
|
let dest = game_dir.join("version.dll");
|
|
if dest.exists() {
|
|
std::fs::remove_file(&dest)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Check whether our hook DLL is deployed in the game directory.
|
|
pub fn hook_dll_deployed(game_dir: &Path) -> bool {
|
|
game_dir.join("version.dll").exists()
|
|
}
|
|
|
|
/// The Steam launch options the user needs to paste in to enable the override.
|
|
/// Proton loads local DLLs named in WINEDLLOVERRIDES ahead of system ones.
|
|
pub const STEAM_LAUNCH_OPTIONS: &str =
|
|
"WINEDLLOVERRIDES=\"version=n,b\" %command%";
|