Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
572c66cc16 | |||
cf87f7c741 | |||
dc8b01b4c8 | |||
9e8893ce75 | |||
6f92e1fb71 | |||
96c3e504f8 | |||
00c14d2a02 | |||
0378f19a75 | |||
64f4ae6429 | |||
b2cc21aed0 | |||
dc5957ea41 | |||
54abce4d30 | |||
6ae33cdcb3 | |||
c9d30fa95a | |||
dc81430f6b | |||
9c122506ce | |||
f2ba92c31d | |||
c92fb88e83 | |||
1e0e0090f5 | |||
48f6a96a01 | |||
f4fe1c6699 | |||
59f1b09337 | |||
237fa8c16f | |||
78e155408e | |||
3033dd2315 | |||
48851fa8d3 | |||
84ea4e48af | |||
b408f13cce | |||
959c3a8a61 | |||
73df20ebb6 | |||
08814a8c3e | |||
65094d4701 | |||
a20b1acdda | |||
75b1d6254b | |||
65f05a5a1c | |||
fa6bdc9f29 | |||
7595a46b44 | |||
c41a843315 | |||
a90a60ec3a | |||
209b599120 | |||
9f00b0c0e7 | |||
aaede9b6cb | |||
8a14008706 | |||
ad7e78ec47 |
13
.github/workflows/release.yml
vendored
13
.github/workflows/release.yml
vendored
@ -18,14 +18,21 @@ jobs:
|
|||||||
upload-assets:
|
upload-assets:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os:
|
include:
|
||||||
- ubuntu-20.04
|
- target: x86_64-unknown-linux-gnu
|
||||||
- windows-latest
|
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 }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: taiki-e/upload-rust-binary-action@v1
|
- uses: taiki-e/upload-rust-binary-action@v1
|
||||||
with:
|
with:
|
||||||
|
target: ${{ matrix.target }}
|
||||||
bin: alterware-launcher
|
bin: alterware-launcher
|
||||||
tar: unix
|
tar: unix
|
||||||
zip: windows
|
zip: windows
|
||||||
|
54
Cargo.lock
generated
54
Cargo.lock
generated
@ -30,8 +30,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alterware-launcher"
|
name = "alterware-launcher"
|
||||||
version = "0.4.8"
|
version = "0.5.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"colored",
|
||||||
"http_req",
|
"http_req",
|
||||||
"mslnk",
|
"mslnk",
|
||||||
"rand",
|
"rand",
|
||||||
@ -41,7 +42,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha1_smol",
|
"sha1_smol",
|
||||||
"steamlocate",
|
"steamlocate",
|
||||||
"windows-sys",
|
|
||||||
"winres",
|
"winres",
|
||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
@ -137,6 +137,17 @@ dependencies = [
|
|||||||
"inout",
|
"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]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@ -284,6 +295,12 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@ -295,9 +312,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http_req"
|
name = "http_req"
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f680177f2ebe4aabd573d07b322d15a5e0fbc97cd739fd627b08043c89041f8"
|
checksum = "42ce34c74ec562d68f2c23a532c62c1332ff1d1b6147fd118bd1938e090137d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"unicase",
|
"unicase",
|
||||||
@ -323,6 +340,17 @@ dependencies = [
|
|||||||
"cfg-if",
|
"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]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -372,6 +400,12 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.147"
|
version = "0.2.147"
|
||||||
@ -672,9 +706,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "self-replace"
|
name = "self-replace"
|
||||||
version = "1.3.6"
|
version = "1.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c56335359191626938ef6fdeb478f9f6a7c6020254d7f4641c7d810369fa0ec1"
|
checksum = "525db198616b2bcd0f245daf7bfd8130222f7ee6af9ff9984c19a61bf1160c55"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand 1.9.0",
|
"fastrand 1.9.0",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@ -683,9 +717,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.18"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@ -709,9 +743,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.105"
|
version = "1.0.107"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
|
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
15
Cargo.toml
15
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "alterware-launcher"
|
name = "alterware-launcher"
|
||||||
version = "0.4.8"
|
version = "0.5.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
build = "res/build.rs"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
http_req = { version = "0.9.2", default-features = false, features = [
|
http_req = { version = "0.9.3", default-features = false, features = [
|
||||||
"rust-tls",
|
"rust-tls",
|
||||||
] }
|
] }
|
||||||
sha1_smol = "1.0.0"
|
sha1_smol = "1.0.0"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.105"
|
serde_json = "1.0.107"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
semver = "1.0.18"
|
semver = "1.0.19"
|
||||||
zip = "0.6.6"
|
zip = "0.6.6"
|
||||||
|
colored = "2.0.4"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
steamlocate = "2.0.0-alpha.0"
|
steamlocate = "2.0.0-alpha.0"
|
||||||
mslnk = "0.1.8"
|
mslnk = "0.1.8"
|
||||||
# https://github.com/mitsuhiko/self-replace/pull/16/
|
self-replace = "1.3.7"
|
||||||
windows-sys = { version = "0.48", features = [
|
|
||||||
"Win32_Security",
|
|
||||||
] }
|
|
||||||
self-replace = "1.3.6"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
winres = "0.1.12"
|
winres = "0.1.12"
|
||||||
|
27
README.md
27
README.md
@ -16,9 +16,28 @@
|
|||||||
|
|
||||||
#### Command line arguments
|
#### Command line arguments
|
||||||
|
|
||||||
- Passing ```iw4-sp```, ```iw4x```, ```iw5-mod```, ```iw6-mod``` or ```s1-mod``` as the first argument will skip automatic game detection
|
- ```iw4-sp```, ```iw4x```, ```iw5-mod```, ```iw6-mod```, ```s1-mod```
|
||||||
- Passing ```update``` will stop the launcher from launching the game
|
- Skip automatic detection and launch the specified game
|
||||||
- ```skip-launcher-update``` skips launcher self-update
|
- 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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -43,7 +62,7 @@ When the launcher updates itself it needs to be restarted. It will return exit c
|
|||||||
```
|
```
|
||||||
@echo off
|
@echo off
|
||||||
:loop
|
:loop
|
||||||
start /wait alterware-launcher.exe update
|
start /wait alterware-launcher.exe --update
|
||||||
if %errorlevel% equ 201 (
|
if %errorlevel% equ 201 (
|
||||||
goto loop
|
goto loop
|
||||||
)
|
)
|
||||||
|
42
src/config.rs
Normal file
42
src/config.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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) {
|
||||||
|
match fs::write(
|
||||||
|
config_path.clone(),
|
||||||
|
serde_json::to_string_pretty(&config).unwrap(),
|
||||||
|
) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => match e.kind() {
|
||||||
|
std::io::ErrorKind::NotFound => {
|
||||||
|
fs::create_dir_all(config_path.parent().unwrap()).unwrap();
|
||||||
|
save(config_path, config);
|
||||||
|
}
|
||||||
|
_ => println!("Could not save config file, got:\n{}\n", e),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
76
src/http.rs
76
src/http.rs
@ -1,20 +1,40 @@
|
|||||||
|
use crate::global;
|
||||||
|
use crate::misc;
|
||||||
use std::{fs, io::Write, path::Path, str};
|
use std::{fs, io::Write, path::Path, str};
|
||||||
|
|
||||||
pub fn get_body(url: &str) -> Vec<u8> {
|
pub fn get_body(url: &str) -> Vec<u8> {
|
||||||
let mut res: Vec<u8> = Vec::new();
|
let mut res: Vec<u8> = Vec::new();
|
||||||
let req = http_req::request::Request::new(&url.try_into().unwrap())
|
|
||||||
|
match http_req::request::Request::new(&url.try_into().unwrap())
|
||||||
.header(
|
.header(
|
||||||
"User-Agent",
|
"User-Agent",
|
||||||
"AlterWare Launcher | github.com/mxve/alterware-launcher",
|
&format!(
|
||||||
|
"AlterWare Launcher | github.com/{}/{}",
|
||||||
|
global::GH_OWNER,
|
||||||
|
global::GH_REPO
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.send(&mut res)
|
.send(&mut res)
|
||||||
.unwrap_or_else(|error| {
|
{
|
||||||
panic!("\n\n{}:\n{:?}", "Error", error);
|
Ok(req) => {
|
||||||
});
|
if req.status_code() == http_req::response::StatusCode::new(302)
|
||||||
|
|| req.status_code() == http_req::response::StatusCode::new(301)
|
||||||
|
{
|
||||||
|
let location = req.headers().get("Location").unwrap().as_str();
|
||||||
|
return get_body(location);
|
||||||
|
}
|
||||||
|
|
||||||
if req.status_code() == http_req::response::StatusCode::new(302) {
|
if req.status_code() != http_req::response::StatusCode::new(200) {
|
||||||
let location = req.headers().get("Location").unwrap().as_str();
|
misc::fatal_error(&format!(
|
||||||
return get_body(location);
|
"Could not get body from {}, got {}",
|
||||||
|
url,
|
||||||
|
req.status_code()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
misc::fatal_error(&format!("Could not get body from {}, got:\n{}", url, e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res
|
||||||
@ -27,10 +47,38 @@ pub fn get_body_string(url: &str) -> String {
|
|||||||
pub fn download_file(url: &str, file_path: &Path) {
|
pub fn download_file(url: &str, file_path: &Path) {
|
||||||
let body = get_body(url);
|
let body = get_body(url);
|
||||||
|
|
||||||
let mut f = fs::File::create(file_path).unwrap_or_else(|error| {
|
match fs::File::create(file_path) {
|
||||||
panic!("\n\n{}:\n{:?}", "Error", error);
|
Ok(mut file) => match file.write_all(&body) {
|
||||||
});
|
Ok(_) => (),
|
||||||
f.write_all(&body).unwrap_or_else(|error| {
|
Err(e) => {
|
||||||
panic!("\n\n{}:\n{:?}", "Error", error);
|
misc::fatal_error(&format!(
|
||||||
});
|
"Could not write to file {}, got:\n{}",
|
||||||
|
file_path.to_str().unwrap(),
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
match e.kind() {
|
||||||
|
std::io::ErrorKind::NotFound => {
|
||||||
|
fs::create_dir_all(file_path.parent().unwrap()).unwrap();
|
||||||
|
return download_file(url, file_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{}",
|
||||||
|
file_path.to_str().unwrap(),
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
misc::fatal_error(&format!(
|
||||||
|
"Could not create file {}, got:\n{}",
|
||||||
|
file_path.to_str().unwrap(),
|
||||||
|
e
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ use crate::global::*;
|
|||||||
use crate::http;
|
use crate::http;
|
||||||
use crate::misc;
|
use crate::misc;
|
||||||
|
|
||||||
|
use colored::*;
|
||||||
use std::{fs, path::Path};
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
pub fn local_revision(dir: &Path) -> u16 {
|
pub fn local_revision(dir: &Path) -> u16 {
|
||||||
@ -25,7 +26,11 @@ pub fn update(dir: &Path) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Updating IW4x...");
|
println!(
|
||||||
|
"[{}] {}",
|
||||||
|
"Downloading".bright_yellow(),
|
||||||
|
dir.join("iw4x.dll").display()
|
||||||
|
);
|
||||||
http::download_file(
|
http::download_file(
|
||||||
&format!(
|
&format!(
|
||||||
"{}/download/iw4x.dll",
|
"{}/download/iw4x.dll",
|
||||||
|
294
src/main.rs
294
src/main.rs
@ -1,3 +1,4 @@
|
|||||||
|
mod config;
|
||||||
mod github;
|
mod github;
|
||||||
mod global;
|
mod global;
|
||||||
mod http;
|
mod http;
|
||||||
@ -9,9 +10,10 @@ mod structs;
|
|||||||
use global::*;
|
use global::*;
|
||||||
use structs::*;
|
use structs::*;
|
||||||
|
|
||||||
|
use colored::*;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use mslnk::ShellLink;
|
use mslnk::ShellLink;
|
||||||
use std::{fs, path::Path, path::PathBuf};
|
use std::{borrow::Cow, collections::HashMap, fs, path::Path, path::PathBuf};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use steamlocate::SteamDir;
|
use steamlocate::SteamDir;
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
|
|||||||
let mut steamdir = match SteamDir::locate() {
|
let mut steamdir = match SteamDir::locate() {
|
||||||
Some(steamdir) => steamdir,
|
Some(steamdir) => steamdir,
|
||||||
None => {
|
None => {
|
||||||
println!("Steam not found.");
|
println!("{}", "Steam not found!".yellow());
|
||||||
return installed_games;
|
return installed_games;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -35,26 +37,35 @@ fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
|
|||||||
installed_games
|
installed_games
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn create_shortcut(path: &Path, target: &Path, icon: String, args: String) {
|
||||||
|
if let Ok(mut sl) = ShellLink::new(target) {
|
||||||
|
sl.set_arguments(Some(args));
|
||||||
|
sl.set_icon_location(Some(icon));
|
||||||
|
sl.create_lnk(path).unwrap_or_else(|error| {
|
||||||
|
println!("Error creating shortcut.\n{:#?}", error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
println!("Error creating shortcut.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
fn setup_client_links(game: &Game, game_dir: &Path) {
|
fn setup_client_links(game: &Game, game_dir: &Path) {
|
||||||
if game.client.len() > 1 {
|
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");
|
|
||||||
|
|
||||||
for c in game.client.iter() {
|
for c in game.client.iter() {
|
||||||
let lnk = game_dir.join(format!("launch-{}.lnk", c));
|
create_shortcut(
|
||||||
|
&game_dir.join(format!("launch-{}.lnk", c)),
|
||||||
let mut sl = ShellLink::new(target.clone()).unwrap();
|
&game_dir.join("alterware-launcher.exe"),
|
||||||
sl.set_arguments(Some(c.to_string()));
|
|
||||||
sl.set_icon_location(Some(
|
|
||||||
game_dir
|
game_dir
|
||||||
.join(format!("{}.exe", c))
|
.join(format!("{}.exe", c))
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
));
|
c.to_string(),
|
||||||
sl.create_lnk(&lnk).unwrap();
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,19 +80,15 @@ fn setup_desktop_links(path: &Path, game: &Game) {
|
|||||||
std::env::var("USERPROFILE").unwrap()
|
std::env::var("USERPROFILE").unwrap()
|
||||||
));
|
));
|
||||||
|
|
||||||
let target = path.join("alterware-launcher.exe");
|
|
||||||
|
|
||||||
for c in game.client.iter() {
|
for c in game.client.iter() {
|
||||||
let lnk = desktop.join(format!("{}.lnk", c));
|
create_shortcut(
|
||||||
|
&desktop.join(format!("{}.lnk", c)),
|
||||||
let mut sl = ShellLink::new(target.clone()).unwrap();
|
&path.join("alterware-launcher.exe"),
|
||||||
sl.set_arguments(Some(c.to_string()));
|
|
||||||
sl.set_icon_location(Some(
|
|
||||||
path.join(format!("{}.exe", c))
|
path.join(format!("{}.exe", c))
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into_owned(),
|
.into_owned(),
|
||||||
));
|
c.to_string(),
|
||||||
sl.create_lnk(lnk).unwrap();
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,16 +97,18 @@ fn setup_desktop_links(path: &Path, game: &Game) {
|
|||||||
fn auto_install(path: &Path, game: &Game) {
|
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);
|
update(game, path, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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..".yellow()
|
||||||
|
);
|
||||||
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();
|
let current_dir = std::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) {
|
||||||
@ -165,79 +174,225 @@ fn prompt_client_selection(games: &[Game]) -> String {
|
|||||||
fn manual_install(games: &[Game]) {
|
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());
|
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.");
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(game: &Game, dir: &Path) {
|
fn update_dir(
|
||||||
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
|
cdn_info: &Vec<CdnFile>,
|
||||||
format!("{}/files.json", MASTER).as_str(),
|
remote_dir: &str,
|
||||||
))
|
dir: &Path,
|
||||||
.unwrap();
|
hashes: &mut HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let remote_dir = format!("{}/", remote_dir);
|
||||||
|
|
||||||
let engine_str = format!("{}/", game.engine);
|
|
||||||
for file in cdn_info {
|
for file in cdn_info {
|
||||||
if !file.name.starts_with(&engine_str) || file.name == "iw4/iw4x.dll" {
|
if !file.name.starts_with(&remote_dir) || file.name == "iw4/iw4x.dll" {
|
||||||
continue;
|
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() {
|
if file_path.exists() {
|
||||||
let sha1_local = misc::get_file_sha1(&file_path).to_lowercase();
|
let sha1_local = hashes
|
||||||
let sha1_remote = file.hash.to_lowercase();
|
.get(file_name)
|
||||||
|
.map(Cow::Borrowed)
|
||||||
|
.unwrap_or_else(|| Cow::Owned(misc::get_file_sha1(&file_path)))
|
||||||
|
.to_string();
|
||||||
|
|
||||||
if sha1_local != sha1_remote {
|
if sha1_local != sha1_remote {
|
||||||
println!(
|
println!(
|
||||||
"Updating {}...\nLocal hash: {}\nRemote hash: {}",
|
"[{}] {}",
|
||||||
file_path.display(),
|
"Updating".bright_yellow(),
|
||||||
sha1_local,
|
file_path.display()
|
||||||
sha1_remote
|
|
||||||
);
|
);
|
||||||
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
|
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 {
|
} else {
|
||||||
println!("Downloading {}...", file_path.display());
|
println!(
|
||||||
|
"[{}] {}",
|
||||||
|
"Downloading".bright_yellow(),
|
||||||
|
file_path.display()
|
||||||
|
);
|
||||||
if let Some(parent) = file_path.parent() {
|
if let Some(parent) = file_path.parent() {
|
||||||
if !parent.exists() {
|
if !parent.exists() {
|
||||||
fs::create_dir_all(parent).unwrap();
|
fs::create_dir_all(parent).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
|
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" {
|
if game.engine == "iw4" {
|
||||||
iw4x::update(dir);
|
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) {
|
fn launch(file_path: &PathBuf, args: &str) {
|
||||||
println!("Launching {}...", file_path.display());
|
println!("Launching {} {}", file_path.display(), args);
|
||||||
std::process::Command::new(file_path)
|
std::process::Command::new(file_path)
|
||||||
|
.args(args.trim().split(' '))
|
||||||
|
.current_dir(file_path.parent().unwrap())
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to launch the game")
|
.expect("Failed to launch the game")
|
||||||
.wait()
|
.wait()
|
||||||
.expect("Failed to wait for the game process to finish");
|
.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() {
|
fn main() {
|
||||||
|
#[cfg(windows)]
|
||||||
|
setup_env();
|
||||||
|
|
||||||
let mut args: Vec<String> = std::env::args().collect();
|
let mut args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
let mut update_only = false;
|
if arg_bool(&args, "--help") {
|
||||||
if args.contains(&String::from("update")) {
|
println!("CLI Args:");
|
||||||
update_only = true;
|
println!(" <client>: Specify the client to launch");
|
||||||
args.iter()
|
println!(" --help: Display this help message");
|
||||||
.position(|r| r == "update")
|
println!(" --version: Display the launcher version");
|
||||||
.map(|e| args.remove(e));
|
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")) {
|
if arg_bool(&args, "--version") || arg_bool(&args, "-v") {
|
||||||
self_update::run(update_only);
|
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 {
|
} else {
|
||||||
args.iter()
|
install_path = std::env::current_dir().unwrap();
|
||||||
.position(|r| r == "skip-launcher-update")
|
}
|
||||||
.map(|e| args.remove(e));
|
|
||||||
|
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());
|
let games_json = http::get_body_string(format!("{}/games.json", MASTER).as_str());
|
||||||
@ -249,9 +404,9 @@ fn main() {
|
|||||||
} else {
|
} else {
|
||||||
'main: for g in games.iter() {
|
'main: for g in games.iter() {
|
||||||
for r in g.references.iter() {
|
for r in g.references.iter() {
|
||||||
if std::path::Path::new(r).exists() {
|
if install_path.join(r).exists() {
|
||||||
if g.client.len() > 1 {
|
if g.client.len() > 1 {
|
||||||
if update_only {
|
if cfg.update_only {
|
||||||
game = String::from(g.client[0]);
|
game = String::from(g.client[0]);
|
||||||
break 'main;
|
break 'main;
|
||||||
}
|
}
|
||||||
@ -278,9 +433,30 @@ 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 {
|
||||||
update(g, &std::env::current_dir().unwrap());
|
if cfg.ask_bonus_content && !g.bonus.is_empty() {
|
||||||
if !update_only {
|
println!("Download bonus content? (Y/n)");
|
||||||
launch(&PathBuf::from(format!("{}.exe", c)));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@ -293,7 +469,7 @@ fn main() {
|
|||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
manual_install(&games);
|
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!("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...");
|
println!("Press enter to exit...");
|
||||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
|
||||||
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();
|
||||||
sha1.update(&fs::read(path).unwrap());
|
sha1.update(&fs::read(path).unwrap());
|
||||||
@ -18,3 +20,9 @@ pub fn rev_to_int(rev: &str) -> u16 {
|
|||||||
.parse::<u16>()
|
.parse::<u16>()
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fatal_error(error: &str) {
|
||||||
|
println!("\n\n{}:\n{}", "Error".bright_red(), error);
|
||||||
|
stdin();
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ pub fn run(update_only: bool) {
|
|||||||
use std::{fs, path::PathBuf};
|
use std::{fs, path::PathBuf};
|
||||||
|
|
||||||
use crate::http;
|
use crate::http;
|
||||||
|
use crate::misc;
|
||||||
|
|
||||||
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();
|
||||||
@ -60,10 +61,17 @@ pub fn run(update_only: bool) {
|
|||||||
fs::remove_file(&update_binary).unwrap();
|
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(
|
http::download_file(
|
||||||
&format!(
|
&format!(
|
||||||
"{}/download/alterware-launcher.exe",
|
"{}/download/{}",
|
||||||
github::latest_release_url(GH_OWNER, GH_REPO)
|
github::latest_release_url(GH_OWNER, GH_REPO),
|
||||||
|
launcher_name
|
||||||
),
|
),
|
||||||
&file_path,
|
&file_path,
|
||||||
);
|
);
|
||||||
@ -75,9 +83,13 @@ 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. Please run it again.");
|
println!(
|
||||||
|
"Launcher updated. View the changelog at https://github.com/{}/{}/releases/latest",
|
||||||
|
GH_OWNER, GH_REPO,
|
||||||
|
);
|
||||||
|
println!("Please restart the launcher.");
|
||||||
if !update_only {
|
if !update_only {
|
||||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
misc::stdin();
|
||||||
}
|
}
|
||||||
std::process::exit(201);
|
std::process::exit(201);
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,28 @@ pub struct Game<'a> {
|
|||||||
pub client: Vec<&'a str>,
|
pub client: Vec<&'a str>,
|
||||||
pub references: Vec<&'a str>,
|
pub references: Vec<&'a str>,
|
||||||
pub app_id: u32,
|
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(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user