7 Commits

Author SHA1 Message Date
757c1acf1b v0.2.3 2023-06-12 18:11:16 +02:00
b15d0eb7d7 Add "update" cli arg 2023-06-12 02:54:55 +02:00
eb508f2b48 v0.2.2 2023-06-11 18:52:17 +02:00
6ae9bcabad get available mods from master 2023-06-11 18:48:27 +02:00
d25e7cf3cd Keep console open if game not found 2023-06-11 18:30:55 +02:00
d587553845 update all game-specific files, add iw6 2023-06-11 16:16:29 +02:00
4a488abb8e lint 2023-06-10 13:42:42 +02:00
4 changed files with 89 additions and 53 deletions

2
Cargo.lock generated
View File

@ -4,7 +4,7 @@ version = 3
[[package]] [[package]]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"http_req", "http_req",
"rand", "rand",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.2.1" version = "0.2.3"
edition = "2021" edition = "2021"
build = "res/build.rs" build = "res/build.rs"

View File

@ -1,5 +1,10 @@
# alterware-launcher # alterware-launcher
1. Download [latest release](https://github.com/mxve/alterware-launcher/releases/latest/download/alterware-launcher-x86_64-pc-windows-msvc.zip) 1. Download [latest release](https://github.com/mxve/alterware-launcher/releases/latest/download/alterware-launcher-x86_64-pc-windows-msvc.zip)
2. Unpack the archive and place alterware-launcher.exe in MW2/MW3 game directory 2. Unpack the archive and place alterware-launcher.exe in MW2/MW3/Ghosts game directory
3. Run alterware-launcher.exe 3. Run alterware-launcher.exe
---
- Passing ```iw4-sp```, ```iw5-mod``` or ```iw6-mod``` as the first argument will skip automatic game detection
- Passing ```update``` will stop the launcher from launching the game

View File

@ -8,25 +8,14 @@ struct CdnFile {
hash: String, hash: String,
} }
#[derive(serde::Deserialize, serde::Serialize)]
struct Game<'a> { struct Game<'a> {
engine: &'a str, engine: &'a str,
client: &'a str, client: &'a str,
references: &'a [&'a str], references: Vec<&'a str>,
} }
const MASTER: &str = "https://master.alterware.dev"; 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 file_get_sha1(path: &PathBuf) -> String { fn file_get_sha1(path: &PathBuf) -> String {
let mut sha1 = sha1_smol::Sha1::new(); let mut sha1 = sha1_smol::Sha1::new();
@ -34,27 +23,67 @@ fn file_get_sha1(path: &PathBuf) -> String {
sha1.digest().to_string() sha1.digest().to_string()
} }
fn download_and_launch(url: &str, file_path: &PathBuf, hash: Option<String>) { fn update(game: &Game) {
let cdn_info: Vec<CdnFile> = 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();
let mut files_to_update: Vec<CdnFile> = Vec::new();
for file in cdn_info {
if file.name.starts_with(game.engine) {
files_to_update.push(file);
}
}
for file in files_to_update {
let file_path = PathBuf::from(&file.name.replace(&format!("{}/", game.engine), ""));
if file_path.exists() {
let sha1_local = file_get_sha1(&file_path).to_lowercase();
let sha1_remote = file.hash.to_lowercase();
if sha1_local != sha1_remote {
println!(
"Updating {}...\nLocal hash: {}\nRemote hash: {}",
file_path.display(),
sha1_local,
sha1_remote
);
http::download_file(
&format!(
"{}/{}?{}",
MASTER,
file.name,
rand::Rng::gen_range(&mut rand::thread_rng(), 0..1000)
),
&file_path,
);
}
} else {
println!("Downloading {}...", file_path.display());
if let Some(parent) = file_path.parent() { if let Some(parent) = file_path.parent() {
if !parent.exists() { if !parent.exists() {
fs::create_dir_all(parent).unwrap(); fs::create_dir_all(parent).unwrap();
} }
} }
http::download_file(
if file_path.exists() && hash.is_some() { &format!(
let sha1_local = file_get_sha1(file_path).to_lowercase(); "{}/{}?{}",
let sha1_remote = hash.unwrap().to_lowercase(); MASTER,
if sha1_local != sha1_remote { file.name,
println!("Local hash: {}", sha1_local); rand::Rng::gen_range(&mut rand::thread_rng(), 0..1000)
println!("Remote hash: {}", sha1_remote); ),
println!("Updating {}...", file_path.display()); &file_path,
http::download_file(url, file_path); );
} }
} else {
println!("Downloading {}...", file_path.display());
http::download_file(url, file_path);
} }
}
fn launch(file_path: &PathBuf) {
println!("Launching {}...", file_path.display()); println!("Launching {}...", file_path.display());
std::process::Command::new(file_path) std::process::Command::new(file_path)
.spawn() .spawn()
@ -63,28 +92,25 @@ fn download_and_launch(url: &str, file_path: &PathBuf, hash: Option<String>) {
.unwrap(); .unwrap();
} }
fn get_hash(game: &Game) -> Option<String> {
let cdn_info: Vec<CdnFile> = 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);
}
}
None
}
fn main() { fn main() {
let args: Vec<String> = std::env::args().collect(); let mut args: Vec<String> = std::env::args().collect();
let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str());
let games: Vec<Game> = serde_json::from_str(&games_json).unwrap();
let mut update_only = false;
if args.contains(&String::from("update")) {
update_only = true;
args.iter()
.position(|r| r == "update")
.map(|e| args.remove(e));
}
let mut game: String = String::new(); let mut game: String = String::new();
if args.len() > 1 { if args.len() > 1 {
game = String::from(&args[1]); game = String::from(&args[1]);
} else { } else {
'main: for g in GAMES.iter() { 'main: for g in games.iter() {
for r in g.references.iter() { for r in g.references.iter() {
if std::path::Path::new(r).exists() { if std::path::Path::new(r).exists() {
game = String::from(g.client); game = String::from(g.client);
@ -94,14 +120,19 @@ fn main() {
} }
} }
for g in GAMES.iter() { for g in games.iter() {
if g.client == game { if g.client == game {
download_and_launch( update(g);
&format!("{}/{}/{}.exe?{}", MASTER, g.engine, g.client, rand::Rng::gen_range(&mut rand::thread_rng(), 0..1000)), if update_only {
&PathBuf::from(format!("{}.exe", g.client)), return;
get_hash(g) }
); launch(&PathBuf::from(format!("{}.exe", g.client)));
return; return;
} }
} }
println!("Game not found!");
println!("Place the launcher in the game folder, if that doesn't work specify the client on the command line (ex. alterware-launcher.exe iw4-sp)");
println!("Press enter to exit...");
std::io::stdin().read_line(&mut String::new()).unwrap();
} }