diff --git a/src/github.rs b/src/github.rs new file mode 100644 index 0000000..5bae962 --- /dev/null +++ b/src/github.rs @@ -0,0 +1,25 @@ +use crate::global::*; + +use semver::Version; + +pub fn latest_version() -> Version { + let github_body = crate::http::get_body_string( + format!( + "https://api.github.com/repos/{}/{}/releases/latest", + GH_OWNER, GH_REPO + ) + .as_str(), + ); + let github_json: serde_json::Value = serde_json::from_str(&github_body).unwrap(); + let latest_version = github_json["tag_name"] + .to_string() + .replace(['v', '"'].as_ref(), ""); + Version::parse(&latest_version).unwrap() +} + +pub fn latest_release_url() -> String { + format!( + "https://github.com/{}/{}/releases/latest", + GH_OWNER, GH_REPO + ) +} diff --git a/src/global.rs b/src/global.rs new file mode 100644 index 0000000..40485bd --- /dev/null +++ b/src/global.rs @@ -0,0 +1,3 @@ +pub const MASTER: &str = "https://master.alterware.dev"; +pub const GH_OWNER: &str = "mxve"; +pub const GH_REPO: &str = "alterware-launcher"; diff --git a/src/main.rs b/src/main.rs index 37382cc..1c3b240 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,118 +1,19 @@ +mod github; +mod global; mod http; +mod misc; +mod self_update; +mod structs; + +use global::*; +use structs::*; + #[cfg(windows)] use mslnk::ShellLink; -use semver::Version; use std::{fs, path::Path, path::PathBuf}; -#[cfg(not(windows))] -use std::{thread, time}; #[cfg(windows)] use steamlocate::SteamDir; -#[derive(serde::Deserialize, serde::Serialize)] -struct CdnFile { - name: String, - size: u32, - hash: String, -} - -#[derive(serde::Deserialize, serde::Serialize)] -struct Game<'a> { - engine: &'a str, - client: Vec<&'a str>, - references: Vec<&'a str>, - app_id: u32, -} - -const MASTER: &str = "https://master.alterware.dev"; -const REPO: &str = "mxve/alterware-launcher"; - -fn get_file_sha1(path: &PathBuf) -> String { - let mut sha1 = sha1_smol::Sha1::new(); - sha1.update(&fs::read(path).unwrap()); - sha1.digest().to_string() -} - -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(), - ); - let github_json: serde_json::Value = serde_json::from_str(&github_body).unwrap(); - let latest_version = github_json["tag_name"] - .to_string() - .replace(['v', '"'].as_ref(), ""); - let latest_version = Version::parse(&latest_version).unwrap(); - - current_version < latest_version -} - -#[cfg(not(windows))] -fn self_update(_update_only: bool) { - if self_update_available() { - println!("A new version of the AlterWare launcher is available."); - println!("Download it at https://github.com/{}/releases/latest", REPO); - println!("Launching in 10 seconds.."); - thread::sleep(time::Duration::from_secs(10)); - } -} - -#[cfg(windows)] -fn self_update(update_only: bool) { - 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."); - if !update_only { - std::io::stdin().read_line(&mut String::new()).unwrap(); - } - std::process::exit(201); - } -} - #[cfg(windows)] fn get_installed_games(games: &Vec) -> Vec<(u32, PathBuf)> { let mut installed_games = Vec::new(); @@ -156,6 +57,41 @@ fn setup_client_links(game: &Game, game_dir: &Path) { } } +#[cfg(windows)] +fn setup_desktop_links(path: &Path, game: &Game) { + println!("Create Desktop shortcut? (Y/n)"); + let input = misc::stdin().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"); + + for c in game.client.iter() { + let lnk = desktop.join(format!("{}.lnk", c)); + + let mut sl = ShellLink::new(target.clone()).unwrap(); + sl.set_arguments(Some(c.to_string())); + sl.set_icon_location(Some( + path.join(format!("{}.exe", c)) + .to_string_lossy() + .into_owned(), + )); + sl.create_lnk(lnk).unwrap(); + } + } +} + +#[cfg(windows)] +fn auto_install(path: &Path, game: &Game) { + setup_client_links(game, path); + setup_desktop_links(path, game); + update(game, path); +} + #[cfg(windows)] fn windows_launcher_install(games: &Vec) { println!("No game specified/found. Checking for installed Steam games.."); @@ -169,8 +105,7 @@ fn windows_launcher_install(games: &Vec) { println!("Found game in current directory."); println!("Installing AlterWare client for {}.", id); let game = games.iter().find(|&g| g.app_id == *id).unwrap(); - setup_client_links(game, path); - update(game, path); + auto_install(path, game); 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); @@ -184,7 +119,7 @@ fn windows_launcher_install(games: &Vec) { } println!("Enter the ID of the game you want to install the AlterWare client for, enter 0 for manual selection:"); - let input: u32 = get_input().parse().unwrap(); + let input: u32 = misc::stdin().parse().unwrap(); if input == 0 { return manual_install(games); @@ -201,34 +136,7 @@ fn windows_launcher_install(games: &Vec) { fs::copy(launcher_path, target_path).unwrap(); println!("Launcher copied to {}", path.display()); } - setup_client_links(game, path); - - 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"); - - for c in game.client.iter() { - let lnk = desktop.join(format!("{}.lnk", c)); - - let mut sl = ShellLink::new(target.clone()).unwrap(); - sl.set_arguments(Some(c.to_string())); - sl.set_icon_location(Some( - path.join(format!("{}.exe", c)) - .to_string_lossy() - .into_owned(), - )); - sl.create_lnk(lnk).unwrap(); - } - } - - update(game, path); + auto_install(path, game); 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(); break; @@ -249,7 +157,7 @@ fn prompt_client_selection(games: &[Game]) -> String { println!("{}: {}", i, c); } } - let input: usize = get_input().parse().unwrap(); + let input: usize = misc::stdin().parse().unwrap(); String::from(games[input].client[0]) } @@ -275,7 +183,7 @@ fn update(game: &Game, dir: &Path) { let file_path = dir.join(&file.name.replace(&format!("{}/", game.engine), "")); if file_path.exists() { - let sha1_local = get_file_sha1(&file_path).to_lowercase(); + let sha1_local = misc::get_file_sha1(&file_path).to_lowercase(); let sha1_remote = file.hash.to_lowercase(); if sha1_local != sha1_remote { println!( @@ -319,7 +227,7 @@ fn main() { } if !args.contains(&String::from("skip-launcher-update")) { - self_update(update_only); + self_update::run(update_only); } else { args.iter() .position(|r| r == "skip-launcher-update") @@ -347,11 +255,11 @@ fn main() { #[cfg(not(windows))] println!("Multiple clients installed, set the client as the first argument to launch a specific client."); - + println!("Select a client to launch:"); for (i, c) in g.client.iter().enumerate() { println!("{}: {}", i, c); } - game = String::from(g.client[get_input().parse::().unwrap()]); + game = String::from(g.client[misc::stdin().parse::().unwrap()]); break 'main; } game = String::from(g.client[0]); diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..d2de582 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,13 @@ +use std::{fs, path::PathBuf}; + +pub fn get_file_sha1(path: &PathBuf) -> String { + let mut sha1 = sha1_smol::Sha1::new(); + sha1.update(&fs::read(path).unwrap()); + sha1.digest().to_string() +} + +pub fn stdin() -> String { + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + input.trim().to_string() +} diff --git a/src/self_update.rs b/src/self_update.rs new file mode 100644 index 0000000..87eeae4 --- /dev/null +++ b/src/self_update.rs @@ -0,0 +1,80 @@ +use crate::github; + +use semver::Version; +#[cfg(not(windows))] +use std::{thread, time}; + +pub fn self_update_available() -> bool { + let current_version: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap(); + let latest_version = github::latest_version(); + + current_version < latest_version +} + +#[cfg(not(windows))] +pub fn run(_update_only: bool) { + if self_update_available() { + println!("A new version of the AlterWare launcher is available."); + println!("Download it at {}", github::latest_release_url()); + println!("Launching in 10 seconds.."); + thread::sleep(time::Duration::from_secs(10)); + } +} + +#[cfg(windows)] +pub fn run(update_only: bool) { + use std::{fs, path::PathBuf}; + + use crate::http; + + 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 {}", + github::latest_release_url() + ); + + 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!( + "{}/download/alterware-launcher.exe", + github::latest_release_url() + ), + &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."); + if !update_only { + std::io::stdin().read_line(&mut String::new()).unwrap(); + } + std::process::exit(201); + } +} diff --git a/src/structs.rs b/src/structs.rs new file mode 100644 index 0000000..1a188dc --- /dev/null +++ b/src/structs.rs @@ -0,0 +1,14 @@ +#[derive(serde::Deserialize, serde::Serialize)] +pub struct CdnFile { + pub name: String, + pub size: u32, + pub hash: String, +} + +#[derive(serde::Deserialize, serde::Serialize)] +pub struct Game<'a> { + pub engine: &'a str, + pub client: Vec<&'a str>, + pub references: Vec<&'a str>, + pub app_id: u32, +}