1 Commits

Author SHA1 Message Date
dependabot[bot]
a2432f57ef Bump steamlocate from 2.0.0-beta.2 to 2.0.1
Bumps [steamlocate](https://github.com/WilliamVenner/steamlocate-rs) from 2.0.0-beta.2 to 2.0.1.
- [Release notes](https://github.com/WilliamVenner/steamlocate-rs/releases)
- [Changelog](https://github.com/WilliamVenner/steamlocate-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/WilliamVenner/steamlocate-rs/commits/2.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-12 20:06:36 +00:00
16 changed files with 577 additions and 249 deletions

View File

@@ -15,7 +15,7 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: Install stable toolchain - name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
@@ -46,7 +46,7 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: Install stable toolchain - name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
@@ -77,7 +77,7 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- name: Install stable toolchain - name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable

View File

@@ -9,7 +9,7 @@ jobs:
create-release: create-release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- uses: taiki-e/create-gh-release-action@v1.9.1 - uses: taiki-e/create-gh-release-action@v1.9.1
with: with:
draft: true draft: true
@@ -29,8 +29,8 @@ jobs:
os: windows-latest os: windows-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v4
- uses: taiki-e/upload-rust-binary-action@v1.27.0 - uses: taiki-e/upload-rust-binary-action@v1.26.0
with: with:
target: ${{ matrix.target }} target: ${{ matrix.target }}
bin: alterware-launcher bin: alterware-launcher

286
Cargo.lock generated
View File

@@ -19,7 +19,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.11.5" version = "0.11.1"
dependencies = [ dependencies = [
"blake3", "blake3",
"colored", "colored",
@@ -29,7 +29,7 @@ dependencies = [
"mslnk", "mslnk",
"once_cell", "once_cell",
"openssl", "openssl",
"rand 0.9.2", "rand 0.9.1",
"reqwest", "reqwest",
"self-replace", "self-replace",
"semver", "semver",
@@ -207,15 +207,15 @@ dependencies = [
[[package]] [[package]]
name = "console" name = "console"
version = "0.16.0" version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"lazy_static",
"libc", "libc",
"once_cell", "unicode-width 0.1.11",
"unicode-width", "windows-sys 0.45.0",
"windows-sys 0.60.2",
] ]
[[package]] [[package]]
@@ -304,32 +304,11 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "dirs"
version = "5.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
dependencies = [
"libc",
"option-ext",
"redox_users",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
version = "1.0.0" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
@@ -561,6 +540,15 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "home"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "http" name = "http"
version = "1.2.0" version = "1.2.0"
@@ -678,7 +666,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2 0.5.10", "socket2",
"system-configuration", "system-configuration",
"tokio", "tokio",
"tower-service", "tower-service",
@@ -731,28 +719,17 @@ dependencies = [
[[package]] [[package]]
name = "indicatif" name = "indicatif"
version = "0.18.0" version = "0.17.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
dependencies = [ dependencies = [
"console", "console",
"number_prefix",
"portable-atomic", "portable-atomic",
"unicode-width", "unicode-width 0.2.0",
"unit-prefix",
"web-time", "web-time",
] ]
[[package]]
name = "io-uring"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.9.0" version = "2.9.0"
@@ -819,16 +796,6 @@ version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.4.0",
"libc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@@ -959,6 +926,12 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "number_prefix"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.1" version = "0.32.1"
@@ -976,9 +949,9 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.74" version = "0.10.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd"
dependencies = [ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
"cfg-if", "cfg-if",
@@ -1017,9 +990,9 @@ dependencies = [
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.110" version = "0.9.106"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@@ -1028,12 +1001,6 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "2.10.1" version = "2.10.1"
@@ -1177,9 +1144,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.2" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [ dependencies = [
"rand_chacha 0.9.0", "rand_chacha 0.9.0",
"rand_core 0.9.2", "rand_core 0.9.2",
@@ -1233,22 +1200,11 @@ dependencies = [
"bitflags 2.4.0", "bitflags 2.4.0",
] ]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom 0.2.10",
"libredox",
"thiserror",
]
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.24" version = "0.12.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" checksum = "eabf4c97d9130e2bf606614eb937e86edac8292eaa6f422f995d7e8de1eb1813"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -1437,17 +1393,16 @@ dependencies = [
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.27" version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [ dependencies = [
"serde_core",
"serde_derive", "serde_derive",
] ]
@@ -1461,20 +1416,11 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.228" version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1483,15 +1429,14 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.145" version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
"ryu", "ryu",
"serde", "serde",
"serde_core",
] ]
[[package]] [[package]]
@@ -1572,9 +1517,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]] [[package]]
name = "simple-log" name = "simple-log"
version = "2.4.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8030713cfe4aa369ef3c8cbf5e391cc18d7f6fba1f2a68ec959c5504413e51" checksum = "b08896e123e7fc00305e1df04999a25679768122daf636ccc01c7ca5465163ea"
dependencies = [ dependencies = [
"log", "log",
"log4rs", "log4rs",
@@ -1608,16 +1553,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "static_vcruntime" name = "static_vcruntime"
version = "2.0.0" version = "2.0.0"
@@ -1626,12 +1561,12 @@ checksum = "954e3e877803def9dc46075bf4060147c55cd70db97873077232eae0269dc89b"
[[package]] [[package]]
name = "steamlocate" name = "steamlocate"
version = "2.0.0-beta.2" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b6a4810c4e7fecb0123a9a8ba99b335c17d92e636c265ef99108ee4734c812" checksum = "a13160bc6ea5cd80cde195ad4a4c629701db2bf397b62c139aa9e739016d2499"
dependencies = [ dependencies = [
"crc", "crc",
"dirs", "home",
"keyvalues-parser", "keyvalues-parser",
"keyvalues-serde", "keyvalues-serde",
"serde", "serde",
@@ -1765,20 +1700,18 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.47.1" version = "1.45.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
"io-uring",
"libc", "libc",
"mio", "mio",
"pin-project-lite", "pin-project-lite",
"slab", "socket2",
"socket2 0.6.0",
"tokio-macros", "tokio-macros",
"windows-sys 0.59.0", "windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -1963,18 +1896,18 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
[[package]]
name = "unit-prefix"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817"
[[package]] [[package]]
name = "unsafe-libyaml" name = "unsafe-libyaml"
version = "0.2.11" version = "0.2.11"
@@ -2231,6 +2164,15 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@@ -2259,12 +2201,18 @@ dependencies = [
] ]
[[package]] [[package]]
name = "windows-sys" name = "windows-targets"
version = "0.60.2" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [ dependencies = [
"windows-targets 0.53.2", "windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
] ]
[[package]] [[package]]
@@ -2291,7 +2239,7 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6", "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm 0.52.6",
@@ -2299,20 +2247,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows_aarch64_gnullvm"
version = "0.53.2" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
@@ -2327,10 +2265,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_msvc"
version = "0.53.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@@ -2345,10 +2283,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_i686_gnu"
version = "0.53.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@@ -2362,12 +2300,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
@@ -2375,10 +2307,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_msvc"
version = "0.53.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@@ -2393,10 +2325,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_x86_64_gnu"
version = "0.53.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@@ -2411,10 +2343,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnullvm"
version = "0.53.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@@ -2429,10 +2361,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_msvc"
version = "0.53.0" version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@@ -2446,12 +2378,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.20" version = "0.6.20"
@@ -2472,19 +2398,19 @@ dependencies = [
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.51.0" version = "0.55.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "937f3df7948156640f46aacef17a70db0de5917bda9c92b0f751f3a955b588fc" checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "winresource" name = "winresource"
version = "0.1.23" version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edcacf11b6f48dd21b9ba002f991bdd5de29b2da8cc2800412f4b80f677e4957" checksum = "7276691b353ad4547af8c3268488d1311f4be791ffdc0c65b8cfa8f41eed693b"
dependencies = [ dependencies = [
"toml", "toml",
"version_check", "version_check",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "alterware-launcher" name = "alterware-launcher"
version = "0.11.5" version = "0.11.1"
edition = "2021" edition = "2021"
build = "res/build.rs" build = "res/build.rs"
@@ -21,9 +21,9 @@ once_cell = "1.21"
reqwest = { version = "0.12", features = ["stream"] } reqwest = { version = "0.12", features = ["stream"] }
futures-util = "0.3" futures-util = "0.3"
futures = "0.3" futures = "0.3"
indicatif = "0.18" indicatif = "0.17"
tokio = { version="1.47", features = ["rt-multi-thread", "macros"] } tokio = { version="1.45", features = ["rt-multi-thread", "macros"] }
simple-log = "2.4" simple-log = "2.3"
walkdir = "2.5" walkdir = "2.5"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
@@ -31,7 +31,7 @@ openssl = { version = "0.10", default-features = false, features = ["vendored"]
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
# todo: update to 2.0.x; needs testing on windows # todo: update to 2.0.x; needs testing on windows
steamlocate = "=2.0.0-beta.2" steamlocate = "=2.0.1"
mslnk = "0.1" mslnk = "0.1"
self-replace = "1.5" self-replace = "1.5"

View File

@@ -146,8 +146,11 @@
- Install or reinstall redistributables - Install or reinstall redistributables
- ```--prerelease``` - ```--prerelease```
- Update to prerelease version of the launcher - Update to prerelease version of the launcher
- ```--rate```
- Rate and display CDN servers
- ```--cdn-url``` - ```--cdn-url```
- ```--offline``` - ```--offline```
- ```--skip-connectivity-check```
##### Example: ##### Example:
@@ -172,6 +175,7 @@ alterware-launcher.exe iw6 --bonus -u --path "C:\Games\IW6x" --pass "-headless"
- `prerelease`: Update to prerelease version of clients and launcher. Default: `false`. - `prerelease`: Update to prerelease version of clients and launcher. Default: `false`.
- `cdn_url` - `cdn_url`
- `offline` - `offline`
- `skip-connectivity-check`
--- ---

View File

@@ -15,7 +15,7 @@ pub fn save_cache(dir: &Path, cache: Cache) {
let cache_path = dir.join("awcache.json"); let cache_path = dir.join("awcache.json");
let cache_serialized = serde_json::to_string_pretty(&cache).unwrap(); let cache_serialized = serde_json::to_string_pretty(&cache).unwrap();
fs::write(cache_path, cache_serialized).unwrap_or_else(|e| { fs::write(cache_path, cache_serialized).unwrap_or_else(|e| {
error!("Failed to save cache: {e}"); error!("Failed to save cache: {}", e);
}); });
} }

View File

@@ -6,19 +6,93 @@ use std::time::Duration;
static CURRENT_CDN: Mutex<Option<Arc<Server>>> = Mutex::new(None); static CURRENT_CDN: Mutex<Option<Arc<Server>>> = Mutex::new(None);
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Region {
Africa,
Asia,
Europe,
NorthAmerica,
Oceania,
SouthAmerica,
Global,
Unknown,
}
impl Region {
pub fn from_str(region_str: &str) -> Self {
match region_str {
"Africa" => Region::Africa,
"Asia" => Region::Asia,
"Europe" => Region::Europe,
"NorthAmerica" => Region::NorthAmerica,
"Oceania" => Region::Oceania,
"SouthAmerica" => Region::SouthAmerica,
_ => Region::Unknown,
}
}
pub fn coordinates(&self) -> Option<(f64, f64)> {
match self {
Region::Europe => Some((54.0, 15.0)),
Region::Asia => Some((35.0, 105.0)),
Region::NorthAmerica => Some((45.0, -100.0)),
Region::SouthAmerica => Some((-15.0, -60.0)),
Region::Africa => Some((0.0, 20.0)),
Region::Oceania => Some((-25.0, 140.0)),
Region::Global => None,
Region::Unknown => None,
}
}
pub fn distance_to(&self, other: Region) -> f64 {
if *self == Region::Global || other == Region::Global {
return 0.0;
}
if *self == other {
return 0.0;
}
let (lat1, lon1) = match self.coordinates() {
Some(coords) => coords,
None => return 20000.0,
};
let (lat2, lon2) = match other.coordinates() {
Some(coords) => coords,
None => return 20000.0,
};
// haversine
let r = 6371.0;
let d_lat = (lat2 - lat1).to_radians();
let d_lon = (lon2 - lon1).to_radians();
let lat1_rad = lat1.to_radians();
let lat2_rad = lat2.to_radians();
let a = (d_lat / 2.0).sin().powi(2)
+ lat1_rad.cos() * lat2_rad.cos() * (d_lon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
r * c
}
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct Server { pub struct Server {
pub host: &'static str, pub host: &'static str,
pub rating: u8, pub rating: u8,
pub latency: Option<std::time::Duration>, pub latency: Option<std::time::Duration>,
pub region: Region,
} }
impl Server { impl Server {
pub const fn new(host: &'static str) -> Self { pub const fn new(host: &'static str, region: Region) -> Self {
Server { Server {
host, host,
rating: 255, rating: 255,
latency: None, latency: None,
region,
} }
} }
@@ -26,7 +100,7 @@ impl Server {
format!("https://{}/", self.host) format!("https://{}/", self.host)
} }
async fn rate(&mut self, asn: u32, is_initial: bool) { async fn rate(&mut self, asn: u32, user_region: Region, is_initial: bool) {
let timeout = if is_initial { let timeout = if is_initial {
Duration::from_millis(1000) Duration::from_millis(1000)
} else { } else {
@@ -35,15 +109,16 @@ impl Server {
match http::rating_request(&self.url(), timeout).await { match http::rating_request(&self.url(), timeout).await {
Ok((latency, is_cloudflare)) => { Ok((latency, is_cloudflare)) => {
self.latency = Some(latency); self.latency = Some(latency);
self.rating = self.calculate_rating(latency, is_cloudflare, asn); self.rating = self.calculate_rating(latency, is_cloudflare, asn, user_region);
info!( info!(
"Server {} rated {} ({}ms, rating: {}, cloudflare: {})", "Server {} rated {} ({}ms, rating: {}, cloudflare: {}, region: {:?})",
self.host, self.host,
self.rating, self.rating,
latency.as_millis(), latency.as_millis(),
self.rating, self.rating,
is_cloudflare, is_cloudflare,
self.region
); );
} }
Err(e) => { Err(e) => {
@@ -72,7 +147,13 @@ impl Server {
rating.clamp(1.0, 255.0) as u8 rating.clamp(1.0, 255.0) as u8
} }
fn calculate_rating(&self, latency: std::time::Duration, is_cloudflare: bool, asn: u32) -> u8 { fn calculate_rating(
&self,
latency: std::time::Duration,
is_cloudflare: bool,
asn: u32,
user_region: Region,
) -> u8 {
let mut rating = self.rate_latency(latency); let mut rating = self.rate_latency(latency);
// Additional factors for full rating // Additional factors for full rating
@@ -84,6 +165,25 @@ impl Server {
} }
} }
let distance_km = user_region.distance_to(self.region);
let region_multiplier = if self.region == Region::Global {
1.4
} else if distance_km == 0.0 {
1.3
} else if user_region == Region::Unknown {
1.0
} else if distance_km <= 2000.0 {
1.25
} else if distance_km <= 5000.0 {
1.15
} else if distance_km <= 10000.0 {
1.05
} else {
1.0
};
rating = (rating as f32 * region_multiplier).min(255.0) as u8;
rating rating
} }
} }
@@ -98,13 +198,23 @@ impl Hosts {
pub async fn new() -> Self { pub async fn new() -> Self {
let cdn_hosts = crate::global::CDN_HOSTS.to_vec(); let cdn_hosts = crate::global::CDN_HOSTS.to_vec();
let hosts = Hosts { let mut hosts = Hosts {
servers: cdn_hosts, servers: cdn_hosts,
active_index: RwLock::new(None), active_index: RwLock::new(None),
}; };
let (asn, region_str) = crate::http::get_location_info().await;
let user_region = Region::from_str(&region_str);
info!(
"Detected user region as {:?} (region: {})",
user_region, region_str
);
hosts.rate(asn, user_region, true).await;
if hosts.servers.iter().all(|server| server.rating == 0) { if hosts.servers.iter().all(|server| server.rating == 0) {
info!("All CDN servers failed with 1000ms timeout, retrying with 5000ms timeout"); info!("All CDN servers failed with 1000ms timeout, retrying with 5000ms timeout");
hosts.rate(asn, user_region, false).await;
} }
hosts hosts
@@ -140,11 +250,11 @@ impl Hosts {
} }
/// rate and order all servers, then select the best one /// rate and order all servers, then select the best one
pub async fn rate(&mut self, asn: u32, is_initial: bool) { pub async fn rate(&mut self, asn: u32, user_region: Region, is_initial: bool) {
let rating_futures: Vec<_> = self let rating_futures: Vec<_> = self
.servers .servers
.iter_mut() .iter_mut()
.map(|server| server.rate(asn, is_initial)) .map(|server| server.rate(asn, user_region, is_initial))
.collect(); .collect();
join_all(rating_futures).await; join_all(rating_futures).await;
@@ -160,3 +270,61 @@ impl Hosts {
self.active_url() self.active_url()
} }
} }
/// CDN rating function for --rate flag
pub async fn rate_cdns_and_display() {
use colored::Colorize;
let (asn, region_str) = crate::http::get_location_info().await;
let user_region = Region::from_str(&region_str);
if user_region == Region::Unknown {
println!(
"User region: {} (using Global server preference)",
"Unknown".bright_red()
);
} else {
println!("User region: {:?}", user_region);
}
println!("Rating CDNs...");
let cdn_hosts = crate::global::CDN_HOSTS.to_vec();
let mut hosts = Hosts {
servers: cdn_hosts,
active_index: RwLock::new(None),
};
hosts.rate(asn, user_region, true).await;
if hosts.servers.iter().all(|server| server.rating == 0) {
println!("Retrying with longer timeout...");
hosts.rate(asn, user_region, false).await;
}
println!();
for server in hosts.servers.iter() {
let latency_str = server
.latency
.map_or("timeout".to_string(), |l| format!("{} ms", l.as_millis()));
println!(
"{}: rating {}, latency {}",
server.host.bright_white(),
server.rating.to_string().bright_cyan(),
latency_str
);
}
// Show selected CDN
if hosts.next() {
if let Some(best_url) = hosts.active_url() {
println!();
println!("Selected: {}", best_url.bright_green());
}
} else {
println!();
println!("{}", "No available CDN servers".bright_red());
}
}

View File

@@ -17,13 +17,17 @@ pub async fn latest_tag_full(
repo: &str, repo: &str,
) -> Result<String, Box<dyn std::error::Error>> { ) -> Result<String, Box<dyn std::error::Error>> {
let github_body = crate::http_async::get_body_string( let github_body = crate::http_async::get_body_string(
format!("https://api.github.com/repos/{owner}/{repo}/releases/latest").as_str(), format!(
"https://api.github.com/repos/{}/{}/releases/latest",
owner, repo
)
.as_str(),
) )
.await .await
.map_err(|e| format!("Failed to fetch GitHub API: {e}"))?; .map_err(|e| format!("Failed to fetch GitHub API: {}", e))?;
let github_json: serde_json::Value = serde_json::from_str(&github_body) let github_json: serde_json::Value = serde_json::from_str(&github_body)
.map_err(|e| format!("Failed to parse GitHub API response: {e}"))?; .map_err(|e| format!("Failed to parse GitHub API response: {}", e))?;
let tag_name = github_json let tag_name = github_json
.get("tag_name") .get("tag_name")
@@ -39,13 +43,13 @@ pub async fn latest_tag_prerelease(
repo: &str, repo: &str,
) -> Result<String, Box<dyn std::error::Error>> { ) -> Result<String, Box<dyn std::error::Error>> {
let github_body = crate::http_async::get_body_string( let github_body = crate::http_async::get_body_string(
format!("https://api.github.com/repos/{owner}/{repo}/releases").as_str(), format!("https://api.github.com/repos/{}/{}/releases", owner, repo).as_str(),
) )
.await .await
.map_err(|e| format!("Failed to fetch GitHub API: {e}"))?; .map_err(|e| format!("Failed to fetch GitHub API: {}", e))?;
let github_json: serde_json::Value = serde_json::from_str(&github_body) let github_json: serde_json::Value = serde_json::from_str(&github_body)
.map_err(|e| format!("Failed to parse GitHub API response: {e}"))?; .map_err(|e| format!("Failed to parse GitHub API response: {}", e))?;
let latest_release = github_json.get(0).ok_or("No releases found")?; let latest_release = github_json.get(0).ok_or("No releases found")?;
@@ -66,7 +70,7 @@ pub async fn latest_version(
let tag = latest_tag(owner, repo, prerelease).await?; let tag = latest_tag(owner, repo, prerelease).await?;
let cleaned_tag = tag.replace('v', ""); let cleaned_tag = tag.replace('v', "");
Version::parse(&cleaned_tag) Version::parse(&cleaned_tag)
.map_err(|e| format!("Failed to parse version '{cleaned_tag}': {e}").into()) .map_err(|e| format!("Failed to parse version '{}': {}", cleaned_tag, e).into())
} }
pub fn download_url(owner: &str, repo: &str, tag: Option<&str>) -> String { pub fn download_url(owner: &str, repo: &str, tag: Option<&str>) -> String {

View File

@@ -6,13 +6,20 @@ use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Mutex; use std::sync::Mutex;
use crate::cdn::{Hosts, Server}; use crate::cdn::{Hosts, Region, Server};
pub const GH_OWNER: &str = "alterware"; pub const GH_OWNER: &str = "alterware";
pub const GH_REPO: &str = "alterware-launcher"; pub const GH_REPO: &str = "alterware-launcher";
pub const GH_IW4X_OWNER: &str = "iw4x";
pub const GH_IW4X_REPO: &str = "iw4x-client";
pub const DEFAULT_MASTER: &str = "https://cdn.alterware.ovh"; pub const DEFAULT_MASTER: &str = "https://cdn.alterware.ovh";
pub const CDN_HOSTS: [Server; 1] = [Server::new("cdn.alterware.ovh")]; pub const CDN_HOSTS: [Server; 2] = [
Server::new("cdn.alterware.ovh", Region::Global),
Server::new("us-cdn.alterware.ovh", Region::NorthAmerica),
];
pub const IP2ASN: &str = "https://ip2asn.getserve.rs/v1/as/ip/self";
pub static USER_AGENT: Lazy<String> = Lazy::new(|| { pub static USER_AGENT: Lazy<String> = Lazy::new(|| {
format!( format!(
@@ -26,6 +33,8 @@ pub static USER_AGENT: Lazy<String> = Lazy::new(|| {
pub static MASTER_URL: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::from(DEFAULT_MASTER))); pub static MASTER_URL: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::from(DEFAULT_MASTER)));
pub static IS_OFFLINE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
pub static PREFIXES: Lazy<HashMap<&'static str, PrintPrefix>> = Lazy::new(|| { pub static PREFIXES: Lazy<HashMap<&'static str, PrintPrefix>> = Lazy::new(|| {
HashMap::from([ HashMap::from([
( (
@@ -72,3 +81,54 @@ pub static PREFIXES: Lazy<HashMap<&'static str, PrintPrefix>> = Lazy::new(|| {
), ),
]) ])
}); });
pub async fn check_connectivity_and_rate_cdns() -> Pin<Box<dyn Future<Output = bool> + Send>> {
Box::pin(async move {
crate::println_info!("Initializing CDN rating system...");
let hosts = Hosts::new().await;
let best_cdn = hosts.get_master_url();
if let Some(cdn_url) = best_cdn {
let cdn_url = cdn_url.trim_end_matches('/');
*MASTER_URL.lock().unwrap() = cdn_url.to_string();
crate::println_info!("Selected CDN: {}", cdn_url);
match crate::http_async::get_body_string(cdn_url).await {
Ok(_) => {
info!("Successfully connected to CDN: {}", cdn_url);
true
}
Err(e) => {
error!("Failed to connect to selected CDN {}: {}", cdn_url, e);
*IS_OFFLINE.lock().unwrap() = true;
false
}
}
} else {
crate::println_error!("No CDN hosts are available");
*IS_OFFLINE.lock().unwrap() = true;
false
}
})
}
pub fn check_connectivity(
master_url: Option<String>,
) -> Pin<Box<dyn Future<Output = bool> + Send>> {
Box::pin(async move {
if let Some(url) = master_url {
*MASTER_URL.lock().unwrap() = url.clone();
crate::println_info!("Using fallback connectivity check on {}", url);
match crate::http_async::get_body_string(&url).await {
Ok(_) => true,
Err(_) => {
*IS_OFFLINE.lock().unwrap() = true;
false
}
}
} else {
check_connectivity_and_rate_cdns().await.await
}
})
}

View File

@@ -1,4 +1,5 @@
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
use serde_json::Value;
use simple_log::*; use simple_log::*;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -16,18 +17,18 @@ pub async fn quick_request(url: &str) -> Result<String, Box<dyn std::error::Erro
.await; .await;
if let Err(e) = &res { if let Err(e) = &res {
error!("Failed to get {url}: {e}"); error!("Failed to get {}: {}", url, e);
return Err(format!("Failed to get {url} {e}").into()); return Err(format!("Failed to get {} {}", url, e).into());
} }
let res = res.unwrap(); let res = res.unwrap();
match res.text().await { match res.text().await {
Ok(text) => { Ok(text) => {
info!("Successfully received response from: {url}"); info!("Successfully received response from: {}", url);
Ok(text) Ok(text)
} }
Err(e) => { Err(e) => {
warn!("Failed to get response text from {url}: {e}"); warn!("Failed to get response text from {}: {}", url, e);
Err(e.into()) Err(e.into())
} }
} }
@@ -63,8 +64,8 @@ pub async fn rating_request(
let latency = start.elapsed(); let latency = start.elapsed();
if let Err(e) = &res { if let Err(e) = &res {
error!("Failed to get {url}: {e} (after {latency:?})"); error!("Failed to get {}: {} (after {:?})", url, e, latency);
return Err(format!("Failed to get {url} {e} (after {latency:?})").into()); return Err(format!("Failed to get {} {} (after {:?})", url, e, latency).into());
} }
let res = res.unwrap(); let res = res.unwrap();
@@ -73,10 +74,36 @@ pub async fn rating_request(
// We don't need the response body for rating // We don't need the response body for rating
if let Err(e) = res.text().await { if let Err(e) = res.text().await {
warn!("Failed to get response text from {url}: {e} (after {latency:?})"); warn!(
"Failed to get response text from {}: {} (after {:?})",
url, e, latency
);
return Err(e.into()); return Err(e.into());
} }
info!("Successfully rated {url} in {latency:?} (cloudflare: {is_cloudflare})"); info!(
"Successfully rated {} in {:?} (cloudflare: {})",
url, latency, is_cloudflare
);
Ok((latency, is_cloudflare)) Ok((latency, is_cloudflare))
} }
/// Retrieve client ASN and region
pub async fn get_location_info() -> (u32, String) {
let response = quick_request(crate::global::IP2ASN).await;
if let Ok(as_data_str) = response {
if let Ok(as_data) = serde_json::from_str::<Value>(&as_data_str) {
let as_number = as_data
.get("as_number")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u32;
let region = as_data
.get("region")
.and_then(|v| v.as_str())
.unwrap_or("Unknown")
.to_string();
return (as_number, region);
}
}
(0, "Unknown".to_string())
}

View File

@@ -32,7 +32,7 @@ pub async fn download_file_progress(
.send() .send()
.await .await
.map_err(|e| { .map_err(|e| {
error!("Failed to GET from '{url}': {e}"); error!("Failed to GET from '{}': {}", url, e);
format!("Failed to GET from '{url}'") format!("Failed to GET from '{url}'")
})?; })?;
@@ -89,14 +89,14 @@ pub async fn get_body(url: &str) -> Result<Vec<u8>, String> {
) )
.send() .send()
.await .await
.map_err(|e| format!("Failed to send request: {e}"))?; .map_err(|e| format!("Failed to send request: {}", e))?;
debug!("{} {url}", res.status()); debug!("{} {url}", res.status());
res.bytes() res.bytes()
.await .await
.map(|b| b.to_vec()) .map(|b| b.to_vec())
.map_err(|e| format!("Failed to get body: {e}")) .map_err(|e| format!("Failed to get body: {}", e))
} }
pub async fn get_body_string(url: &str) -> Result<String, String> { pub async fn get_body_string(url: &str) -> Result<String, String> {
@@ -119,7 +119,7 @@ pub async fn get_json<T: serde::de::DeserializeOwned>(url: &str) -> Result<T, St
.header("Accept", "application/json") .header("Accept", "application/json")
.send() .send()
.await .await
.map_err(|e| format!("Failed to send request: {e}"))?; .map_err(|e| format!("Failed to send request: {}", e))?;
debug!("{} {}", res.status(), url); debug!("{} {}", res.status(), url);
@@ -130,7 +130,7 @@ pub async fn get_json<T: serde::de::DeserializeOwned>(url: &str) -> Result<T, St
let body = res let body = res
.bytes() .bytes()
.await .await
.map_err(|e| format!("Failed to read response body: {e}"))?; .map_err(|e| format!("Failed to read response body: {}", e))?;
serde_json::from_slice::<T>(&body).map_err(|e| format!("Failed to parse JSON: {e}")) serde_json::from_slice::<T>(&body).map_err(|e| format!("Failed to parse JSON: {}", e))
} }

46
src/iw4x.rs Normal file
View File

@@ -0,0 +1,46 @@
use crate::extend::*;
use crate::github;
use crate::global::*;
use crate::http_async;
use crate::misc;
use crate::structs;
use std::path::Path;
pub async fn remote_revision(prerelease: Option<bool>) -> u16 {
match github::latest_tag(GH_IW4X_OWNER, GH_IW4X_REPO, prerelease).await {
Ok(tag) => misc::rev_to_int(&tag),
Err(_) => {
crate::println_error!("Failed to get latest version for {GH_IW4X_OWNER}/{GH_IW4X_REPO}, assuming we are up to date.");
0
}
}
}
pub async fn update(dir: &Path, cache: &mut structs::Cache, prerelease: Option<bool>) {
let remote = remote_revision(prerelease).await;
let local = misc::rev_to_int(&cache.iw4x_revision);
if remote <= local && dir.join("iw4x.dll").exists() {
crate::println_info!("No files to download for IW4x");
return;
}
crate::println_info!("Downloading outdated or missing files for IW4x",);
println!(
"{}{}",
misc::prefix("downloading"),
dir.join("iw4x.dll").cute_path()
);
http_async::download_file(
&format!(
"{}/iw4x.dll",
github::download_url(GH_IW4X_OWNER, GH_IW4X_REPO, Some(&format!("r{remote}")))
),
&dir.join("iw4x.dll"),
)
.await
.unwrap();
cache.iw4x_revision = format!("r{remote}");
}

View File

@@ -6,6 +6,7 @@ mod github;
mod global; mod global;
mod http; mod http;
mod http_async; mod http_async;
mod iw4x;
mod misc; mod misc;
mod self_update; mod self_update;
mod structs; mod structs;
@@ -27,7 +28,7 @@ use mslnk::ShellLink;
use simple_log::LogConfigBuilder; use simple_log::LogConfigBuilder;
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::HashMap, collections::{HashMap, HashSet},
env, fs, env, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@@ -116,7 +117,7 @@ fn setup_desktop_links(path: &Path, game: &Game) {
async fn auto_install(path: &Path, game: &Game<'_>) { async 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, false, false, None).await; update(game, path, false, false, None, None, None).await;
} }
#[cfg(windows)] #[cfg(windows)]
@@ -186,7 +187,7 @@ fn total_download_size(cdn_info: &Vec<CdnFile>, remote_dir: &str) -> u64 {
let remote_dir = format!("{remote_dir}/"); let remote_dir = format!("{remote_dir}/");
let mut size: u64 = 0; let mut size: u64 = 0;
for file in cdn_info { for file in cdn_info {
if !file.name.starts_with(&remote_dir) { if !file.name.starts_with(&remote_dir) || file.name == "iw4/iw4x.dll" {
continue; continue;
} }
size += file.size as u64; size += file.size as u64;
@@ -200,6 +201,7 @@ async fn update_dir(
dir: &Path, dir: &Path,
hashes: &mut HashMap<String, String>, hashes: &mut HashMap<String, String>,
pb: &ProgressBar, pb: &ProgressBar,
skip_iw4x_sp: bool,
) { ) {
misc::pb_style_download(pb, false); misc::pb_style_download(pb, false);
@@ -208,7 +210,10 @@ async fn update_dir(
let mut files_to_download: Vec<CdnFile> = vec![]; let mut files_to_download: Vec<CdnFile> = vec![];
for file in cdn_info { for file in cdn_info {
if !file.name.starts_with(&remote_dir_pre) { if !file.name.starts_with(&remote_dir_pre) || file.name == "iw4/iw4x.dll" {
continue;
}
if skip_iw4x_sp && file.name == "iw4/iw4x-sp.exe" {
continue; continue;
} }
@@ -332,12 +337,15 @@ async fn update(
dir: &Path, dir: &Path,
bonus_content: bool, bonus_content: bool,
force: bool, force: bool,
skip_iw4x_sp: Option<bool>,
ignore_required_files: Option<bool>, ignore_required_files: Option<bool>,
prerelease: Option<bool>,
) { ) {
info!("Starting update for game engine: {}", game.engine); info!("Starting update for game engine: {}", game.engine);
info!("Update path: {}", dir.display()); info!("Update path: {}", dir.display());
debug!("Bonus content: {}, Force update: {}", bonus_content, force); debug!("Bonus content: {}, Force update: {}", bonus_content, force);
let skip_iw4x_sp = skip_iw4x_sp.unwrap_or(false);
let ignore_required_files = ignore_required_files.unwrap_or(false); let ignore_required_files = ignore_required_files.unwrap_or(false);
let res = let res =
@@ -397,12 +405,66 @@ async fn update(
} }
} }
if game.engine == "iw4" {
iw4x::update(dir, &mut cache, prerelease).await;
let scan_dirs = ["iw4x", "zone/patch"];
let valid_files: HashSet<_> = cdn_info
.iter()
.filter_map(|cdn_file| {
if cdn_file.name.starts_with("iw4/") || cdn_file.name.starts_with("iw4-dlc/") {
Some(Path::new(&cdn_file.name).to_path_buf())
} else {
None
}
})
.collect();
for scan_dir in scan_dirs.iter() {
let full_scan_dir = dir.join(scan_dir);
if !full_scan_dir.exists() || !full_scan_dir.is_dir() {
continue;
}
for entry in walkdir::WalkDir::new(&full_scan_dir)
.into_iter()
.filter_map(Result::ok)
.filter(|e| e.file_type().is_file())
{
let rel_path = entry.path().strip_prefix(dir).unwrap_or(entry.path());
let cdn_paths = [
Path::new("iw4").join(rel_path),
Path::new("iw4-dlc").join(rel_path),
];
if !cdn_paths.iter().any(|path| valid_files.contains(path)) {
crate::println_info!("{}{}", misc::prefix("removed"), entry.path().cute_path());
if std::fs::remove_file(entry.path()).is_err() {
crate::println_error!(
"{}Couldn't delete {}",
misc::prefix("error"),
entry.path().cute_path()
);
}
}
}
}
}
let pb = ProgressBar::new(0); let pb = ProgressBar::new(0);
update_dir(&cdn_info, game.engine, dir, &mut cache.hashes, &pb).await; update_dir(
&cdn_info,
game.engine,
dir,
&mut cache.hashes,
&pb,
skip_iw4x_sp,
)
.await;
if bonus_content && !game.bonus.is_empty() { if bonus_content && !game.bonus.is_empty() {
for bonus in game.bonus.iter() { for bonus in game.bonus.iter() {
update_dir(&cdn_info, bonus, dir, &mut cache.hashes, &pb).await; update_dir(&cdn_info, bonus, dir, &mut cache.hashes, &pb, skip_iw4x_sp).await;
} }
} }
@@ -487,18 +549,9 @@ fn launch(file_path: &PathBuf, args: &str) {
fn launch(file_path: &PathBuf, args: &str) { fn launch(file_path: &PathBuf, args: &str) {
println!("\n\nJoin the AlterWare Discord server:\nhttps://discord.gg/2ETE8engZM\n\n"); println!("\n\nJoin the AlterWare Discord server:\nhttps://discord.gg/2ETE8engZM\n\n");
crate::println_info!("Launching {} {args}", file_path.display()); crate::println_info!("Launching {} {args}", file_path.display());
let exit_status = if misc::is_program_in_path("wine") {
let launcher = if misc::is_program_in_path("umu-run") { println!("Found wine, launching game using wine.\nIf you run into issues or want to launch a different way, run {} manually.", file_path.display());
Some("umu-run") std::process::Command::new("wine")
} else if misc::is_program_in_path("wine") {
Some("wine")
} else {
None
};
let exit_status = if let Some(launcher) = launcher {
println!("Found {launcher}, launching game using {launcher}.\nIf you run into issues or want to launch a different way, run {} manually.", file_path.display());
std::process::Command::new(launcher)
.args([file_path.to_str().unwrap(), args.trim()]) .args([file_path.to_str().unwrap(), args.trim()])
.current_dir(file_path.parent().unwrap()) .current_dir(file_path.parent().unwrap())
.spawn() .spawn()
@@ -574,7 +627,7 @@ fn show_iw4x_info() {
"{}", "{}",
"IW4x is not provided through AlterWare anymore.".bright_red() "IW4x is not provided through AlterWare anymore.".bright_red()
); );
println!("Please visit https://aka.alterware.dev/iw4x for more information"); println!("Please use iw4x-launcher.exe instead or visit www.iw4x.dev/install");
misc::stdin(); misc::stdin();
std::process::exit(0); std::process::exit(0);
} }
@@ -626,6 +679,8 @@ async fn main() {
println!(" --redist: (Re-)Install redistributables"); println!(" --redist: (Re-)Install redistributables");
println!(" --prerelease: Update to prerelease version of clients and launcher"); println!(" --prerelease: Update to prerelease version of clients and launcher");
println!(" --offline: Run in offline mode"); println!(" --offline: Run in offline mode");
println!(" --skip-connectivity-check: Don't check connectivity");
println!(" --rate: Display CDN rating information and exit");
println!("\nExample:\n alterware-launcher.exe iw6 --pass \"-headless\""); println!("\nExample:\n alterware-launcher.exe iw6 --pass \"-headless\"");
return; return;
} }
@@ -661,6 +716,11 @@ async fn main() {
install_path = env::current_dir().unwrap(); install_path = env::current_dir().unwrap();
} }
if arg_bool(&args, "--rate") {
cdn::rate_cdns_and_display().await;
return;
}
let mut cfg = config::load(install_path.join("alterware-launcher.json")); let mut cfg = config::load(install_path.join("alterware-launcher.json"));
if let Some(cdn_url) = arg_value(&args, "--cdn-url") { if let Some(cdn_url) = arg_value(&args, "--cdn-url") {
@@ -673,6 +733,11 @@ async fn main() {
arg_remove(&mut args, "--offline"); arg_remove(&mut args, "--offline");
} }
if arg_bool(&args, "--skip-connectivity-check") {
cfg.skip_connectivity_check = true;
arg_remove(&mut args, "--skip-connectivity-check");
}
let initial_cdn = if !cfg.cdn_url.is_empty() { let initial_cdn = if !cfg.cdn_url.is_empty() {
info!("Using custom CDN URL: {}", cfg.cdn_url); info!("Using custom CDN URL: {}", cfg.cdn_url);
Some(cfg.cdn_url.clone()) Some(cfg.cdn_url.clone())
@@ -680,6 +745,14 @@ async fn main() {
None None
}; };
if !cfg.offline && !cfg.skip_connectivity_check {
if initial_cdn.is_some() {
cfg.offline = !global::check_connectivity(initial_cdn).await;
} else {
cfg.offline = !global::check_connectivity_and_rate_cdns().await.await;
}
}
if cfg.offline { if cfg.offline {
// Check if this is a first-time run (no stored data) // Check if this is a first-time run (no stored data)
let stored_data = cache::get_stored_data(); let stored_data = cache::get_stored_data();
@@ -743,9 +816,9 @@ async fn main() {
std::process::exit(1); std::process::exit(1);
}; };
info!("Launching game in offline mode with client: {client}"); info!("Launching game in offline mode with client: {}", client);
// Launch game without updates // Launch game without updates
launch(&install_path.join(format!("{client}.exe")), &cfg.args); launch(&install_path.join(format!("{}.exe", client)), &cfg.args);
return; return;
} }
@@ -871,6 +944,14 @@ async fn main() {
"engine", "engine",
cfg.engine.clone(), cfg.engine.clone(),
); );
if cfg.engine == "iw4" && cfg.args.is_empty() {
cfg.args = String::from("-stdout");
config::save_value_s(
install_path.join("alterware-launcher.json"),
"args",
cfg.args.clone(),
);
}
#[cfg(windows)] #[cfg(windows)]
if !cfg.skip_redist { if !cfg.skip_redist {
@@ -899,7 +980,9 @@ async fn main() {
install_path.as_path(), install_path.as_path(),
cfg.download_bonus_content, cfg.download_bonus_content,
cfg.force_update, cfg.force_update,
Some(&game != "iw4x-sp"),
Some(ignore_required_files), Some(ignore_required_files),
Some(cfg.prerelease),
) )
.await; .await;
if !cfg.update_only { if !cfg.update_only {

View File

@@ -12,6 +12,13 @@ pub fn stdin() -> String {
input.trim().to_string() 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 human_readable_bytes(bytes: u64) -> String { pub fn human_readable_bytes(bytes: u64) -> String {
let mut bytes = bytes as f64; let mut bytes = bytes as f64;
let mut i = 0; let mut i = 0;
@@ -41,7 +48,7 @@ pub fn is_program_in_path(program: &str) -> bool {
paths.to_str().map(|paths| { paths.to_str().map(|paths| {
paths paths
.split(':') .split(':')
.any(|dir| fs::metadata(format!("{dir}/{program}")).is_ok()) .any(|dir| fs::metadata(format!("{}/{}", dir, program)).is_ok())
}) })
}) })
.unwrap_or(false) .unwrap_or(false)
@@ -53,9 +60,9 @@ pub fn is_program_in_path(program: &str) -> bool {
.and_then(|paths| { .and_then(|paths| {
paths.to_str().map(|paths| { paths.to_str().map(|paths| {
paths.split(';').any(|dir| { paths.split(';').any(|dir| {
fs::metadata(format!("{dir}\\{program}.exe")).is_ok() fs::metadata(format!("{}\\{}.exe", dir, program)).is_ok()
|| fs::metadata(format!("{dir}\\{program}.cmd")).is_ok() || fs::metadata(format!("{}\\{}.cmd", dir, program)).is_ok()
|| fs::metadata(format!("{dir}\\{program}.bat")).is_ok() || fs::metadata(format!("{}\\{}.bat", dir, program)).is_ok()
}) })
}) })
}) })

View File

@@ -15,7 +15,7 @@ pub async fn self_update_available(prerelease: Option<bool>) -> bool {
let latest_version = match github::latest_version(GH_OWNER, GH_REPO, prerelease).await { let latest_version = match github::latest_version(GH_OWNER, GH_REPO, prerelease).await {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
error!("Failed to get latest version: {e}"); error!("Failed to get latest version: {}", e);
return false; return false;
} }
}; };

View File

@@ -54,6 +54,8 @@ pub struct Config {
pub cdn_url: String, pub cdn_url: String,
#[serde(default)] #[serde(default)]
pub offline: bool, pub offline: bool,
#[serde(default)]
pub skip_connectivity_check: bool,
} }
impl Default for Config { impl Default for Config {
@@ -71,6 +73,7 @@ impl Default for Config {
prerelease: false, prerelease: false,
cdn_url: String::default(), cdn_url: String::default(),
offline: false, offline: false,
skip_connectivity_check: false,
} }
} }
} }