Skip to content

Commit

Permalink
offline mode, connectivity check, backup cdn
Browse files Browse the repository at this point in the history
probably the last real update for this codebase
  • Loading branch information
mxve committed Feb 23, 2025
1 parent a2e33ce commit 845e3ac
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 45 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "alterware-launcher"
version = "0.9.3"
version = "0.10.2"
edition = "2021"
build = "res/build.rs"

Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@
- Install or reinstall redistributables
- ```--prerelease```
- Update to prerelease version of clients (currently only available for IW4x) and launcher
- ```--cdn-url```
- ```--offline```
- ```--skip-connectivity-check```

##### Example:
```shell
Expand All @@ -171,6 +174,9 @@ alterware-launcher.exe iw4x --bonus -u --path "C:\Games\IW4x" --pass "-console"
- `use_https`: Use HTTPS for downloads. Default: `true`.
- `skip_redist`: Skip redistributable installations. Default: `false`.
- `prerelease`: Update to prerelease version of clients and launcher. Default: `false`.
- `cdn_url`
- `offline`
- `skip-connectivity-check`

---

Expand Down
65 changes: 55 additions & 10 deletions src/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ use colored::Colorize;
use once_cell::sync::Lazy;
use std::collections::HashMap;
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_REPO: &str = "alterware-launcher";
pub const GH_IW4X_OWNER: &str = "iw4x";
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>> =
Lazy::new(|| Mutex::new("https://cdn.alterware.ovh".to_owned()));
pub static MASTER_URL: Lazy<Mutex<String>> = Lazy::new(|| {
Mutex::new(String::from(DEFAULT_MASTER))
});

pub static IS_OFFLINE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));

Expand Down Expand Up @@ -54,14 +61,52 @@ pub static PREFIXES: Lazy<HashMap<&'static str, PrintPrefix>> = Lazy::new(|| {
])
});

pub async fn check_connectivity() -> bool {
let master_url = MASTER.lock().unwrap().clone();
pub fn check_connectivity(master_url: Option<String>) -> Pin<Box<dyn Future<Output = bool> + Send>> {
Box::pin(async move {
let retry = master_url.is_some();
if !retry {
crate::println_info!("Running connectivity check on {}", DEFAULT_MASTER);
} else {
let master = master_url.unwrap();
*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);
}

match crate::http_async::get_body_string(&master_url).await {
Ok(_) => true,
Err(_) => {
*IS_OFFLINE.lock().unwrap() = true;
false
// If we switched to backup, do not retry
if !retry && !result && !switched_to_backup {
check_connectivity(Some(String::from(BACKUP_MASTER))).await
} else {
result
}
}
})
}
31 changes: 31 additions & 0 deletions src/http_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,34 @@ pub async fn get_body_string(url: &str) -> Result<String, String> {
let body = get_body(url).await?;
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))
}
79 changes: 46 additions & 33 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ async fn update_dir(
let mut bust_cache = false;
let mut local_hash = String::default();
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 {
bust_cache = false;
format!("{}?{}", url, misc::random_string(6))
Expand Down Expand Up @@ -347,7 +347,7 @@ async fn update(
let ignore_required_files = ignore_required_files.unwrap_or(false);

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
.unwrap();
debug!("Retrieved files.json from server");
Expand Down Expand Up @@ -653,6 +653,8 @@ async fn main() {
println!(" --skip-redist: Skip redistributable installation");
println!(" --redist: (Re-)Install redistributables");
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!(
"\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\""
);
Expand All @@ -679,8 +681,46 @@ async fn main() {
return;
}

let offline_mode = !global::check_connectivity().await;
if offline_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 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)
let stored_data = cache::get_stored_data();
if stored_data.is_none() {
Expand All @@ -701,20 +741,6 @@ async fn main() {
);
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
let stored_data = cache::get_stored_data();
if let Some(ref data) = stored_data {
Expand Down Expand Up @@ -763,21 +789,8 @@ async fn main() {
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 {
let mut master_url = MASTER.lock().unwrap();
let mut master_url = MASTER_URL.lock().unwrap();
*master_url = master_url.replace("https://", "http://");
};

Expand Down Expand Up @@ -841,7 +854,7 @@ async fn main() {
}

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
.unwrap_or_else(|error| {
crate::println_error!("Failed to get games.json: {:#?}", error);
Expand Down
9 changes: 9 additions & 0 deletions src/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ pub struct Config {
pub skip_redist: bool,
#[serde(default)]
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 {
Expand All @@ -63,6 +69,9 @@ impl Default for Config {
use_https: true,
skip_redist: false,
prerelease: false,
cdn_url: String::default(),
offline: false,
skip_connectivity_check: false,
}
}
}
Expand Down

0 comments on commit 845e3ac

Please sign in to comment.