From 910187dca2fc1c9d88989e72dfdcd2d188bad828 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Tue, 21 Apr 2026 16:56:57 -0700 Subject: [PATCH 1/3] Proxied asset downloading --- app/page.tsx | 2 +- app/settings/LauncherSettingsTab.tsx | 17 + app/types.ts | 1 + resources/defaults/config.json | 3 +- src-tauri/Cargo.lock | 622 ++++++++++++++++++++++++++- src-tauri/Cargo.toml | 1 + src-tauri/src/config.rs | 4 + src-tauri/src/lib.rs | 44 +- src-tauri/src/state.rs | 4 + 9 files changed, 692 insertions(+), 6 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 25e3c14..e58e838 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -291,7 +291,7 @@ export default function Home() { } stopLoading("launch"); setCurrentSession(undefined); - if (config!.launcher.launch_behavior == "hide") { + if (config!.launcher.launch_behavior != "stay_open") { await getCurrentWindow().hide(); } const exitCode: number = await invoke("do_launch"); diff --git a/app/settings/LauncherSettingsTab.tsx b/app/settings/LauncherSettingsTab.tsx index 13d4a0c..882ba16 100644 --- a/app/settings/LauncherSettingsTab.tsx +++ b/app/settings/LauncherSettingsTab.tsx @@ -148,6 +148,23 @@ export default function LauncherSettingsTab({ })) } /> + + setSettings((current) => ({ + ...current!, + proxy_asset_downloads: value, + })) + } + /> Self { @@ -55,6 +58,7 @@ impl Default for LauncherSettings { launch_behavior: LaunchBehavior::Hide, game_cache_path: util::get_default_cache_dir(), offline_cache_path: util::get_default_offline_cache_dir(), + proxy_asset_downloads: true, } } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c1acfc9..ad818fc 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,6 +7,7 @@ use config::{LaunchBehavior, LauncherSettings}; use endpoint::{AccountInfo, InfoResponse, RegisterResponse, Session}; use ffbuildtool::{ItemProgress, Version}; use regex::Regex; +use rust_proxy::{common::auth::AuthManager, proxy::tcp::TcpProxy}; use serde::{Deserialize, Serialize}; use state::{ get_app_statics, AppState, Config, FlatServer, FlatServers, Server, ServerInfo, Versions, @@ -20,7 +21,10 @@ use std::{ sync::{mpsc, Arc, LazyLock, OnceLock}, vec, }; -use tokio::sync::{Mutex, Semaphore}; +use tokio::{ + net::TcpListener, + sync::{Mutex, Semaphore}, +}; use log::*; use tauri::Manager; @@ -101,6 +105,7 @@ async fn do_launch(app_handle: tauri::AppHandle) -> CommandResult { debug!("do_launch"); let state = app_handle.state::>(); let mut state = state.lock().await; + let proxy_enabled = state.config.launcher.proxy_asset_downloads; let launch_behavior = state.config.launcher.launch_behavior; let mut cmd = state.launch_cmd.take().ok_or("No launch prepared")?; let cmd_str = util::get_launch_cmd_dbg_str(&cmd, false); @@ -113,11 +118,29 @@ async fn do_launch(app_handle: tauri::AppHandle) -> CommandResult { .to_string(); format!("{} (launch command was: {})", e, censored_cmd_str) })?; + + if launch_behavior == LaunchBehavior::Quit && !proxy_enabled { + // no need to keep the proxy alive; we can quit immediately + app_handle.exit(0); + return Ok(0); + } + + let exit_result = proc.wait(); + + // shutdown the asset proxy + let state = app_handle.state::>(); + let mut state = state.lock().await; + if let Some(proxy) = state.proxy.take() { + proxy.abort(); + }; + + // no need to do any error handling. quit now so the user doesn't see us again. if launch_behavior == LaunchBehavior::Quit { app_handle.exit(0); return Ok(0); } - let exit_code = proc.wait().map_err(|e| e.to_string())?; + + let exit_code = exit_result.map_err(|e| e.to_string())?; Ok(exit_code.code().unwrap_or(0)) } @@ -515,6 +538,23 @@ async fn prep_launch( asset_url = offline_asset_url; main_url = offline_main_url; } + } else if state.config.launcher.proxy_asset_downloads { + let mut proxy = TcpProxy::default(); + proxy.set_base_path(asset_url.clone()); + let listener = TcpListener::bind("127.0.0.1:0").await?; + let proxy_addr = listener.local_addr()?; + let new_asset_url = format!("http://{}", proxy_addr); + + // NOTE: this assumes that the main file is under the asset URL somewhere, + // which is true for all known versions, but not technically guaranteed by + // the FFBuildTool manifest format (sorry guys) + main_url = main_url.replace(&asset_url, &new_asset_url); + asset_url = new_asset_url; + + let handle = tokio::spawn(async move { + proxy.run(&listener).await; + }); + state.proxy = Some(handle); } debug!("Asset URL: {}", asset_url); diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index fdb77d5..f41ace4 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -2,8 +2,10 @@ use std::{collections::HashMap, path::PathBuf, process::Command, sync::OnceLock} use ffbuildtool::Version; use log::*; +use rust_proxy::proxy::tcp::TcpProxy; use serde::{Deserialize, Serialize}; use tauri::{path::BaseDirectory, Manager}; +use tokio::task::JoinHandle; use uuid::Uuid; use crate::{ @@ -86,6 +88,7 @@ pub struct AppState { pub temp_tokens: HashMap, pub write_config: bool, pub launch_cmd: Option, + pub proxy: Option>, } impl AppState { pub fn load(app_handle: tauri::AppHandle) -> Self { @@ -122,6 +125,7 @@ impl AppState { temp_tokens: HashMap::new(), write_config, launch_cmd: None, + proxy: None, } } From 3a7ad02a84d9ad247703784976cef2768c0289a7 Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Tue, 21 Apr 2026 17:05:00 -0700 Subject: [PATCH 2/3] Cleanup imports --- src-tauri/src/lib.rs | 2 +- src-tauri/src/state.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ad818fc..425b452 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,7 +7,7 @@ use config::{LaunchBehavior, LauncherSettings}; use endpoint::{AccountInfo, InfoResponse, RegisterResponse, Session}; use ffbuildtool::{ItemProgress, Version}; use regex::Regex; -use rust_proxy::{common::auth::AuthManager, proxy::tcp::TcpProxy}; +use rust_proxy::proxy::tcp::TcpProxy; use serde::{Deserialize, Serialize}; use state::{ get_app_statics, AppState, Config, FlatServer, FlatServers, Server, ServerInfo, Versions, diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index f41ace4..318aa96 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -2,7 +2,6 @@ use std::{collections::HashMap, path::PathBuf, process::Command, sync::OnceLock} use ffbuildtool::Version; use log::*; -use rust_proxy::proxy::tcp::TcpProxy; use serde::{Deserialize, Serialize}; use tauri::{path::BaseDirectory, Manager}; use tokio::task::JoinHandle; From 72b68aa82713384c8eaabf71c22b504d7d73faec Mon Sep 17 00:00:00 2001 From: Gent Semaj Date: Tue, 21 Apr 2026 17:36:19 -0700 Subject: [PATCH 3/3] Bonus: Fix LaunchBehavior::Quit crashing game on Unix --- src-tauri/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 425b452..060efc9 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -18,6 +18,7 @@ use util::AlertVariant; use std::{ collections::{HashMap, HashSet}, env, + process::Stdio, sync::{mpsc, Arc, LazyLock, OnceLock}, vec, }; @@ -668,6 +669,12 @@ async fn prep_launch( } } + // Detach stdio so the child doesn't crash from broken pipes + // when the launcher exits (e.g. LaunchBehavior::Quit) + cmd.stdin(Stdio::null()); + cmd.stdout(Stdio::null()); + cmd.stderr(Stdio::null()); + util::log_command(&cmd); state.launch_cmd = Some(cmd); Ok(timeout_sec)