42 Commits

Author SHA1 Message Date
64f4ae6429 v0.5.2 2023-09-25 18:52:35 +02:00
b2cc21aed0 Merge branch 'main' of github.com:mxve/alterware-launcher 2023-09-25 18:51:54 +02:00
dc5957ea41 build i686 target 2023-09-25 18:51:43 +02:00
54abce4d30 Merge pull request #34 from mxve/dependabot/cargo/semver-1.0.19
Bump semver from 1.0.18 to 1.0.19
2023-09-25 17:51:50 +02:00
6ae33cdcb3 self-replace 1.3.7 2023-09-25 17:51:21 +02:00
c9d30fa95a Bump semver from 1.0.18 to 1.0.19
Bumps [semver](https://github.com/dtolnay/semver) from 1.0.18 to 1.0.19.
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.18...1.0.19)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 15:28:54 +00:00
dc81430f6b don't ask if --bonus set 2023-09-23 01:29:04 +02:00
9c122506ce Merge pull request #31 from mxve/dependabot/cargo/http_req-0.9.3
Bump http_req from 0.9.2 to 0.9.3
2023-09-18 20:12:38 +02:00
f2ba92c31d Merge pull request #32 from mxve/dependabot/cargo/serde_json-1.0.107
Bump serde_json from 1.0.106 to 1.0.107
2023-09-18 20:12:30 +02:00
c92fb88e83 Bump serde_json from 1.0.106 to 1.0.107
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.106...v1.0.107)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 15:05:36 +00:00
1e0e0090f5 Bump http_req from 0.9.2 to 0.9.3
Bumps [http_req](https://github.com/jayjamesjay/http_req) from 0.9.2 to 0.9.3.
- [Release notes](https://github.com/jayjamesjay/http_req/releases)
- [Commits](https://github.com/jayjamesjay/http_req/compare/v0.9.2...v0.9.3)

---
updated-dependencies:
- dependency-name: http_req
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 15:05:29 +00:00
48f6a96a01 pwettify comfig file :3 2023-09-18 16:29:37 +02:00
f4fe1c6699 v0.5.1 2023-09-18 12:27:01 +02:00
59f1b09337 set current_dir when launching 2023-09-18 12:26:47 +02:00
237fa8c16f v0.5.0 2023-09-17 17:49:50 +02:00
78e155408e print passed args when launching; trim args 2023-09-16 16:41:18 +02:00
Edo
3033dd2315 Merge pull request #30 from diamante0018/main
maint(main): simply return out of the main function
2023-09-15 19:22:11 +02:00
Edo
48851fa8d3 maint(main): simply return out of the main function
instead of calling exit()
2023-09-15 19:19:04 +02:00
84ea4e48af update readme 2023-09-15 01:48:04 +02:00
b408f13cce add --help, --version/-v 2023-09-15 01:45:47 +02:00
959c3a8a61 allow loading client args from config 2023-09-14 10:32:03 +02:00
73df20ebb6 💩 2023-09-14 10:25:44 +02:00
08814a8c3e Merge pull request #29 from mxve/dependabot/cargo/serde_json-1.0.106
Bump serde_json from 1.0.105 to 1.0.106
2023-09-14 10:24:26 +02:00
65094d4701 add --pass to allow passing args to the client 2023-09-14 10:23:48 +02:00
a20b1acdda remove debug print 2023-09-14 10:09:21 +02:00
75b1d6254b strip value AND arg 😑 2023-09-14 10:08:26 +02:00
65f05a5a1c add --path, -p 2023-09-14 09:44:35 +02:00
fa6bdc9f29 prepend args with --, -, add short args 2023-09-14 09:37:35 +02:00
7595a46b44 update readme 2023-09-12 21:35:50 +02:00
c41a843315 store file hashes; added "force" arg
close #28
2023-09-12 21:28:53 +02:00
a90a60ec3a Bump serde_json from 1.0.105 to 1.0.106
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.105 to 1.0.106.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.105...v1.0.106)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 15:16:24 +00:00
209b599120 improve progress prints 2023-09-11 13:05:18 +02:00
9f00b0c0e7 Show more progress 2023-09-10 20:00:04 +02:00
aaede9b6cb download bonus content 2023-09-10 18:29:30 +02:00
8a14008706 use config values 2023-09-10 17:36:01 +02:00
ad7e78ec47 cfg 2023-09-10 16:58:42 +02:00
140f4c335f v0.4.8 2023-09-10 12:39:44 +02:00
3e1a266c3e check first dir when matching files to download 2023-09-10 12:38:21 +02:00
92663425ef update readme 2023-09-03 19:03:46 +02:00
ffa379e6dd misc
- reduce github calls
- latest_tag returns full tag
- rev_to_int default to 0 on strip_prefix
- lint
2023-08-30 13:06:49 +02:00
a41375a791 github::latest -> latest_tag 2023-08-30 12:46:13 +02:00
ac76e9bb89 steamlocate 2.0.0-alpha.0 2023-08-30 12:45:10 +02:00
12 changed files with 441 additions and 136 deletions

View File

@ -18,9 +18,15 @@ jobs:
upload-assets:
strategy:
matrix:
os:
- ubuntu-20.04
- windows-latest
include:
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
- target: i686-unknown-linux-gnu
os: ubuntu-20.04
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: i686-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

96
Cargo.lock generated
View File

@ -30,8 +30,9 @@ dependencies = [
[[package]]
name = "alterware-launcher"
version = "0.4.7"
version = "0.5.2"
dependencies = [
"colored",
"http_req",
"mslnk",
"rand",
@ -41,7 +42,6 @@ dependencies = [
"serde_json",
"sha1_smol",
"steamlocate",
"windows-sys",
"winres",
"zip",
]
@ -137,6 +137,17 @@ dependencies = [
"inout",
]
[[package]]
name = "colored"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6"
dependencies = [
"is-terminal",
"lazy_static",
"windows-sys",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@ -199,23 +210,22 @@ dependencies = [
[[package]]
name = "dirs"
version = "5.0.1"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys",
"winapi",
]
[[package]]
@ -285,6 +295,12 @@ dependencies = [
"wasi",
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hmac"
version = "0.12.1"
@ -296,9 +312,9 @@ dependencies = [
[[package]]
name = "http_req"
version = "0.9.2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f680177f2ebe4aabd573d07b322d15a5e0fbc97cd739fd627b08043c89041f8"
checksum = "42ce34c74ec562d68f2c23a532c62c1332ff1d1b6147fd118bd1938e090137d0"
dependencies = [
"rustls",
"unicase",
@ -324,6 +340,17 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix",
"windows-sys",
]
[[package]]
name = "itoa"
version = "1.0.6"
@ -373,6 +400,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
@ -417,24 +450,12 @@ dependencies = [
"log",
]
[[package]]
name = "nom"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
[[package]]
name = "once_cell"
version = "1.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "password-hash"
version = "0.4.2"
@ -685,9 +706,9 @@ dependencies = [
[[package]]
name = "self-replace"
version = "1.3.6"
version = "1.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56335359191626938ef6fdeb478f9f6a7c6020254d7f4641c7d810369fa0ec1"
checksum = "525db198616b2bcd0f245daf7bfd8130222f7ee6af9ff9984c19a61bf1160c55"
dependencies = [
"fastrand 1.9.0",
"tempfile",
@ -696,9 +717,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.18"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
[[package]]
name = "serde"
@ -722,9 +743,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.105"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
@ -767,27 +788,17 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "steamlocate"
version = "1.2.1"
version = "2.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec01c74611d14a808cb212d17c6e03f0e30736a15ed1d5736f8a53154cea3ae"
checksum = "2b1568c4a70a26c4373fe1131ffa4eff055459631b6e40c6bc118615f2d870c3"
dependencies = [
"dirs",
"keyvalues-parser",
"keyvalues-serde",
"serde",
"steamy-vdf",
"winreg",
]
[[package]]
name = "steamy-vdf"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533127ad49314bfe71c3d3fd36b3ebac3d24f40618092e70e1cfe8362c7fac79"
dependencies = [
"nom",
]
[[package]]
name = "subtle"
version = "2.5.0"
@ -1082,11 +1093,10 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "winreg"
version = "0.11.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"cfg-if",
"winapi",
]

View File

@ -1,6 +1,6 @@
[package]
name = "alterware-launcher"
version = "0.4.7"
version = "0.5.2"
edition = "2021"
build = "res/build.rs"
@ -15,24 +15,21 @@ panic = "abort"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
http_req = { version = "0.9.2", default-features = false, features = [
http_req = { version = "0.9.3", default-features = false, features = [
"rust-tls",
] }
sha1_smol = "1.0.0"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105"
serde_json = "1.0.107"
rand = "0.8.5"
semver = "1.0.18"
semver = "1.0.19"
zip = "0.6.6"
colored = "2.0.4"
[target.'cfg(windows)'.dependencies]
steamlocate = "1.2.1"
steamlocate = "2.0.0-alpha.0"
mslnk = "0.1.8"
# https://github.com/mitsuhiko/self-replace/pull/16/
windows-sys = { version = "0.48", features = [
"Win32_Security",
] }
self-replace = "1.3.6"
self-replace = "1.3.7"
[build-dependencies]
winres = "0.1.12"

View File

@ -1,14 +1,69 @@
# 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)
2. Unpack the archive and place alterware-launcher.exe in the game directory
### [AlterWare.dev](https://alterware.dev)
##### IW4x | IW4-SP | IW5-Mod | IW6-Mod | S1-Mod
---
#### Installation
1. Download the [latest alterware-launcher.exe](https://github.com/mxve/alterware-launcher/releases/latest/download/alterware-launcher.exe)
2. Place alterware-launcher.exe in the game directory
3. Run alterware-launcher.exe, after updating the game will launch automatically
---
- 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
- ```skip-launcher-update``` skips self-update
#### Command line arguments
- ```iw4-sp```, ```iw4x```, ```iw5-mod```, ```iw6-mod```, ```s1-mod```
- Skip automatic detection and launch the specified game
- This should always be the first argument if used
- ```--update```, ```-u```
- Only update the game, don't launch it
- ```--skip-launcher-update```
- Don't update the launcher
- ```--bonus```
- Download bonus content
- ```--force```, ```-f```
- Force file hash recheck
- ```--path```, ```-p```
- Set the game path
- Do not include a trailing backslash in the path
- ```--pass```
- Pass additional arguments to the game
- ```--version```, ```-v```
- Print the launcher version
Example: ```alterware-launcher.exe iw4x --bonus -u --path "C:\Games\IW4x" --pass "-console"```
Some arguments can be set in alterware-launcher.json, args generally override the values of the config.
---
#### Support
Visit the [AlterWare Forum](https://forum.alterware.dev/) or [Discord](https://discord.gg/2ETE8engZM) for support.
---
#### Building from Source
- [Install Rust](https://rustup.rs/)
- Clone the repository
- Run ```cargo build --release```
- The executable will be located in ```target/release```
---
### Note for server owners:
When the launcher updates itself it needs to be restarted. It will return exit code 201 in this case.
When the launcher updates itself it needs to be restarted. It will return exit code 201 in this case.
```
@echo off
:loop
start /wait alterware-launcher.exe --update
if %errorlevel% equ 201 (
goto loop
)
```

30
src/config.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::structs::Config;
use std::{fs, path::PathBuf};
pub fn load(config_path: PathBuf) -> Config {
if config_path.exists() {
let cfg = fs::read_to_string(&config_path).unwrap();
let cfg: Config = serde_json::from_str(&cfg).unwrap_or(Config::default());
return cfg;
}
save(config_path.clone(), Config::default());
Config::default()
}
pub fn save(config_path: PathBuf, config: Config) {
fs::write(config_path, serde_json::to_string_pretty(&config).unwrap()).unwrap();
}
pub fn save_value(config_path: PathBuf, key: &str, value: bool) {
let mut config = load(config_path.clone());
match key {
"update_only" => config.update_only = value,
"skip_self_update" => config.skip_self_update = value,
"download_bonus_content" => config.download_bonus_content = value,
"ask_bonus_content" => config.ask_bonus_content = value,
"force_update" => config.force_update = value,
_ => (),
}
save(config_path, config);
}

View File

@ -1,6 +1,6 @@
use semver::Version;
pub fn latest(owner: &str, repo: &str) -> String {
pub fn latest_tag(owner: &str, repo: &str) -> String {
let github_body = crate::http::get_body_string(
format!(
"https://api.github.com/repos/{}/{}/releases/latest",
@ -9,13 +9,12 @@ pub fn latest(owner: &str, repo: &str) -> String {
.as_str(),
);
let github_json: serde_json::Value = serde_json::from_str(&github_body).unwrap();
github_json["tag_name"]
.to_string()
.replace(['v', '"'].as_ref(), "")
github_json["tag_name"].to_string().replace('"', "")
}
pub fn latest_version(owner: &str, repo: &str) -> Version {
Version::parse(&latest(owner, repo)).unwrap()
let tag = latest_tag(owner, repo).replace('v', "");
Version::parse(&tag).unwrap()
}
pub fn latest_release_url(owner: &str, repo: &str) -> String {

View File

@ -2,4 +2,4 @@ pub const MASTER: &str = "https://master.alterware.dev";
pub const GH_OWNER: &str = "mxve";
pub const GH_REPO: &str = "alterware-launcher";
pub const GH_IW4X_OWNER: &str = "iw4x";
pub const GH_IW4X_REPO: &str = "iw4x-client";
pub const GH_IW4X_REPO: &str = "iw4x-client";

View File

@ -1,8 +1,9 @@
use crate::github;
use crate::global::*;
use crate::http;
use crate::misc;
use crate::global::*;
use colored::*;
use std::{fs, path::Path};
pub fn local_revision(dir: &Path) -> u16 {
@ -14,30 +15,28 @@ pub fn local_revision(dir: &Path) -> u16 {
}
pub fn remote_revision() -> u16 {
misc::rev_to_int(&github::latest(GH_IW4X_OWNER, GH_IW4X_REPO))
}
pub fn update_available(dir: &Path) -> bool {
if !dir.join("iw4x.dll").exists() {
return true;
}
local_revision(dir) < remote_revision()
misc::rev_to_int(&github::latest_tag(GH_IW4X_OWNER, GH_IW4X_REPO))
}
pub fn update(dir: &Path) {
if update_available(dir) {
println!("Updating IW4x...");
http::download_file(
&format!(
"{}/download/iw4x.dll",
github::latest_release_url(GH_IW4X_OWNER, GH_IW4X_REPO)
),
&dir.join("iw4x.dll"),
);
fs::write(
dir.join(".iw4xrevision"),
github::latest(GH_IW4X_OWNER, GH_IW4X_REPO),
)
.unwrap();
let remote = remote_revision();
let local = local_revision(dir);
if remote <= local && dir.join("iw4x.dll").exists() {
return;
}
println!(
"[{}] {}",
"Downloading".bright_yellow(),
dir.join("iw4x.dll").display()
);
http::download_file(
&format!(
"{}/download/iw4x.dll",
github::latest_release_url(GH_IW4X_OWNER, GH_IW4X_REPO)
),
&dir.join("iw4x.dll"),
);
fs::write(dir.join(".iw4xrevision"), format!("r{}", remote)).unwrap();
}

View File

@ -1,3 +1,4 @@
mod config;
mod github;
mod global;
mod http;
@ -9,9 +10,10 @@ mod structs;
use global::*;
use structs::*;
use colored::*;
#[cfg(windows)]
use mslnk::ShellLink;
use std::{fs, path::Path, path::PathBuf};
use std::{borrow::Cow, collections::HashMap, fs, path::Path, path::PathBuf};
#[cfg(windows)]
use steamlocate::SteamDir;
@ -21,7 +23,7 @@ fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
let mut steamdir = match SteamDir::locate() {
Some(steamdir) => steamdir,
None => {
println!("Steam not found.");
println!("{}", "Steam not found!".yellow());
return installed_games;
}
};
@ -38,7 +40,7 @@ fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
#[cfg(windows)]
fn setup_client_links(game: &Game, game_dir: &Path) {
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.");
println!("Multiple clients installed, use the shortcuts (launch-<client>.lnk in the game directory or on the desktop) to launch a specific client.");
}
let target = game_dir.join("alterware-launcher.exe");
@ -90,16 +92,18 @@ fn setup_desktop_links(path: &Path, game: &Game) {
fn auto_install(path: &Path, game: &Game) {
setup_client_links(game, path);
setup_desktop_links(path, game);
update(game, path);
update(game, path, false, false);
}
#[cfg(windows)]
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..".yellow()
);
let installed_games = get_installed_games(games);
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) {
@ -165,78 +169,225 @@ fn prompt_client_selection(games: &[Game]) -> String {
fn manual_install(games: &[Game]) {
let selection = prompt_client_selection(games);
let game = games.iter().find(|&g| g.client[0] == selection).unwrap();
update(game, &std::env::current_dir().unwrap());
update(game, &std::env::current_dir().unwrap(), false, false);
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);
}
fn update(game: &Game, dir: &Path) {
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
format!("{}/files.json", MASTER).as_str(),
))
.unwrap();
fn update_dir(
cdn_info: &Vec<CdnFile>,
remote_dir: &str,
dir: &Path,
hashes: &mut HashMap<String, String>,
) {
let remote_dir = format!("{}/", remote_dir);
for file in cdn_info {
if !file.name.starts_with(game.engine) || file.name == "iw4/iw4x.dll" {
if !file.name.starts_with(&remote_dir) || file.name == "iw4/iw4x.dll" {
continue;
}
let file_path = dir.join(&file.name.replace(&format!("{}/", game.engine), ""));
let sha1_remote = file.hash.to_lowercase();
let file_name = &file.name.replace(remote_dir.as_str(), "");
let file_path = dir.join(file_name);
if file_path.exists() {
let sha1_local = misc::get_file_sha1(&file_path).to_lowercase();
let sha1_remote = file.hash.to_lowercase();
let sha1_local = hashes
.get(file_name)
.map(Cow::Borrowed)
.unwrap_or_else(|| Cow::Owned(misc::get_file_sha1(&file_path)))
.to_string();
if sha1_local != sha1_remote {
println!(
"Updating {}...\nLocal hash: {}\nRemote hash: {}",
file_path.display(),
sha1_local,
sha1_remote
"[{}] {}",
"Updating".bright_yellow(),
file_path.display()
);
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
} else {
println!("[{}] {}", "Checked".bright_blue(), file_path.display());
}
hashes.insert(file_name.to_owned(), sha1_remote.to_owned());
} else {
println!("Downloading {}...", file_path.display());
println!(
"[{}] {}",
"Downloading".bright_yellow(),
file_path.display()
);
if let Some(parent) = file_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent).unwrap();
}
}
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
hashes.insert(file_name.to_owned(), sha1_remote.to_owned());
}
}
}
fn update(game: &Game, dir: &Path, bonus_content: bool, force: bool) {
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
format!("{}/files.json", MASTER).as_str(),
))
.unwrap();
let mut hashes = HashMap::new();
let hash_file = dir.join(".sha-sums");
if hash_file.exists() && !force {
let hash_file = fs::read_to_string(hash_file).unwrap();
for line in hash_file.lines() {
let mut split = line.split_whitespace();
let hash = split.next().unwrap();
let file = split.next().unwrap();
hashes.insert(file.to_owned(), hash.to_owned());
}
}
update_dir(&cdn_info, game.engine, dir, &mut hashes);
if game.engine == "iw4" {
iw4x::update(dir);
}
if bonus_content && !game.bonus.is_empty() {
for bonus in game.bonus.iter() {
update_dir(&cdn_info, bonus, dir, &mut hashes);
}
}
let mut hash_file_content = String::new();
for (file, hash) in hashes.iter() {
hash_file_content.push_str(&format!("{} {}\n", hash, file));
}
fs::write(dir.join(".sha-sums"), hash_file_content).unwrap();
}
fn launch(file_path: &PathBuf) {
println!("Launching {}...", file_path.display());
fn launch(file_path: &PathBuf, args: &str) {
println!("Launching {} {}", file_path.display(), args);
std::process::Command::new(file_path)
.args(args.trim().split(' '))
.current_dir(file_path.parent().unwrap())
.spawn()
.expect("Failed to launch the game")
.wait()
.expect("Failed to wait for the game process to finish");
}
#[cfg(windows)]
fn setup_env() {
colored::control::set_virtual_terminal(true).unwrap_or_else(|error| {
println!("{:#?}", error);
colored::control::SHOULD_COLORIZE.set_override(false);
});
}
fn arg_value(args: &[String], arg: &str) -> Option<String> {
args.iter()
.position(|r| r == arg)
.map(|e| args[e + 1].clone())
}
fn arg_bool(args: &[String], arg: &str) -> bool {
args.iter().any(|r| r == arg)
}
fn arg_remove(args: &mut Vec<String>, arg: &str) {
args.iter().position(|r| r == arg).map(|e| args.remove(e));
}
fn arg_remove_value(args: &mut Vec<String>, arg: &str) {
if let Some(e) = args.iter().position(|r| r == arg) {
args.remove(e);
args.remove(e);
};
}
fn main() {
#[cfg(windows)]
setup_env();
let mut args: Vec<String> = std::env::args().collect();
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));
if arg_bool(&args, "--help") {
println!("CLI Args:");
println!(" <client>: Specify the client to launch");
println!(" --help: Display this help message");
println!(" --version: Display the launcher version");
println!(" --path/-p <path>: Specify the game directory");
println!(" --update/-u: Update only, don't launch the game");
println!(" --bonus: Download bonus content");
println!(" --force/-f: Force file hash recheck");
println!(" --pass <args>: Pass arguments to the game");
println!(" --skip-launcher-update: Skip launcher self-update");
println!(
"\nExample:\n alterware-launcher.exe iw4x --bonus --pass \"-console -nointro\""
);
return;
}
if !args.contains(&String::from("skip-launcher-update")) {
self_update::run(update_only);
if arg_bool(&args, "--version") || arg_bool(&args, "-v") {
println!(
"{} v{}",
"AlterWare Launcher".bright_green(),
env!("CARGO_PKG_VERSION")
);
println!("https://github.com/{}/{}", GH_OWNER, GH_REPO);
println!(
"\n{}{}{}{}{}{}{}",
"For ".on_black(),
"Alter".bright_blue().on_black().underline(),
"Ware".white().on_black().underline(),
".dev".on_black().underline(),
" by ".on_black(),
"mxve".bright_magenta().on_black().underline(),
".de".on_black().underline()
);
return;
}
let install_path: PathBuf;
if let Some(path) = arg_value(&args, "--path") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "--path");
} else if let Some(path) = arg_value(&args, "-p") {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "-p");
} else {
args.iter()
.position(|r| r == "skip-launcher-update")
.map(|e| args.remove(e));
install_path = std::env::current_dir().unwrap();
}
let mut cfg = config::load(install_path.join("alterware-launcher.json"));
if !arg_bool(&args, "--skip-launcher-update") && !cfg.skip_self_update {
self_update::run(cfg.update_only);
} else {
arg_remove(&mut args, "--skip-launcher-update");
}
if arg_bool(&args, "--update") || arg_bool(&args, "-u") {
cfg.update_only = true;
arg_remove(&mut args, "--update");
arg_remove(&mut args, "-u");
}
if arg_bool(&args, "--bonus") {
cfg.download_bonus_content = true;
cfg.ask_bonus_content = false;
arg_remove(&mut args, "--bonus");
}
if arg_bool(&args, "--force") || arg_bool(&args, "-f") {
cfg.force_update = true;
arg_remove(&mut args, "--force");
arg_remove(&mut args, "-f");
}
if let Some(pass) = arg_value(&args, "--pass") {
cfg.args = pass;
arg_remove_value(&mut args, "--pass");
} else if cfg.args.is_empty() {
cfg.args = String::from("");
}
let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str());
@ -248,9 +399,9 @@ fn main() {
} else {
'main: for g in games.iter() {
for r in g.references.iter() {
if std::path::Path::new(r).exists() {
if install_path.join(r).exists() {
if g.client.len() > 1 {
if update_only {
if cfg.update_only {
game = String::from(g.client[0]);
break 'main;
}
@ -277,9 +428,30 @@ fn main() {
for g in games.iter() {
for c in g.client.iter() {
if c == &game {
update(g, &std::env::current_dir().unwrap());
if !update_only {
launch(&PathBuf::from(format!("{}.exe", c)));
if cfg.ask_bonus_content && !g.bonus.is_empty() {
println!("Download bonus content? (Y/n)");
let input = misc::stdin().to_ascii_lowercase();
cfg.download_bonus_content = input != "n";
config::save_value(
install_path.join("alterware-launcher.json"),
"download_bonus_content",
cfg.download_bonus_content,
);
config::save_value(
install_path.join("alterware-launcher.json"),
"ask_bonus_content",
false,
);
}
update(
g,
install_path.as_path(),
cfg.download_bonus_content,
cfg.force_update,
);
if !cfg.update_only {
launch(&install_path.join(format!("{}.exe", c)), &cfg.args);
}
return;
}
@ -292,7 +464,7 @@ fn main() {
#[cfg(not(windows))]
manual_install(&games);
println!("Game not found!");
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!("Press enter to exit...");
std::io::stdin().read_line(&mut String::new()).unwrap();

View File

@ -13,5 +13,8 @@ pub fn stdin() -> String {
}
pub fn rev_to_int(rev: &str) -> u16 {
rev.strip_prefix('r').unwrap().parse::<u16>().unwrap_or(0)
rev.strip_prefix('r')
.unwrap_or("0")
.parse::<u16>()
.unwrap_or(0)
}

View File

@ -16,7 +16,10 @@ pub fn self_update_available() -> bool {
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(GH_OWNER, GH_REPO));
println!(
"Download it at {}",
github::latest_release_url(GH_OWNER, GH_REPO)
);
println!("Launching in 10 seconds..");
thread::sleep(time::Duration::from_secs(10));
}
@ -57,10 +60,17 @@ pub fn run(update_only: bool) {
fs::remove_file(&update_binary).unwrap();
}
let launcher_name = if cfg!(target_arch = "x86") {
"alterware-launcher-x86.exe"
} else {
"alterware-launcher.exe"
};
println!("{}", launcher_name);
http::download_file(
&format!(
"{}/download/alterware-launcher.exe",
github::latest_release_url(GH_OWNER, GH_REPO)
"{}/download/{}",
github::latest_release_url(GH_OWNER, GH_REPO),
launcher_name
),
&file_path,
);

View File

@ -11,4 +11,28 @@ pub struct Game<'a> {
pub client: Vec<&'a str>,
pub references: Vec<&'a str>,
pub app_id: u32,
pub bonus: Vec<&'a str>,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct Config {
pub update_only: bool,
pub skip_self_update: bool,
pub download_bonus_content: bool,
pub ask_bonus_content: bool,
pub force_update: bool,
pub args: String,
}
impl Default for Config {
fn default() -> Self {
Self {
update_only: false,
skip_self_update: false,
download_bonus_content: false,
ask_bonus_content: true,
force_update: false,
args: String::from(""),
}
}
}