offline mode, connectivity check, backup cdn

probably the last real update for this codebase
This commit is contained in:
2025-02-24 00:30:20 +01:00
parent a2e33ce986
commit 845e3acb9f
7 changed files with 150 additions and 46 deletions

2
Cargo.lock generated
View File

@@ -19,7 +19,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.9.3" version = "0.10.2"
dependencies = [ dependencies = [
"blake3", "blake3",
"colored", "colored",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.9.3" version = "0.10.2"
edition = "2021" edition = "2021"
build = "res/build.rs" build = "res/build.rs"

View File

@@ -150,6 +150,9 @@
- Install or reinstall redistributables - Install or reinstall redistributables
- ```--prerelease``` - ```--prerelease```
- Update to prerelease version of clients (currently only available for IW4x) and launcher - Update to prerelease version of clients (currently only available for IW4x) and launcher
- ```--cdn-url```
- ```--offline```
- ```--skip-connectivity-check```
##### Example: ##### Example:
```shell ```shell
@@ -171,6 +174,9 @@ alterware-launcher.exe iw4x --bonus -u --path "C:\Games\IW4x" --pass "-console"
- `use_https`: Use HTTPS for downloads. Default: `true`. - `use_https`: Use HTTPS for downloads. Default: `true`.
- `skip_redist`: Skip redistributable installations. Default: `false`. - `skip_redist`: Skip redistributable installations. Default: `false`.
- `prerelease`: Update to prerelease version of clients and launcher. Default: `false`. - `prerelease`: Update to prerelease version of clients and launcher. Default: `false`.
- `cdn_url`
- `offline`
- `skip-connectivity-check`
--- ---

View File

@@ -3,14 +3,21 @@ use colored::Colorize;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;
use std::pin::Pin;
use std::future::Future;
use crate::http_async;
use serde_json::Value;
pub const GH_OWNER: &str = "mxve"; pub const GH_OWNER: &str = "mxve";
pub const GH_REPO: &str = "alterware-launcher"; pub const GH_REPO: &str = "alterware-launcher";
pub const GH_IW4X_OWNER: &str = "iw4x"; pub const GH_IW4X_OWNER: &str = "iw4x";
pub const GH_IW4X_REPO: &str = "iw4x-client"; pub const GH_IW4X_REPO: &str = "iw4x-client";
pub const DEFAULT_MASTER: &str = "https://cdn.alterware.ovh";
pub const BACKUP_MASTER: &str = "https://cdn.iw4x.getserve.rs";
pub static MASTER: Lazy<Mutex<String>> = pub static MASTER_URL: Lazy<Mutex<String>> = Lazy::new(|| {
Lazy::new(|| Mutex::new("https://cdn.alterware.ovh".to_owned())); Mutex::new(String::from(DEFAULT_MASTER))
});
pub static IS_OFFLINE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false)); pub static IS_OFFLINE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
@@ -54,14 +61,52 @@ pub static PREFIXES: Lazy<HashMap<&'static str, PrintPrefix>> = Lazy::new(|| {
]) ])
}); });
pub async fn check_connectivity() -> bool { pub fn check_connectivity(master_url: Option<String>) -> Pin<Box<dyn Future<Output = bool> + Send>> {
let master_url = MASTER.lock().unwrap().clone(); Box::pin(async move {
let retry = master_url.is_some();
match crate::http_async::get_body_string(&master_url).await { if !retry {
Ok(_) => true, crate::println_info!("Running connectivity check on {}", DEFAULT_MASTER);
Err(_) => { } else {
*IS_OFFLINE.lock().unwrap() = true; let master = master_url.unwrap();
false *MASTER_URL.lock().unwrap() = master.clone();
crate::println_info!("Running connectivity check on {}", master);
} }
}
let master_url = MASTER_URL.lock().unwrap().clone();
// Check ASN number using the new get_json function
let asn_response: Result<Value, String> = http_async::get_json("https://ip2asn.getserve.rs/v1/as/ip/self").await;
let mut switched_to_backup = false;
if let Ok(asn_data) = asn_response {
if let Some(as_number) = asn_data.get("as_number").and_then(|v| v.as_i64()) {
if as_number == 3320 && master_url == DEFAULT_MASTER {
*MASTER_URL.lock().unwrap() = String::from(BACKUP_MASTER);
crate::println_info!("Detected DTAG as ISP, switched to backup master URL: {}", BACKUP_MASTER);
switched_to_backup = true;
}
}
}
// Run connectivity check regardless of ASN switch
let result = match crate::http_async::get_body_string(&master_url).await {
Ok(_) => true,
Err(_) => {
*IS_OFFLINE.lock().unwrap() = true;
false
}
};
if !result {
crate::println_error!("Failed to connect to CDN {}", master_url);
}
// If we switched to backup, do not retry
if !retry && !result && !switched_to_backup {
check_connectivity(Some(String::from(BACKUP_MASTER))).await
} else {
result
}
})
} }

View File

@@ -103,3 +103,34 @@ pub async fn get_body_string(url: &str) -> Result<String, String> {
let body = get_body(url).await?; let body = get_body(url).await?;
Ok(String::from_utf8(body).unwrap()) Ok(String::from_utf8(body).unwrap())
} }
pub async fn get_json<T: serde::de::DeserializeOwned>(url: &str) -> Result<T, String> {
let client = Client::new();
let res = client
.get(url)
.header(
"User-Agent",
format!(
"AlterWare Launcher | github.com/{}/{}",
crate::global::GH_OWNER,
crate::global::GH_REPO
),
)
.header("Accept", "application/json")
.send()
.await
.map_err(|e| format!("Failed to send request: {}", e))?;
debug!("{} {}", res.status(), url);
if !res.status().is_success() {
return Err(format!("Request failed with status: {}", res.status()));
}
let body = res.bytes()
.await
.map_err(|e| format!("Failed to read response body: {}", e))?;
serde_json::from_slice::<T>(&body)
.map_err(|e| format!("Failed to parse JSON: {}", e))
}

View File

@@ -272,7 +272,7 @@ async fn update_dir(
let mut bust_cache = false; let mut bust_cache = false;
let mut local_hash = String::default(); let mut local_hash = String::default();
while !download_complete { while !download_complete {
let url = format!("{}/{}", MASTER.lock().unwrap(), file.name); let url = format!("{}/{}", MASTER_URL.lock().unwrap(), file.name);
let url = if bust_cache { let url = if bust_cache {
bust_cache = false; bust_cache = false;
format!("{}?{}", url, misc::random_string(6)) format!("{}?{}", url, misc::random_string(6))
@@ -347,7 +347,7 @@ async fn update(
let ignore_required_files = ignore_required_files.unwrap_or(false); let ignore_required_files = ignore_required_files.unwrap_or(false);
let res = let res =
http_async::get_body_string(format!("{}/files.json", MASTER.lock().unwrap()).as_str()) http_async::get_body_string(format!("{}/files.json", MASTER_URL.lock().unwrap()).as_str())
.await .await
.unwrap(); .unwrap();
debug!("Retrieved files.json from server"); debug!("Retrieved files.json from server");
@@ -653,6 +653,8 @@ async fn main() {
println!(" --skip-redist: Skip redistributable installation"); println!(" --skip-redist: Skip redistributable installation");
println!(" --redist: (Re-)Install redistributables"); println!(" --redist: (Re-)Install redistributables");
println!(" --prerelease: Update to prerelease version of clients and launcher"); println!(" --prerelease: Update to prerelease version of clients and launcher");
println!(" --offline: Run in offline mode");
println!(" --skip-connectivity-check: Don't check connectivity");
println!( println!(
"\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\"" "\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\""
); );
@@ -679,8 +681,46 @@ async fn main() {
return; return;
} }
let offline_mode = !global::check_connectivity().await; let install_path: PathBuf;
if offline_mode { if let Some(path) = arg_value(&args, "--path") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "--path");
} else if let Some(path) = arg_value(&args, "-p") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "-p");
} else {
install_path = env::current_dir().unwrap();
}
let mut cfg = config::load(install_path.join("alterware-launcher.json"));
if let Some(cdn_url) = arg_value(&args, "--cdn-url") {
cfg.cdn_url = cdn_url;
arg_remove_value(&mut args, "--cdn-url");
}
if arg_bool(&args, "--offline") {
cfg.offline = true;
arg_remove(&mut args, "--offline");
}
if arg_bool(&args, "--skip-connectivity-check") {
cfg.skip_connectivity_check = true;
arg_remove(&mut args, "--skip-connectivity-check");
}
let initial_cdn = if !cfg.cdn_url.is_empty() {
info!("Using custom CDN URL: {}", cfg.cdn_url);
Some(cfg.cdn_url.clone())
} else {
None
};
if !cfg.offline && !cfg.skip_connectivity_check {
cfg.offline = !global::check_connectivity(initial_cdn).await;
}
if cfg.offline {
// Check if this is a first-time run (no stored data) // Check if this is a first-time run (no stored data)
let stored_data = cache::get_stored_data(); let stored_data = cache::get_stored_data();
if stored_data.is_none() { if stored_data.is_none() {
@@ -701,20 +741,6 @@ async fn main() {
); );
warn!("No internet connection or MASTER server is unreachable. Running in offline mode..."); warn!("No internet connection or MASTER server is unreachable. Running in offline mode...");
// Handle path the same way as online mode
let install_path: PathBuf;
if let Some(path) = arg_value(&args, "--path") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "--path");
} else if let Some(path) = arg_value(&args, "-p") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "-p");
} else {
install_path = env::current_dir().unwrap();
}
let cfg = config::load(install_path.join("alterware-launcher.json"));
// Try to get stored game data // Try to get stored game data
let stored_data = cache::get_stored_data(); let stored_data = cache::get_stored_data();
if let Some(ref data) = stored_data { if let Some(ref data) = stored_data {
@@ -763,21 +789,8 @@ async fn main() {
return; return;
} }
let install_path: PathBuf;
if let Some(path) = arg_value(&args, "--path") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "--path");
} else if let Some(path) = arg_value(&args, "-p") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "-p");
} else {
install_path = env::current_dir().unwrap();
}
let mut cfg = config::load(install_path.join("alterware-launcher.json"));
if !cfg.use_https { if !cfg.use_https {
let mut master_url = MASTER.lock().unwrap(); let mut master_url = MASTER_URL.lock().unwrap();
*master_url = master_url.replace("https://", "http://"); *master_url = master_url.replace("https://", "http://");
}; };
@@ -841,7 +854,7 @@ async fn main() {
} }
let games_json = let games_json =
http_async::get_body_string(format!("{}/games.json", MASTER.lock().unwrap()).as_str()) http_async::get_body_string(format!("{}/games.json", MASTER_URL.lock().unwrap()).as_str())
.await .await
.unwrap_or_else(|error| { .unwrap_or_else(|error| {
crate::println_error!("Failed to get games.json: {:#?}", error); crate::println_error!("Failed to get games.json: {:#?}", error);

View File

@@ -48,6 +48,12 @@ pub struct Config {
pub skip_redist: bool, pub skip_redist: bool,
#[serde(default)] #[serde(default)]
pub prerelease: bool, pub prerelease: bool,
#[serde(default)]
pub cdn_url: String,
#[serde(default)]
pub offline: bool,
#[serde(default)]
pub skip_connectivity_check: bool,
} }
impl Default for Config { impl Default for Config {
@@ -63,6 +69,9 @@ impl Default for Config {
use_https: true, use_https: true,
skip_redist: false, skip_redist: false,
prerelease: false, prerelease: false,
cdn_url: String::default(),
offline: false,
skip_connectivity_check: false,
} }
} }
} }