mirror of
https://github.com/alterware/alterware-launcher.git
synced 2025-12-04 15:27:48 +00:00
offline mode, connectivity check, backup cdn
probably the last real update for this codebase
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
|
|||||||
79
src/main.rs
79
src/main.rs
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user