forked from RabbyDevs/rooms-launcher
Update dependencies and refactor code for improved functionality and performance
- Added new dependencies including `aes`, `bzip2`, `zip`, and others in `Cargo.lock`. - Updated existing dependencies to their latest versions. - Refactored image handling in `video.rs` for better readability and performance. - Enhanced game installation logic in `installer.rs` to support new game types. - Introduced settings management in the application state. - Improved error handling and logging throughout the codebase. - Cleaned up unused code and optimized existing functions for better efficiency.
This commit is contained in:
parent
37a4172f3c
commit
0ae31d8627
11 changed files with 976 additions and 292 deletions
366
Cargo.lock
generated
366
Cargo.lock
generated
|
@ -33,6 +33,17 @@ version = "2.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
|
@ -102,6 +113,9 @@ name = "arbitrary"
|
|||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arg_enum_proc_macro"
|
||||
|
@ -188,9 +202,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "avif-serialize"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19135c0c7a60bfee564dbe44ab5ce0557c6bf3884e5291a50be76a15640c4fbd"
|
||||
checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
@ -264,6 +278,15 @@ version = "0.1.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.5.1"
|
||||
|
@ -317,6 +340,25 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
|
||||
dependencies = [
|
||||
"bzip2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bzip2-sys"
|
||||
version = "0.1.13+1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.13.0"
|
||||
|
@ -345,9 +387,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.27"
|
||||
version = "1.2.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
|
||||
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
|
@ -372,9 +414,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cfg-expr"
|
||||
version = "0.20.0"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e34e221e91c7eb5e8315b5c9cf1a61670938c0626451f954a51693ed44b37f45"
|
||||
checksum = "0d0390889d58f934f01cd49736275b4c2da15bcfc328c78ff2349907e6cabf22"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"target-lexicon 0.13.2",
|
||||
|
@ -392,6 +434,16 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[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 = "clipboard-win"
|
||||
version = "5.4.0"
|
||||
|
@ -466,6 +518,12 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
@ -563,6 +621,15 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
|
@ -615,6 +682,16 @@ dependencies = [
|
|||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor-lite"
|
||||
version = "0.1.0"
|
||||
|
@ -627,6 +704,43 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
|
@ -846,6 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-rs-sys",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
|
@ -1033,6 +1148,16 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.4.3"
|
||||
|
@ -1061,9 +1186,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1397,6 +1524,15 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
|
@ -1491,9 +1627,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb"
|
||||
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
|
@ -1862,6 +1998,15 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
|
@ -1873,6 +2018,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
|
@ -2011,9 +2167,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
|||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.9"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
|
||||
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
|
@ -2029,6 +2185,26 @@ dependencies = [
|
|||
"windows-targets 0.53.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0791ab7e08ccc8e0ce893f6906eb2703ed8739d8e89b57c0714e71bad09024c8"
|
||||
dependencies = [
|
||||
"liblzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "liblzma-sys"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
|
@ -2046,6 +2222,15 @@ dependencies = [
|
|||
"redox_syscall 0.5.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-rs-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "172a788537a2221661b480fee8dc5f96c580eb34fa88764d3205dc356c7e4221"
|
||||
dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lilt"
|
||||
version = "0.8.1"
|
||||
|
@ -2320,6 +2505,12 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
|
@ -2724,6 +2915,16 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -2805,6 +3006,12 @@ dependencies = [
|
|||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
|
@ -3104,9 +3311,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
version = "0.8.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
checksum = "a457e416a0f90d246a4c3288bd7a25b2304ca727f253f95be383dd17af56be8f"
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
|
@ -3136,6 +3343,7 @@ dependencies = [
|
|||
"strum 0.27.1",
|
||||
"strum_macros 0.27.1",
|
||||
"url",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3375,6 +3583,17 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
@ -3685,7 +3904,7 @@ version = "7.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb"
|
||||
dependencies = [
|
||||
"cfg-expr 0.20.0",
|
||||
"cfg-expr 0.20.1",
|
||||
"heck",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
|
@ -3777,6 +3996,25 @@ dependencies = [
|
|||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
|
@ -3843,15 +4081,17 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.45.1"
|
||||
version = "1.46.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
|
||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"socket2",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
@ -4023,6 +4263,12 @@ version = "0.25.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
|
@ -5118,6 +5364,20 @@ name = "zeroize"
|
|||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerotrie"
|
||||
|
@ -5152,6 +5412,78 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ab361742de920c5535880f89bbd611ee62002bf11341d16a5f057bb8ba6899"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"getrandom 0.3.3",
|
||||
"hmac",
|
||||
"indexmap",
|
||||
"liblzma",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"sha1",
|
||||
"time",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "626bd9fa9734751fc50d6060752170984d7053f5a39061f524cda68023d4db8a"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.15+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
|
@ -5169,9 +5501,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.18"
|
||||
version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa"
|
||||
checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
|
|
@ -18,6 +18,7 @@ reqwest = { version = "0.12.22", features = [
|
|||
# "blocking",
|
||||
"gzip",
|
||||
"json"] }
|
||||
zip = "4.2.0"
|
||||
|
||||
[profile.release]
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
|
|
|
@ -49,7 +49,7 @@ impl Frame {
|
|||
Self(gst::Sample::builder().build())
|
||||
}
|
||||
|
||||
pub fn readable(&self) -> Option<gst::BufferMap<gst::buffer::Readable>> {
|
||||
pub fn readable(&self) -> Option<gst::BufferMap<'_, gst::buffer::Readable>> {
|
||||
self.0.buffer().and_then(|x| x.map_readable().ok())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use reqwest::Client;
|
|||
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT_ENCODING};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ORGANIZATION, PRODUCT};
|
||||
use crate::{PossibleGames, ORGANIZATION, PRODUCT};
|
||||
|
||||
const KURO_BASE_URL: &str = "https://prod-alicdn-gamestarter.kurogame.com/launcher/launcher/50004_obOHXFrFanqsaIEOmuKroCcbZkQRBC7c/G153";
|
||||
|
||||
|
@ -76,11 +76,11 @@ pub async fn refresh_install() -> Result<(), String> {
|
|||
);
|
||||
|
||||
if let Err(e) = kuro_result {
|
||||
eprintln!("Error in Kuro install: {}", e);
|
||||
eprintln!("Error in Kuro install: {e}");
|
||||
}
|
||||
|
||||
if let Err(e) = hoyo_result {
|
||||
eprintln!("Error in Hoyo install: {}", e);
|
||||
eprintln!("Error in Hoyo install: {e}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -94,26 +94,26 @@ fn build_client() -> Result<Client, String> {
|
|||
.default_headers(headers)
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to build HTTP client: {}", e))
|
||||
.map_err(|e| format!("Failed to build HTTP client: {e}"))
|
||||
}
|
||||
|
||||
async fn refresh_kuro_install(proj_dirs: &ProjectDirs, client: &Client) -> Result<(), String> {
|
||||
let data_dir_buf = proj_dirs.data_dir().join("kuro/wuwa");
|
||||
let data_dir_buf = proj_dirs.data_dir().join("kurogames\\wuwa");
|
||||
let data_dir = data_dir_buf.as_path();
|
||||
|
||||
create_dir_all(data_dir)
|
||||
.map_err(|e| format!("Failed to create directory: {}", e))?;
|
||||
.map_err(|e| format!("Failed to create directory: {e}"))?;
|
||||
|
||||
let kuro_url = KURO_BASE_URL.to_string();
|
||||
let index_url = format!("{}{}", kuro_url, "/index.json");
|
||||
|
||||
eprintln!("Fetching index from: {}", index_url);
|
||||
eprintln!("Fetching index from: {index_url}");
|
||||
|
||||
let response = client
|
||||
.get(&index_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send HTTP request: {}", e))?;
|
||||
.map_err(|e| format!("Failed to send HTTP request: {e}"))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("HTTP request failed with status {}", response.status()));
|
||||
|
@ -125,10 +125,10 @@ async fn refresh_kuro_install(proj_dirs: &ProjectDirs, client: &Client) -> Resul
|
|||
.json()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
format!("Failed to parse JSON - {}", e)
|
||||
format!("Failed to parse JSON - {e}")
|
||||
})?;
|
||||
|
||||
eprintln!("Successfully parsed index.json: {:?}", version_index);
|
||||
eprintln!("Successfully parsed index.json: {version_index:?}");
|
||||
|
||||
let bg_info_url = format!("{}{}{}{}",
|
||||
"https://prod-alicdn-gamestarter.kurogame.com/launcher/50004_obOHXFrFanqsaIEOmuKroCcbZkQRBC7c/G153",
|
||||
|
@ -137,13 +137,13 @@ async fn refresh_kuro_install(proj_dirs: &ProjectDirs, client: &Client) -> Resul
|
|||
"/en.json"
|
||||
);
|
||||
|
||||
eprintln!("Fetching background info from: {}", bg_info_url);
|
||||
eprintln!("Fetching background info from: {bg_info_url}");
|
||||
|
||||
let bg_response = client
|
||||
.get(&bg_info_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send background info request: {}", e))?;
|
||||
.map_err(|e| format!("Failed to send background info request: {e}"))?;
|
||||
|
||||
if !bg_response.status().is_success() {
|
||||
return Err(format!("Background info request failed with status {}", bg_response.status()));
|
||||
|
@ -152,9 +152,9 @@ async fn refresh_kuro_install(proj_dirs: &ProjectDirs, client: &Client) -> Resul
|
|||
let bg_information: KuroBackgroundInformation = bg_response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse background info JSON: {}", e))?;
|
||||
.map_err(|e| format!("Failed to parse background info JSON: {e}"))?;
|
||||
|
||||
eprintln!("Successfully parsed background info: {:?}", bg_information);
|
||||
eprintln!("Successfully parsed background info: {bg_information:?}");
|
||||
|
||||
update_file_if_needed(data_dir, client, &bg_information.background_file, "background").await?;
|
||||
update_file_if_needed(data_dir, client, &bg_information.slogan, "splash").await?;
|
||||
|
@ -168,7 +168,7 @@ async fn update_file_if_needed(dir: &Path, client: &Client, file_url: &str, file
|
|||
let mut current_file = None;
|
||||
for path in dir.read_dir().unwrap() {
|
||||
let path = path.unwrap();
|
||||
if path.file_name().into_string().unwrap().starts_with(format!("{}_", file_type).as_str()) {
|
||||
if path.file_name().into_string().unwrap().starts_with(format!("{file_type}_").as_str()) {
|
||||
current_file = Some(path);
|
||||
}
|
||||
}
|
||||
|
@ -176,15 +176,15 @@ async fn update_file_if_needed(dir: &Path, client: &Client, file_url: &str, file
|
|||
};
|
||||
|
||||
let filename = extract_filename_from_url(file_url);
|
||||
let expected_file = format!("{}_{}", file_type, filename);
|
||||
let expected_file = format!("{file_type}_{filename}");
|
||||
let file_path = dir.join(&expected_file);
|
||||
|
||||
if let Some(file) = current_file {
|
||||
if filename != file.file_name().into_string().unwrap().strip_prefix(format!("{}_", file_type).as_str()).unwrap() {
|
||||
if filename == file.file_name().into_string().unwrap().strip_prefix(format!("{file_type}_").as_str()).unwrap() {
|
||||
eprintln!("{} file already exists at: {}", file_type, file.path().display());
|
||||
} else {
|
||||
update_file(client, file_path, file_type, file_url).await?;
|
||||
remove_file(file.path()).unwrap();
|
||||
} else {
|
||||
eprintln!("{} file already exists at: {}", file_type, file.path().display());
|
||||
}
|
||||
} else {
|
||||
update_file(client, file_path, file_type, file_url).await?;
|
||||
|
@ -194,15 +194,15 @@ async fn update_file_if_needed(dir: &Path, client: &Client, file_url: &str, file
|
|||
}
|
||||
|
||||
async fn update_file(client: &Client, file_path: PathBuf, file_type: &str, file_url: &str) -> Result<(), String> {
|
||||
eprintln!("Downloading {} file from: {}", file_type, file_url);
|
||||
eprintln!("Downloading {file_type} file from: {file_url}");
|
||||
let file_bytes = client
|
||||
.get(file_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to send request for {} file: {}", file_type, e))?
|
||||
.map_err(|e| format!("Failed to send request for {file_type} file: {e}"))?
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to get bytes for {} file: {}", file_type, e))?;
|
||||
.map_err(|e| format!("Failed to get bytes for {file_type} file: {e}"))?;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
|
@ -212,10 +212,10 @@ async fn update_file(client: &Client, file_path: PathBuf, file_type: &str, file_
|
|||
.map_err(|e| format!("Failed to create {} file ({}): {}", file_type, file_path.display(), e))?;
|
||||
|
||||
file.write_all(&file_bytes)
|
||||
.map_err(|e| format!("Failed to write {} file: {}", file_type, e))?;
|
||||
.map_err(|e| format!("Failed to write {file_type} file: {e}"))?;
|
||||
|
||||
file.flush()
|
||||
.map_err(|e| format!("Failed to flush {} file: {}", file_type, e))?;
|
||||
.map_err(|e| format!("Failed to flush {file_type} file: {e}"))?;
|
||||
|
||||
eprintln!("Successfully downloaded {} file to: {}", file_type, file_path.display());
|
||||
|
||||
|
@ -225,13 +225,13 @@ async fn update_file(client: &Client, file_path: PathBuf, file_type: &str, file_
|
|||
async fn refresh_hoyo_install(proj_dirs: &ProjectDirs, client: &Client) -> Result<(), String> {
|
||||
let hoyo_url = "https://sg-hyp-api.hoyoverse.com/hyp/hyp-connect/api/getGames?launcher_id=VYTpXlbWo8&language=en-us";
|
||||
|
||||
eprintln!("Fetching Hoyo launcher info from: {}", hoyo_url);
|
||||
eprintln!("Fetching Hoyo launcher info from: {hoyo_url}");
|
||||
|
||||
let response = client
|
||||
.get(hoyo_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch Hoyo launcher info: {}", e))?;
|
||||
.map_err(|e| format!("Failed to fetch Hoyo launcher info: {e}"))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Hoyo launcher info request failed with status {}", response.status()));
|
||||
|
@ -240,7 +240,7 @@ async fn refresh_hoyo_install(proj_dirs: &ProjectDirs, client: &Client) -> Resul
|
|||
let hoyo_launcher_info: HoyoLauncherInformation = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse Hoyo launcher info: {}", e))?;
|
||||
.map_err(|e| format!("Failed to parse Hoyo launcher info: {e}"))?;
|
||||
|
||||
eprintln!("Successfully parsed Hoyo launcher info");
|
||||
|
||||
|
@ -255,11 +255,11 @@ async fn refresh_hoyo_install(proj_dirs: &ProjectDirs, client: &Client) -> Resul
|
|||
}
|
||||
};
|
||||
|
||||
let data_dir_buf = proj_dirs.data_dir().join(format!("hoyoverse/{}", game_abbreviation));
|
||||
let data_dir_buf = proj_dirs.data_dir().join(format!("hoyoverse\\{game_abbreviation}"));
|
||||
let data_dir = data_dir_buf.as_path();
|
||||
|
||||
create_dir_all(data_dir)
|
||||
.map_err(|e| format!("Failed to create directory for {}: {}", game_abbreviation, e))?;
|
||||
.map_err(|e| format!("Failed to create directory for {game_abbreviation}: {e}"))?;
|
||||
|
||||
eprintln!("Processing game: {} ({})", game.display.name, game_abbreviation);
|
||||
|
||||
|
@ -278,4 +278,13 @@ fn extract_filename_from_url(url: &str) -> String {
|
|||
Some(filename) => String::from(filename),
|
||||
None => String::from("unknown_file"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn install_game(game: PossibleGames) -> Result<(), String> {
|
||||
match game {
|
||||
PossibleGames::WutheringWaves => {
|
||||
crate::utils::wuthering_waves_installer::install_wuthering_waves().await
|
||||
}
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
pub mod installer;
|
||||
pub mod installer;
|
||||
pub mod settings;
|
52
src/components/settings.rs
Normal file
52
src/components/settings.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use iced::{Element, Length};
|
||||
use iced::widget::{Button, TextInput, Container, Column, Row, text};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SettingsMessage {
|
||||
GamePathChanged(String),
|
||||
Save,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SettingsModal {
|
||||
pub game_path: String,
|
||||
pub is_open: bool,
|
||||
}
|
||||
|
||||
impl SettingsModal {
|
||||
pub fn new(game_path: String) -> Self {
|
||||
Self {
|
||||
game_path,
|
||||
is_open: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<'_, SettingsMessage> {
|
||||
if !self.is_open {
|
||||
return Container::new(text("")).into();
|
||||
}
|
||||
let input = TextInput::new(
|
||||
"Enter game path...",
|
||||
&self.game_path,
|
||||
)
|
||||
.on_input(SettingsMessage::GamePathChanged)
|
||||
.width(Length::Fill);
|
||||
|
||||
let save = Button::new(text("Save")).on_press(SettingsMessage::Save);
|
||||
let cancel = Button::new(text("Cancel")).on_press(SettingsMessage::Cancel);
|
||||
|
||||
let content = Column::new()
|
||||
.push(text("Settings").size(30))
|
||||
.push(input)
|
||||
.push(Row::new().push(save).push(cancel).spacing(10))
|
||||
.spacing(20);
|
||||
|
||||
Container::new(content)
|
||||
.width(Length::Fixed(400.0))
|
||||
.height(Length::Fixed(200.0))
|
||||
.padding(20)
|
||||
.center(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
427
src/main.rs
427
src/main.rs
|
@ -1,8 +1,8 @@
|
|||
// #![windows_subsystem = "windows"]
|
||||
mod utils;
|
||||
mod components;
|
||||
|
||||
use components::installer::refresh_install;
|
||||
use components::settings::{SettingsModal, SettingsMessage};
|
||||
use components::installer::{install_game, refresh_install};
|
||||
use directories::ProjectDirs;
|
||||
use ::image::{DynamicImage, ImageReader};
|
||||
use iced::{
|
||||
|
@ -20,32 +20,27 @@ use std::{
|
|||
pub const ORGANIZATION: &str = "ReversedRooms";
|
||||
pub const PRODUCT: &str = "RoomsLauncher";
|
||||
|
||||
// Trait for converting DynamicImage to image::Handle
|
||||
trait InterfaceImage {
|
||||
fn into_handle(self) -> image::Handle;
|
||||
}
|
||||
|
||||
impl InterfaceImage for DynamicImage {
|
||||
fn into_handle(self) -> image::Handle {
|
||||
// todo: figure out why images which are blurred are made slightly transparent. maybe even use this to my advantange.
|
||||
image::Handle::from_rgba(self.width(), self.height(), self.to_rgba8().into_raw())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() -> iced::Result {
|
||||
let segoe_assets = include_bytes!("../resources/segoe-mdl2-assets.ttf");
|
||||
let main_font = include_bytes!("../resources/QuodlibetSans-Regular.ttf");
|
||||
let icon_file = include_bytes!("../resources/icon.png");
|
||||
|
||||
let icon_image = ImageReader::new(Cursor::new(icon_file))
|
||||
let icon_image = ImageReader::new(Cursor::new(include_bytes!("../resources/icon.png")))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap();
|
||||
let rgba_vec = icon_image.as_rgba8().unwrap().to_vec();
|
||||
|
||||
let settings = Settings {
|
||||
decorations: false,
|
||||
icon: Some(icon::from_rgba(rgba_vec, icon_image.width(), icon_image.height()).unwrap()),
|
||||
icon: Some(icon::from_rgba(icon_image.as_rgba8().unwrap().to_vec(), icon_image.width(), icon_image.height()).unwrap()),
|
||||
size: Size::new(0.0, 0.0),
|
||||
maximized: false,
|
||||
fullscreen: false,
|
||||
|
@ -64,8 +59,8 @@ pub fn main() -> iced::Result {
|
|||
.subscription(Launcher::subscription)
|
||||
.title(Launcher::title)
|
||||
.window(settings)
|
||||
.font(segoe_assets)
|
||||
.font(main_font)
|
||||
.font(include_bytes!("../resources/segoe-mdl2-assets.ttf"))
|
||||
.font(include_bytes!("../resources/QuodlibetSans-Regular.ttf"))
|
||||
.window_size((1280.0, 720.0))
|
||||
.run()
|
||||
}
|
||||
|
@ -79,17 +74,11 @@ enum PossibleGames {
|
|||
GenshinImpact,
|
||||
}
|
||||
|
||||
trait IdentifibleGameType {
|
||||
fn get_game_preferred_size(&self) -> (u32, u32);
|
||||
}
|
||||
|
||||
impl IdentifibleGameType for PossibleGames {
|
||||
fn get_game_preferred_size(&self) -> (u32, u32) {
|
||||
impl PossibleGames {
|
||||
fn get_preferred_size(&self) -> (u32, u32) {
|
||||
match self {
|
||||
PossibleGames::WutheringWaves => (1280, 760),
|
||||
PossibleGames::HonkaiStarRail => (1280, 720),
|
||||
PossibleGames::ZenlessZoneZero => (1280, 720),
|
||||
PossibleGames::GenshinImpact => (1280, 720),
|
||||
Self::WutheringWaves => (1280, 760),
|
||||
_ => (1280, 720),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,41 +94,61 @@ struct VideoBackground {
|
|||
first_frame: DynamicImage
|
||||
}
|
||||
|
||||
impl Clone for VideoBackground {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
video: None, // Do not clone the video field
|
||||
first_frame: self.first_frame.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum LauncherBackground {
|
||||
Video(VideoBackground),
|
||||
Image(DynamicImage),
|
||||
}
|
||||
|
||||
impl Clone for LauncherBackground {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Video(vb) => Self::Video(vb.clone()),
|
||||
Self::Image(img) => Self::Image(img.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct State {
|
||||
selected_game: PossibleGames,
|
||||
installed_games: Vec<PossibleGames>,
|
||||
installed_game_servers: Vec<PossibleGames>,
|
||||
db_software_installed: bool,
|
||||
downloaded_games: Vec<PossibleGames>,
|
||||
background: Option<LauncherBackground>,
|
||||
splash_images: HashMap<PossibleGames, DynamicImage>,
|
||||
icon_images: HashMap<PossibleGames, DynamicImage>
|
||||
icon_images: HashMap<PossibleGames, DynamicImage>,
|
||||
notification: Option<String>,
|
||||
is_installing: bool,
|
||||
game_path: String,
|
||||
settings_modal: SettingsModal,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn get_background_element(&self) -> Option<Element<Message>> {
|
||||
if let Some(background) = &self.background {
|
||||
match background {
|
||||
LauncherBackground::Video(video_object) => {
|
||||
if let Some(video) = &video_object.video {
|
||||
Some(VideoPlayer::new(video).into())
|
||||
} else {
|
||||
Some(image(video_object.first_frame.clone().into_handle()).into())
|
||||
}
|
||||
},
|
||||
LauncherBackground::Image(img) => Some(image(img.clone().into_handle()).into()),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
fn get_background_element(&'_ self) -> Option<Element<'_, Message>> {
|
||||
self.background.as_ref().map(|bg| match bg {
|
||||
LauncherBackground::Video(video_object) => {
|
||||
if let Some(video) = &video_object.video {
|
||||
VideoPlayer::new(video).into()
|
||||
} else {
|
||||
image(video_object.first_frame.clone().into_handle()).into()
|
||||
}
|
||||
},
|
||||
LauncherBackground::Image(img) => image(img.clone().into_handle()).into(),
|
||||
})
|
||||
}
|
||||
fn get_game_icon_row(&self) -> Element<Message> {
|
||||
|
||||
fn get_game_icon_row(&'_ self) -> Element<'_, Message> {
|
||||
container(row![
|
||||
self.create_game_icon(&PossibleGames::WutheringWaves),
|
||||
self.create_game_icon(&PossibleGames::HonkaiStarRail),
|
||||
|
@ -149,9 +158,11 @@ impl State {
|
|||
.spacing(10)
|
||||
).align_x(Center).width(Length::Fill).into()
|
||||
}
|
||||
fn create_game_icon(&self, game: &PossibleGames) -> Element<Message> {
|
||||
|
||||
fn create_game_icon(&self, game: &PossibleGames) -> Element<'_, Message> {
|
||||
if let Some(icon) = self.icon_images.get(game) {
|
||||
let img = if &self.selected_game == game {
|
||||
let is_selected = &self.selected_game == game;
|
||||
let img = if is_selected {
|
||||
image(icon.clone().into_handle())
|
||||
.width(Length::Fixed(52.0))
|
||||
.height(Length::Fixed(68.0))
|
||||
|
@ -161,54 +172,75 @@ impl State {
|
|||
.height(Length::Fixed(64.0))
|
||||
};
|
||||
|
||||
if game != &self.selected_game {
|
||||
mouse_area(
|
||||
img
|
||||
)
|
||||
.on_press(Message::GameSelected(Some(game.clone())))
|
||||
.interaction(Interaction::Pointer)
|
||||
.into()
|
||||
} else {
|
||||
if is_selected {
|
||||
opaque(img)
|
||||
} else {
|
||||
mouse_area(img)
|
||||
.on_press(Message::GameSelected(Some(game.clone())))
|
||||
.interaction(Interaction::Pointer)
|
||||
.into()
|
||||
}
|
||||
} else {
|
||||
text("loading").size(10).into()
|
||||
}
|
||||
}
|
||||
fn get_splash(&self) -> Option<Element<Message>> {
|
||||
if let Some(splash) = self.splash_images.get(&self.selected_game) {
|
||||
let preferred_size = self.selected_game.get_game_preferred_size();
|
||||
Some(image(splash.clone().into_handle())
|
||||
.width(Length::Fixed(preferred_size.0 as f32))
|
||||
.height(Length::Fixed(preferred_size.1 as f32)).into())
|
||||
} else {
|
||||
None
|
||||
|
||||
fn get_splash(&self) -> Option<Element<'_, Message>> {
|
||||
self.splash_images.get(&self.selected_game).map(|splash| {
|
||||
let (width, height) = self.selected_game.get_preferred_size();
|
||||
image(splash.clone().into_handle())
|
||||
.width(Length::Fixed(width as f32))
|
||||
.height(Length::Fixed(height as f32))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn is_game_downloaded(&self, game: &PossibleGames) -> bool {
|
||||
self.downloaded_games.contains(game)
|
||||
}
|
||||
|
||||
fn mark_game_as_downloaded(&mut self, game: &PossibleGames) {
|
||||
if !self.downloaded_games.contains(game) {
|
||||
self.downloaded_games.push(game.clone());
|
||||
}
|
||||
}
|
||||
|
||||
fn path() -> PathBuf {
|
||||
let project_dirs = ProjectDirs::from("com", ORGANIZATION, PRODUCT).unwrap();
|
||||
let path = project_dirs.data_dir();
|
||||
|
||||
path.join("launcher-state.json").to_path_buf()
|
||||
fn start_install(&mut self) {
|
||||
self.is_installing = true;
|
||||
}
|
||||
fn finish_install(&mut self) {
|
||||
self.is_installing = false;
|
||||
}
|
||||
|
||||
fn load(self) -> Result<State, LoadError> {
|
||||
if let Ok(contents) = read_to_string(Self::path()) {
|
||||
let saved_state: SavedState = serde_json::from_str(&contents).map_err(|_| LoadError::Format)?;
|
||||
fn path() -> PathBuf {
|
||||
ProjectDirs::from("com", ORGANIZATION, PRODUCT)
|
||||
.unwrap()
|
||||
.data_dir()
|
||||
.join("launcher-state.json")
|
||||
}
|
||||
|
||||
Ok(State {
|
||||
fn load(self) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let background = self.background.clone();
|
||||
let splash_images = self.splash_images.clone();
|
||||
let icon_images = self.icon_images.clone();
|
||||
Ok(read_to_string(Self::path())
|
||||
.ok()
|
||||
.and_then(|contents| serde_json::from_str::<SavedState>(&contents).ok())
|
||||
.map(|saved_state| Self {
|
||||
selected_game: saved_state.selected_game,
|
||||
installed_games: saved_state.installed_games,
|
||||
installed_game_servers: saved_state.installed_game_servers,
|
||||
db_software_installed: saved_state.db_software_installed,
|
||||
background: self.background,
|
||||
splash_images: self.splash_images,
|
||||
icon_images: self.icon_images,
|
||||
downloaded_games: saved_state.downloaded_games,
|
||||
background,
|
||||
splash_images,
|
||||
icon_images,
|
||||
notification: saved_state.notification,
|
||||
is_installing: saved_state.is_installing,
|
||||
game_path: saved_state.game_path.clone(),
|
||||
settings_modal: SettingsModal::new(saved_state.game_path),
|
||||
})
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
.unwrap_or(self))
|
||||
}
|
||||
|
||||
fn save(&mut self) -> Result<(), SaveError> {
|
||||
|
@ -217,22 +249,48 @@ impl State {
|
|||
installed_games: self.installed_games.clone(),
|
||||
installed_game_servers: self.installed_game_servers.clone(),
|
||||
db_software_installed: self.db_software_installed,
|
||||
downloaded_games: self.downloaded_games.clone(),
|
||||
notification: self.notification.clone(),
|
||||
is_installing: self.is_installing,
|
||||
game_path: self.game_path.clone(),
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?;
|
||||
|
||||
let path = Self::path();
|
||||
|
||||
if let Some(dir) = path.parent() {
|
||||
create_dir_all(dir).map_err(|_| SaveError::Write)?;
|
||||
}
|
||||
|
||||
let json = serde_json::to_string_pretty(&saved_state).map_err(|_| SaveError::Format)?;
|
||||
let mut file = fs::File::create(path).map_err(|_| SaveError::Write)?;
|
||||
file.write_all(json.as_bytes()).map_err(|_| SaveError::Write)?;
|
||||
file.flush().map_err(|_| SaveError::Write)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn show_notification(&mut self, msg: &str) {
|
||||
self.notification = Some(msg.to_string());
|
||||
}
|
||||
|
||||
fn dismiss_notification(&mut self) {
|
||||
self.notification = None;
|
||||
}
|
||||
|
||||
fn update_settings(&mut self, msg: SettingsMessage) {
|
||||
match msg {
|
||||
SettingsMessage::GamePathChanged(path) => {
|
||||
self.game_path = path.clone();
|
||||
self.settings_modal.game_path = path;
|
||||
}
|
||||
SettingsMessage::Save => {
|
||||
self.settings_modal.is_open = false;
|
||||
let _ = self.save();
|
||||
}
|
||||
SettingsMessage::Cancel => {
|
||||
self.settings_modal.is_open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
|
@ -241,17 +299,10 @@ struct SavedState {
|
|||
installed_games: Vec<PossibleGames>,
|
||||
installed_game_servers: Vec<PossibleGames>,
|
||||
db_software_installed: bool,
|
||||
}
|
||||
|
||||
impl From<SavedState> for Box<State> {
|
||||
fn from(val: SavedState) -> Self {
|
||||
Box::new(State { selected_game: val.selected_game, installed_games: val.installed_games, installed_game_servers: val.installed_game_servers, db_software_installed: val.db_software_installed, ..State::default() })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum LoadError {
|
||||
Format,
|
||||
downloaded_games: Vec<PossibleGames>,
|
||||
notification: Option<String>,
|
||||
is_installing: bool,
|
||||
game_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -267,45 +318,39 @@ enum Message {
|
|||
LoadIcons(HashMap<PossibleGames, DynamicImage>),
|
||||
LoadSplashes(HashMap<PossibleGames, DynamicImage>),
|
||||
LoadVideo(()),
|
||||
RefreshInstall(Result<(), String>),
|
||||
RefreshInstall,
|
||||
GameSelected(Option<PossibleGames>),
|
||||
DownloadOrStart,
|
||||
InstallGameResult(Result<(), String>),
|
||||
Close,
|
||||
Minimize
|
||||
Minimize,
|
||||
DismissNotification,
|
||||
OpenSettings,
|
||||
Settings(SettingsMessage),
|
||||
}
|
||||
|
||||
async fn get_icons() -> HashMap<PossibleGames, DynamicImage> {
|
||||
let mut icons: HashMap<PossibleGames, DynamicImage> = HashMap::new();
|
||||
for game in PossibleGames::iter() {
|
||||
let icon = get_game_icon_dynamic_image(&game);
|
||||
icons.insert(game, icon);
|
||||
};
|
||||
icons
|
||||
PossibleGames::iter()
|
||||
.map(|game| (game.clone(), get_game_icon_dynamic_image(&game)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn get_splashes() -> HashMap<PossibleGames, DynamicImage> {
|
||||
let mut splashes: HashMap<PossibleGames, DynamicImage> = HashMap::new();
|
||||
for game in PossibleGames::iter() {
|
||||
let splash = get_game_splash_dynamic_image(&game);
|
||||
if let Some(splash) = splash {splashes.insert(game, splash)} else {continue};
|
||||
};
|
||||
splashes
|
||||
PossibleGames::iter()
|
||||
.filter_map(|game| get_game_splash_dynamic_image(&game).map(|splash| (game, splash)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn empty() {}
|
||||
async fn empty_option() -> Option<PossibleGames> {None}
|
||||
|
||||
impl Launcher {
|
||||
fn boot() -> (Self, Task<Message>) {
|
||||
(
|
||||
Self::Loaded(Box::new(State::default().load().unwrap())),
|
||||
{
|
||||
let refresh_task = Task::perform(refresh_install(), Message::RefreshInstall);
|
||||
refresh_task.chain(Task::batch([
|
||||
Task::perform(empty_option(), Message::GameSelected),
|
||||
Task::perform(refresh_install(), |_| Message::RefreshInstall)
|
||||
.chain(Task::batch([
|
||||
Task::perform(async { None::<PossibleGames> }, Message::GameSelected),
|
||||
Task::perform(get_icons(), Message::LoadIcons),
|
||||
Task::perform(get_splashes(), Message::LoadSplashes),
|
||||
]))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -315,74 +360,96 @@ impl Launcher {
|
|||
|
||||
fn update(&mut self, message: Message) -> Task<Message> {
|
||||
match self {
|
||||
Launcher::Loaded(state) => {
|
||||
Self::Loaded(state) => {
|
||||
match message {
|
||||
Message::DragWindow => {
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::drag(id)
|
||||
})
|
||||
window::get_latest().and_then(move |id: window::Id| {window::drag(id)})
|
||||
}
|
||||
Message::Close => {
|
||||
state.save().unwrap();
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::close(id)
|
||||
})
|
||||
window::get_latest().and_then(window::close)
|
||||
},
|
||||
Message::Minimize => {
|
||||
window::get_latest().and_then(move |id: window::Id| {
|
||||
window::minimize(id, true)
|
||||
})
|
||||
window::get_latest().and_then(|id| window::minimize(id, true))
|
||||
},
|
||||
Message::EventOccurred(event) => {
|
||||
if let Event::Window(window::Event::CloseRequested) = event {
|
||||
state.save().unwrap();
|
||||
println!("hm");
|
||||
window::get_latest().and_then(window::close)
|
||||
} else {
|
||||
Task::none()
|
||||
}
|
||||
Message::EventOccurred(Event::Window(window::Event::CloseRequested)) => {
|
||||
state.save().unwrap();
|
||||
window::get_latest().and_then(window::close)
|
||||
},
|
||||
Message::EventOccurred(_) => Task::none(),
|
||||
Message::LoadSplashes(splashes) => {
|
||||
state.splash_images = splashes;
|
||||
Task::none()
|
||||
},
|
||||
Message::LoadVideo(()) => {
|
||||
if let Ok(background) = get_game_background(&state.selected_game, true) {
|
||||
state.background = Some(background);
|
||||
|
||||
} else {
|
||||
state.background = None
|
||||
}
|
||||
|
||||
state.background = get_game_background(&state.selected_game, true).ok();
|
||||
Task::none()
|
||||
},
|
||||
Message::LoadIcons(icons) => {
|
||||
state.icon_images = icons;
|
||||
Task::none()
|
||||
},
|
||||
Message::RefreshInstall(_result) => {
|
||||
Task::none()
|
||||
},
|
||||
Message::RefreshInstall => Task::none(),
|
||||
Message::GameSelected(game) => {
|
||||
if let Some(game) = game {
|
||||
state.selected_game = game;
|
||||
}
|
||||
let (width, height) = state.selected_game.get_game_preferred_size();
|
||||
let task = window::get_latest().and_then(move |id: window::Id| {
|
||||
let (width, height) = state.selected_game.get_preferred_size();
|
||||
let resize_task = window::get_latest().and_then(move |id| {
|
||||
window::resize(id, Size { width: width as f32, height: height as f32 })
|
||||
});
|
||||
|
||||
if let Ok(background) = get_game_background(&state.selected_game, false) {
|
||||
if let LauncherBackground::Video(_) = background {
|
||||
state.background = Some(background);
|
||||
return task.chain(Task::perform(empty(), Message::LoadVideo));
|
||||
return resize_task.chain(Task::perform(async {}, Message::LoadVideo));
|
||||
}
|
||||
state.background = Some(background)
|
||||
state.background = Some(background);
|
||||
} else {
|
||||
state.background = None
|
||||
state.background = None;
|
||||
}
|
||||
|
||||
task
|
||||
resize_task
|
||||
},
|
||||
Message::DownloadOrStart => {
|
||||
let selected_game = state.selected_game.clone();
|
||||
if state.is_game_downloaded(&selected_game) {
|
||||
println!("Starting game: {selected_game:?}");
|
||||
// TODO: Add actual game starting logic here
|
||||
Task::none()
|
||||
} else {
|
||||
println!("Downloading game: {selected_game:?}");
|
||||
state.start_install();
|
||||
Task::perform(install_game(selected_game), Message::InstallGameResult)
|
||||
}
|
||||
},
|
||||
Message::InstallGameResult(result) => {
|
||||
state.finish_install();
|
||||
match result {
|
||||
Ok(()) => {
|
||||
let selected_game = state.selected_game.clone();
|
||||
state.mark_game_as_downloaded(&selected_game);
|
||||
if selected_game == PossibleGames::WutheringWaves {
|
||||
state.show_notification("All dependencies for Wuthering Waves were installed successfully.");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
state.show_notification(&format!("Failed to install game: {e}"));
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
},
|
||||
Message::DismissNotification => {
|
||||
state.dismiss_notification();
|
||||
Task::none()
|
||||
},
|
||||
Message::OpenSettings => {
|
||||
state.settings_modal.is_open = true;
|
||||
Task::none()
|
||||
},
|
||||
Message::Settings(msg) => {
|
||||
state.update_settings(msg);
|
||||
Task::none()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -390,15 +457,16 @@ impl Launcher {
|
|||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
event::listen_with(|event, _status, _| {
|
||||
match event {
|
||||
Event::Window(window::Event::CloseRequested) => Some(Message::EventOccurred(event)),
|
||||
_ => None,
|
||||
event::listen_with(|event, _, _| {
|
||||
if event == Event::Window(window::Event::CloseRequested) {
|
||||
Some(Message::EventOccurred(event))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
fn view(&'_ self) -> Element<'_, Message> {
|
||||
let mut font = Font::with_name("Quodlibet Sans");
|
||||
font.weight = Weight::Normal;
|
||||
font.stretch = Stretch::Normal;
|
||||
|
@ -406,9 +474,9 @@ impl Launcher {
|
|||
let mut bolded_font = Font::with_name("Quodlibet Sans");
|
||||
bolded_font.weight = Weight::Bold;
|
||||
bolded_font.stretch = Stretch::Normal;
|
||||
println!("Rerender triggered!");
|
||||
|
||||
match self {
|
||||
Launcher::Loaded(state) => {
|
||||
Self::Loaded(state) => {
|
||||
let top_bar = stack![
|
||||
mouse_area(container(
|
||||
stack![
|
||||
|
@ -437,10 +505,38 @@ impl Launcher {
|
|||
.interaction(Interaction::Move)
|
||||
];
|
||||
|
||||
let button_text = if state.is_game_downloaded(&state.selected_game) {
|
||||
"Start"
|
||||
} else if state.is_installing {
|
||||
"Installing..."
|
||||
} else {
|
||||
"Download"
|
||||
};
|
||||
|
||||
let download_button = button(text(button_text).size(25).font(bolded_font).align_x(Center))
|
||||
.padding(Padding { top: 10.0, right: 70.0, bottom: 10.0, left: 70.0 })
|
||||
.style(|_, _| button::Style {
|
||||
text_color: Color::from_rgba8(0, 0, 0, 1.0),
|
||||
background: Some(Color::from_rgba8(255, 255, 255, 1.0).into()),
|
||||
border: border::rounded(5),
|
||||
..button::Style::default()
|
||||
})
|
||||
.on_press(Message::DownloadOrStart);
|
||||
|
||||
let settings_button = button(text("Settings").size(25).font(bolded_font).align_x(Center))
|
||||
.padding(Padding { top: 10.0, right: 30.0, bottom: 10.0, left: 30.0 })
|
||||
.style(|_, _| button::Style {
|
||||
text_color: Color::from_rgba8(0, 0, 0, 1.0),
|
||||
background: Some(Color::from_rgba8(255, 255, 255, 1.0).into()),
|
||||
border: border::rounded(5),
|
||||
..button::Style::default()
|
||||
})
|
||||
.on_press(Message::OpenSettings);
|
||||
|
||||
let bottom_bar = container(row![
|
||||
text("The quick brown fox jumped over the lazy dog.").size(25).font(font),
|
||||
Space::new(Length::Fill, Length::Fixed(0.0)),
|
||||
opaque(mouse_area(button(text("Start").size(25).font(bolded_font).align_x(Center))
|
||||
opaque(mouse_area(download_button
|
||||
.padding(Padding { top: 10.0, right: 70.0, bottom: 10.0, left: 70.0 })
|
||||
.style(move |_, _| {
|
||||
button::Style {
|
||||
|
@ -450,8 +546,10 @@ impl Launcher {
|
|||
..button::Style::default()
|
||||
}
|
||||
}))
|
||||
.interaction(Interaction::Pointer)
|
||||
)
|
||||
.interaction(Interaction::Pointer)),
|
||||
Space::new(Length::Fixed(20.0), Length::Fixed(0.0)),
|
||||
opaque(mouse_area(settings_button)
|
||||
.interaction(Interaction::Pointer)),
|
||||
])
|
||||
.align_y(Bottom)
|
||||
.width(Length::Fill)
|
||||
|
@ -476,6 +574,31 @@ impl Launcher {
|
|||
|
||||
final_stack = final_stack.push(user_area);
|
||||
|
||||
if let Some(ref msg) = state.notification {
|
||||
let msgbox = container(
|
||||
column![
|
||||
text(msg).size(22),
|
||||
button("OK").on_press(Message::DismissNotification)
|
||||
]
|
||||
.spacing(20)
|
||||
.padding(20)
|
||||
)
|
||||
.width(Length::Fixed(400.0))
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.style(|_| container::Style {
|
||||
background: Some(Color::from_rgba8(0,0,0,0.85).into()),
|
||||
border: border::rounded(10),
|
||||
..container::Style::default()
|
||||
});
|
||||
final_stack = final_stack.push(center(msgbox));
|
||||
}
|
||||
|
||||
if state.settings_modal.is_open {
|
||||
let modal_elem = state.settings_modal.view().map(Message::Settings);
|
||||
final_stack = final_stack.push(modal_elem);
|
||||
}
|
||||
|
||||
final_stack.into()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,22 +9,22 @@ pub fn is_in_rounded_rect(x: u32, y: u32, width: u32, height: u32, radius: f32)
|
|||
if x < radius && y < radius {
|
||||
let dx = x - radius;
|
||||
let dy = y - radius;
|
||||
let distance = (dx.powi(2) + dy.powi(2)).sqrt();
|
||||
let distance = dx.hypot(dy);
|
||||
return distance <= radius;
|
||||
} else if x > width - radius && y < radius {
|
||||
let dx = x - (width - radius);
|
||||
let dy = y - radius;
|
||||
let distance = (dx.powi(2) + dy.powi(2)).sqrt();
|
||||
let distance = dx.hypot(dy);
|
||||
return distance <= radius;
|
||||
} else if x < radius && y > height - radius {
|
||||
let dx = x - radius;
|
||||
let dy = y - (height - radius);
|
||||
let distance = (dx.powi(2) + dy.powi(2)).sqrt();
|
||||
let distance = dx.hypot(dy);
|
||||
return distance <= radius;
|
||||
} else if x > width - radius && y > height - radius {
|
||||
let dx = x - (width - radius);
|
||||
let dy = y - (height - radius);
|
||||
let distance = (dx.powi(2) + dy.powi(2)).sqrt();
|
||||
let distance = dx.hypot(dy);
|
||||
return distance <= radius;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod img_utils;
|
||||
pub mod visual_helper;
|
||||
pub mod visual_helper;
|
||||
pub mod wuthering_waves_installer;
|
|
@ -15,11 +15,7 @@ pub fn get_game_background(game: &PossibleGames, make_video: bool) -> Result<Lau
|
|||
|
||||
if background_path.extension().unwrap().to_str().unwrap() == "mp4" {
|
||||
let first_frame_path = get_asset_file(&proj_dirs, game, "first_frame_image")?;
|
||||
let first_frame = ImageReader::new(Cursor::new(std::fs::read(first_frame_path)?))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap();
|
||||
let first_frame = load_image_from_path(&first_frame_path)?;
|
||||
|
||||
if !make_video {
|
||||
return Ok(LauncherBackground::Video(VideoBackground {
|
||||
|
@ -27,116 +23,102 @@ pub fn get_game_background(game: &PossibleGames, make_video: bool) -> Result<Lau
|
|||
video: None
|
||||
}));
|
||||
}
|
||||
match Video::new(&url::Url::from_file_path(background_path).unwrap()) {
|
||||
Ok(video) => {
|
||||
let video_object = VideoBackground {
|
||||
first_frame,
|
||||
video: Some(video)
|
||||
};
|
||||
|
||||
Ok(LauncherBackground::Video(video_object))
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("{:#?}", err);
|
||||
Ok(LauncherBackground::Video(VideoBackground {
|
||||
first_frame,
|
||||
video: None
|
||||
}))
|
||||
},
|
||||
}
|
||||
|
||||
let video = Video::new(&url::Url::from_file_path(background_path).unwrap())
|
||||
.map_err(|err| {
|
||||
eprintln!("{err:#?}");
|
||||
std::io::Error::other("Video creation failed")
|
||||
})?;
|
||||
|
||||
Ok(LauncherBackground::Video(VideoBackground {
|
||||
first_frame,
|
||||
video: Some(video)
|
||||
}))
|
||||
} else {
|
||||
let img = ImageReader::new(Cursor::new(std::fs::read(background_path)?))
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap();
|
||||
let img = load_image_from_path(&background_path)?;
|
||||
Ok(LauncherBackground::Image(img))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_asset_file(proj_dirs: &ProjectDirs, game: &PossibleGames, identifier: &str) -> Result<PathBuf, std::io::Error> {
|
||||
let game_dir = match game {
|
||||
PossibleGames::WutheringWaves => proj_dirs.data_dir().join("kuro/wuwa"),
|
||||
PossibleGames::ZenlessZoneZero => proj_dirs.data_dir().join("hoyoverse/zzz"),
|
||||
PossibleGames::HonkaiStarRail => proj_dirs.data_dir().join("hoyoverse/hsr"),
|
||||
PossibleGames::GenshinImpact => proj_dirs.data_dir().join("hoyoverse/gi"),
|
||||
PossibleGames::WutheringWaves => proj_dirs.data_dir().join("kurogames\\wuwa"),
|
||||
PossibleGames::ZenlessZoneZero => proj_dirs.data_dir().join("hoyoverse\\zzz"),
|
||||
PossibleGames::HonkaiStarRail => proj_dirs.data_dir().join("hoyoverse\\hsr"),
|
||||
PossibleGames::GenshinImpact => proj_dirs.data_dir().join("hoyoverse\\gi"),
|
||||
};
|
||||
|
||||
if create_dir_all(&game_dir).is_err() {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("Game directory does not exist: {:?}", game_dir)
|
||||
));
|
||||
}
|
||||
|
||||
let entries = std::fs::read_dir(&game_dir)?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry?;
|
||||
let file_name = entry.file_name().into_string().unwrap_or_default();
|
||||
|
||||
if file_name.starts_with(format!("{}_", identifier).as_str()) {
|
||||
return Ok(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
Err(std::io::Error::new(
|
||||
create_dir_all(&game_dir).map_err(|_| std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("No background file found in {:?}", game_dir)
|
||||
))
|
||||
format!("Failed to create game directory: {game_dir:?}")
|
||||
))?;
|
||||
|
||||
std::fs::read_dir(&game_dir)?
|
||||
.filter_map(std::result::Result::ok)
|
||||
.find(|entry| {
|
||||
entry.file_name()
|
||||
.to_string_lossy()
|
||||
.starts_with(&format!("{identifier}_"))
|
||||
})
|
||||
.map(|entry| entry.path())
|
||||
.ok_or_else(|| std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
format!("No {identifier} file found in {game_dir:?}")
|
||||
))
|
||||
}
|
||||
|
||||
fn load_image_from_path(path: &PathBuf) -> Result<DynamicImage, std::io::Error> {
|
||||
let data = std::fs::read(path)?;
|
||||
let cursor = Cursor::new(data);
|
||||
|
||||
ImageReader::new(cursor)
|
||||
.with_guessed_format()
|
||||
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid image format"))?
|
||||
.decode()
|
||||
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Failed to decode image"))
|
||||
}
|
||||
|
||||
pub fn get_game_splash_dynamic_image(game: &PossibleGames) -> Option<DynamicImage> {
|
||||
let proj_dirs = ProjectDirs::from("com", ORGANIZATION, PRODUCT).unwrap();
|
||||
let file_path = get_asset_file(&proj_dirs, game, "splash");
|
||||
|
||||
if let Ok(path) = file_path {
|
||||
let data_cursor = Cursor::new(std::fs::read(path).unwrap());
|
||||
|
||||
Some(ImageReader::new(data_cursor)
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap())
|
||||
} else {None}
|
||||
get_asset_file(&proj_dirs, game, "splash")
|
||||
.and_then(|path| load_image_from_path(&path))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn get_game_icon_dynamic_image(game: &PossibleGames) -> DynamicImage {
|
||||
let proj_dirs = ProjectDirs::from("com", ORGANIZATION, PRODUCT).unwrap();
|
||||
let file_data: &[u8] = match game {
|
||||
PossibleGames::WutheringWaves => include_bytes!("../../resources/wutheringwaves-icon.png"),
|
||||
PossibleGames::ZenlessZoneZero => &std::fs::read(get_asset_file(&proj_dirs, game, "icon").unwrap()).unwrap(),
|
||||
PossibleGames::HonkaiStarRail => &std::fs::read(get_asset_file(&proj_dirs, game, "icon").unwrap()).unwrap(),
|
||||
PossibleGames::GenshinImpact => &std::fs::read(get_asset_file(&proj_dirs, game, "icon").unwrap()).unwrap(),
|
||||
let file_data: &[u8] = if game == &PossibleGames::WutheringWaves { include_bytes!("../../resources/wutheringwaves-icon.png") } else {
|
||||
let proj_dirs = ProjectDirs::from("com", ORGANIZATION, PRODUCT).unwrap();
|
||||
&std::fs::read(get_asset_file(&proj_dirs, game, "icon").unwrap()).unwrap()
|
||||
};
|
||||
|
||||
let data_cursor = Cursor::new(file_data);
|
||||
|
||||
let img = ImageReader::new(data_cursor)
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap()
|
||||
.resize(128, 128, ::image::imageops::FilterType::Lanczos3);
|
||||
|
||||
round_image(img)
|
||||
let cursor = Cursor::new(file_data);
|
||||
let img = ImageReader::new(cursor)
|
||||
.with_guessed_format()
|
||||
.unwrap()
|
||||
.decode()
|
||||
.unwrap()
|
||||
.resize(128, 128, ::image::imageops::FilterType::Lanczos3);
|
||||
|
||||
round_image(img).unwrap()
|
||||
}
|
||||
|
||||
fn rad(deg: f32) -> f32 {
|
||||
deg * std::f32::consts::PI / 180.0
|
||||
deg.to_radians()
|
||||
}
|
||||
|
||||
pub fn style_container(direction: f32, use_gradient: bool) -> container::Style {
|
||||
let angle = rad(direction);
|
||||
let gradient: Option<iced::Background> = if use_gradient {
|
||||
Some(gradient::Linear::new(angle)
|
||||
.add_stop(0.0, Color::from_rgba8(0, 0, 0, 0.0))
|
||||
.add_stop(0.75, Color::from_rgba8(0, 0, 0, 0.75)).into())
|
||||
} else {None};
|
||||
let background = if use_gradient {
|
||||
Some(gradient::Linear::new(rad(direction))
|
||||
.add_stop(0.0, Color::from_rgba8(0, 0, 0, 0.0))
|
||||
.add_stop(0.75, Color::from_rgba8(0, 0, 0, 0.75))
|
||||
.into())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
container::Style {
|
||||
text_color: Color::from_rgba8(255, 255, 255, 1.0).into(),
|
||||
background: gradient,
|
||||
background,
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
183
src/utils/wuthering_waves_installer.rs
Normal file
183
src/utils/wuthering_waves_installer.rs
Normal file
|
@ -0,0 +1,183 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use reqwest::Client;
|
||||
use directories::ProjectDirs;
|
||||
|
||||
const POSTGRES_URL: &str = "https://sbp.enterprisedb.com/getfile.jsp?fileid=1259603";
|
||||
const GIT_URL: &str = "https://github.com/git-for-windows/git/releases/download/v2.50.1.windows.1/Git-2.50.1-64-bit.exe";
|
||||
const PROTOC_RELEASES_API: &str = "https://api.github.com/repos/protocolbuffers/protobuf/releases/latest";
|
||||
const PROTOC_ASSET_KEYWORD: &str = "protoc-31.1-win64.zip";
|
||||
const WICKED_WAIFUS_REPO: &str = "https://git.xeondev.com/wickedwaifus/wicked-waifus-rs.git";
|
||||
const RUSTUP_URL: &str = "https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe";
|
||||
|
||||
/// Steps:
|
||||
/// 0. Download and install Rust
|
||||
/// 1. Download and install `PostgreSQL` 16.9 (user handles admin prompt)
|
||||
/// 2. Download and install Protoc, add bin to PATH
|
||||
/// 3. Download and install Git
|
||||
/// 4. Clone wicked-waifus-rs repo recursively
|
||||
/// 5. Build all required binaries in sequence
|
||||
pub async fn install_wuthering_waves() -> Result<(), String> {
|
||||
let client = Client::new();
|
||||
let proj_dirs = ProjectDirs::from("com", "ReversedRooms", "RoomsLauncher").ok_or("Could not get ProjectDirs")?;
|
||||
let deps_dir = proj_dirs.data_dir().join("kurogames\\wuwa\\dependencies");
|
||||
std::fs::create_dir_all(&deps_dir).map_err(|e| e.to_string())?;
|
||||
|
||||
// 0. Download and install Rust
|
||||
let rustup_installer = deps_dir.join("rustup-init.exe");
|
||||
download_file(&client, RUSTUP_URL, &rustup_installer).await?;
|
||||
let mut rustup_cmd = Command::new(&rustup_installer);
|
||||
rustup_cmd.arg("-y");
|
||||
let mut child = rustup_cmd.spawn().map_err(|e| format!("Failed to run rustup-init: {e}"))?;
|
||||
let status = child.wait().map_err(|e| format!("Failed to wait for rustup-init: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err("rustup-init failed".into());
|
||||
}
|
||||
println!("Rust installed");
|
||||
std::fs::remove_file(&rustup_installer).ok();
|
||||
|
||||
// 1. Download and install PostgreSQL (user handles admin prompt)
|
||||
let postgres_installer = deps_dir.join("postgresql-installer.exe");
|
||||
download_file(&client, POSTGRES_URL, &postgres_installer).await?;
|
||||
run_installer(&postgres_installer, false)?; // not silent
|
||||
println!("PostgreSQL installed");
|
||||
std::fs::remove_file(&postgres_installer).ok();
|
||||
|
||||
// 2. Download and install Protoc, add bin to PATH
|
||||
let protoc_zip = deps_dir.join("protoc.zip");
|
||||
let protoc_dir = deps_dir.join("protoc");
|
||||
download_latest_protoc(&client, &protoc_zip).await?;
|
||||
unzip_file(&protoc_zip, &protoc_dir)?;
|
||||
std::fs::remove_file(&protoc_zip).ok();
|
||||
add_bin_to_path(&protoc_dir.join("bin"))?;
|
||||
println!("Protoc installed");
|
||||
|
||||
// 3. Download and install Git
|
||||
let git_installer = deps_dir.join("git-installer.exe");
|
||||
download_file(&client, GIT_URL, &git_installer).await?;
|
||||
run_installer(&git_installer, true)?; // silent
|
||||
println!("Git installed");
|
||||
std::fs::remove_file(&git_installer).ok();
|
||||
|
||||
// 4. Clone wicked-waifus-rs repo recursively
|
||||
let repo_dir = deps_dir.join("wicked-waifus-rs");
|
||||
if !repo_dir.exists() {
|
||||
let status = Command::new("git")
|
||||
.arg("clone")
|
||||
.arg("--recursive")
|
||||
.arg(WICKED_WAIFUS_REPO)
|
||||
.arg(&repo_dir)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.map_err(|e| format!("Failed to run git clone: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err("git clone failed".into());
|
||||
}
|
||||
println!("Wicked Waifus repo cloned");
|
||||
}
|
||||
|
||||
// 5. Build all required binaries in sequence
|
||||
let bins = [
|
||||
"wicked-waifus-config-server",
|
||||
"wicked-waifus-login-server",
|
||||
"wicked-waifus-gateway-server",
|
||||
"wicked-waifus-game-server",
|
||||
];
|
||||
for bin in &bins {
|
||||
let status = Command::new("cargo")
|
||||
.arg("build")
|
||||
.arg("--bin")
|
||||
.arg(bin)
|
||||
.current_dir(&repo_dir)
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.status()
|
||||
.map_err(|e| format!("Failed to run cargo build for {bin}: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err(format!("cargo build failed for {bin}"));
|
||||
}
|
||||
println!("{bin} built");
|
||||
}
|
||||
|
||||
println!("All binaries built");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_file(client: &Client, url: &str, dest: &Path) -> Result<(), String> {
|
||||
let resp = client.get(url)
|
||||
.header("User-Agent", "rooms-launcher")
|
||||
.send().await
|
||||
.map_err(|e| format!("Failed to download {url}: {e}"))?;
|
||||
let bytes = resp.bytes().await.map_err(|e| format!("Failed to read bytes: {e}"))?;
|
||||
let mut file = fs::File::create(dest).map_err(|e| format!("Failed to create file: {e}"))?;
|
||||
file.write_all(&bytes).map_err(|e| format!("Failed to write file: {e}"))?;
|
||||
println!("Downloaded {url} to {}", dest.to_string_lossy());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_installer(installer: &Path, silent: bool) -> Result<(), String> {
|
||||
let mut cmd = Command::new(installer);
|
||||
if silent {
|
||||
cmd.arg("/VERYSILENT");
|
||||
}
|
||||
let mut child = cmd.spawn().map_err(|e| format!("Failed to run installer: {e}"))?;
|
||||
let status = child.wait().map_err(|e| format!("Failed to wait for installer: {e}"))?;
|
||||
if !status.success() {
|
||||
return Err(format!("Installer failed: {installer:?}"));
|
||||
}
|
||||
println!("{installer:?} ran successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn download_latest_protoc(client: &Client, dest: &Path) -> Result<(), String> {
|
||||
let resp = client.get(PROTOC_RELEASES_API)
|
||||
.header("User-Agent", "rooms-launcher")
|
||||
.send().await
|
||||
.map_err(|e| format!("Failed to fetch Protoc releases: {e}"))?;
|
||||
let json: serde_json::Value = resp.json().await.map_err(|e| format!("Failed to parse Protoc release JSON: {e}"))?;
|
||||
let assets = json["assets"].as_array().ok_or("No assets in Protoc release JSON")?;
|
||||
let asset = assets.iter().find(|a| a["name"].as_str().is_some_and(|n| n.contains(PROTOC_ASSET_KEYWORD)))
|
||||
.ok_or("No zip asset found for Protoc")?;
|
||||
let url = asset["browser_download_url"].as_str().ok_or("No download URL for Protoc asset")?;
|
||||
download_file(client, url, dest).await?;
|
||||
println!("Downloaded Protoc to {}", dest.to_string_lossy());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unzip_file(zip_path: &Path, dest_dir: &Path) -> Result<(), String> {
|
||||
let file = fs::File::open(zip_path).map_err(|e| format!("Failed to open zip: {e}"))?;
|
||||
let mut archive = zip::ZipArchive::new(file).map_err(|e| format!("Failed to read zip: {e}"))?;
|
||||
fs::create_dir_all(dest_dir).map_err(|e| format!("Failed to create unzip dir: {e}"))?;
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i).map_err(|e| format!("Failed to get file in zip: {e}"))?;
|
||||
let outpath = dest_dir.join(file.name());
|
||||
if file.is_dir() {
|
||||
fs::create_dir_all(&outpath).map_err(|e| format!("Failed to create dir in zip: {e}"))?;
|
||||
} else {
|
||||
if let Some(p) = outpath.parent() {
|
||||
fs::create_dir_all(p).map_err(|e| format!("Failed to create parent dir: {e}"))?;
|
||||
}
|
||||
let mut outfile = fs::File::create(&outpath).map_err(|e| format!("Failed to create file in zip: {e}"))?;
|
||||
std::io::copy(&mut file, &mut outfile).map_err(|e| format!("Failed to extract file: {e}"))?;
|
||||
}
|
||||
}
|
||||
println!("Unzipped {} to {}", zip_path.to_string_lossy(), dest_dir.to_string_lossy());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_bin_to_path(bin_dir: &Path) -> Result<(), String> {
|
||||
let bin_str = bin_dir.to_str().ok_or("Invalid bin path")?;
|
||||
let path = env::var("Path").unwrap_or_default();
|
||||
let new_path = format!("{bin_str};{path}");
|
||||
unsafe {
|
||||
env::set_var("Path", &new_path);
|
||||
}
|
||||
println!("Added {} to Path", bin_dir.to_string_lossy());
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue