22 Commits

Author SHA1 Message Date
18651a68ca v0.4.4 2023-08-18 14:38:15 +02:00
79559fe46a Don't ask what client to launch if update is set 2023-08-18 13:36:06 +02:00
8fd66d16af v0.4.3 2023-08-18 12:53:28 +02:00
4d3c6b9dab update after windows setup; match cur dir to steam dirs 2023-08-18 12:51:51 +02:00
7ca7615222 v0.4.2 2023-08-17 20:26:25 +02:00
2195f42abc fix: unix.. again 2023-08-17 20:26:08 +02:00
f7635d4089 return 101 when restart is required after self-update 2023-08-17 18:32:32 +02:00
9598ec3dfe v0.4.1 2023-08-16 13:01:32 +02:00
37266207e7 create release as draft 2023-08-16 13:01:25 +02:00
e041df80c5 don't copy if current & target path are the same 2023-08-16 13:00:15 +02:00
b157bcb2c2 remove cache busting 2023-08-16 12:57:27 +02:00
beae0adce5 Merge pull request #21 from mxve/build/symbols
build: add symbols
2023-08-15 19:05:22 +02:00
Edo
f9ec044a15 build: add symbols 2023-08-15 19:03:38 +02:00
0be3adf8d1 v0.4.0 2023-08-15 13:02:32 +02:00
59f347462d fix: remove windows target from get_input 2023-08-15 11:13:59 +02:00
78e4e18176 fix: don't run setup_client_links on unix 2023-08-15 11:10:40 +02:00
e0f4a5102e Merge branch 'main' of github.com:mxve/alterware-launcher 2023-08-15 09:45:21 +02:00
c80765d091 feat: support multiple clients per game
- Game.client changed from str to Vec<str>
- Create launch shortcuts for multi-client games
- Add prompt if multiple clients available and none specified
2023-08-15 09:45:18 +02:00
3b77755848 bruh 2023-08-12 15:35:41 +02:00
4c1114f3e0 Update readme 2023-08-12 15:30:28 +02:00
b69611e66b v0.3.1 2023-08-12 15:27:39 +02:00
6ec4deed32 feat(flag): skip-launcher-update 2023-08-12 15:26:35 +02:00
5 changed files with 123 additions and 53 deletions

View File

@ -11,8 +11,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: taiki-e/create-gh-release-action@v1 - uses: taiki-e/create-gh-release-action@v1
env: with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} draft: true
token: ${{ secrets.GITHUB_TOKEN }}
upload-assets: upload-assets:
strategy: strategy:

2
Cargo.lock generated
View File

@ -13,7 +13,7 @@ dependencies = [
[[package]] [[package]]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.3.0" version = "0.4.4"
dependencies = [ dependencies = [
"http_req", "http_req",
"mslnk", "mslnk",

View File

@ -1,11 +1,15 @@
[package] [package]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.3.0" version = "0.4.4"
edition = "2021" edition = "2021"
build = "res/build.rs" build = "res/build.rs"
[profile.release] [profile.release]
opt-level = "s" opt-level = "s"
# Symbols are a nice thing
debug = true
panic = "abort" 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

View File

@ -8,3 +8,7 @@
- Passing ```iw4-sp```, ```iw5-mod```, ```iw6-mod``` or ```s1-mod``` as the first argument will skip automatic game detection - Passing ```iw4-sp```, ```iw5-mod```, ```iw6-mod``` or ```s1-mod``` as the first argument will skip automatic game detection
- Passing ```update``` will stop the launcher from launching the game - Passing ```update``` will stop the launcher from launching the game
- ```skip-launcher-update``` skips self-update
### Note for server owners:
When the launcher updates itself it needs to be restarted. It will return exit code 101 in this case.

View File

@ -2,7 +2,6 @@ mod http;
#[cfg(windows)] #[cfg(windows)]
use mslnk::ShellLink; use mslnk::ShellLink;
use semver::Version; use semver::Version;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
#[cfg(not(windows))] #[cfg(not(windows))]
use std::{thread, time}; use std::{thread, time};
@ -19,7 +18,7 @@ struct CdnFile {
#[derive(serde::Deserialize, serde::Serialize)] #[derive(serde::Deserialize, serde::Serialize)]
struct Game<'a> { struct Game<'a> {
engine: &'a str, engine: &'a str,
client: &'a str, client: Vec<&'a str>,
references: Vec<&'a str>, references: Vec<&'a str>,
app_id: u32, app_id: u32,
} }
@ -27,20 +26,12 @@ struct Game<'a> {
const MASTER: &str = "https://master.alterware.dev"; const MASTER: &str = "https://master.alterware.dev";
const REPO: &str = "mxve/alterware-launcher"; const REPO: &str = "mxve/alterware-launcher";
fn get_cache_buster() -> u64 {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => 1,
}
}
fn get_file_sha1(path: &PathBuf) -> String { fn get_file_sha1(path: &PathBuf) -> String {
let mut sha1 = sha1_smol::Sha1::new(); let mut sha1 = sha1_smol::Sha1::new();
sha1.update(&fs::read(path).unwrap()); sha1.update(&fs::read(path).unwrap());
sha1.digest().to_string() sha1.digest().to_string()
} }
#[cfg(windows)]
fn get_input() -> String { fn get_input() -> String {
let mut input = String::new(); let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap(); std::io::stdin().read_line(&mut input).unwrap();
@ -62,7 +53,7 @@ fn self_update_available() -> bool {
} }
#[cfg(not(windows))] #[cfg(not(windows))]
fn self_update() { fn self_update(_update_only: bool) {
if self_update_available() { if self_update_available() {
println!("A new version of the AlterWare launcher is available."); println!("A new version of the AlterWare launcher is available.");
println!("Download it at https://github.com/{}/releases/latest", REPO); println!("Download it at https://github.com/{}/releases/latest", REPO);
@ -72,7 +63,7 @@ fn self_update() {
} }
#[cfg(windows)] #[cfg(windows)]
fn self_update() { fn self_update(update_only: bool) {
let working_dir = std::env::current_dir().unwrap(); let working_dir = std::env::current_dir().unwrap();
let files = fs::read_dir(&working_dir).unwrap(); let files = fs::read_dir(&working_dir).unwrap();
@ -115,8 +106,10 @@ fn self_update() {
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. Please run it again."); println!("Launcher updated. Please run it again.");
std::io::stdin().read_line(&mut String::new()).unwrap(); if !update_only {
std::process::exit(0); std::io::stdin().read_line(&mut String::new()).unwrap();
}
std::process::exit(101);
} }
} }
@ -134,12 +127,50 @@ fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
installed_games installed_games
} }
#[cfg(windows)]
fn setup_client_links(game: &Game, game_dir: &PathBuf) {
if game.client.len() > 1 {
println!("Multiple clients installed, use the shortcuts (launch-<client>.lnk in the game directory or desktop shortcuts) to launch a specific client.");
}
let target = game_dir.join("alterware-launcher.exe");
for c in game.client.iter() {
let lnk = game_dir.join(format!("launch-{}.lnk", c));
let mut sl = ShellLink::new(target.clone()).unwrap();
sl.set_arguments(Some(c.to_string()));
sl.set_icon_location(Some(
game_dir
.join(format!("{}.exe", c))
.to_string_lossy()
.into_owned(),
));
sl.create_lnk(&lnk).unwrap();
}
}
#[cfg(windows)] #[cfg(windows)]
fn windows_launcher_install(games: &Vec<Game>) { fn windows_launcher_install(games: &Vec<Game>) {
println!("No game specified/found. Checking for installed Steam games.."); println!("No game specified/found. Checking for installed Steam games..");
let installed_games = get_installed_games(games); let installed_games = get_installed_games(games);
if !installed_games.is_empty() { if !installed_games.is_empty() {
// if current directory is in the steamapps/common folder of a game, use that game
let current_dir = std::env::current_dir().unwrap();
for (id, path) in installed_games.iter() {
if current_dir.starts_with(path) {
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);
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);
}
}
println!("Installed games:"); println!("Installed games:");
for (id, path) in installed_games.iter() { for (id, path) in installed_games.iter() {
@ -154,8 +185,13 @@ fn windows_launcher_install(games: &Vec<Game>) {
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 = std::env::current_exe().unwrap();
fs::copy(launcher_path, path.join("alterware-launcher.exe")).unwrap(); let target_path = path.join("alterware-launcher.exe");
println!("Launcher copied to {}", path.display());
if launcher_path != target_path {
fs::copy(launcher_path, target_path).unwrap();
println!("Launcher copied to {}", path.display());
}
setup_client_links(game, path);
println!("Create Desktop shortcut? (Y/n)"); println!("Create Desktop shortcut? (Y/n)");
let input = get_input().to_ascii_lowercase(); let input = get_input().to_ascii_lowercase();
@ -167,17 +203,24 @@ fn windows_launcher_install(games: &Vec<Game>) {
)); ));
let target = path.join("alterware-launcher.exe"); let target = path.join("alterware-launcher.exe");
let lnk = desktop.join(format!("{}.lnk", game.client));
let mut sl = ShellLink::new(target).unwrap(); for c in game.client.iter() {
sl.set_icon_location(Some( let lnk = desktop.join(format!("{}.lnk", c));
path.join(format!("{}.exe", game.client))
.to_string_lossy() let mut sl = ShellLink::new(target.clone()).unwrap();
.into_owned(), sl.set_arguments(Some(c.to_string()));
)); sl.set_icon_location(Some(
sl.create_lnk(lnk).unwrap(); path.join(format!("{}.exe", c))
.to_string_lossy()
.into_owned(),
));
sl.create_lnk(lnk).unwrap();
}
} }
update(game, path);
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; break;
} }
} }
@ -189,9 +232,9 @@ fn windows_launcher_install(games: &Vec<Game>) {
std::process::exit(0); std::process::exit(0);
} }
fn update(game: &Game) { fn update(game: &Game, dir: &PathBuf) {
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, get_cache_buster()).as_str(), format!("{}/files.json", MASTER).as_str(),
)) ))
.unwrap(); .unwrap();
@ -200,7 +243,7 @@ fn update(game: &Game) {
continue; continue;
} }
let file_path = PathBuf::from(&file.name.replace(&format!("{}/", game.engine), "")); let file_path = dir.join(&file.name.replace(&format!("{}/", game.engine), ""));
if file_path.exists() { if file_path.exists() {
let sha1_local = get_file_sha1(&file_path).to_lowercase(); let sha1_local = get_file_sha1(&file_path).to_lowercase();
let sha1_remote = file.hash.to_lowercase(); let sha1_remote = file.hash.to_lowercase();
@ -211,10 +254,7 @@ fn update(game: &Game) {
sha1_local, sha1_local,
sha1_remote sha1_remote
); );
http::download_file( http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
&format!("{}/{}?{}", MASTER, file.name, get_cache_buster()),
&file_path,
);
} }
} else { } else {
println!("Downloading {}...", file_path.display()); println!("Downloading {}...", file_path.display());
@ -223,10 +263,7 @@ fn update(game: &Game) {
fs::create_dir_all(parent).unwrap(); fs::create_dir_all(parent).unwrap();
} }
} }
http::download_file( http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
&format!("{}/{}?{}", MASTER, file.name, get_cache_buster()),
&file_path,
);
} }
} }
} }
@ -241,14 +278,8 @@ fn launch(file_path: &PathBuf) {
} }
fn main() { fn main() {
self_update();
let mut 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, get_cache_buster()).as_str());
let games: Vec<Game> = serde_json::from_str(&games_json).unwrap();
let mut update_only = false; let mut update_only = false;
if args.contains(&String::from("update")) { if args.contains(&String::from("update")) {
update_only = true; update_only = true;
@ -257,6 +288,17 @@ fn main() {
.map(|e| args.remove(e)); .map(|e| args.remove(e));
} }
if !args.contains(&String::from("skip-launcher-update")) {
self_update(update_only);
} else {
args.iter()
.position(|r| r == "skip-launcher-update")
.map(|e| args.remove(e));
}
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 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]);
@ -264,7 +306,25 @@ fn main() {
'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); if g.client.len() > 1 {
if update_only {
game = String::from(g.client[0]);
break 'main;
}
#[cfg(windows)]
setup_client_links(g, &std::env::current_dir().unwrap());
#[cfg(not(windows))]
println!("Multiple clients installed, set the client as the first argument to launch a specific client.");
for (i, c) in g.client.iter().enumerate() {
println!("{}: {}", i, c);
}
game = String::from(g.client[get_input().parse::<usize>().unwrap()]);
break 'main;
}
game = String::from(g.client[0]);
break 'main; break 'main;
} }
} }
@ -272,13 +332,14 @@ fn main() {
} }
for g in games.iter() { for g in games.iter() {
if g.client == game { for c in g.client.iter() {
update(g); if c == &game {
if update_only { update(g, &std::env::current_dir().unwrap());
if !update_only {
launch(&PathBuf::from(format!("{}.exe", c)));
}
return; return;
} }
launch(&PathBuf::from(format!("{}.exe", g.client)));
return;
} }
} }