diff --git a/README.md b/README.md index a3acd1a..6b08449 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ - Skip installing redistributables - ```--redist``` - Install or reinstall redistributables +- ```--prerelease``` + - Update to prerelease version of clients (currently only available for IW4x) and launcher ##### Example: ```shell @@ -168,6 +170,7 @@ alterware-launcher.exe iw4x --bonus -u --path "C:\Games\IW4x" --pass "-console" - `args`: Pass additional arguments to the game. Default: `""`. - `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`. --- diff --git a/src/config.rs b/src/config.rs index f99f404..4899b54 100644 --- a/src/config.rs +++ b/src/config.rs @@ -44,6 +44,7 @@ pub fn save_value(config_path: PathBuf, key: &str, value: bool) { "force_update" => config.force_update = value, "use_https" => config.use_https = value, "skip_redist" => config.skip_redist = value, + "prerelease" => config.prerelease = value, _ => (), } save(config_path, config); diff --git a/src/github.rs b/src/github.rs index 2b62c64..efe28b8 100644 --- a/src/github.rs +++ b/src/github.rs @@ -1,6 +1,21 @@ use semver::Version; -pub async fn latest_tag(owner: &str, repo: &str) -> Result> { +pub async fn latest_tag( + owner: &str, + repo: &str, + prerelease: Option, +) -> Result> { + if prerelease.unwrap_or(false) { + latest_tag_prerelease(owner, repo).await + } else { + latest_tag_full(owner, repo).await + } +} + +pub async fn latest_tag_full( + owner: &str, + repo: &str, +) -> Result> { let github_body = crate::http_async::get_body_string( format!( "https://api.github.com/repos/{}/{}/releases/latest", @@ -8,34 +23,60 @@ pub async fn latest_tag(owner: &str, repo: &str) -> Result Version { - match latest_tag(owner, repo).await { - Ok(tag) => { - let cleaned_tag = tag.replace('v', ""); - Version::parse(&cleaned_tag).unwrap_or_else(|_| Version::new(0, 0, 0)) - } - Err(_) => { - crate::println_error!( - "Failed to get latest version for {owner}/{repo}, assuming we are up to date." - ); - Version::new(0, 0, 0) - } +pub async fn latest_tag_prerelease( + owner: &str, + repo: &str, +) -> Result> { + let github_body = crate::http_async::get_body_string( + format!("https://api.github.com/repos/{}/{}/releases", owner, repo).as_str(), + ) + .await + .map_err(|e| format!("Failed to fetch GitHub API: {}", e))?; + + let github_json: serde_json::Value = serde_json::from_str(&github_body) + .map_err(|e| format!("Failed to parse GitHub API response: {}", e))?; + + let latest_release = github_json.get(0).ok_or("No releases found")?; + + let tag_name = latest_release + .get("tag_name") + .ok_or("Release missing tag_name")? + .as_str() + .ok_or("tag_name is not a string")?; + + Ok(tag_name.replace('"', "")) +} + +pub async fn latest_version( + owner: &str, + repo: &str, + prerelease: Option, +) -> Result> { + let tag = latest_tag(owner, repo, prerelease).await?; + let cleaned_tag = tag.replace('v', ""); + Version::parse(&cleaned_tag) + .map_err(|e| format!("Failed to parse version '{}': {}", cleaned_tag, e).into()) +} + +pub fn download_url(owner: &str, repo: &str, tag: Option<&str>) -> String { + if let Some(tag) = tag { + format!("https://github.com/{owner}/{repo}/releases/download/{tag}") + } else { + format!("https://github.com/{owner}/{repo}/releases/latest") } } - -pub fn latest_release_url(owner: &str, repo: &str) -> String { - format!("https://github.com/{owner}/{repo}/releases/latest") -} diff --git a/src/iw4x.rs b/src/iw4x.rs index b5413bf..e28e3aa 100644 --- a/src/iw4x.rs +++ b/src/iw4x.rs @@ -7,8 +7,8 @@ use crate::structs; use std::path::Path; -pub async fn remote_revision() -> u16 { - match github::latest_tag(GH_IW4X_OWNER, GH_IW4X_REPO).await { +pub async fn remote_revision(prerelease: Option) -> u16 { + match github::latest_tag(GH_IW4X_OWNER, GH_IW4X_REPO, prerelease).await { Ok(tag) => misc::rev_to_int(&tag), Err(_) => { crate::println_error!("Failed to get latest version for {GH_IW4X_OWNER}/{GH_IW4X_REPO}, assuming we are up to date."); @@ -17,8 +17,8 @@ pub async fn remote_revision() -> u16 { } } -pub async fn update(dir: &Path, cache: &mut structs::Cache) { - let remote = remote_revision().await; +pub async fn update(dir: &Path, cache: &mut structs::Cache, prerelease: Option) { + let remote = remote_revision(prerelease).await; let local = misc::rev_to_int(&cache.iw4x_revision); if remote <= local && dir.join("iw4x.dll").exists() { @@ -34,8 +34,8 @@ pub async fn update(dir: &Path, cache: &mut structs::Cache) { ); http_async::download_file( &format!( - "{}/download/iw4x.dll", - github::latest_release_url(GH_IW4X_OWNER, GH_IW4X_REPO) + "{}/iw4x.dll", + github::download_url(GH_IW4X_OWNER, GH_IW4X_REPO, Some(&format!("r{remote}"))) ), &dir.join("iw4x.dll"), ) diff --git a/src/main.rs b/src/main.rs index c3fb268..0ee3b88 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,7 +115,7 @@ fn setup_desktop_links(path: &Path, game: &Game) { async fn auto_install(path: &Path, game: &Game<'_>) { setup_client_links(game, path); setup_desktop_links(path, game); - update(game, path, false, false, None, None).await; + update(game, path, false, false, None, None, None).await; } #[cfg(windows)] @@ -337,6 +337,7 @@ async fn update( force: bool, skip_iw4x_sp: Option, ignore_required_files: Option, + prerelease: Option, ) { info!("Starting update for game engine: {}", game.engine); info!("Update path: {}", dir.display()); @@ -381,7 +382,7 @@ async fn update( }; if game.engine == "iw4" { - iw4x::update(dir, &mut cache).await; + iw4x::update(dir, &mut cache, prerelease).await; let iw4x_dirs = vec!["iw4x", "zone/patch"]; for d in &iw4x_dirs { @@ -651,6 +652,7 @@ async fn main() { println!(" --ignore-required-files: Skip required files check"); println!(" --skip-redist: Skip redistributable installation"); println!(" --redist: (Re-)Install redistributables"); + println!(" --prerelease: Update to prerelease version of clients and launcher"); println!( "\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\"" ); @@ -779,8 +781,13 @@ async fn main() { *master_url = master_url.replace("https://", "http://"); }; + if arg_bool(&args, "--prerelease") { + cfg.prerelease = true; + arg_remove(&mut args, "--prerelease"); + } + if !arg_bool(&args, "--skip-launcher-update") && !cfg.skip_self_update { - self_update::run(cfg.update_only).await; + self_update::run(cfg.update_only, Some(cfg.prerelease)).await; } else { arg_remove(&mut args, "--skip-launcher-update"); } @@ -927,6 +934,7 @@ async fn main() { cfg.force_update, Some(&game != "iw4x-sp"), Some(ignore_required_files), + Some(cfg.prerelease), ) .await; if !cfg.update_only { diff --git a/src/self_update.rs b/src/self_update.rs index 3d2f9ff..6533ebc 100644 --- a/src/self_update.rs +++ b/src/self_update.rs @@ -3,20 +3,33 @@ use crate::global::*; use semver::Version; -pub async fn self_update_available() -> bool { - let current_version: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); - let latest_version = github::latest_version(GH_OWNER, GH_REPO).await; +pub async fn self_update_available(prerelease: Option) -> bool { + let current_version = match Version::parse(env!("CARGO_PKG_VERSION")) { + Ok(v) => v, + Err(e) => { + error!("Failed to parse current version: {}", e); + return false; + } + }; + + let latest_version = match github::latest_version(GH_OWNER, GH_REPO, prerelease).await { + Ok(v) => v, + Err(e) => { + error!("Failed to get latest version: {}", e); + return false; + } + }; current_version < latest_version } #[cfg(not(windows))] -pub async fn run(_update_only: bool) { - if self_update_available().await { +pub async fn run(_update_only: bool, _prerelease: Option) { + if self_update_available(None).await { crate::println_info!("A new version of the AlterWare launcher is available."); crate::println_info!( "Download it at {}", - github::latest_release_url(GH_OWNER, GH_REPO) + github::download_url(GH_OWNER, GH_REPO, None) ); println!("Launching in 10 seconds.."); tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; @@ -37,7 +50,7 @@ pub fn restart() -> std::io::Error { } #[cfg(windows)] -pub async fn run(update_only: bool) { +pub async fn run(update_only: bool, prerelease: Option) { use std::{fs, path::PathBuf}; use crate::http_async; @@ -60,11 +73,11 @@ pub async fn run(update_only: bool) { } } - if self_update_available().await { + if self_update_available(prerelease).await { crate::println_info!("Performing launcher self-update."); println!( "If you run into any issues, please download the latest version at {}", - github::latest_release_url(GH_OWNER, GH_REPO) + github::download_url(GH_OWNER, GH_REPO, None) ); let update_binary = PathBuf::from("alterware-launcher-update.exe"); @@ -83,7 +96,7 @@ pub async fn run(update_only: bool) { http_async::download_file( &format!( "{}/download/{}", - github::latest_release_url(GH_OWNER, GH_REPO), + github::download_url(GH_OWNER, GH_REPO, None), launcher_name ), &file_path, diff --git a/src/structs.rs b/src/structs.rs index 3f56054..f49c4e6 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -46,6 +46,8 @@ pub struct Config { pub use_https: bool, #[serde(default)] pub skip_redist: bool, + #[serde(default)] + pub prerelease: bool, } impl Default for Config { @@ -60,6 +62,7 @@ impl Default for Config { engine: String::default(), use_https: true, skip_redist: false, + prerelease: false, } } }