37 Commits

Author SHA1 Message Date
6ebb421a7b fix linux 2023-11-06 22:33:58 +01:00
6ef80be01f v0.6.2 2023-11-06 22:27:53 +01:00
1a3c090612 cleanup 2023-11-06 22:27:09 +01:00
fe7c8d2834 self restart 2023-11-06 03:20:46 +01:00
b3168b0a58 don't panic if cleaning up left over files fails 2023-11-06 02:08:29 +01:00
b1cffb44ad v0.6.1 2023-11-06 01:56:49 +01:00
5414213a19 fix existing file hashes always being calculated 2023-11-06 01:56:26 +01:00
6ce845e761 v0.6.0 2023-11-05 17:20:59 +01:00
ecff4846ed improve setup_env on Windows
(real)
935a72e05be1a213a12238f522c8b35f872ecf63
2023-11-04 11:48:04 +01:00
1578924579 lint 2023-11-04 11:35:19 +01:00
33fc7d0f6a import std::env 2023-11-04 07:48:40 +01:00
461eb05ac5 maint(win): improve setup_env on Windows 2023-11-04 07:27:58 +01:00
cb210b16e1 Merge pull request #40 from mxve/async
Async
2023-11-02 16:05:10 +01:00
41ff4acd65 update iw4x first
so we don't have to pass the progressbar to iw4x just to not have it break by a little println
2023-11-02 11:44:08 +01:00
24672aa500 only show filename in pb 2023-11-02 11:31:50 +01:00
05def16ba6 use String::default() for empty strings
as suggested by diamante quality control ™️
2023-11-02 11:27:43 +01:00
c3cda3b845 remove linux i686 2023-11-02 11:05:28 +01:00
3858e743f3 consume 2023-11-02 10:43:36 +01:00
d28fed938c Merge branch 'main' of github.com:mxve/alterware-launcher 2023-11-02 10:40:45 +01:00
ff22c49ced stream files to disk
- Greatly reduced memory usage while downloading
- Added progressbar, download rate
- Added async runtime
- Tweaks to allow usage of async download function
2023-11-02 10:39:58 +01:00
82a1e4ba53 Merge pull request #38 from mxve/dependabot/cargo/serde-1.0.190
Bump serde from 1.0.189 to 1.0.190
2023-10-30 22:00:19 +01:00
de7a661465 Merge pull request #39 from mxve/dependabot/cargo/http_req-0.10.1
Bump http_req from 0.10.0 to 0.10.1
2023-10-30 22:00:07 +01:00
7e8e0fbc97 Bump http_req from 0.10.0 to 0.10.1
Bumps [http_req](https://github.com/jayjamesjay/http_req) from 0.10.0 to 0.10.1.
- [Release notes](https://github.com/jayjamesjay/http_req/releases)
- [Commits](https://github.com/jayjamesjay/http_req/compare/v0.10.0...v0.10.1)

---
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-10-30 15:31:10 +00:00
b09b92ee7f Bump serde from 1.0.189 to 1.0.190
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.189 to 1.0.190.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.189...v1.0.190)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 15:30:54 +00:00
917640c4e6 dc 2023-10-28 20:14:39 +02:00
949ff87bca typo 2023-10-28 19:49:21 +02:00
8685961eed clippy 2023-10-28 19:38:53 +02:00
7965314c6b print download size 2023-10-28 19:35:28 +02:00
cb70b8c415 add av info on permission denied 2023-10-26 06:25:12 +02:00
79a1533eaa Merge pull request #36 from mxve/dependabot/cargo/serde-1.0.189
Bump serde from 1.0.188 to 1.0.189
2023-10-16 17:46:15 +02:00
665e580c79 Merge pull request #37 from mxve/dependabot/cargo/semver-1.0.20
Bump semver from 1.0.19 to 1.0.20
2023-10-16 17:46:08 +02:00
d5de7244e3 Bump semver from 1.0.19 to 1.0.20
Bumps [semver](https://github.com/dtolnay/semver) from 1.0.19 to 1.0.20.
- [Release notes](https://github.com/dtolnay/semver/releases)
- [Commits](https://github.com/dtolnay/semver/compare/1.0.19...1.0.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 15:07:26 +00:00
c2e90e17db Bump serde from 1.0.188 to 1.0.189
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.188 to 1.0.189.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.188...v1.0.189)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 15:07:20 +00:00
c81518aec7 update actions 2023-10-16 16:11:56 +02:00
f48cd874f0 Merge pull request #35 from mxve/dependabot/cargo/http_req-0.10.0
Bump http_req from 0.9.3 to 0.10.0
2023-10-09 18:29:43 +02:00
228abe5317 Bump http_req from 0.9.3 to 0.10.0
Bumps [http_req](https://github.com/jayjamesjay/http_req) from 0.9.3 to 0.10.0.
- [Release notes](https://github.com/jayjamesjay/http_req/releases)
- [Commits](https://github.com/jayjamesjay/http_req/compare/v0.9.3...v0.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 15:58:32 +00:00
9efd34600a add client-args.md; document config file 2023-10-07 13:24:26 +02:00
13 changed files with 1197 additions and 338 deletions

View File

@ -1,9 +1,3 @@
# Based on https://github.com/actions-rs/meta/blob/master/recipes/quickstart.md
#
# While our "example" application has the platform-specific code,
# for simplicity we are compiling and testing everything on the Ubuntu environment only.
# For multi-OS testing see the `cross.yml` workflow.
on: [push, pull_request]
name: lint
@ -14,18 +8,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo check
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
uses: clechasseur/rs-cargo@v2
continue-on-error: true
with:
command: check
@ -34,18 +26,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: stable
override: true
- name: Run cargo test
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
uses: clechasseur/rs-cargo@v2
continue-on-error: true
with:
command: test
@ -54,26 +44,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Run cargo fmt
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
uses: clechasseur/rs-cargo@v2
continue-on-error: true
with:
command: fmt
args: --all -- --check
- name: Run cargo clippy
uses: actions-rs/cargo@v1
continue-on-error: true # WARNING: only for this example, remove it!
uses: clechasseur/rs-cargo@v2
continue-on-error: true
with:
command: clippy
args: -- -D warnings

View File

@ -9,7 +9,7 @@ jobs:
create-release:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1
with:
draft: true
@ -21,15 +21,13 @@ jobs:
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
- uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1
with:
target: ${{ matrix.target }}

1113
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "alterware-launcher"
version = "0.5.3"
version = "0.6.2"
edition = "2021"
build = "res/build.rs"
@ -15,16 +15,19 @@ panic = "abort"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
http_req = { version = "0.9.3", default-features = false, features = [
http_req = { version = "0.10.1", default-features = false, features = [
"rust-tls",
] }
sha1_smol = "1.0.0"
serde = { version = "1.0.188", features = ["derive"] }
serde = { version = "1.0.190", features = ["derive"] }
serde_json = "1.0.107"
rand = "0.8.5"
semver = "1.0.19"
zip = "0.6.6"
semver = "1.0.20"
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(windows)'.dependencies]
steamlocate = "2.0.0-alpha.0"

View File

@ -32,6 +32,7 @@
- Do not include a trailing backslash in the path
- ```--pass```
- Pass additional arguments to the game
- See [client-args.md](client-args.md)
- ```--version```, ```-v```
- Print the launcher version
@ -41,6 +42,30 @@ Some arguments can be set in alterware-launcher.json, args generally override th
---
#### Config file
alterware-launcher.json
- ```update_only```
- See --update
- Default: false
- ```skip_self_update```
- See --skip-launcher-update
- Default: false
- ```download_bonus_content```
- See --bonus
- Default: false
- ```ask_bonus_content```
- Ask the user if they want to download bonus content
- Default: true; false after asking
- ```force_update```
- See --force
- Default: false
- ```args```
- See --pass
- Default: ""
---
#### Support
Visit the [AlterWare Forum](https://forum.alterware.dev/) or [Discord](https://discord.gg/2ETE8engZM) for support.

32
client-args.md Normal file
View File

@ -0,0 +1,32 @@
# IW4x
[github.com/iw4x/iw4x-client#command-line-arguments](https://github.com/iw4x/iw4x-client#command-line-arguments)
| Argument | Description |
|:------------------------|:-----------------------------------------------|
| `-tests` | Perform unit tests. |
| `-entries` | Print to the console a list of every asset as they are loaded from zonefiles. |
| `-stdout` | Redirect all logging output to the terminal iw4x is started from, or if there is none, creates a new terminal window to write log information in. |
| `-console` | Allow the game to display its own separate interactive console window. |
| `-dedicated` | Starts the game as a headless dedicated server. |
| `-bigminidumps` | Include all code sections from loaded modules in the dump. |
| `-reallybigminidumps` | Include data sections from all loaded modules in the dump. |
| `-dump` | Write info of loaded assets to the raw folder as they are being loaded. |
| `-nointro` | Skip game's cinematic intro. |
| `-version` | Print IW4x build info on startup. |
| `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. |
| `-unprotect-dvars` | Allow the server to modify saved/archive dvars. |
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
| `-disable-notifies` | Disable "Anti-CFG" checks |
| `-disable-mongoose` | Disable Mongoose HTTP server |
| `-disable-rate-limit-check` | Disable RCOn rate limit checks |
| `+<command>` | Execute game command (ex. `+set net_port 1337`)|
# S1-Mod, IW6-Mod
| Argument | Description |
|:------------------------|:-----------------------------------------------|
| `-headless` | Use system console |
| `-dedicated` | Dedicated server |
| `-singleplayer` | Start singleplayer; Skip launcher |
| `-multiplayer` | Start multiplayer; Skip launcher |
| `+<command>` | Execute game command (ex. `+set net_port 1337`)|

View File

@ -66,7 +66,7 @@ pub fn download_file(url: &str, file_path: &Path) {
}
std::io::ErrorKind::PermissionDenied => {
misc::fatal_error(&format!(
"Permission to {} denied.\n Please try:\n 1. Running the launcher as administrator.\n 2. Manually deleting the last downloaded file.\n 3. If your game is in the program files directory try moving it to another location.\n\n\n{}",
"Permission to {} denied.\n Please try:\n 1. Running the launcher as administrator.\n 2. Manually deleting the last downloaded file.\n 3. If your game is in the program files directory try moving it to another location.\n 4. Create an exception/exclusion in your Anti-Virus Software for either the last downloaded file or the entire game directory.\n\n\n{}",
file_path.to_str().unwrap(),
e
));

52
src/http_async.rs Normal file
View File

@ -0,0 +1,52 @@
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)
.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(())
}

View File

@ -23,13 +23,21 @@ pub fn update(dir: &Path) {
let local = local_revision(dir);
if remote <= local && dir.join("iw4x.dll").exists() {
println!(
"[{}] No files to download for IW4x",
"Info".bright_magenta(),
);
return;
}
println!(
"[{}] Downloading outdated or missing files for IW4x",
"Info".bright_magenta()
);
println!(
"[{}] {}",
"Downloading".bright_yellow(),
dir.join("iw4x.dll").display()
misc::cute_path(&dir.join("iw4x.dll"))
);
http::download_file(
&format!(

View File

@ -2,6 +2,7 @@ mod config;
mod github;
mod global;
mod http;
mod http_async;
mod iw4x;
mod misc;
mod self_update;
@ -11,9 +12,10 @@ use global::*;
use structs::*;
use colored::*;
use indicatif::ProgressBar;
#[cfg(windows)]
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)]
use steamlocate::SteamDir;
@ -75,10 +77,7 @@ fn setup_desktop_links(path: &Path, game: &Game) {
let input = misc::stdin().to_ascii_lowercase();
if input == "y" || input.is_empty() {
let desktop = PathBuf::from(&format!(
"{}\\Desktop",
std::env::var("USERPROFILE").unwrap()
));
let desktop = PathBuf::from(&format!("{}\\Desktop", env::var("USERPROFILE").unwrap()));
for c in game.client.iter() {
create_shortcut(
@ -94,14 +93,14 @@ fn setup_desktop_links(path: &Path, game: &Game) {
}
#[cfg(windows)]
fn auto_install(path: &Path, game: &Game) {
async fn auto_install(path: &Path, game: &Game<'_>) {
setup_client_links(game, path);
setup_desktop_links(path, game);
update(game, path, false, false);
update(game, path, false, false).await;
}
#[cfg(windows)]
fn windows_launcher_install(games: &Vec<Game>) {
async fn windows_launcher_install(games: &Vec<Game<'_>>) {
println!(
"{}",
"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);
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() {
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();
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.");
std::io::stdin().read_line(&mut String::new()).unwrap();
std::process::exit(0);
@ -132,21 +131,21 @@ fn windows_launcher_install(games: &Vec<Game>) {
let input: u32 = misc::stdin().parse().unwrap();
if input == 0 {
return manual_install(games);
return manual_install(games).await;
}
for (id, path) in installed_games.iter() {
if *id == input {
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");
if launcher_path != target_path {
fs::copy(launcher_path, target_path).unwrap();
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.");
std::io::stdin().read_line(&mut String::new()).unwrap();
break;
@ -154,7 +153,7 @@ fn windows_launcher_install(games: &Vec<Game>) {
}
std::process::exit(0);
} else {
manual_install(games);
manual_install(games).await;
}
}
@ -171,30 +170,47 @@ fn prompt_client_selection(games: &[Game]) -> String {
String::from(games[input].client[0])
}
fn manual_install(games: &[Game]) {
async 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(), 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.");
std::io::stdin().read_line(&mut String::new()).unwrap();
std::process::exit(0);
}
fn update_dir(
cdn_info: &Vec<CdnFile>,
remote_dir: &str,
dir: &Path,
hashes: &mut HashMap<String, String>,
) {
fn total_download_size(cdn_info: &Vec<CdnFile>, remote_dir: &str) -> u64 {
let remote_dir = format!("{}/", remote_dir);
let mut size: u64 = 0;
for file in cdn_info {
if !file.name.starts_with(&remote_dir) || file.name == "iw4/iw4x.dll" {
continue;
}
size += file.size as u64;
}
size
}
async fn update_dir(
cdn_info: &Vec<CdnFile>,
remote_dir: &str,
dir: &Path,
hashes: &mut HashMap<String, String>,
pb: &ProgressBar,
) {
misc::pb_style_download(pb, false);
let remote_dir_pre = format!("{}/", remote_dir);
let mut files_to_download: Vec<CdnFile> = vec![];
for file in cdn_info {
if !file.name.starts_with(&remote_dir_pre) || file.name == "iw4/iw4x.dll" {
continue;
}
let sha1_remote = file.hash.to_lowercase();
let file_name = &file.name.replace(remote_dir.as_str(), "");
let file_name = &file.name.replace(remote_dir_pre.as_str(), "");
let file_path = dir.join(file_name);
if file_path.exists() {
let sha1_local = hashes
@ -204,34 +220,60 @@ fn update_dir(
.to_string();
if sha1_local != sha1_remote {
println!(
"[{}] {}",
"Updating".bright_yellow(),
file_path.display()
);
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
files_to_download.push(file.clone());
} 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());
}
hashes.insert(file_name.to_owned(), sha1_remote.to_owned());
} else {
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());
files_to_download.push(file.clone());
}
}
if files_to_download.is_empty() {
pb.println(format!(
"[{}] No files to download for {}",
"Info".bright_magenta(),
remote_dir
));
return;
}
pb.println(format!(
"[{}] Downloading outdated or missing files for {}, {}",
"Info".bright_magenta(),
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 {
let file_name = &file.name.replace(&remote_dir_pre, "");
let file_path = dir.join(file_name);
if let Some(parent) = file_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent).unwrap();
}
}
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());
}
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(
format!("{}/files.json", MASTER).as_str(),
))
@ -249,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" {
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() {
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();
for (file, hash) in hashes.iter() {
hash_file_content.push_str(&format!("{} {}\n", hash, file));
@ -269,6 +314,7 @@ fn update(game: &Game, dir: &Path, bonus_content: bool, force: bool) {
}
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);
std::process::Command::new(file_path)
.args(args.trim().split(' '))
@ -285,6 +331,22 @@ fn setup_env() {
println!("{:#?}", error);
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> {
@ -308,11 +370,11 @@ fn arg_remove_value(args: &mut Vec<String>, arg: &str) {
};
}
fn main() {
#[tokio::main]
async fn main() {
#[cfg(windows)]
setup_env();
let mut args: Vec<String> = std::env::args().collect();
let mut args: Vec<String> = env::args().collect();
if arg_bool(&args, "--help") {
println!("CLI Args:");
@ -359,7 +421,7 @@ fn main() {
install_path = PathBuf::from(path);
arg_remove_value(&mut args, "-p");
} 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"));
@ -392,7 +454,7 @@ fn main() {
cfg.args = pass;
arg_remove_value(&mut args, "--pass");
} 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());
@ -412,7 +474,7 @@ fn main() {
}
#[cfg(windows)]
setup_client_links(g, &std::env::current_dir().unwrap());
setup_client_links(g, &env::current_dir().unwrap());
#[cfg(not(windows))]
println!("Multiple clients installed, set the client as the first argument to launch a specific client.");
@ -454,7 +516,8 @@ fn main() {
install_path.as_path(),
cfg.download_bonus_content,
cfg.force_update,
);
)
.await;
if !cfg.update_only {
launch(&install_path.join(format!("{}.exe", c)), &cfg.args);
}
@ -464,10 +527,10 @@ fn main() {
}
#[cfg(windows)]
windows_launcher_install(&games);
windows_launcher_install(&games).await;
#[cfg(not(windows))]
manual_install(&games);
manual_install(&games).await;
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)");

View File

@ -1,6 +1,10 @@
use std::{fs, path::PathBuf};
use std::{
fs,
path::{Path, PathBuf},
};
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
pub fn get_file_sha1(path: &PathBuf) -> String {
let mut sha1 = sha1_smol::Sha1::new();
@ -26,3 +30,29 @@ pub fn fatal_error(error: &str) {
stdin();
std::process::exit(1);
}
pub fn human_readable_bytes(bytes: u64) -> String {
let mut bytes = bytes as f64;
let mut i = 0;
let units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
while bytes > 1024.0 {
bytes /= 1024.0;
i += 1;
}
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('\\', "/")
}

View File

@ -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)]
pub fn run(update_only: bool) {
use std::{fs, path::PathBuf};
@ -43,7 +56,9 @@ pub fn run(update_only: bool) {
&& (file_name.contains(".__relocated__.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 {
"alterware-launcher.exe"
};
println!("{}", launcher_name);
http::download_file(
&format!(
"{}/download/{}",
@ -83,12 +98,12 @@ pub fn run(update_only: bool) {
self_replace::self_replace("alterware-launcher-update.exe").unwrap();
fs::remove_file(&file_path).unwrap();
println!(
"Launcher updated. View the changelog at https://github.com/{}/{}/releases/latest",
GH_OWNER, GH_REPO,
);
println!("Please restart the launcher.");
// restarting spawns a new console, automation should manually restart on exit code 201
if !update_only {
let restart_error = restart().to_string();
println!("Failed to restart launcher: {}", restart_error);
println!("Please restart the launcher manually.");
misc::stdin();
}
std::process::exit(201);

View File

@ -1,4 +1,4 @@
#[derive(serde::Deserialize, serde::Serialize)]
#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct CdnFile {
pub name: String,
pub size: u32,
@ -32,7 +32,7 @@ impl Default for Config {
download_bonus_content: false,
ask_bonus_content: true,
force_update: false,
args: String::from(""),
args: String::default(),
}
}
}