diff --git a/Cargo.lock b/Cargo.lock index 2af4440..961ed64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,11 +18,13 @@ dependencies = [ "http_req", "mslnk", "rand", + "self-replace", "semver", "serde", "serde_json", "sha1_smol", "steamlocate", + "windows-sys", "winres", ] @@ -38,6 +40,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + [[package]] name = "block-buffer" version = "0.10.4" @@ -121,6 +129,42 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "generic-array" version = "0.14.7" @@ -154,6 +198,15 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.6" @@ -196,9 +249,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.144" +version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" [[package]] name = "log" @@ -218,7 +277,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86c97310150b7f496a93f31690da7822b99d95ff68ca9d30fb09d3ad54375c76" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "log", ] @@ -351,7 +410,16 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -361,7 +429,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] @@ -409,6 +477,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "rustls" version = "0.19.1" @@ -438,6 +519,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "self-replace" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e7c919783db74b5995f13506069227e4721d388bea4a8ac3055acac864ac16" +dependencies = [ + "fastrand 1.9.0", + "tempfile", + "windows-sys", +] + [[package]] name = "semver" version = "1.0.18" @@ -532,6 +624,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" +dependencies = [ + "cfg-if", + "fastrand 2.0.0", + "redox_syscall 0.3.5", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.40" diff --git a/Cargo.toml b/Cargo.toml index fb3cebc..31fd809 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,16 @@ semver = "1.0.18" [target.'cfg(windows)'.dependencies] steamlocate = "1.2.1" mslnk = "0.1.8" +# https://github.com/mitsuhiko/self-replace/pull/16/ +windows-sys = { version = "0.48", features = [ + "Win32_Security", +] } +self-replace = "1.3.5" [build-dependencies] winres = "0.1.12" [package.metadata.winres] OriginalFilename = "alterware-launcher.exe" -FileDescription = "AlterWare mod updater & launcher" +FileDescription = "AlterWare Launcher" ProductName = "AlterWare Launcher" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dd62263..c464f55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use mslnk::ShellLink; use semver::Version; use std::time::{SystemTime, UNIX_EPOCH}; use std::{fs, path::PathBuf}; -use std::{thread, time}; #[cfg(windows)] use steamlocate::SteamDir; @@ -39,7 +38,14 @@ fn get_file_sha1(path: &PathBuf) -> String { sha1.digest().to_string() } -fn check_for_launcher_update() { +#[cfg(windows)] +fn get_input() -> String { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + input.trim().to_string() +} + +fn self_update_available() -> bool { let current_version: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); let github_body = http::get_body_string( format!("https://api.github.com/repos/{}/releases/latest", REPO).as_str(), @@ -50,7 +56,12 @@ fn check_for_launcher_update() { .replace(['v', '"'].as_ref(), ""); let latest_version = Version::parse(&latest_version).unwrap(); - if current_version < latest_version { + current_version < latest_version +} + +#[cfg(not(windows))] +fn self_update() { + if self_update_available() { println!( "A new version of the AlterWare launcher is available: {}", latest_version @@ -61,6 +72,124 @@ fn check_for_launcher_update() { } } +#[cfg(windows)] +fn self_update() { + let working_dir = std::env::current_dir().unwrap(); + let files = fs::read_dir(&working_dir).unwrap(); + + for file in files { + let file = file.unwrap(); + let file_name = file.file_name().into_string().unwrap(); + + if file_name.contains("alterware-launcher") + && (file_name.contains(".__relocated__.exe") + || file_name.contains(".__selfdelete__.exe")) + { + fs::remove_file(file.path()).unwrap(); + } + } + + if self_update_available() { + println!("Performing launcher self-update."); + println!("If you run into any issues, please download the latest version at https://github.com/{}/releases/latest", REPO); + + let update_binary = PathBuf::from("alterware-launcher-update.exe"); + let file_path = working_dir.join(&update_binary); + + if update_binary.exists() { + fs::remove_file(&update_binary).unwrap(); + } + + http::download_file( + &format!( + "https://github.com/{}/releases/latest/download/alterware-launcher.exe", + REPO + ), + &file_path, + ); + + if !file_path.exists() { + println!("Failed to download launcher update."); + return; + } + + self_replace::self_replace("alterware-launcher-update.exe").unwrap(); + fs::remove_file(&file_path).unwrap(); + println!("Launcher updated. Please run it again."); + std::io::stdin().read_line(&mut String::new()).unwrap(); + std::process::exit(0); + } +} + +#[cfg(windows)] +fn get_installed_games(games: &Vec) -> Vec<(u32, PathBuf)> { + let mut installed_games = Vec::new(); + let mut steamdir = SteamDir::locate().unwrap(); + + for game in games { + if let Some(app) = steamdir.app(&game.app_id) { + installed_games.push((game.app_id, PathBuf::from(&app.path))); + } + } + + installed_games +} + +#[cfg(windows)] +fn windows_launcher_install(games: &Vec) { + println!("No game specified/found. Checking for installed Steam games.."); + let installed_games = get_installed_games(games); + + if !installed_games.is_empty() { + println!("Installed games:"); + + for (id, path) in installed_games.iter() { + println!("{}: {}", id, path.display()); + } + + println!("Enter the ID of the game you want to install the AlterWare client for:"); + let input: u32 = get_input().parse().unwrap(); + + for (id, path) in installed_games.iter() { + if *id == input { + let game = games.iter().find(|&g| g.app_id == input).unwrap(); + + let launcher_path = std::env::current_exe().unwrap(); + fs::copy(launcher_path, path.join("alterware-launcher.exe")).unwrap(); + println!("Launcher copied to {}", path.display()); + + println!("Create Desktop shortcut? (Y/n)"); + let input = get_input().to_ascii_lowercase(); + + if input == "y" || input.is_empty() { + let desktop = PathBuf::from(&format!( + "{}\\Desktop", + std::env::var("USERPROFILE").unwrap() + )); + + let target = path.join("alterware-launcher.exe"); + let lnk = desktop.join(format!("{}.lnk", game.client)); + + let mut sl = ShellLink::new(target).unwrap(); + sl.set_icon_location(Some( + path.join(format!("{}.exe", game.client)) + .to_string_lossy() + .into_owned(), + )); + sl.create_lnk(lnk).unwrap(); + } + + break; + } + } + + std::process::exit(0); + } + + println!("No installed Steam games found. Please install a supported game first or place the launcher in the game folder."); + std::process::exit(0); +} + fn update(game: &Game) { let cdn_info: Vec = serde_json::from_str(&http::get_body_string( format!("{}/files.json?{}", MASTER, get_cache_buster()).as_str(), @@ -112,89 +241,13 @@ fn launch(file_path: &PathBuf) { .expect("Failed to wait for the game process to finish"); } -#[cfg(windows)] -fn get_input() -> String { - let mut input = String::new(); - std::io::stdin().read_line(&mut input).unwrap(); - input.trim().to_string() -} - -#[cfg(windows)] -fn get_installed_games(games: &Vec) -> Vec<(u32, PathBuf)> { - let mut installed_games = Vec::new(); - let mut steamdir = SteamDir::locate().unwrap(); - - for game in games { - if let Some(app) = steamdir.app(&game.app_id) { - installed_games.push((game.app_id, PathBuf::from(&app.path))); - } - } - - installed_games -} - -#[cfg(windows)] -fn windows_launcher_install(games: &Vec) { - println!("No game specified/found. Checking for installed Steam games.."); - let installed_games = get_installed_games(games); - - if !installed_games.is_empty() { - println!("Installed games:"); - - for (id, path) in installed_games.iter() { - println!("{}: {}", id, path.display()); - } - - println!("Enter the ID of the game you want to install the AlterWare client for:"); - let input: u32 = get_input().parse().unwrap(); - - for (id, path) in installed_games.iter() { - if *id == input { - let game = games.iter().find(|&g| g.app_id == input).unwrap(); - - // Copy the launcher to the game folder - let launcher_path = std::env::current_exe().unwrap(); - fs::copy(launcher_path, path.join("alterware-launcher.exe")).unwrap(); - println!("Launcher copied to {}", path.display()); - - println!("Create Desktop shortcut? (Y/n)"); - let input = get_input().to_ascii_lowercase(); - - if input == "y" || input.is_empty() { - let desktop = PathBuf::from(&format!( - "{}\\Desktop", - std::env::var("USERPROFILE").unwrap() - )); - - let target = path.join("alterware-launcher.exe"); - let lnk = desktop.join(format!("{}.lnk", game.client)); - - let mut sl = ShellLink::new(target).unwrap(); - sl.set_icon_location(Some( - path.join(format!("{}.exe", game.client)) - .to_string_lossy() - .into_owned(), - )); - sl.create_lnk(lnk).unwrap(); - } - - break; - } - } - - std::process::exit(0); - } - - println!("No installed Steam games found. Please install a supported game first or place the launcher in the game folder."); - std::process::exit(0); -} - fn main() { - check_for_launcher_update(); + self_update(); let mut args: Vec = std::env::args().collect(); - let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str()); + let games_json = + http::get_body_string(format!("{}/games.json?{}", MASTER, get_cache_buster()).as_str()); let games: Vec = serde_json::from_str(&games_json).unwrap(); let mut update_only = false;