96 Commits

Author SHA1 Message Date
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
572c66cc16 v0.5.3 2023-10-06 09:03:34 +02:00
cf87f7c741 misc 2023-10-06 08:34:42 +02:00
dc8b01b4c8 add changelog link 2023-10-06 08:30:29 +02:00
9e8893ce75 handle notfound; give tips to fix permission denied 2023-10-02 15:49:01 +02:00
6f92e1fb71 Create config path if it doesn't exist; don't panic 2023-10-02 15:48:22 +02:00
96c3e504f8 improve logging of http errors 2023-10-02 04:39:49 +02:00
00c14d2a02 handle shortcut creation errors 2023-10-01 00:36:12 +02:00
0378f19a75 set matrix target 2023-09-25 18:57:51 +02:00
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
546d8c4cdf v0.4.7 2023-08-29 22:13:05 +02:00
12ccc9554f update iw4x if dll doesn't exist 2023-08-29 22:12:42 +02:00
c117eaeb31 whoops/v0.4.6 2023-08-29 22:00:31 +02:00
50c7b9fbd7 v0.4.6 2023-08-29 21:57:35 +02:00
f09271c29c obtain iw4x.dll from iw4x/iw4x-client 2023-08-29 21:53:47 +02:00
b71c15cb8e update io::unzip 2023-08-29 06:59:04 +02:00
a3000bd2fa Merge branch 'main' of github.com:mxve/alterware-launcher 2023-08-29 06:51:52 +02:00
cc00cad4f2 add io::unzip 2023-08-29 06:51:44 +02:00
6a16299cee seperate into multiple source files 2023-08-29 06:51:40 +02:00
565a69566c Merge pull request #26 from mxve/dependabot/cargo/serde-1.0.188
Bump serde from 1.0.185 to 1.0.188
2023-08-28 22:38:55 +02:00
1727446cfc Merge pull request #25 from mxve/dependabot/cargo/self-replace-1.3.6
Bump self-replace from 1.3.5 to 1.3.6
2023-08-28 22:38:47 +02:00
4282eb7b75 Bump serde from 1.0.185 to 1.0.188
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.185 to 1.0.188.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.185...v1.0.188)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 15:52:58 +00:00
a2922540c5 Bump self-replace from 1.3.5 to 1.3.6
Bumps [self-replace](https://github.com/mitsuhiko/self-replace) from 1.3.5 to 1.3.6.
- [Changelog](https://github.com/mitsuhiko/self-replace/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/self-replace/compare/1.3.5...1.3.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 15:52:49 +00:00
601abd008f v0.4.5 2023-08-26 21:17:01 +02:00
322fd2f98a linting 2023-08-26 20:59:38 +02:00
e061bca7a3 feat: manual client selection
fix: crash if steam not installed/steamdir is none
2023-08-26 20:53:41 +02:00
87e86cc954 restart required ex.code 101->201
rust panic returns 101, so this wasn't a good choice
2023-08-22 11:37:34 +02:00
d5d847df75 Merge pull request #23 from mxve/dependabot/cargo/serde-1.0.185
Bump serde from 1.0.183 to 1.0.185
2023-08-21 17:31:09 +02:00
d620bc9838 Bump serde from 1.0.183 to 1.0.185
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.183 to 1.0.185.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.183...v1.0.185)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 15:26:04 +00:00
76a7d8b2c6 Merge pull request #24 from mxve/dependabot/cargo/serde_json-1.0.105
Bump serde_json from 1.0.104 to 1.0.105
2023-08-21 17:25:29 +02:00
6c7fbd1ff1 Bump serde_json from 1.0.104 to 1.0.105
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.104 to 1.0.105.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.104...v1.0.105)

---
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-08-21 15:10:09 +00:00
18651a68ca v0.4.4 2023-08-18 14:38:15 +02:00
79559fe46a Don't ask what client to launch if update is set 2023-08-18 13:36:06 +02:00
8fd66d16af v0.4.3 2023-08-18 12:53:28 +02:00
4d3c6b9dab update after windows setup; match cur dir to steam dirs 2023-08-18 12:51:51 +02:00
7ca7615222 v0.4.2 2023-08-17 20:26:25 +02:00
2195f42abc fix: unix.. again 2023-08-17 20:26:08 +02:00
f7635d4089 return 101 when restart is required after self-update 2023-08-17 18:32:32 +02:00
9598ec3dfe v0.4.1 2023-08-16 13:01:32 +02:00
37266207e7 create release as draft 2023-08-16 13:01:25 +02:00
e041df80c5 don't copy if current & target path are the same 2023-08-16 13:00:15 +02:00
b157bcb2c2 remove cache busting 2023-08-16 12:57:27 +02:00
beae0adce5 Merge pull request #21 from mxve/build/symbols
build: add symbols
2023-08-15 19:05:22 +02:00
Edo
f9ec044a15 build: add symbols 2023-08-15 19:03:38 +02:00
16 changed files with 1286 additions and 353 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,22 +9,30 @@ 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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
token: ${{ secrets.GITHUB_TOKEN }}
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
- uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1
with:
target: ${{ matrix.target }}
bin: alterware-launcher
tar: unix
zip: windows

436
Cargo.lock generated
View File

@ -2,6 +2,23 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "aho-corasick"
version = "1.0.3"
@ -13,8 +30,9 @@ dependencies = [
[[package]]
name = "alterware-launcher"
version = "0.4.0"
version = "0.5.4"
dependencies = [
"colored",
"http_req",
"mslnk",
"rand",
@ -24,15 +42,21 @@ dependencies = [
"serde_json",
"sha1_smol",
"steamlocate",
"windows-sys",
"winres",
"zip",
]
[[package]]
name = "base64"
version = "0.13.1"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bitflags"
@ -67,11 +91,35 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
@ -79,6 +127,33 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "cpufeatures"
version = "0.2.9"
@ -88,6 +163,24 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -98,6 +191,12 @@ dependencies = [
"typenum",
]
[[package]]
name = "deranged"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]]
name = "digest"
version = "0.10.7"
@ -106,27 +205,27 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
"subtle",
]
[[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]]
@ -165,6 +264,16 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]]
name = "flate2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -187,17 +296,42 @@ dependencies = [
]
[[package]]
name = "http_req"
version = "0.9.2"
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f680177f2ebe4aabd573d07b322d15a5e0fbc97cd739fd627b08043c89041f8"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
"digest",
]
[[package]]
name = "http_req"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "158d4edacc70c9bdb0464314063b8d9d60fa776442dc13b00a13581b88b0a0a0"
dependencies = [
"rustls",
"rustls-pemfile",
"unicase",
"webpki",
"webpki-roots",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "instant"
version = "0.1.12"
@ -207,12 +341,32 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "jobserver"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.63"
@ -247,6 +401,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"
@ -271,6 +431,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [
"adler",
]
[[package]]
name = "mslnk"
version = "0.1.8"
@ -282,12 +451,6 @@ 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"
@ -295,10 +458,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
[[package]]
name = "option-ext"
version = "0.2.0"
name = "password-hash"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
dependencies = [
"base64ct",
"rand_core",
"subtle",
]
[[package]]
name = "paste"
@ -306,6 +474,18 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pbkdf2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
dependencies = [
"digest",
"hmac",
"password-hash",
"sha2",
]
[[package]]
name = "pest"
version = "2.7.2"
@ -350,6 +530,12 @@ dependencies = [
"sha2",
]
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -471,12 +657,26 @@ dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"spin 0.5.2",
"untrusted 0.7.1",
"web-sys",
"winapi",
]
[[package]]
name = "ring"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "911b295d2d302948838c8ac142da1ee09fa7863163b44e6715bc9357905878b8"
dependencies = [
"cc",
"getrandom",
"libc",
"spin 0.9.8",
"untrusted 0.9.0",
"windows-sys",
]
[[package]]
name = "rustix"
version = "0.38.8"
@ -492,15 +692,33 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.19.1"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
dependencies = [
"log",
"ring 0.16.20",
"rustls-webpki",
"sct",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustls-webpki"
version = "0.101.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe"
dependencies = [
"ring 0.16.20",
"untrusted 0.7.1",
]
[[package]]
@ -511,19 +729,19 @@ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "sct"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"ring",
"untrusted",
"ring 0.16.20",
"untrusted 0.7.1",
]
[[package]]
name = "self-replace"
version = "1.3.5"
version = "1.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0e7c919783db74b5995f13506069227e4721d388bea4a8ac3055acac864ac16"
checksum = "525db198616b2bcd0f245daf7bfd8130222f7ee6af9ff9984c19a61bf1160c55"
dependencies = [
"fastrand 1.9.0",
"tempfile",
@ -532,24 +750,24 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.18"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
version = "1.0.183"
version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.183"
version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
dependencies = [
"proc-macro2",
"quote",
@ -558,15 +776,26 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.104"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha1"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
@ -591,27 +820,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "steamlocate"
version = "1.2.1"
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec01c74611d14a808cb212d17c6e03f0e30736a15ed1d5736f8a53154cea3ae"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "steamlocate"
version = "2.0.0-alpha.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b1568c4a70a26c4373fe1131ffa4eff055459631b6e40c6bc118615f2d870c3"
dependencies = [
"dirs",
"keyvalues-parser",
"keyvalues-serde",
"serde",
"steamy-vdf",
"winreg",
]
[[package]]
name = "steamy-vdf"
version = "0.2.0"
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533127ad49314bfe71c3d3fd36b3ebac3d24f40618092e70e1cfe8362c7fac79"
dependencies = [
"nom",
]
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "syn"
@ -657,6 +888,23 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
dependencies = [
"deranged",
"serde",
"time-core",
]
[[package]]
name = "time-core"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "toml"
version = "0.5.11"
@ -680,9 +928,9 @@ checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "unicase"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
@ -699,6 +947,12 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "version_check"
version = "0.9.4"
@ -777,22 +1031,19 @@ dependencies = [
[[package]]
name = "webpki"
version = "0.21.4"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53"
dependencies = [
"ring",
"untrusted",
"ring 0.17.2",
"untrusted 0.9.0",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]]
name = "winapi"
@ -884,11 +1135,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",
]
@ -900,3 +1150,53 @@ checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
"toml",
]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time",
"zstd",
]
[[package]]
name = "zstd"
version = "0.11.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "5.0.2+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.8+zstd.1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c"
dependencies = [
"cc",
"libc",
"pkg-config",
]

View File

@ -1,33 +1,35 @@
[package]
name = "alterware-launcher"
version = "0.4.0"
version = "0.5.4"
edition = "2021"
build = "res/build.rs"
[profile.release]
opt-level = "s"
# Symbols are a nice thing
debug = true
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.10.0", default-features = false, features = [
"rust-tls",
] }
sha1_smol = "1.0.0"
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.104"
serde = { version = "1.0.189", features = ["derive"] }
serde_json = "1.0.107"
rand = "0.8.5"
semver = "1.0.18"
semver = "1.0.20"
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.5"
self-replace = "1.3.7"
[build-dependencies]
winres = "0.1.12"

View File

@ -1,11 +1,94 @@
# 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
- See [client-args.md](client-args.md)
- ```--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.
---
#### 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.
---
#### 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.
```
@echo off
:loop
start /wait alterware-launcher.exe --update
if %errorlevel% equ 201 (
goto loop
)
```

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`)|

42
src/config.rs Normal file
View 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);
}

22
src/github.rs Normal file
View File

@ -0,0 +1,22 @@
use semver::Version;
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",
owner, repo
)
.as_str(),
);
let github_json: serde_json::Value = serde_json::from_str(&github_body).unwrap();
github_json["tag_name"].to_string().replace('"', "")
}
pub fn latest_version(owner: &str, repo: &str) -> Version {
let tag = latest_tag(owner, repo).replace('v', "");
Version::parse(&tag).unwrap()
}
pub fn latest_release_url(owner: &str, repo: &str) -> String {
format!("https://github.com/{}/{}/releases/latest", owner, repo)
}

5
src/global.rs Normal file
View File

@ -0,0 +1,5 @@
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";

View File

@ -1,22 +1,42 @@
use crate::global;
use crate::misc;
use std::{fs, io::Write, path::Path, str};
pub fn get_body(url: &str) -> Vec<u8> {
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(
"User-Agent",
"AlterWare Launcher | github.com/mxve/alterware-launcher",
&format!(
"AlterWare Launcher | github.com/{}/{}",
global::GH_OWNER,
global::GH_REPO
),
)
.send(&mut res)
.unwrap_or_else(|error| {
panic!("\n\n{}:\n{:?}", "Error", error);
});
if req.status_code() == http_req::response::StatusCode::new(302) {
{
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(200) {
misc::fatal_error(&format!(
"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
}
@ -27,10 +47,38 @@ pub fn get_body_string(url: &str) -> String {
pub fn download_file(url: &str, file_path: &Path) {
let body = get_body(url);
let mut f = fs::File::create(file_path).unwrap_or_else(|error| {
panic!("\n\n{}:\n{:?}", "Error", error);
});
f.write_all(&body).unwrap_or_else(|error| {
panic!("\n\n{}:\n{:?}", "Error", error);
});
match fs::File::create(file_path) {
Ok(mut file) => match file.write_all(&body) {
Ok(_) => (),
Err(e) => {
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 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
));
}
_ => (),
}
misc::fatal_error(&format!(
"Could not create file {}, got:\n{}",
file_path.to_str().unwrap(),
e
));
}
}
}

22
src/io.rs Normal file
View File

@ -0,0 +1,22 @@
use std::{fs, path::Path};
pub fn unzip(zip_path: &Path, out_path: &Path) {
let mut archive = zip::ZipArchive::new(fs::File::open(zip_path).unwrap()).unwrap();
for i in 0..archive.len() {
let mut file = archive.by_index(i).unwrap();
let outpath = out_path.join(file.name());
if (*file.name()).ends_with('/') {
fs::create_dir_all(outpath).unwrap();
} else {
println!("Unpacking {}", file.name());
if let Some(p) = outpath.parent() {
if !p.exists() {
fs::create_dir_all(p).unwrap();
}
}
let mut outfile = fs::File::create(&outpath).unwrap();
std::io::copy(&mut file, &mut outfile).unwrap();
}
}
}

42
src/iw4x.rs Normal file
View File

@ -0,0 +1,42 @@
use crate::github;
use crate::global::*;
use crate::http;
use crate::misc;
use colored::*;
use std::{fs, path::Path};
pub fn local_revision(dir: &Path) -> u16 {
if let Ok(revision) = fs::read_to_string(dir.join(".iw4xrevision")) {
misc::rev_to_int(&revision)
} else {
0
}
}
pub fn remote_revision() -> u16 {
misc::rev_to_int(&github::latest_tag(GH_IW4X_OWNER, GH_IW4X_REPO))
}
pub fn update(dir: &Path) {
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,128 +1,32 @@
mod config;
mod github;
mod global;
mod http;
mod iw4x;
mod misc;
mod self_update;
mod structs;
use global::*;
use structs::*;
use colored::*;
#[cfg(windows)]
use mslnk::ShellLink;
use semver::Version;
use std::time::{SystemTime, UNIX_EPOCH};
use std::{fs, path::PathBuf};
#[cfg(not(windows))]
use std::{thread, time};
use std::{borrow::Cow, collections::HashMap, fs, path::Path, path::PathBuf};
#[cfg(windows)]
use steamlocate::SteamDir;
#[derive(serde::Deserialize, serde::Serialize)]
struct CdnFile {
name: String,
size: u32,
hash: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
struct Game<'a> {
engine: &'a str,
client: Vec<&'a str>,
references: Vec<&'a str>,
app_id: u32,
}
const MASTER: &str = "https://master.alterware.dev";
const REPO: &str = "mxve/alterware-launcher";
fn get_cache_buster() -> u64 {
match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => 1,
}
}
fn get_file_sha1(path: &PathBuf) -> String {
let mut sha1 = sha1_smol::Sha1::new();
sha1.update(&fs::read(path).unwrap());
sha1.digest().to_string()
}
fn get_input() -> String {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
fn self_update_available() -> bool {
let current_version: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let github_body = http::get_body_string(
format!("https://api.github.com/repos/{}/releases/latest", REPO).as_str(),
);
let github_json: serde_json::Value = serde_json::from_str(&github_body).unwrap();
let latest_version = github_json["tag_name"]
.to_string()
.replace(['v', '"'].as_ref(), "");
let latest_version = Version::parse(&latest_version).unwrap();
current_version < latest_version
}
#[cfg(not(windows))]
fn self_update() {
if self_update_available() {
println!("A new version of the AlterWare launcher is available.");
println!("Download it at https://github.com/{}/releases/latest", REPO);
println!("Launching in 10 seconds..");
thread::sleep(time::Duration::from_secs(10));
}
}
#[cfg(windows)]
fn self_update() {
let working_dir = std::env::current_dir().unwrap();
let files = fs::read_dir(&working_dir).unwrap();
for file in files {
let file = file.unwrap();
let file_name = file.file_name().into_string().unwrap();
if file_name.contains("alterware-launcher")
&& (file_name.contains(".__relocated__.exe")
|| file_name.contains(".__selfdelete__.exe"))
{
fs::remove_file(file.path()).unwrap();
}
}
if self_update_available() {
println!("Performing launcher self-update.");
println!("If you run into any issues, please download the latest version at https://github.com/{}/releases/latest", REPO);
let update_binary = PathBuf::from("alterware-launcher-update.exe");
let file_path = working_dir.join(&update_binary);
if update_binary.exists() {
fs::remove_file(&update_binary).unwrap();
}
http::download_file(
&format!(
"https://github.com/{}/releases/latest/download/alterware-launcher.exe",
REPO
),
&file_path,
);
if !file_path.exists() {
println!("Failed to download launcher update.");
return;
}
self_replace::self_replace("alterware-launcher-update.exe").unwrap();
fs::remove_file(&file_path).unwrap();
println!("Launcher updated. Please run it again.");
std::io::stdin().read_line(&mut String::new()).unwrap();
std::process::exit(0);
}
}
#[cfg(windows)]
fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
let mut installed_games = Vec::new();
let mut steamdir = SteamDir::locate().unwrap();
let mut steamdir = match SteamDir::locate() {
Some(steamdir) => steamdir,
None => {
println!("{}", "Steam not found!".yellow());
return installed_games;
}
};
for game in games {
if let Some(app) = steamdir.app(&game.app_id) {
@ -134,54 +38,41 @@ fn get_installed_games(games: &Vec<Game>) -> Vec<(u32, PathBuf)> {
}
#[cfg(windows)]
fn setup_client_links(game: &Game, game_dir: &PathBuf) {
if game.client.len() > 1 {
println!("Multiple clients installed, use the shortcuts (launch-<client>.lnk in the game directory or desktop shortcuts) to launch a specific client.");
}
let target = game_dir.join("alterware-launcher.exe");
for c in game.client.iter() {
let lnk = game_dir.join(format!("launch-{}.lnk", c));
let mut sl = ShellLink::new(target.clone()).unwrap();
sl.set_arguments(Some(c.to_string()));
sl.set_icon_location(Some(
game_dir
.join(format!("{}.exe", c))
.to_string_lossy()
.into_owned(),
));
sl.create_lnk(&lnk).unwrap();
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)]
fn windows_launcher_install(games: &Vec<Game>) {
println!("No game specified/found. Checking for installed Steam games..");
let installed_games = get_installed_games(games);
if !installed_games.is_empty() {
println!("Installed games:");
for (id, path) in installed_games.iter() {
println!("{}: {}", id, path.display());
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 on the desktop) to launch a specific client.");
}
println!("Enter the ID of the game you want to install the AlterWare client for:");
let input: u32 = get_input().parse().unwrap();
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();
fs::copy(launcher_path, path.join("alterware-launcher.exe")).unwrap();
println!("Launcher copied to {}", path.display());
setup_client_links(game, path);
for c in game.client.iter() {
create_shortcut(
&game_dir.join(format!("launch-{}.lnk", c)),
&game_dir.join("alterware-launcher.exe"),
game_dir
.join(format!("{}.exe", c))
.to_string_lossy()
.into_owned(),
c.to_string(),
);
}
}
#[cfg(windows)]
fn setup_desktop_links(path: &Path, game: &Game) {
println!("Create Desktop shortcut? (Y/n)");
let input = get_input().to_ascii_lowercase();
let input = misc::stdin().to_ascii_lowercase();
if input == "y" || input.is_empty() {
let desktop = PathBuf::from(&format!(
@ -189,125 +80,376 @@ fn windows_launcher_install(games: &Vec<Game>) {
std::env::var("USERPROFILE").unwrap()
));
let target = path.join("alterware-launcher.exe");
for c in game.client.iter() {
let lnk = desktop.join(format!("{}.lnk", c));
let mut sl = ShellLink::new(target.clone()).unwrap();
sl.set_arguments(Some(c.to_string()));
sl.set_icon_location(Some(
create_shortcut(
&desktop.join(format!("{}.lnk", c)),
&path.join("alterware-launcher.exe"),
path.join(format!("{}.exe", c))
.to_string_lossy()
.into_owned(),
));
sl.create_lnk(lnk).unwrap();
c.to_string(),
);
}
}
}
#[cfg(windows)]
fn auto_install(path: &Path, game: &Game) {
setup_client_links(game, path);
setup_desktop_links(path, game);
update(game, path, false, false);
}
#[cfg(windows)]
fn windows_launcher_install(games: &Vec<Game>) {
println!(
"{}",
"No game specified/found. Checking for installed Steam games..".yellow()
);
let installed_games = get_installed_games(games);
if !installed_games.is_empty() {
let current_dir = std::env::current_dir().unwrap();
for (id, path) in installed_games.iter() {
if current_dir.starts_with(path) {
println!("Found game in current directory.");
println!("Installing AlterWare client for {}.", id);
let game = games.iter().find(|&g| g.app_id == *id).unwrap();
auto_install(path, 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::process::exit(0);
}
}
println!("Installed games:");
for (id, path) in installed_games.iter() {
println!("{}: {}", id, path.display());
}
println!("Enter the ID of the game you want to install the AlterWare client for, enter 0 for manual selection:");
let input: u32 = misc::stdin().parse().unwrap();
if input == 0 {
return manual_install(games);
}
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 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);
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;
}
}
std::process::exit(0);
} else {
manual_install(games);
}
}
println!("No installed Steam games found. Please install a supported game first or place the launcher in the game folder.");
fn prompt_client_selection(games: &[Game]) -> String {
println!(
"Couldn't detect any games, please select a client to install in the current directory:"
);
for (i, g) in games.iter().enumerate() {
for c in g.client.iter() {
println!("{}: {}", i, c);
}
}
let input: usize = misc::stdin().parse().unwrap();
String::from(games[input].client[0])
}
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);
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) {
let cdn_info: Vec<CdnFile> = serde_json::from_str(&http::get_body_string(
format!("{}/files.json?{}", MASTER, get_cache_buster()).as_str(),
))
.unwrap();
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
}
fn update_dir(
cdn_info: &Vec<CdnFile>,
remote_dir: &str,
dir: &Path,
hashes: &mut HashMap<String, String>,
) {
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(game.engine) {
if !file.name.starts_with(&remote_dir_pre) || file.name == "iw4/iw4x.dll" {
continue;
}
let file_path = PathBuf::from(&file.name.replace(&format!("{}/", game.engine), ""));
if file_path.exists() {
let sha1_local = get_file_sha1(&file_path).to_lowercase();
let sha1_remote = file.hash.to_lowercase();
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
.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
);
http::download_file(
&format!("{}/{}?{}", MASTER, file.name, get_cache_buster()),
&file_path,
);
files_to_download.push(file.clone());
} else {
println!("[{}] {}", "Checked".bright_blue(), file_path.display());
}
} else {
println!("Downloading {}...", file_path.display());
files_to_download.push(file.clone());
}
}
if files_to_download.is_empty() {
println!(
"[{}] No files to download for {}",
"Info".bright_magenta(),
remote_dir
);
return;
}
println!(
"[{}] Downloading outdated or missing files for {}, {}",
"Info".bright_magenta(),
remote_dir,
misc::human_readable_bytes(total_download_size(&files_to_download, remote_dir))
);
for file in files_to_download {
let file_name = &file.name.replace(&remote_dir_pre, "");
let file_path = dir.join(file_name);
println!(
"[{}] {} ({})",
"Downloading".bright_yellow(),
file_path.display(),
misc::human_readable_bytes(file.size as u64)
);
if let Some(parent) = file_path.parent() {
if !parent.exists() {
fs::create_dir_all(parent).unwrap();
}
}
http::download_file(
&format!("{}/{}?{}", MASTER, file.name, get_cache_buster()),
&file_path,
);
}
http::download_file(&format!("{}/{}", MASTER, file.name), &file_path);
hashes.insert(file_name.to_owned(), file.hash.to_lowercase());
}
}
fn launch(file_path: &PathBuf) {
println!("Launching {}...", file_path.display());
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, 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();
if !args.contains(&String::from("skip-launcher-update")) {
self_update();
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 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 games_json =
http::get_body_string(format!("{}/games.json?{}", MASTER, get_cache_buster()).as_str());
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: Vec<Game> = serde_json::from_str(&games_json).unwrap();
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));
}
let mut game: String = String::new();
if args.len() > 1 {
game = String::from(&args[1]);
} 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 cfg.update_only {
game = String::from(g.client[0]);
break 'main;
}
#[cfg(windows)]
setup_client_links(g, &std::env::current_dir().unwrap());
#[cfg(not(windows))]
println!("Multiple clients installed, set the client as the first argument to launch a specific client.");
println!("Select a client to launch:");
for (i, c) in g.client.iter().enumerate() {
println!("{}: {}", i, c);
}
game = String::from(g.client[get_input().parse::<usize>().unwrap()]);
game = String::from(g.client[misc::stdin().parse::<usize>().unwrap()]);
break 'main;
}
game = String::from(g.client[0]);
@ -320,9 +462,30 @@ fn main() {
for g in games.iter() {
for c in g.client.iter() {
if c == &game {
update(g);
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;
}
@ -332,7 +495,10 @@ fn main() {
#[cfg(windows)]
windows_launcher_install(&games);
println!("Game not found!");
#[cfg(not(windows))]
manual_install(&games);
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();

39
src/misc.rs Normal file
View File

@ -0,0 +1,39 @@
use std::{fs, path::PathBuf};
use colored::Colorize;
pub fn get_file_sha1(path: &PathBuf) -> String {
let mut sha1 = sha1_smol::Sha1::new();
sha1.update(&fs::read(path).unwrap());
sha1.digest().to_string()
}
pub fn stdin() -> String {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input.trim().to_string()
}
pub fn rev_to_int(rev: &str) -> u16 {
rev.strip_prefix('r')
.unwrap_or("0")
.parse::<u16>()
.unwrap_or(0)
}
pub fn fatal_error(error: &str) {
println!("\n\n{}:\n{}", "Error".bright_red(), error);
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])
}

96
src/self_update.rs Normal file
View File

@ -0,0 +1,96 @@
use crate::github;
use crate::global::*;
use semver::Version;
#[cfg(not(windows))]
use std::{thread, time};
pub fn self_update_available() -> bool {
let current_version: Version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
let latest_version = github::latest_version(GH_OWNER, GH_REPO);
current_version < latest_version
}
#[cfg(not(windows))]
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!("Launching in 10 seconds..");
thread::sleep(time::Duration::from_secs(10));
}
}
#[cfg(windows)]
pub fn run(update_only: bool) {
use std::{fs, path::PathBuf};
use crate::http;
use crate::misc;
let working_dir = std::env::current_dir().unwrap();
let files = fs::read_dir(&working_dir).unwrap();
for file in files {
let file = file.unwrap();
let file_name = file.file_name().into_string().unwrap();
if file_name.contains("alterware-launcher")
&& (file_name.contains(".__relocated__.exe")
|| file_name.contains(".__selfdelete__.exe"))
{
fs::remove_file(file.path()).unwrap();
}
}
if self_update_available() {
println!("Performing launcher self-update.");
println!(
"If you run into any issues, please download the latest version at {}",
github::latest_release_url(GH_OWNER, GH_REPO)
);
let update_binary = PathBuf::from("alterware-launcher-update.exe");
let file_path = working_dir.join(&update_binary);
if update_binary.exists() {
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/{}",
github::latest_release_url(GH_OWNER, GH_REPO),
launcher_name
),
&file_path,
);
if !file_path.exists() {
println!("Failed to download launcher update.");
return;
}
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.");
if !update_only {
misc::stdin();
}
std::process::exit(201);
}
}

38
src/structs.rs Normal file
View File

@ -0,0 +1,38 @@
#[derive(serde::Deserialize, serde::Serialize, Clone)]
pub struct CdnFile {
pub name: String,
pub size: u32,
pub hash: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct Game<'a> {
pub engine: &'a str,
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(""),
}
}
}