Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
c3e3d9a7d5 | |||
47a6eaf4cf | |||
1451dab658 | |||
618033e18f | |||
2b891276a4 | |||
5ed9fd4710 | |||
4763759997 | |||
6ebb421a7b | |||
6ef80be01f | |||
1a3c090612 | |||
fe7c8d2834 | |||
b3168b0a58 | |||
b1cffb44ad | |||
5414213a19 | |||
6ce845e761 | |||
ecff4846ed | |||
1578924579 | |||
33fc7d0f6a | |||
461eb05ac5 | |||
cb210b16e1 | |||
41ff4acd65 | |||
24672aa500 | |||
05def16ba6 | |||
c3cda3b845 | |||
3858e743f3 | |||
d28fed938c | |||
ff22c49ced | |||
82a1e4ba53 | |||
de7a661465 | |||
7e8e0fbc97 | |||
b09b92ee7f | |||
917640c4e6 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -21,8 +21,6 @@ jobs:
|
|||||||
include:
|
include:
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
- target: i686-unknown-linux-gnu
|
|
||||||
os: ubuntu-20.04
|
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
- target: i686-pc-windows-msvc
|
- target: i686-pc-windows-msvc
|
||||||
|
1076
Cargo.lock
generated
1076
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "alterware-launcher"
|
name = "alterware-launcher"
|
||||||
version = "0.5.4"
|
version = "0.6.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "res/build.rs"
|
build = "res/build.rs"
|
||||||
|
|
||||||
@ -15,16 +15,20 @@ panic = "abort"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
http_req = { version = "0.10.0", default-features = false, features = [
|
http_req = "0.10.1"
|
||||||
"rust-tls",
|
|
||||||
] }
|
|
||||||
sha1_smol = "1.0.0"
|
sha1_smol = "1.0.0"
|
||||||
serde = { version = "1.0.189", features = ["derive"] }
|
serde = { version = "1.0.190", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
semver = "1.0.20"
|
semver = "1.0.20"
|
||||||
zip = "0.6.6"
|
|
||||||
colored = "2.0.4"
|
colored = "2.0.4"
|
||||||
|
reqwest = { version = "0.11.22", features = ["stream"] }
|
||||||
|
futures-util = "0.3.29"
|
||||||
|
indicatif = "0.17.7"
|
||||||
|
tokio = {version="1.33.0", features = ["rt-multi-thread", "macros"]}
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
openssl = { version = "0.10.58", default-features = false, features = ["vendored"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
steamlocate = "2.0.0-alpha.0"
|
steamlocate = "2.0.0-alpha.0"
|
||||||
|
@ -40,3 +40,13 @@ pub fn save_value(config_path: PathBuf, key: &str, value: bool) {
|
|||||||
}
|
}
|
||||||
save(config_path, config);
|
save(config_path, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_value_s(config_path: PathBuf, key: &str, value: String) {
|
||||||
|
let mut config = load(config_path.clone());
|
||||||
|
match key {
|
||||||
|
"args" => config.args = value.to_string(),
|
||||||
|
"engine" => config.engine = value.to_string(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
save(config_path, config);
|
||||||
|
}
|
60
src/http_async.rs
Normal file
60
src/http_async.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use std::cmp::min;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use colored::*;
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use indicatif::ProgressBar;
|
||||||
|
use reqwest::Client;
|
||||||
|
|
||||||
|
use crate::misc;
|
||||||
|
|
||||||
|
pub async fn download_file(
|
||||||
|
client: &Client,
|
||||||
|
pb: &ProgressBar,
|
||||||
|
url: &str,
|
||||||
|
path: &PathBuf,
|
||||||
|
size: u64,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let res = client
|
||||||
|
.get(url)
|
||||||
|
.header(
|
||||||
|
"User-Agent",
|
||||||
|
&format!(
|
||||||
|
"AlterWare Launcher | github.com/{}/{}",
|
||||||
|
crate::global::GH_OWNER,
|
||||||
|
crate::global::GH_REPO
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.or(Err(format!("Failed to GET from '{}'", &url)))?;
|
||||||
|
// Fix for CF shenanigans
|
||||||
|
let total_size = res.content_length().unwrap_or(size);
|
||||||
|
pb.set_length(total_size);
|
||||||
|
pb.println(format!(
|
||||||
|
"[{}] {} ({})",
|
||||||
|
"Downloading".bright_yellow(),
|
||||||
|
misc::cute_path(path),
|
||||||
|
misc::human_readable_bytes(total_size)
|
||||||
|
));
|
||||||
|
pb.set_message(path.file_name().unwrap().to_str().unwrap().to_string());
|
||||||
|
|
||||||
|
let mut file =
|
||||||
|
File::create(path).or(Err(format!("Failed to create file '{}'", path.display())))?;
|
||||||
|
let mut downloaded: u64 = 0;
|
||||||
|
let mut stream = res.bytes_stream();
|
||||||
|
|
||||||
|
while let Some(item) = stream.next().await {
|
||||||
|
let chunk = item.or(Err("Error while downloading file"))?;
|
||||||
|
file.write_all(&chunk)
|
||||||
|
.or(Err("Error while writing to file"))?;
|
||||||
|
let new = min(downloaded + (chunk.len() as u64), total_size);
|
||||||
|
downloaded = new;
|
||||||
|
pb.set_position(new);
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.set_message(String::default());
|
||||||
|
Ok(())
|
||||||
|
}
|
10
src/iw4x.rs
10
src/iw4x.rs
@ -23,13 +23,21 @@ pub fn update(dir: &Path) {
|
|||||||
let local = local_revision(dir);
|
let local = local_revision(dir);
|
||||||
|
|
||||||
if remote <= local && dir.join("iw4x.dll").exists() {
|
if remote <= local && dir.join("iw4x.dll").exists() {
|
||||||
|
println!(
|
||||||
|
"[{}] No files to download for IW4x",
|
||||||
|
"Info".bright_magenta(),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"[{}] Downloading outdated or missing files for IW4x",
|
||||||
|
"Info".bright_magenta()
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"[{}] {}",
|
"[{}] {}",
|
||||||
"Downloading".bright_yellow(),
|
"Downloading".bright_yellow(),
|
||||||
dir.join("iw4x.dll").display()
|
misc::cute_path(&dir.join("iw4x.dll"))
|
||||||
);
|
);
|
||||||
http::download_file(
|
http::download_file(
|
||||||
&format!(
|
&format!(
|
||||||
|
135
src/main.rs
135
src/main.rs
@ -2,6 +2,7 @@ mod config;
|
|||||||
mod github;
|
mod github;
|
||||||
mod global;
|
mod global;
|
||||||
mod http;
|
mod http;
|
||||||
|
mod http_async;
|
||||||
mod iw4x;
|
mod iw4x;
|
||||||
mod misc;
|
mod misc;
|
||||||
mod self_update;
|
mod self_update;
|
||||||
@ -11,9 +12,10 @@ use global::*;
|
|||||||
use structs::*;
|
use structs::*;
|
||||||
|
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
use indicatif::ProgressBar;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use mslnk::ShellLink;
|
use mslnk::ShellLink;
|
||||||
use std::{borrow::Cow, collections::HashMap, fs, path::Path, path::PathBuf};
|
use std::{borrow::Cow, collections::HashMap, env, fs, path::Path, path::PathBuf};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use steamlocate::SteamDir;
|
use steamlocate::SteamDir;
|
||||||
|
|
||||||
@ -75,10 +77,7 @@ fn setup_desktop_links(path: &Path, game: &Game) {
|
|||||||
let input = misc::stdin().to_ascii_lowercase();
|
let input = misc::stdin().to_ascii_lowercase();
|
||||||
|
|
||||||
if input == "y" || input.is_empty() {
|
if input == "y" || input.is_empty() {
|
||||||
let desktop = PathBuf::from(&format!(
|
let desktop = PathBuf::from(&format!("{}\\Desktop", env::var("USERPROFILE").unwrap()));
|
||||||
"{}\\Desktop",
|
|
||||||
std::env::var("USERPROFILE").unwrap()
|
|
||||||
));
|
|
||||||
|
|
||||||
for c in game.client.iter() {
|
for c in game.client.iter() {
|
||||||
create_shortcut(
|
create_shortcut(
|
||||||
@ -94,14 +93,14 @@ fn setup_desktop_links(path: &Path, game: &Game) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn auto_install(path: &Path, game: &Game) {
|
async fn auto_install(path: &Path, game: &Game<'_>) {
|
||||||
setup_client_links(game, path);
|
setup_client_links(game, path);
|
||||||
setup_desktop_links(path, game);
|
setup_desktop_links(path, game);
|
||||||
update(game, path, false, false);
|
update(game, path, false, false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn windows_launcher_install(games: &Vec<Game>) {
|
async fn windows_launcher_install(games: &Vec<Game<'_>>) {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
"No game specified/found. Checking for installed Steam games..".yellow()
|
"No game specified/found. Checking for installed Steam games..".yellow()
|
||||||
@ -109,13 +108,13 @@ fn windows_launcher_install(games: &Vec<Game>) {
|
|||||||
let installed_games = get_installed_games(games);
|
let installed_games = get_installed_games(games);
|
||||||
|
|
||||||
if !installed_games.is_empty() {
|
if !installed_games.is_empty() {
|
||||||
let current_dir = std::env::current_dir().unwrap();
|
let current_dir = env::current_dir().unwrap();
|
||||||
for (id, path) in installed_games.iter() {
|
for (id, path) in installed_games.iter() {
|
||||||
if current_dir.starts_with(path) {
|
if current_dir.starts_with(path) {
|
||||||
println!("Found game in current directory.");
|
println!("Found game in current directory.");
|
||||||
println!("Installing AlterWare client for {}.", id);
|
println!("Installing AlterWare client for {}.", id);
|
||||||
let game = games.iter().find(|&g| g.app_id == *id).unwrap();
|
let game = games.iter().find(|&g| g.app_id == *id).unwrap();
|
||||||
auto_install(path, game);
|
auto_install(path, game).await;
|
||||||
println!("Installation complete. Please run the launcher again or use a shortcut to launch the 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::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
@ -132,21 +131,21 @@ fn windows_launcher_install(games: &Vec<Game>) {
|
|||||||
let input: u32 = misc::stdin().parse().unwrap();
|
let input: u32 = misc::stdin().parse().unwrap();
|
||||||
|
|
||||||
if input == 0 {
|
if input == 0 {
|
||||||
return manual_install(games);
|
return manual_install(games).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (id, path) in installed_games.iter() {
|
for (id, path) in installed_games.iter() {
|
||||||
if *id == input {
|
if *id == input {
|
||||||
let game = games.iter().find(|&g| g.app_id == input).unwrap();
|
let game = games.iter().find(|&g| g.app_id == input).unwrap();
|
||||||
|
|
||||||
let launcher_path = std::env::current_exe().unwrap();
|
let launcher_path = env::current_exe().unwrap();
|
||||||
let target_path = path.join("alterware-launcher.exe");
|
let target_path = path.join("alterware-launcher.exe");
|
||||||
|
|
||||||
if launcher_path != target_path {
|
if launcher_path != target_path {
|
||||||
fs::copy(launcher_path, target_path).unwrap();
|
fs::copy(launcher_path, target_path).unwrap();
|
||||||
println!("Launcher copied to {}", path.display());
|
println!("Launcher copied to {}", path.display());
|
||||||
}
|
}
|
||||||
auto_install(path, game);
|
auto_install(path, game).await;
|
||||||
println!("Installation complete. Please run the launcher again or use a shortcut to launch the 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::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
break;
|
break;
|
||||||
@ -154,7 +153,7 @@ fn windows_launcher_install(games: &Vec<Game>) {
|
|||||||
}
|
}
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
} else {
|
} else {
|
||||||
manual_install(games);
|
manual_install(games).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,10 +170,10 @@ fn prompt_client_selection(games: &[Game]) -> String {
|
|||||||
String::from(games[input].client[0])
|
String::from(games[input].client[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn manual_install(games: &[Game]) {
|
async fn manual_install(games: &[Game<'_>]) {
|
||||||
let selection = prompt_client_selection(games);
|
let selection = prompt_client_selection(games);
|
||||||
let game = games.iter().find(|&g| g.client[0] == selection).unwrap();
|
let game = games.iter().find(|&g| g.client[0] == selection).unwrap();
|
||||||
update(game, &std::env::current_dir().unwrap(), false, false);
|
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.");
|
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::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
@ -192,12 +191,15 @@ fn total_download_size(cdn_info: &Vec<CdnFile>, remote_dir: &str) -> u64 {
|
|||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_dir(
|
async fn update_dir(
|
||||||
cdn_info: &Vec<CdnFile>,
|
cdn_info: &Vec<CdnFile>,
|
||||||
remote_dir: &str,
|
remote_dir: &str,
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
hashes: &mut HashMap<String, String>,
|
hashes: &mut HashMap<String, String>,
|
||||||
|
pb: &ProgressBar,
|
||||||
) {
|
) {
|
||||||
|
misc::pb_style_download(pb, false);
|
||||||
|
|
||||||
let remote_dir_pre = format!("{}/", remote_dir);
|
let remote_dir_pre = format!("{}/", remote_dir);
|
||||||
|
|
||||||
let mut files_to_download: Vec<CdnFile> = vec![];
|
let mut files_to_download: Vec<CdnFile> = vec![];
|
||||||
@ -220,7 +222,12 @@ fn update_dir(
|
|||||||
if sha1_local != sha1_remote {
|
if sha1_local != sha1_remote {
|
||||||
files_to_download.push(file.clone());
|
files_to_download.push(file.clone());
|
||||||
} else {
|
} else {
|
||||||
println!("[{}] {}", "Checked".bright_blue(), file_path.display());
|
pb.println(format!(
|
||||||
|
"[{}] {}",
|
||||||
|
"Checked".bright_blue(),
|
||||||
|
misc::cute_path(&file_path)
|
||||||
|
));
|
||||||
|
hashes.insert(file_name.to_owned(), file.hash.to_lowercase());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
files_to_download.push(file.clone());
|
files_to_download.push(file.clone());
|
||||||
@ -228,39 +235,45 @@ fn update_dir(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if files_to_download.is_empty() {
|
if files_to_download.is_empty() {
|
||||||
println!(
|
pb.println(format!(
|
||||||
"[{}] No files to download for {}",
|
"[{}] No files to download for {}",
|
||||||
"Info".bright_magenta(),
|
"Info".bright_magenta(),
|
||||||
remote_dir
|
remote_dir
|
||||||
);
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
println!(
|
pb.println(format!(
|
||||||
"[{}] Downloading outdated or missing files for {}, {}",
|
"[{}] Downloading outdated or missing files for {}, {}",
|
||||||
"Info".bright_magenta(),
|
"Info".bright_magenta(),
|
||||||
remote_dir,
|
remote_dir,
|
||||||
misc::human_readable_bytes(total_download_size(&files_to_download, remote_dir))
|
misc::human_readable_bytes(total_download_size(&files_to_download, remote_dir))
|
||||||
);
|
));
|
||||||
|
|
||||||
|
misc::pb_style_download(pb, true);
|
||||||
|
let client = reqwest::Client::new();
|
||||||
for file in files_to_download {
|
for file in files_to_download {
|
||||||
let file_name = &file.name.replace(&remote_dir_pre, "");
|
let file_name = &file.name.replace(&remote_dir_pre, "");
|
||||||
let file_path = dir.join(file_name);
|
let file_path = dir.join(file_name);
|
||||||
println!(
|
|
||||||
"[{}] {} ({})",
|
|
||||||
"Downloading".bright_yellow(),
|
|
||||||
file_path.display(),
|
|
||||||
misc::human_readable_bytes(file.size as u64)
|
|
||||||
);
|
|
||||||
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(&format!("{}/{}", MASTER, file.name), &file_path);
|
http_async::download_file(
|
||||||
|
&client,
|
||||||
|
pb,
|
||||||
|
&format!("{}/{}", MASTER, file.name),
|
||||||
|
&file_path,
|
||||||
|
file.size as u64,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
hashes.insert(file_name.to_owned(), file.hash.to_lowercase());
|
hashes.insert(file_name.to_owned(), file.hash.to_lowercase());
|
||||||
}
|
}
|
||||||
|
misc::pb_style_download(pb, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(game: &Game, dir: &Path, bonus_content: bool, force: bool) {
|
async fn update(game: &Game<'_>, dir: &Path, bonus_content: bool, force: bool) {
|
||||||
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
|
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
|
||||||
format!("{}/files.json", MASTER).as_str(),
|
format!("{}/files.json", MASTER).as_str(),
|
||||||
))
|
))
|
||||||
@ -278,18 +291,21 @@ fn update(game: &Game, dir: &Path, bonus_content: bool, force: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_dir(&cdn_info, game.engine, dir, &mut hashes);
|
|
||||||
|
|
||||||
if game.engine == "iw4" {
|
if game.engine == "iw4" {
|
||||||
iw4x::update(dir);
|
iw4x::update(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pb = ProgressBar::new(0);
|
||||||
|
update_dir(&cdn_info, game.engine, dir, &mut hashes, &pb).await;
|
||||||
|
|
||||||
if bonus_content && !game.bonus.is_empty() {
|
if bonus_content && !game.bonus.is_empty() {
|
||||||
for bonus in game.bonus.iter() {
|
for bonus in game.bonus.iter() {
|
||||||
update_dir(&cdn_info, bonus, dir, &mut hashes);
|
update_dir(&cdn_info, bonus, dir, &mut hashes, &pb).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pb.finish();
|
||||||
|
|
||||||
let mut hash_file_content = String::new();
|
let mut hash_file_content = String::new();
|
||||||
for (file, hash) in hashes.iter() {
|
for (file, hash) in hashes.iter() {
|
||||||
hash_file_content.push_str(&format!("{} {}\n", hash, file));
|
hash_file_content.push_str(&format!("{} {}\n", hash, file));
|
||||||
@ -298,6 +314,7 @@ fn update(game: &Game, dir: &Path, bonus_content: bool, force: bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn launch(file_path: &PathBuf, args: &str) {
|
fn launch(file_path: &PathBuf, args: &str) {
|
||||||
|
println!("\n\nJoin the AlterWare Discord server:\nhttps://discord.gg/2ETE8engZM\n\n");
|
||||||
println!("Launching {} {}", file_path.display(), args);
|
println!("Launching {} {}", file_path.display(), args);
|
||||||
std::process::Command::new(file_path)
|
std::process::Command::new(file_path)
|
||||||
.args(args.trim().split(' '))
|
.args(args.trim().split(' '))
|
||||||
@ -314,6 +331,22 @@ fn setup_env() {
|
|||||||
println!("{:#?}", error);
|
println!("{:#?}", error);
|
||||||
colored::control::SHOULD_COLORIZE.set_override(false);
|
colored::control::SHOULD_COLORIZE.set_override(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Ok(system_root) = env::var("SystemRoot") {
|
||||||
|
if let Ok(current_dir) = env::current_dir() {
|
||||||
|
if current_dir.starts_with(system_root) {
|
||||||
|
if let Ok(current_exe) = env::current_exe() {
|
||||||
|
if let Some(parent) = current_exe.parent() {
|
||||||
|
if let Err(error) = env::set_current_dir(parent) {
|
||||||
|
eprintln!("{:#?}", error);
|
||||||
|
} else {
|
||||||
|
println!("Running from the system directory. Changed working directory to the executable location.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arg_value(args: &[String], arg: &str) -> Option<String> {
|
fn arg_value(args: &[String], arg: &str) -> Option<String> {
|
||||||
@ -337,11 +370,11 @@ fn arg_remove_value(args: &mut Vec<String>, arg: &str) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
setup_env();
|
setup_env();
|
||||||
|
let mut args: Vec<String> = env::args().collect();
|
||||||
let mut args: Vec<String> = std::env::args().collect();
|
|
||||||
|
|
||||||
if arg_bool(&args, "--help") {
|
if arg_bool(&args, "--help") {
|
||||||
println!("CLI Args:");
|
println!("CLI Args:");
|
||||||
@ -388,7 +421,7 @@ fn main() {
|
|||||||
install_path = PathBuf::from(path);
|
install_path = PathBuf::from(path);
|
||||||
arg_remove_value(&mut args, "-p");
|
arg_remove_value(&mut args, "-p");
|
||||||
} else {
|
} else {
|
||||||
install_path = std::env::current_dir().unwrap();
|
install_path = env::current_dir().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cfg = config::load(install_path.join("alterware-launcher.json"));
|
let mut cfg = config::load(install_path.join("alterware-launcher.json"));
|
||||||
@ -421,7 +454,7 @@ fn main() {
|
|||||||
cfg.args = pass;
|
cfg.args = pass;
|
||||||
arg_remove_value(&mut args, "--pass");
|
arg_remove_value(&mut args, "--pass");
|
||||||
} else if cfg.args.is_empty() {
|
} else if cfg.args.is_empty() {
|
||||||
cfg.args = String::from("");
|
cfg.args = String::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str());
|
let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str());
|
||||||
@ -441,7 +474,7 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
setup_client_links(g, &std::env::current_dir().unwrap());
|
setup_client_links(g, &env::current_dir().unwrap());
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
println!("Multiple clients installed, set the client as the first argument to launch a specific client.");
|
println!("Multiple clients installed, set the client as the first argument to launch a specific client.");
|
||||||
@ -462,6 +495,23 @@ fn main() {
|
|||||||
for g in games.iter() {
|
for g in games.iter() {
|
||||||
for c in g.client.iter() {
|
for c in g.client.iter() {
|
||||||
if c == &game {
|
if c == &game {
|
||||||
|
if cfg.engine.is_empty() {
|
||||||
|
cfg.engine = String::from(g.engine);
|
||||||
|
config::save_value_s(
|
||||||
|
install_path.join("alterware-launcher.json"),
|
||||||
|
"engine",
|
||||||
|
cfg.engine.clone(),
|
||||||
|
);
|
||||||
|
if cfg.engine == "iw4" && cfg.args.is_empty() {
|
||||||
|
cfg.args = String::from("-stdout");
|
||||||
|
config::save_value_s(
|
||||||
|
install_path.join("alterware-launcher.json"),
|
||||||
|
"args",
|
||||||
|
cfg.args.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.ask_bonus_content && !g.bonus.is_empty() {
|
if cfg.ask_bonus_content && !g.bonus.is_empty() {
|
||||||
println!("Download bonus content? (Y/n)");
|
println!("Download bonus content? (Y/n)");
|
||||||
let input = misc::stdin().to_ascii_lowercase();
|
let input = misc::stdin().to_ascii_lowercase();
|
||||||
@ -483,7 +533,8 @@ fn main() {
|
|||||||
install_path.as_path(),
|
install_path.as_path(),
|
||||||
cfg.download_bonus_content,
|
cfg.download_bonus_content,
|
||||||
cfg.force_update,
|
cfg.force_update,
|
||||||
);
|
)
|
||||||
|
.await;
|
||||||
if !cfg.update_only {
|
if !cfg.update_only {
|
||||||
launch(&install_path.join(format!("{}.exe", c)), &cfg.args);
|
launch(&install_path.join(format!("{}.exe", c)), &cfg.args);
|
||||||
}
|
}
|
||||||
@ -493,10 +544,10 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
windows_launcher_install(&games);
|
windows_launcher_install(&games).await;
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
manual_install(&games);
|
manual_install(&games).await;
|
||||||
|
|
||||||
println!("{}", "Game not found!".bright_red());
|
println!("{}", "Game not found!".bright_red());
|
||||||
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!("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)");
|
||||||
|
23
src/misc.rs
23
src/misc.rs
@ -1,6 +1,10 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
pub fn get_file_sha1(path: &PathBuf) -> String {
|
pub fn get_file_sha1(path: &PathBuf) -> String {
|
||||||
let mut sha1 = sha1_smol::Sha1::new();
|
let mut sha1 = sha1_smol::Sha1::new();
|
||||||
@ -35,5 +39,20 @@ pub fn human_readable_bytes(bytes: u64) -> String {
|
|||||||
bytes /= 1024.0;
|
bytes /= 1024.0;
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
format!("{:.2} {}", bytes, units[i])
|
format!("{:.2}{}", bytes, units[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pb_style_download(pb: &ProgressBar, state: bool) {
|
||||||
|
if state {
|
||||||
|
pb.set_style(
|
||||||
|
ProgressStyle::with_template("{spinner:.magenta} {msg:.magenta} > {bytes}/{total_bytes} | {bytes_per_sec} | {eta}")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
pb.set_style(ProgressStyle::with_template("{spinner:.magenta} {msg}").unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cute_path(path: &Path) -> String {
|
||||||
|
path.to_str().unwrap().replace('\\', "/")
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,19 @@ pub fn run(_update_only: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn restart() -> std::io::Error {
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
match std::process::Command::new(std::env::current_exe().unwrap())
|
||||||
|
.args(std::env::args().skip(1))
|
||||||
|
.creation_flags(0x00000010) // CREATE_NEW_CONSOLE
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(_) => std::process::exit(0),
|
||||||
|
Err(err) => err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn run(update_only: bool) {
|
pub fn run(update_only: bool) {
|
||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
@ -43,7 +56,9 @@ pub fn run(update_only: bool) {
|
|||||||
&& (file_name.contains(".__relocated__.exe")
|
&& (file_name.contains(".__relocated__.exe")
|
||||||
|| file_name.contains(".__selfdelete__.exe"))
|
|| file_name.contains(".__selfdelete__.exe"))
|
||||||
{
|
{
|
||||||
fs::remove_file(file.path()).unwrap();
|
fs::remove_file(file.path()).unwrap_or_else(|_| {
|
||||||
|
println!("Failed to remove old launcher file.");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +81,7 @@ pub fn run(update_only: bool) {
|
|||||||
} else {
|
} else {
|
||||||
"alterware-launcher.exe"
|
"alterware-launcher.exe"
|
||||||
};
|
};
|
||||||
println!("{}", launcher_name);
|
|
||||||
http::download_file(
|
http::download_file(
|
||||||
&format!(
|
&format!(
|
||||||
"{}/download/{}",
|
"{}/download/{}",
|
||||||
@ -83,12 +98,12 @@ pub fn run(update_only: bool) {
|
|||||||
|
|
||||||
self_replace::self_replace("alterware-launcher-update.exe").unwrap();
|
self_replace::self_replace("alterware-launcher-update.exe").unwrap();
|
||||||
fs::remove_file(&file_path).unwrap();
|
fs::remove_file(&file_path).unwrap();
|
||||||
println!(
|
|
||||||
"Launcher updated. View the changelog at https://github.com/{}/{}/releases/latest",
|
// restarting spawns a new console, automation should manually restart on exit code 201
|
||||||
GH_OWNER, GH_REPO,
|
|
||||||
);
|
|
||||||
println!("Please restart the launcher.");
|
|
||||||
if !update_only {
|
if !update_only {
|
||||||
|
let restart_error = restart().to_string();
|
||||||
|
println!("Failed to restart launcher: {}", restart_error);
|
||||||
|
println!("Please restart the launcher manually.");
|
||||||
misc::stdin();
|
misc::stdin();
|
||||||
}
|
}
|
||||||
std::process::exit(201);
|
std::process::exit(201);
|
||||||
|
@ -22,6 +22,8 @@ pub struct Config {
|
|||||||
pub ask_bonus_content: bool,
|
pub ask_bonus_content: bool,
|
||||||
pub force_update: bool,
|
pub force_update: bool,
|
||||||
pub args: String,
|
pub args: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub engine: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
@ -32,7 +34,8 @@ impl Default for Config {
|
|||||||
download_bonus_content: false,
|
download_bonus_content: false,
|
||||||
ask_bonus_content: true,
|
ask_bonus_content: true,
|
||||||
force_update: false,
|
force_update: false,
|
||||||
args: String::from(""),
|
args: String::default(),
|
||||||
|
engine: String::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user