diff --git a/Cargo.lock b/Cargo.lock index a89341d..248cf40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,7 @@ dependencies = [ "openssl", "rand", "reqwest", + "runas", "self-replace", "semver", "serde", @@ -272,6 +273,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -483,6 +490,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "1.1.0" @@ -1246,6 +1262,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "runas" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96d6b6c505282b007a9b009f2aa38b2fd0359b81a0430ceacc60f69ade4c6a0" +dependencies = [ + "libc", + "security-framework-sys", + "which", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1948,6 +1976,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 00927b4..2db587a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ openssl = { version = "0.10.64", default-features = false, features = ["vendored steamlocate = "=2.0.0-beta.2" mslnk = "0.1.8" self-replace = "1.3.7" +runas = "1.2.0" [build-dependencies] winresource = "0.1.17" diff --git a/README.md b/README.md index 3c5c38e..aaa10e9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,10 @@ --- +**Only legitimate copies of the games are supported. If you don't own the game, please buy it.** + +--- + ## Installation 1. Download the game from [Steam](https://store.steampowered.com/) @@ -22,6 +26,8 @@ - ```iw4-sp```, ```iw4x```, ```iw5-mod```, ```iw6-mod```, ```s1-mod``` - Skip automatic detection and launch the specified game - This should always be the first argument if used +- ```--help``` + - Print help - ```--update```, ```-u``` - Only update the game, don't launch it - ```--skip-launcher-update``` @@ -40,6 +46,10 @@ - Print the launcher version - ```--ignore-required-files``` - Install client even if required files are missing +- ```--skip-redist``` + - Skip redistributable installation +- ```--redist``` + - (Re-)install redistributables Example: ```alterware-launcher.exe iw4x --bonus -u --path "C:\Games\IW4x" --pass "-console"``` diff --git a/src/config.rs b/src/config.rs index 373c26b..23279bf 100644 --- a/src/config.rs +++ b/src/config.rs @@ -37,6 +37,7 @@ pub fn save_value(config_path: PathBuf, key: &str, value: bool) { "ask_bonus_content" => config.ask_bonus_content = value, "force_update" => config.force_update = value, "use_https" => config.use_https = value, + "skip_redist" => config.skip_redist = value, _ => (), } save(config_path, config); diff --git a/src/main.rs b/src/main.rs index a04efcf..142b731 100644 --- a/src/main.rs +++ b/src/main.rs @@ -167,28 +167,6 @@ async fn windows_launcher_install(games: &Vec>, master_url: &String) { } } -// fn prompt_client_selection(games: &[Game]) -> String { -// println!( -// "Couldn't detect any games, please select a client to install in the current directory:" -// ); -// for (i, g) in games.iter().enumerate() { -// for c in g.client.iter() { -// println!("{}: {}", i, c); -// } -// } -// let input: usize = misc::stdin().parse().unwrap(); -// String::from(games[input].client[0]) -// } - -// async fn manual_install(games: &[Game<'_>]) { -// let selection = prompt_client_selection(games); -// let game = games.iter().find(|&g| g.client[0] == selection).unwrap(); -// update(game, &env::current_dir().unwrap(), false, false).await; -// println!("Installation complete. Please run the launcher again or use a shortcut to launch the game."); -// std::io::stdin().read_line(&mut String::new()).unwrap(); -// std::process::exit(0); -// } - fn total_download_size(cdn_info: &Vec, remote_dir: &str) -> u64 { let remote_dir = format!("{}/", remote_dir); let mut size: u64 = 0; @@ -598,6 +576,8 @@ async fn main() { println!(" --pass : Pass arguments to the game"); println!(" --skip-launcher-update: Skip launcher self-update"); println!(" --ignore-required-files: Skip required files check"); + println!(" --skip-redist: Skip redistributable installation"); + println!(" --redist: (Re-)Install redistributables"); println!( "\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\"" ); @@ -679,6 +659,20 @@ async fn main() { cfg.args = String::default(); } + if arg_bool(&args, "--skip-redist") { + cfg.skip_redist = true; + arg_remove(&mut args, "--skip-redist"); + } + + #[cfg(windows)] + if arg_bool(&args, "--redist") { + arg_remove(&mut args, "--redist"); + misc::install_dependencies(&install_path).await; + println_info!("Redistributables installation finished. Press enter to exit..."); + misc::stdin(); + std::process::exit(0); + } + let games_json = http_async::get_body_string(format!("{}/games.json", master_url).as_str()) .await .unwrap(); @@ -738,6 +732,11 @@ async fn main() { cfg.args.clone(), ); } + + #[cfg(windows)] + if !cfg.skip_redist { + misc::install_dependencies(&install_path).await; + } } if cfg.engine == "iw4" && !cfg.args.contains("+set logfile 1") { diff --git a/src/misc.rs b/src/misc.rs index 9c026e7..40c2e12 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -85,3 +85,71 @@ macro_rules! println_error { error!($($arg)*); }} } + +#[cfg(windows)] +fn install_dependency(path: &Path, args: &[&str]) { + if path.exists() { + match runas::Command::new(path).args(args).status() { + Ok(status) => { + if !status.success() && !matches!(status.code(), Some(1638) | Some(3010)) { + println_error!("Error installing dependency {}, {}", path.display(), status); + } else { + info!("{} installed successfully", path.display()); + } + } + Err(e) => { + if let Some(740) = e.raw_os_error() { + println_error!( + "Error: Process requires elevation. Please run the launcher as administrator or install {} manually", + path.display() + ); + } else { + println_error!("Error running file {}: {}", path.display(), e); + } + } + } + } else { + println_error!("Installer not found: {}", path.display()); + } +} + +#[cfg(windows)] +async fn download_and_install_dependency(url: &str, path: &Path, args: &[&str]) { + if !path.exists() { + info!("Downloading {} from {}", path.display(), url); + if let Some(parent) = path.parent() { + match fs::create_dir_all(parent) { + Ok(_) => (), + Err(e) => { + println_error!("Error creating directory {}: {}", parent.display(), e); + return; + } + } + } + match crate::http_async::download_file(url, &PathBuf::from(path)).await { + Ok(_) => info!("Downloaded {}", path.display()), + Err(e) => println_error!("Error downloading {}: {}", path.display(), e), + } + } + install_dependency(path, args); +} + +#[cfg(windows)] +pub async fn install_dependencies(install_path: &Path) { + println!("If you run into issues during dependency installation, open alterware-launcher.json and set \"skip_redist\" to true"); + + let redist_dir = install_path.join("redist\\alterware"); + let redists = [ + ("VC++ 2005", "https://download.microsoft.com/download/8/B/4/8B42259F-5D70-43F4-AC2E-4B208FD8D66A/vcredist_x86.EXE", "vcredist_2005_x86.exe", &["/Q"] as &[&str]), + ("VC++ 2008", "https://download.microsoft.com/download/5/D/8/5D8C65CB-C849-4025-8E95-C3966CAFD8AE/vcredist_x86.exe", "vcredist_2008_x86.exe", &["/Q"] as &[&str]), + ("VC++ 2010", "https://download.microsoft.com/download/1/6/5/165255E7-1014-4D0A-B094-B6A430A6BFFC/vcredist_x86.exe", "vcredist_2010_x86.exe", &["/Q"] as &[&str]), + ("VC++ 2015", "https://download.microsoft.com/download/9/3/F/93FCF1E7-E6A4-478B-96E7-D4B285925B00/vc_redist.x86.exe", "vcredist_2015_x86.exe", &["/install", "/passive", "/norestart"] as &[&str]), + ("DirectX End User Runtime", "https://download.microsoft.com/download/1/7/1/1718CCC4-6315-4D8E-9543-8E28A4E18C4C/dxwebsetup.exe", "dxwebsetup.exe", &["/Q"] as &[&str]), + ]; + + for (name, url, file, args) in redists.iter() { + let path = redist_dir.join(file); + println_info!("Installing {}", name); + download_and_install_dependency(url, &path, args).await; + } +} diff --git a/src/structs.rs b/src/structs.rs index 60808d2..dba96b4 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -43,6 +43,8 @@ pub struct Config { pub engine: String, #[serde(default)] pub use_https: bool, + #[serde(default)] + pub skip_redist: bool, } impl Default for Config { @@ -56,6 +58,7 @@ impl Default for Config { args: String::default(), engine: String::default(), use_https: true, + skip_redist: false, } } }