feat: replace hosts file with DLL injection hook
Adds openfut-hook/, a Windows DLL (cdylib, x86_64-pc-windows-gnu) that patches the IAT of FIFA 23 at load time to redirect getaddrinfo calls for fut.ea.com / utas.*.fut.ea.com to 127.0.0.1, sending all FUT traffic to the local bridge — no /etc/hosts changes needed. Deployment: the launcher copies openfut_hook.dll into the FIFA 23 game folder as version.dll (a DLL FIFA loads but delegates to system). Proton picks up the local copy automatically when you set: WINEDLLOVERRIDES="version=n,b" %command% in Steam launch options. Also updates cert install to try the Wine/Proton cert store (wine certutil) before falling back to the Linux system CA store, and removes all hosts file code from setup.rs / app.rs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+74
-29
@@ -33,7 +33,7 @@ pub struct LauncherApp {
|
||||
log_follow: bool,
|
||||
|
||||
// Setup state
|
||||
hosts_configured: bool,
|
||||
hook_deployed: bool,
|
||||
cert_path: Option<std::path::PathBuf>,
|
||||
setup_message: Option<(bool, String)>, // (success, text)
|
||||
}
|
||||
@@ -54,7 +54,8 @@ impl LauncherApp {
|
||||
|
||||
let config = LauncherConfig::load();
|
||||
let cert_path = setup::find_bridge_cert(&config.bridge_captures_dir);
|
||||
let hosts_configured = setup::hosts_configured();
|
||||
let hook_deployed =
|
||||
setup::hook_dll_deployed(std::path::Path::new(&config.fifa_game_dir));
|
||||
|
||||
Self {
|
||||
config,
|
||||
@@ -66,7 +67,7 @@ impl LauncherApp {
|
||||
active_tab: Tab::Dashboard,
|
||||
log_tab: 0,
|
||||
log_follow: true,
|
||||
hosts_configured,
|
||||
hook_deployed,
|
||||
cert_path,
|
||||
setup_message: None,
|
||||
}
|
||||
@@ -189,11 +190,11 @@ impl LauncherApp {
|
||||
.num_columns(2)
|
||||
.spacing([12.0, 4.0])
|
||||
.show(ui, |ui| {
|
||||
ui.label("Hosts file:");
|
||||
if self.hosts_configured {
|
||||
ui.colored_label(Color32::from_rgb(80, 200, 120), "Configured");
|
||||
ui.label("Hook DLL:");
|
||||
if self.hook_deployed {
|
||||
ui.colored_label(Color32::from_rgb(80, 200, 120), "Deployed (version.dll)");
|
||||
} else {
|
||||
ui.colored_label(Color32::from_rgb(220, 150, 0), "Not configured");
|
||||
ui.colored_label(Color32::from_rgb(220, 150, 0), "Not deployed");
|
||||
}
|
||||
ui.end_row();
|
||||
|
||||
@@ -263,51 +264,81 @@ impl LauncherApp {
|
||||
}
|
||||
|
||||
fn ui_setup(&mut self, ui: &mut Ui) {
|
||||
use std::path::Path;
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.heading("System Setup");
|
||||
ui.add_space(4.0);
|
||||
ui.label("These steps route FIFA 23 traffic through the emulator. Both require elevated privileges (polkit/sudo).");
|
||||
ui.label("These steps route FIFA 23 traffic through the emulator via DLL injection (no hosts file changes needed).");
|
||||
ui.add_space(12.0);
|
||||
|
||||
// ── Hosts file ────────────────────────────────────────────────────────
|
||||
// ── Hook DLL deployment ───────────────────────────────────────────────
|
||||
ui.group(|ui| {
|
||||
ui.set_min_width(ui.available_width());
|
||||
ui.strong("Step 1 — Redirect EA hostnames");
|
||||
ui.strong("Step 1 — Deploy network hook DLL");
|
||||
ui.add_space(4.0);
|
||||
ui.label("Adds entries to /etc/hosts so the game connects to the bridge instead of EA's servers.");
|
||||
ui.label("Copies openfut_hook.dll into the FIFA 23 folder as version.dll. \
|
||||
When Proton loads the game it will intercept network calls and redirect \
|
||||
EA hostnames to the local bridge — no hosts file changes required.");
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Refresh status
|
||||
self.hosts_configured = setup::hosts_configured();
|
||||
let game_dir = Path::new(&self.config.fifa_game_dir);
|
||||
self.hook_deployed = setup::hook_dll_deployed(game_dir);
|
||||
|
||||
let dll_src = Path::new(&self.config.hook_dll_path);
|
||||
let dll_built = dll_src.exists();
|
||||
|
||||
if !dll_built {
|
||||
ui.colored_label(
|
||||
Color32::from_rgb(220, 150, 0),
|
||||
"⚠ Hook DLL not built yet. Run in openfut-hook/:",
|
||||
);
|
||||
ui.monospace("cargo build --release --target x86_64-pc-windows-gnu");
|
||||
ui.add_space(4.0);
|
||||
}
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if self.hosts_configured {
|
||||
ui.colored_label(Color32::from_rgb(80, 200, 120), "✔ Configured");
|
||||
if self.hook_deployed {
|
||||
ui.colored_label(Color32::from_rgb(80, 200, 120), "✔ Deployed (version.dll)");
|
||||
ui.add_space(8.0);
|
||||
if ui.button("Remove entries").clicked() {
|
||||
match setup::remove_hosts_entries() {
|
||||
if ui.button("Remove").clicked() {
|
||||
match setup::remove_hook_dll(game_dir) {
|
||||
Ok(()) => {
|
||||
self.setup_message = Some((true, "Hosts entries removed.".into()));
|
||||
self.hosts_configured = false;
|
||||
self.setup_message = Some((true, "Hook DLL removed.".into()));
|
||||
self.hook_deployed = false;
|
||||
}
|
||||
Err(e) => self.setup_message = Some((false, format!("Failed: {e}"))),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.colored_label(Color32::from_rgb(220, 60, 60), "✘ Not configured");
|
||||
ui.colored_label(Color32::from_rgb(220, 60, 60), "✘ Not deployed");
|
||||
ui.add_space(8.0);
|
||||
if ui.button("Add entries").clicked() {
|
||||
match setup::add_hosts_entries() {
|
||||
if ui.add_enabled(dll_built, egui::Button::new("Deploy")).clicked() {
|
||||
match setup::deploy_hook_dll(dll_src, game_dir) {
|
||||
Ok(()) => {
|
||||
self.setup_message = Some((true, "Hosts entries added.".into()));
|
||||
self.hosts_configured = true;
|
||||
self.setup_message = Some((true, "Hook DLL deployed as version.dll.".into()));
|
||||
self.hook_deployed = true;
|
||||
}
|
||||
Err(e) => self.setup_message = Some((false, format!("Failed: {e}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ui.add_space(8.0);
|
||||
ui.separator();
|
||||
ui.add_space(4.0);
|
||||
ui.strong("Steam Launch Options");
|
||||
ui.label("Paste this into FIFA 23 → Properties → Launch Options in Steam:");
|
||||
ui.add_space(2.0);
|
||||
let launch_opt = setup::STEAM_LAUNCH_OPTIONS;
|
||||
ui.horizontal(|ui| {
|
||||
ui.monospace(launch_opt);
|
||||
if ui.small_button("Copy").clicked() {
|
||||
ui.output_mut(|o| o.copied_text = launch_opt.to_string());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.add_space(12.0);
|
||||
@@ -317,11 +348,10 @@ impl LauncherApp {
|
||||
ui.set_min_width(ui.available_width());
|
||||
ui.strong("Step 2 — Install TLS certificate");
|
||||
ui.add_space(4.0);
|
||||
ui.label("Installs the bridge's self-signed cert into the system CA store so the game accepts HTTPS connections.");
|
||||
ui.label("Installs the bridge's self-signed cert into the Wine/Proton cert store \
|
||||
so the game accepts HTTPS connections to the bridge.");
|
||||
|
||||
ui.add_space(6.0);
|
||||
|
||||
// Refresh cert path
|
||||
self.cert_path = setup::find_bridge_cert(&self.config.bridge_captures_dir);
|
||||
|
||||
match &self.cert_path.clone() {
|
||||
@@ -334,7 +364,7 @@ impl LauncherApp {
|
||||
Some(cert) => {
|
||||
ui.label(format!("Cert: {}", cert.display()));
|
||||
ui.add_space(4.0);
|
||||
if ui.button("Install cert (requires sudo)").clicked() {
|
||||
if ui.button("Install cert (Wine cert store)").clicked() {
|
||||
match setup::install_cert(cert) {
|
||||
Ok(()) => self.setup_message = Some((true, "Certificate installed.".into())),
|
||||
Err(e) => self.setup_message = Some((false, format!("Failed: {e}"))),
|
||||
@@ -346,7 +376,6 @@ impl LauncherApp {
|
||||
|
||||
ui.add_space(12.0);
|
||||
|
||||
// ── Status message ────────────────────────────────────────────────────
|
||||
if let Some((ok, msg)) = &self.setup_message {
|
||||
let color = if *ok {
|
||||
Color32::from_rgb(80, 200, 120)
|
||||
@@ -416,6 +445,22 @@ impl LauncherApp {
|
||||
ui.label("TLS enabled:");
|
||||
changed |= ui.checkbox(&mut self.config.bridge_tls_enabled, "").changed();
|
||||
ui.end_row();
|
||||
|
||||
ui.add_space(4.0);
|
||||
ui.label("");
|
||||
ui.end_row();
|
||||
|
||||
ui.strong("Hook DLL");
|
||||
ui.label("");
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Hook DLL path:");
|
||||
changed |= ui.text_edit_singleline(&mut self.config.hook_dll_path).changed();
|
||||
ui.end_row();
|
||||
|
||||
ui.label("FIFA 23 game dir:");
|
||||
changed |= ui.text_edit_singleline(&mut self.config.fifa_game_dir).changed();
|
||||
ui.end_row();
|
||||
});
|
||||
|
||||
if changed {
|
||||
|
||||
Reference in New Issue
Block a user