diff --git a/Cargo.lock b/Cargo.lock index a7009cb..5b4adef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,13 @@ version = 3 [[package]] name = "alterware-launcher" -version = "0.2.0" +version = "0.2.1" dependencies = [ "http_req", + "rand", + "serde", + "serde_json", + "sha1_smol", "winres", ] @@ -34,6 +38,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "http_req" version = "0.9.1" @@ -46,6 +61,12 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + [[package]] name = "js-sys" version = "0.3.63" @@ -73,6 +94,12 @@ version = "1.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro2" version = "1.0.59" @@ -91,6 +118,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "ring" version = "0.16.20" @@ -119,6 +176,12 @@ dependencies = [ "webpki", ] +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + [[package]] name = "sct" version = "0.6.1" @@ -134,6 +197,37 @@ name = "serde" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" [[package]] name = "spin" @@ -188,6 +282,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.86" diff --git a/Cargo.toml b/Cargo.toml index ba3d009..ffc1e6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alterware-launcher" -version = "0.2.0" +version = "0.2.1" edition = "2021" build = "res/build.rs" @@ -14,6 +14,10 @@ panic = "abort" http_req = { version = "0.9.0", default-features = false, features = [ "rust-tls", ] } +sha1_smol = "1.0.0" +serde = { version = "1.0.164", features = ["derive"] } +serde_json = "1.0.96" +rand = "0.8.5" [build-dependencies] winres = "0.1.12" diff --git a/src/http.rs b/src/http.rs index e59fc44..46e16e5 100644 --- a/src/http.rs +++ b/src/http.rs @@ -9,7 +9,7 @@ pub fn get_body(url: &str) -> Vec { res } -pub fn _get_body_string(url: &str) -> String { +pub fn get_body_string(url: &str) -> String { String::from_utf8(get_body(url)).unwrap() } diff --git a/src/main.rs b/src/main.rs index d8e6dcd..644e81a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,61 @@ mod http; -use std::path::PathBuf; +use std::{fs, path::PathBuf}; + +#[derive(serde::Deserialize, serde::Serialize)] +struct CdnFile { + name: String, + size: u32, + hash: String, +} + +struct Game<'a> { + engine: &'a str, + client: &'a str, + references: &'a [&'a str], +} const MASTER: &str = "https://master.alterware.dev"; +const GAMES: [Game; 2] = [ + Game { + engine: "iw4", + client: "iw4-sp", + references: &["iw4sp.exe", "iw4mp.exe"], + }, + Game { + engine: "iw5", + client: "iw5-mod", + references: &["iw5sp.exe", "iw5mp.exe", "iw5mp_server.exe"], + }, +]; -fn download_and_launch(url: &str, file_path: &PathBuf) { - http::download_file(url, file_path); +fn file_get_sha1(path: &PathBuf) -> String { + let mut sha1 = sha1_smol::Sha1::new(); + sha1.update(&fs::read(path).unwrap()); + sha1.digest().to_string() +} + +fn download_and_launch(url: &str, file_path: &PathBuf, hash: Option) { + if let Some(parent) = file_path.parent() { + if !parent.exists() { + fs::create_dir_all(parent).unwrap(); + } + } + + if file_path.exists() && hash.is_some() { + let sha1_local = file_get_sha1(file_path).to_lowercase(); + let sha1_remote = hash.unwrap().to_lowercase(); + if sha1_local != sha1_remote { + println!("Local hash: {}", sha1_local); + println!("Remote hash: {}", sha1_remote); + println!("Updating {}...", file_path.display()); + http::download_file(url, file_path); + } + } else { + println!("Downloading {}...", file_path.display()); + http::download_file(url, file_path); + } + + println!("Launching {}...", file_path.display()); std::process::Command::new(file_path) .spawn() .unwrap() @@ -12,38 +63,45 @@ fn download_and_launch(url: &str, file_path: &PathBuf) { .unwrap(); } -fn main() { - let args: Vec = std::env::args().collect(); - let game: String; - if args.len() > 1 { - game = String::from(&args[1]); - } else { - // check if iw4sp.exe or iw4mp.exe exists - if std::path::Path::new("iw4sp.exe").exists() || std::path::Path::new("iw4mp.exe").exists() - { - game = String::from("iw4-sp"); - } else if std::path::Path::new("iw5sp.exe").exists() - || std::path::Path::new("iw5mp.exe").exists() - || std::path::Path::new("iw5mp_server.exe").exists() - { - game = String::from("iw5-mod"); - } else { - println!("No game specified and no game found in current directory"); - return; +fn get_hash(game: &Game) -> Option { + let cdn_info: Vec = serde_json::from_str(&http::get_body_string( + format!("{}/files.json?{}", MASTER, rand::Rng::gen_range(&mut rand::thread_rng(), 0..1000)).as_str(), + )) + .unwrap(); + + for file in cdn_info { + if file.name == format!("{}/{}.exe", game.engine, game.client) { + return Some(file.hash); } } - if game == "iw4-sp" { - download_and_launch( - &format!("{}/iw4/iw4-sp.exe", MASTER), - &PathBuf::from("iw4-sp.exe"), - ); - } else if game == "iw5-mod" { - download_and_launch( - &format!("{}/iw5/iw5-mod.exe", MASTER), - &PathBuf::from("iw5-mod.exe"), - ); + None +} + +fn main() { + let args: Vec = std::env::args().collect(); + let mut game: String = String::new(); + if args.len() > 1 { + game = String::from(&args[1]); } else { - println!("Invalid game"); + 'main: for g in GAMES.iter() { + for r in g.references.iter() { + if std::path::Path::new(r).exists() { + game = String::from(g.client); + break 'main; + } + } + } + } + + for g in GAMES.iter() { + if g.client == game { + download_and_launch( + &format!("{}/{}/{}.exe?{}", MASTER, g.engine, g.client, rand::Rng::gen_range(&mut rand::thread_rng(), 0..1000)), + &PathBuf::from(format!("{}.exe", g.client)), + get_hash(g) + ); + return; + } } }