mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2024-11-23 05:13:40 +03:00
commit
8a355f4af1
42 changed files with 1099 additions and 649 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -2,9 +2,26 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added option to use additional xlua patch
|
||||
- Added menu option to open wishes history url
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed downloaded wine version selection on "download wine" button
|
||||
- Fixed game downloading (it wasn't working since some version????)
|
||||
- Fixed infinite retries to download some update or patch the game if it failed
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `launcher.speed_limit` config
|
||||
|
||||
## [3.2.1] - 18.03.2023
|
||||
|
||||
### Fixed
|
||||
|
@ -121,7 +138,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
<br>
|
||||
|
||||
[unreleased]: https://github.com/an-anime-team/an-anime-game-launcher/compare/3.2.1...HEAD
|
||||
[unreleased]: https://github.com/an-anime-team/an-anime-game-launcher/compare/3.2.1...next
|
||||
[3.2.1]: https://github.com/an-anime-team/an-anime-game-launcher/compare/3.2.0...3.2.1
|
||||
[3.2.0]: https://github.com/an-anime-team/an-anime-game-launcher/compare/3.1.5...3.2.0
|
||||
[3.1.5]: https://github.com/an-anime-team/an-anime-game-launcher/compare/3.1.4...3.1.5
|
||||
|
|
132
Cargo.lock
generated
132
Cargo.lock
generated
|
@ -31,7 +31,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anime-game-core"
|
||||
version = "1.3.12"
|
||||
version = "1.4.5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bzip2",
|
||||
|
@ -76,12 +76,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anime-launcher-sdk"
|
||||
version = "0.5.3"
|
||||
version = "0.5.7"
|
||||
dependencies = [
|
||||
"anime-game-core",
|
||||
"anyhow",
|
||||
"cached",
|
||||
"dirs",
|
||||
"dirs 5.0.0",
|
||||
"discord-rich-presence",
|
||||
"enum-ordinalize",
|
||||
"lazy_static",
|
||||
|
@ -339,9 +339,9 @@ checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663"
|
|||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.16.7"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3125b15ec28b84c238f6f476c6034016a5f6cc0221cb514ca46c532139fc97d"
|
||||
checksum = "a8af54f5d48af1226928adc1f57edd22f5df1349e7da1fc96ae15cf43db0e871"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-sys-rs",
|
||||
|
@ -353,9 +353,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cairo-sys-rs"
|
||||
version = "0.16.3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c48f4af05fabdcfa9658178e1326efa061853f040ce7d72e33af6885196f421"
|
||||
checksum = "f55382a01d30e5e53f185eee269124f5e21ab526595b872751278dfbb463594e"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
@ -581,7 +581,16 @@ version = "4.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
"dirs-sys 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -595,6 +604,17 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discord-rich-presence"
|
||||
version = "0.2.3"
|
||||
|
@ -959,22 +979,23 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf"
|
||||
version = "0.16.7"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3578c60dee9d029ad86593ed88cb40f35c1b83360e12498d055022385dd9a05"
|
||||
checksum = "b023fbe0c6b407bd3d9805d107d9800da3829dc5a676653210f1d5f16d7f59bf"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"gdk-pixbuf-sys",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdk-pixbuf-sys"
|
||||
version = "0.16.3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3092cf797a5f1210479ea38070d9ae8a5b8e9f8f1be9f32f4643c529c7d70016"
|
||||
checksum = "7b41bd2b44ed49d99277d3925652a163038bd5ed943ec9809338ffb2f4391e3b"
|
||||
dependencies = [
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
|
@ -985,9 +1006,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4"
|
||||
version = "0.5.5"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2181330ebf9d091f8ea7fed6877f7adc92114128592e1fdaeb1da28e0d01e9"
|
||||
checksum = "c3abf96408a26e3eddf881a7f893a1e111767137136e347745e8ea6ed12731ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
|
@ -1001,9 +1022,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gdk4-sys"
|
||||
version = "0.5.5"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de55cb49432901fe2b3534177fa06844665b9b0911d85d8601a8d8b88b7791db"
|
||||
checksum = "1bc92aa1608c089c49393d014c38ac0390d01e4841e1fedaa75dbcef77aaed64"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
@ -1041,9 +1062,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.16.7"
|
||||
version = "0.17.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a1c84b4534a290a29160ef5c6eff2a9c95833111472e824fc5cb78b513dd092"
|
||||
checksum = "2261a3b4e922ec676d1c27ac466218c38cf5dcb49a759129e54bb5046e442125"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
|
@ -1061,9 +1082,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.16.3"
|
||||
version = "0.17.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9b693b8e39d042a95547fc258a7b07349b1f0b48f4b2fa3108ba3c51c0b5229"
|
||||
checksum = "6b1d43b0d7968b48455244ecafe41192871257f5740aa6b095eb19db78e362a5"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -1074,9 +1095,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.16.7"
|
||||
version = "0.17.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd4df61a866ed7259d6189b8bcb1464989a77f1d85d25d002279bbe9dd38b2f"
|
||||
checksum = "cfb53061756195d76969292c2d2e329e01259276524a9bae6c9b73af62854773"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
|
@ -1089,6 +1110,7 @@ dependencies = [
|
|||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
|
@ -1102,9 +1124,9 @@ checksum = "8f8480c9ba9cc06aa8d5baf446037f8dc237bee127e9b62080c4db7e293d8ea0"
|
|||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.16.3"
|
||||
version = "0.17.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e084807350b01348b6d9dbabb724d1a0bb987f47a2c85de200e98e12e30733bf"
|
||||
checksum = "454924cafe58d9174dc32972261fe271d6cd3c10f5e9ff505522a28dcf601a40"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
|
@ -1117,9 +1139,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.16.3"
|
||||
version = "0.17.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61a4f46316d06bfa33a7ac22df6f0524c8be58e3db2d9ca99ccb1f357b62a65"
|
||||
checksum = "49f00ad0a1bf548e61adfff15d83430941d9e1bb620e334f779edd1c745680a5"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
|
@ -1140,9 +1162,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.16.3"
|
||||
version = "0.17.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3520bb9c07ae2a12c7f2fbb24d4efc11231c8146a86956413fb1a79bb760a0f1"
|
||||
checksum = "15e75b0000a64632b2d8ca3cf856af9308e3a970844f6e9659bd197f026793d0"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
@ -1151,9 +1173,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-rs"
|
||||
version = "0.16.3"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95ecb4d347e6d09820df3bdfd89a74a8eec07753a06bb92a3aac3ad31d04447b"
|
||||
checksum = "21cf11565bb0e4dfc2f99d4775b6c329f0d40a2cff9c0066214d31a0e1b46256"
|
||||
dependencies = [
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
|
@ -1162,9 +1184,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "graphene-sys"
|
||||
version = "0.16.3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9aa82337d3972b4eafdea71e607c23f47be6f27f749aab613f1ad8ddbe6dcd6"
|
||||
checksum = "cf80a4849a8d9565410a8fec6fc3678e9c617f4ac7be182ca55ab75016e07af9"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"libc",
|
||||
|
@ -1174,9 +1196,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4"
|
||||
version = "0.5.5"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "591239f5c52ca803b222124ac9c47f230cd180cee9b114c4d672e4a94b74f491"
|
||||
checksum = "6f01ef44fa7cac15e2da9978529383e6bee03e570ba5bf7036b4c10a15cc3a3c"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
|
@ -1190,9 +1212,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gsk4-sys"
|
||||
version = "0.5.5"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "195a63f0be42529f98c3eb3bae0decfd0428ba2cc683b3e20ced88f340904ec5"
|
||||
checksum = "c07a84fb4dcf1323d29435aa85e2f5f58bef564342bef06775ec7bd0da1f01b0"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
|
@ -1206,9 +1228,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4"
|
||||
version = "0.5.5"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd89dba65def483a233dc4fdd3f3dab01576e3d83f80f6c9303ebe421661855e"
|
||||
checksum = "1e30e124b5a605f6f5513db13958bfcd51d746607b20bc7bb718b33e303274ed"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cairo-rs",
|
||||
|
@ -1229,9 +1251,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-macros"
|
||||
version = "0.5.5"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "832687a415d9d8bc11fe9c17dda1bf13ee262c41b995dd4df1d1cce33cead405"
|
||||
checksum = "30e21acdeb9a02b8cba83e65afbb8aa45f977785e50f1113407024c6b6256988"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro-crate",
|
||||
|
@ -1243,9 +1265,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "gtk4-sys"
|
||||
version = "0.5.5"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e370564e3fdacff7cffc99f7366b6a4689feb44e819d3ccee598a9a215b71605"
|
||||
checksum = "5f8283f707b07e019e76c7f2934bdd4180c277e08aa93f4c0d8dd07b7a34e22f"
|
||||
dependencies = [
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
|
@ -1418,9 +1440,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
|||
|
||||
[[package]]
|
||||
name = "libadwaita"
|
||||
version = "0.2.1"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dfa0722d4f1724f661cbf668c273c5926296ca411ed3814e206f8fd082b6c48"
|
||||
checksum = "b1c4efd2020a4fcedbad2c4a97de97bf6045e5dc49d61d5a5d0cfd753db60700"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"futures-channel",
|
||||
|
@ -1437,9 +1459,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libadwaita-sys"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de902982372b454a0081d7fd9dd567b37b73ae29c8f6da1820374d345fd95d5b"
|
||||
checksum = "0727b85b4fe2b1bed5ac90df6343de15cbf8118bfb96d7c3cc1512681a4b34ac"
|
||||
dependencies = [
|
||||
"gdk4-sys",
|
||||
"gio-sys",
|
||||
|
@ -1755,9 +1777,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
|||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.16.5"
|
||||
version = "0.17.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdff66b271861037b89d028656184059e03b0b6ccb36003820be19f7200b1e94"
|
||||
checksum = "52c280b82a881e4208afb3359a8e7fde27a1b272280981f1f34610bed5770d37"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"gio",
|
||||
|
@ -1769,9 +1791,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pango-sys"
|
||||
version = "0.16.3"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e134909a9a293e04d2cc31928aa95679c5e4df954d0b85483159bd20d8f047f"
|
||||
checksum = "4293d0f0b5525eb5c24734d30b0ed02cd02aa734f216883f376b54de49625de8"
|
||||
dependencies = [
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
|
@ -2058,9 +2080,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
|||
|
||||
[[package]]
|
||||
name = "relm4"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0-alpha.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01a298c830b43bdafdef3d6eeb2d48920b04f541974b03caa52b840e8afb8fbf"
|
||||
checksum = "7acc5e3ddd682eeb0ca33da36b821fc907017c6d7758b09ce280247b0b0ea8cd"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"flume",
|
||||
|
@ -2076,9 +2098,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "relm4-macros"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0-alpha.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d4191e434a6524bb17d16addb3a0aff740e13f1c32a4a2e9562b590d27a326"
|
||||
checksum = "c8525ce12dcd7d2a9e9070d84b8b885600eccd0a143df6712ce34a87d001a8b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3051,7 +3073,7 @@ dependencies = [
|
|||
"async-trait",
|
||||
"byteorder",
|
||||
"derivative",
|
||||
"dirs",
|
||||
"dirs 4.0.0",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
|
|
|
@ -16,9 +16,9 @@ opt-level = "s"
|
|||
glib-build-tools = "0.17"
|
||||
|
||||
[dependencies]
|
||||
relm4 = { version = "0.5", features = ["macros", "libadwaita"] }
|
||||
gtk = { package = "gtk4", version = "0.5", features = ["v4_8"] }
|
||||
adw = { package = "libadwaita", version = "0.2", features = ["v1_2"] }
|
||||
relm4 = { version = "0.6.0-alpha.2", features = ["macros", "libadwaita"] }
|
||||
gtk = { package = "gtk4", version = "0.6", features = ["v4_8"] }
|
||||
adw = { package = "libadwaita", version = "0.3", features = ["v1_2"] }
|
||||
rfd = { version = "0.11", features = ["xdg-portal"], default-features = false }
|
||||
open = "4.0.0"
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit f244a20b42dd1cf43ae7955d2a26c0b33b2ccdbe
|
||||
Subproject commit 19ceddca82367514b5c806cfbc94f527ccd75167
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = Spieleordner konnte nicht geöffnet werden
|
|||
config-file-opening-error = Konfig-Datei konnte nicht geöffnet werden
|
||||
debug-file-opening-error = Debug-Datei konnte nicht geöffnet werden
|
||||
|
||||
wish-url-search-failed = Kein Wünsche URL gefunden
|
||||
wish-url-opening-error = Wünsche URL konnte nicht geöffnet werden
|
||||
|
||||
game-launching-failed = Spiel konnte nicht gestartet werden
|
||||
failed-get-selected-wine = Die ausgewählte Wine version konnte nicht abgerufen werden.
|
||||
downloading-failed = Herunterladen fehlgeschlagen
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = Vorab-Download von Spiel-Updates verfügbar: {$old}
|
|||
game-update-available = Spiel-Update verfügbar: {$old} -> {$new}
|
||||
game-outdated = Das Spiel ist zu veraltet und kann nicht mehr aktualisiert werden. Letzte Version: {$latest}
|
||||
|
||||
patch-version = Patch version
|
||||
player-patch-version = Hauptpatch-Version
|
||||
player-patch-version-description = Hauptpatch, mit dem du das Spiel unter Linux spielen kannst
|
||||
|
||||
xlua-patch-version = zusätzliche Patch-Version
|
||||
xlua-patch-version-description = Zusätzlicher Patch, der einige Probleme behebt und die Leistung auf Low-End-PCs verbessert
|
||||
|
||||
patch-not-available = nicht verfügbar
|
||||
patch-not-available-tooltip = Patch-Server sind unerreichbar
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = Vorbereitung
|
|||
patch-preparation-tooltip = Patch ist in Entwicklung
|
||||
|
||||
patch-testing-tooltip = Test-Patch ist verfügbar
|
||||
patch-not-applied-tooltip = Patch ist nicht angewendet
|
||||
|
||||
apply-xlua-patch = Zusätzlichen Patch anwenden
|
||||
|
||||
ask-superuser-permissions = Superuser-Berechtigungen anfordern
|
||||
ask-superuser-permissions-description = Launcher benötigt Superuser-Zugriff, um Ihre Hosts-Datei automatisch zu aktualisieren. Dies ist in der Flatpak-Edition nicht erforderlich
|
||||
|
||||
selected-version = Ausgewählte version
|
||||
recommended-only = Nur empfohlene
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = Launcher-Ordner
|
|||
game-folder = Spielordner
|
||||
config-file = Konfig-Datei
|
||||
debug-file = Debug-Datei
|
||||
wish-url = Wünsche öffnen
|
||||
about = Über
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = Failed to open game folder
|
|||
config-file-opening-error = Failed to open config file
|
||||
debug-file-opening-error = Failed to open debug file
|
||||
|
||||
wish-url-search-failed = No wishes url found
|
||||
wish-url-opening-error = Could not open wishes url
|
||||
|
||||
game-launching-failed = Failed to launch game
|
||||
failed-get-selected-wine = Failed to get selected wine version
|
||||
downloading-failed = Downloading failed
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = Game update pre-downloading available: {$old} -> {$
|
|||
game-update-available = Game update available: {$old} -> {$new}
|
||||
game-outdated = Game is too outdated and can't be updated. Latest version: {$latest}
|
||||
|
||||
patch-version = Patch version
|
||||
player-patch-version = Player patch version
|
||||
player-patch-version-description = Main patch that lets you play the game on Linux
|
||||
|
||||
xlua-patch-version = Xlua patch version
|
||||
xlua-patch-version-description = Additional patch that fixes some issues and improves performance on low-end PCs
|
||||
|
||||
patch-not-available = not available
|
||||
patch-not-available-tooltip = Patch servers are unreachable
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = preparation
|
|||
patch-preparation-tooltip = Patch is in development
|
||||
|
||||
patch-testing-tooltip = Test patch is available
|
||||
patch-not-applied-tooltip = Patch is not applied
|
||||
|
||||
apply-xlua-patch = Apply xlua patch
|
||||
|
||||
ask-superuser-permissions = Ask superuser permissions
|
||||
ask-superuser-permissions-description = Launcher will use them to automatically update your hosts file. This is not needed in flatpak edition
|
||||
|
||||
selected-version = Selected version
|
||||
recommended-only = Recommended only
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = Launcher folder
|
|||
game-folder = Game folder
|
||||
config-file = Config file
|
||||
debug-file = Debug file
|
||||
wish-url = Open wishes
|
||||
about = About
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = Fallo al abrir la carpeta del juego
|
|||
config-file-opening-error = Fallo al abrir el archivo de configuración
|
||||
debug-file-opening-error = Fallo al abrir el archivo de debug
|
||||
|
||||
wish-url-search-failed = No se encontró la URL del historial de deseos
|
||||
wish-url-opening-error = No se pudo abrir la URL del historial de deseos
|
||||
|
||||
game-launching-failed = Fallo al iniciar el juego
|
||||
failed-get-selected-wine = Fallo al buscar la versión elegida de Wine
|
||||
downloading-failed = Descarga fallida
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = Pre-descarga de actualización disponible: {$old} -
|
|||
game-update-available = Actualización disponible: {$old} -> {$new}
|
||||
game-outdated = El juego está demasiado desactualizado y no puede actualizarse. Última versión: {$latest}
|
||||
|
||||
patch-version = Versión del parche
|
||||
player-patch-version = Versión del parche del jugador
|
||||
player-patch-version-description = El parche principal que te permite jugar al juego en Linux
|
||||
|
||||
xlua-patch-version = Versión del parche Xlua
|
||||
xlua-patch-version-description = Parche adicional que arregla algunos problemas y mejora el rendimiento en PCs de gama baja
|
||||
|
||||
patch-not-available = No disponible
|
||||
patch-not-available-tooltip = Los servidores del parche no pudieron contactarse
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = Preparación
|
|||
patch-preparation-tooltip = El parche está en desarrollo
|
||||
|
||||
patch-testing-tooltip = Está disponible un parche de prueba
|
||||
patch-not-applied-tooltip = El parche no está aplicado
|
||||
|
||||
apply-xlua-patch = Aplicar parche Xlua
|
||||
|
||||
ask-superuser-permissions = Pedir permisos de superusuario
|
||||
ask-superuser-permissions-description = El launcher usará los permisos para actualizar automáticamente tu archivo hosts. Esto no es necesario en la versión de Flatpak
|
||||
|
||||
selected-version = Versión seleccionada
|
||||
recommended-only = Sólo recomendadas
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = Carpeta del launcher
|
|||
game-folder = Carpeta del juego
|
||||
config-file = Archivo de configuración
|
||||
debug-file = Archivo de debug
|
||||
wish-url = Abrir historial de deseos
|
||||
about = Acerca de
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = Impossible d'ouvrir l'emplacement du jeu
|
|||
config-file-opening-error = Impossible d'ouvrir le fichier de configuration
|
||||
debug-file-opening-error = Impossible d'ouvrir le fichier débug
|
||||
|
||||
wish-url-search-failed = No wishes url found
|
||||
wish-url-opening-error = Could not open wishes url
|
||||
|
||||
game-launching-failed = Impossible de lancer le jeu
|
||||
failed-get-selected-wine = Impossible de récupérer la version de wine sélectionnée
|
||||
downloading-failed = Le téléchargement a échoué
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = Mise à jour du jeu disponible en pré-télécharge
|
|||
game-update-available = Mise à jour du jeu disponible : {$old} -> {$new}
|
||||
game-outdated = La version du jeu installée est trop ancienne et ne peut pas être mise à jour. Dernière version : {$latest}
|
||||
|
||||
patch-version = Version du patch
|
||||
player-patch-version = Player patch version
|
||||
player-patch-version-description = Main patch that lets you play the game on Linux
|
||||
|
||||
xlua-patch-version = Xlua patch version
|
||||
xlua-patch-version-description = Additional patch that fixes some issues and improves performance on low-end PCs
|
||||
|
||||
patch-not-available = patch non disponible
|
||||
patch-not-available-tooltip = Impossible d'accéder aux serveurs de patch
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = préparation
|
|||
patch-preparation-tooltip = Le patch est en développement
|
||||
|
||||
patch-testing-tooltip = Patch de test disponible
|
||||
patch-not-applied-tooltip = Patch is not applied
|
||||
|
||||
apply-xlua-patch = Apply xlua patch
|
||||
|
||||
ask-superuser-permissions = Ask superuser permissions
|
||||
ask-superuser-permissions-description = Launcher will use them to automatically update your hosts file. This is not needed in flatpak edition
|
||||
|
||||
selected-version = Version sélectionnée
|
||||
recommended-only = Versions recommandées uniquement
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = Dossier du launcher
|
|||
game-folder = Dossier du jeu
|
||||
config-file = Fichier de configuration
|
||||
debug-file = Fichier débug
|
||||
wish-url = Open wishes
|
||||
about = À propos
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = Не удалось открыть папку игр
|
|||
config-file-opening-error = Не удалось открыть файл настроек
|
||||
debug-file-opening-error = Не удалось открыть файл отладки
|
||||
|
||||
wish-url-search-failed = Ссылка на историю молитв не найдена
|
||||
wish-url-opening-error = Не удалось открыть ссылку с историей молитв
|
||||
|
||||
game-launching-failed = Не удалось запустить игру
|
||||
failed-get-selected-wine = Не удалось найти выбранную версию Wine
|
||||
downloading-failed = Ошибка загрузки
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = Доступна предзагрузка обно
|
|||
game-update-available = Доступно обновление игры: {$old} -> {$new}
|
||||
game-outdated = Версия игры слишком устаревшая и не может быть обновлена. Последняя версия: {$latest}
|
||||
|
||||
patch-version = Версия патча
|
||||
player-patch-version = Версия основного патча
|
||||
player-patch-version-description = Основной патч, позволяющий вам играть в игру на линуксе
|
||||
|
||||
xlua-patch-version = Версия патча xlua
|
||||
xlua-patch-version-description = Дополнительный патч, устраняющий некоторые проблемы и улучшающий производительность на слабых ПК
|
||||
|
||||
patch-not-available = недоступен
|
||||
patch-not-available-tooltip = Серверы патча недоступны
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = подготовка
|
|||
patch-preparation-tooltip = Патч в разработке
|
||||
|
||||
patch-testing-tooltip = Доступна тестовая версия патча
|
||||
patch-not-applied-tooltip = Патч не применен
|
||||
|
||||
apply-xlua-patch = Применить патч xlua
|
||||
|
||||
ask-superuser-permissions = Запрашивать права суперпользователя
|
||||
ask-superuser-permissions-description = Лаунчер будет использовать их чтобы автоматически обновлять ваш hosts файл. Это не требуется при использовании Flatpak
|
||||
|
||||
selected-version = Выбранная версия
|
||||
recommended-only = Только рекомендуемое
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = Папка лаунчера
|
|||
game-folder = Папка игры
|
||||
config-file = Файл настроек
|
||||
debug-file = Журнал отладки
|
||||
wish-url = История молитв
|
||||
about = О программе
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = Oyun dosyasını açma başarısız oldu
|
|||
config-file-opening-error = Config dosyasını açma başarısız oldu
|
||||
debug-file-opening-error = Debug dosyasını açma başarısız oldu
|
||||
|
||||
wish-url-search-failed = Dilekler urlsi bulunamadı
|
||||
wish-url-opening-error = Dilekler urlsi açılamadı
|
||||
|
||||
game-launching-failed = Oyunu açma başarısız oldu
|
||||
failed-get-selected-wine = Seçilen Wine versiyonunu alma başarısız oldu
|
||||
downloading-failed = İndirme başarısız oldu
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = Güncelleme önceden indirilebilir: {$old} -> {$new
|
|||
game-update-available = Güncelleme mevcut: {$old} -> {$new}
|
||||
game-outdated = Oyun çok eski bu yüzden güncellenemez. En son sürüm: {$latest}
|
||||
|
||||
patch-version = Yama versiyonu
|
||||
player-patch-version = Ana yama versiyonu
|
||||
player-patch-version-description = Oyunu linuxda oynamanıza izin veren ana yama
|
||||
|
||||
xlua-patch-version = Xlua yama versiyonu
|
||||
xlua-patch-version-description = Eski bilgisayarlarda performansı arttıran ve bir kaç sorunu düzelten ekstra yama
|
||||
|
||||
patch-not-available = Mevcut değil
|
||||
patch-not-available-tooltip = Yama sunucularına erişelemiyor
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = Hazırlık
|
|||
patch-preparation-tooltip = Yama hala geliştiriliyor
|
||||
|
||||
patch-testing-tooltip = Test yaması mevcut
|
||||
patch-not-applied-tooltip = Yama uygulanmamış
|
||||
|
||||
apply-xlua-patch = Xlua yamasını uygula
|
||||
|
||||
ask-superuser-permissions = Yönetici izinlerini sor
|
||||
ask-superuser-permissions-description = İstemci yöneticini iznini hostunuzun dosyalarını güncellemek için otomatik olarak kullanıcaktır. Buna flatpak versiyonunda gerek yoktur
|
||||
|
||||
selected-version = Seçilmiş versiyon
|
||||
recommended-only = Sadece önerilenler
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = İstemci dosyası
|
|||
game-folder = Oyun dosyası
|
||||
config-file = Config dosyaso
|
||||
debug-file = Debug dosyası
|
||||
wish-url = Dilekleri aç
|
||||
about = Hakkında
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ game-folder-opening-error = 打开游戏文件夹失败
|
|||
config-file-opening-error = 打开配置文件失败
|
||||
debug-file-opening-error = 打开调试文件失败
|
||||
|
||||
wish-url-search-failed = No wishes url found
|
||||
wish-url-opening-error = Could not open wishes url
|
||||
|
||||
game-launching-failed = 启动游戏失败
|
||||
failed-get-selected-wine = 选择 Wine 版本失败
|
||||
downloading-failed = 下载失败
|
||||
|
|
|
@ -24,7 +24,11 @@ game-predownload-available = 可以预下载游戏更新: {$old} -> {$new}
|
|||
game-update-available = 游戏版本更新: {$old} -> {$new}
|
||||
game-outdated = 游戏版本过旧,无法更新。最新版本: {$latest}
|
||||
|
||||
patch-version = 补丁版本
|
||||
player-patch-version = Player patch version
|
||||
player-patch-version-description = Main patch that lets you play the game on Linux
|
||||
|
||||
xlua-patch-version = Xlua patch version
|
||||
xlua-patch-version-description = Additional patch that fixes some issues and improves performance on low-end PCs
|
||||
|
||||
patch-not-available = 不可用
|
||||
patch-not-available-tooltip = 无法连接补丁服务器
|
||||
|
@ -36,6 +40,12 @@ patch-preparation = 开发中
|
|||
patch-preparation-tooltip = 补丁还在开发中
|
||||
|
||||
patch-testing-tooltip = 有测试版补丁可用
|
||||
patch-not-applied-tooltip = Patch is not applied
|
||||
|
||||
apply-xlua-patch = Apply xlua patch
|
||||
|
||||
ask-superuser-permissions = Ask superuser permissions
|
||||
ask-superuser-permissions-description = Launcher will use them to automatically update your hosts file. This is not needed in flatpak edition
|
||||
|
||||
selected-version = 选择版本
|
||||
recommended-only = 仅显示推荐版本
|
||||
|
|
|
@ -12,6 +12,7 @@ launcher-folder = 启动器文件夹
|
|||
game-folder = 游戏文件夹
|
||||
config-file = 配置文件
|
||||
debug-file = 调试文件
|
||||
wish-url = Open wishes
|
||||
about = 关于
|
||||
|
||||
|
||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -142,13 +142,14 @@ fn main() {
|
|||
}}
|
||||
|
||||
window.classic-style progressbar {{
|
||||
background-color: #00000040;
|
||||
background-color: #00000020;
|
||||
border-radius: 16px;
|
||||
padding: 8px 16px;
|
||||
}}
|
||||
|
||||
window.classic-style progressbar:hover {{
|
||||
background-color: #00000090;
|
||||
background-color: #00000060;
|
||||
color: #ffffff;
|
||||
transition-duration: 0.5s;
|
||||
transition-timing-function: linear;
|
||||
}}
|
||||
|
@ -168,11 +169,12 @@ fn main() {
|
|||
|
||||
tracing::info!("Set UI language to {}", i18n::get_lang());
|
||||
|
||||
// Create the app
|
||||
let app = RelmApp::new(APP_ID);
|
||||
|
||||
// Run FirstRun window if .first-run file persist
|
||||
if FIRST_RUN_FILE.exists() {
|
||||
// Create the app
|
||||
let app = RelmApp::new(APP_ID);
|
||||
|
||||
// Show first run window
|
||||
app.run::<FirstRunApp>(());
|
||||
}
|
||||
|
||||
|
@ -190,7 +192,8 @@ fn main() {
|
|||
}
|
||||
|
||||
LauncherState::PredownloadAvailable { .. } |
|
||||
LauncherState::PatchAvailable(Patch::NotAvailable) => {
|
||||
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) |
|
||||
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) => {
|
||||
if just_run_game {
|
||||
anime_launcher_sdk::game::run().expect("Failed to run the game");
|
||||
|
||||
|
@ -202,6 +205,10 @@ fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
// Create the app
|
||||
let app = RelmApp::new(APP_ID);
|
||||
|
||||
// Show main window
|
||||
app.run::<App>(());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ lazy_static::lazy_static! {
|
|||
static ref CURL_INFO: curl_sys::Version = curl_sys::Version::get();
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AboutDialog {
|
||||
visible: bool
|
||||
}
|
||||
|
|
|
@ -151,10 +151,6 @@ impl SimpleAsyncComponent for ComponentVersion {
|
|||
installer.set_temp_folder(temp);
|
||||
}
|
||||
|
||||
installer.downloader
|
||||
.set_downloading_speed(config.launcher.speed_limit)
|
||||
.expect("Failed to set downloading speed limit");
|
||||
|
||||
self.state = VersionState::Downloading;
|
||||
|
||||
let progress_bar_sender = self.progress_bar.sender().clone();
|
||||
|
|
|
@ -239,7 +239,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
|
|||
patch: CONFIG.patch.path.clone(),
|
||||
|
||||
#[allow(clippy::or_fun_call)]
|
||||
temp: CONFIG.launcher.temp.clone().unwrap_or(PathBuf::from("/tmp"))
|
||||
temp: CONFIG.launcher.temp.clone().unwrap_or(std::env::temp_dir())
|
||||
};
|
||||
|
||||
let widgets = view_output!();
|
||||
|
|
|
@ -16,11 +16,9 @@ use crate::ui::components::*;
|
|||
use crate::i18n::*;
|
||||
use crate::*;
|
||||
|
||||
fn get_installer(uri: &str, temp: Option<&PathBuf>, speed_limit: u64) -> anyhow::Result<Installer> {
|
||||
fn get_installer(uri: &str, temp: Option<&PathBuf>) -> anyhow::Result<Installer> {
|
||||
let mut installer = Installer::new(uri)?;
|
||||
|
||||
installer.downloader.set_downloading_speed(speed_limit)?;
|
||||
|
||||
if let Some(temp) = temp {
|
||||
installer.set_temp_folder(temp);
|
||||
}
|
||||
|
@ -374,7 +372,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
tracing::info!("Installing wine: {}", wine.name);
|
||||
|
||||
// Install wine
|
||||
match get_installer(&wine.uri, config.launcher.temp.as_ref(), config.launcher.speed_limit) {
|
||||
match get_installer(&wine.uri, config.launcher.temp.as_ref()) {
|
||||
Ok(mut installer) => {
|
||||
// Create wine builds folder
|
||||
if config.game.wine.builds.exists() {
|
||||
|
@ -495,7 +493,7 @@ impl SimpleAsyncComponent for DownloadComponentsApp {
|
|||
// Install DXVK
|
||||
tracing::info!("Installing DXVK: {}", dxvk.name);
|
||||
|
||||
match get_installer(&dxvk.uri, config.launcher.temp.as_ref(), config.launcher.speed_limit) {
|
||||
match get_installer(&dxvk.uri, config.launcher.temp.as_ref()) {
|
||||
Ok(mut installer) => {
|
||||
let progress_bar_input = progress_bar_input.clone();
|
||||
let sender = sender.clone();
|
||||
|
|
|
@ -227,9 +227,9 @@ impl SimpleComponent for FirstRunApp {
|
|||
#[allow(unused_must_use)]
|
||||
std::thread::spawn(move || {
|
||||
match components.is_sync(config.components.servers) {
|
||||
Ok(true) => (),
|
||||
Ok(Some(_)) => (),
|
||||
|
||||
Ok(false) => {
|
||||
Ok(None) => {
|
||||
for host in &CONFIG.components.servers {
|
||||
match components.sync(host) {
|
||||
Ok(true) => break,
|
||||
|
@ -280,7 +280,7 @@ impl SimpleComponent for FirstRunApp {
|
|||
FirstRunAppMsg::Toast { title, description } => unsafe {
|
||||
let toast = adw::Toast::new(&title);
|
||||
|
||||
toast.set_timeout(5);
|
||||
toast.set_timeout(4);
|
||||
|
||||
if let Some(description) = description {
|
||||
toast.set_button_label(Some(&tr("details")));
|
||||
|
@ -299,11 +299,11 @@ impl SimpleComponent for FirstRunApp {
|
|||
});
|
||||
|
||||
toast.connect_button_clicked(move |_| {
|
||||
dialog.show();
|
||||
dialog.present();
|
||||
});
|
||||
}
|
||||
|
||||
self.toast_overlay.add_toast(&toast);
|
||||
self.toast_overlay.add_toast(toast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
46
src/ui/main/apply_patch.rs
Normal file
46
src/ui/main/apply_patch.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use relm4::prelude::*;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
|
||||
use crate::*;
|
||||
use crate::i18n::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn apply_patch<T: PatchExt + Send + Sync + 'static>(sender: ComponentSender<App>, patch: T) {
|
||||
match patch.status() {
|
||||
PatchStatus::NotAvailable |
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } => unreachable!(),
|
||||
|
||||
PatchStatus::Testing { .. } |
|
||||
PatchStatus::Available { .. } => {
|
||||
sender.input(AppMsg::DisableButtons(true));
|
||||
|
||||
let config = config::get().unwrap();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut apply_patch_if_needed = true;
|
||||
|
||||
if let Err(err) = patch.apply(&config.game.path, config.patch.root) {
|
||||
tracing::error!("Failed to patch the game");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-patching-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
// Don't try to apply the patch after state updating
|
||||
// because we just failed to do it
|
||||
apply_patch_if_needed = false;
|
||||
}
|
||||
|
||||
sender.input(AppMsg::DisableButtons(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed,
|
||||
show_status_page: true
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
59
src/ui/main/create_prefix.rs
Normal file
59
src/ui/main/create_prefix.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use relm4::prelude::*;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
use anime_launcher_sdk::wincompatlib::prelude::*;
|
||||
|
||||
use crate::i18n::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn create_prefix(sender: ComponentSender<App>) {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
match config.get_selected_wine() {
|
||||
Ok(Some(wine)) => {
|
||||
sender.input(AppMsg::DisableButtons(true));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let wine = wine
|
||||
.to_wine(config.components.path, Some(config.game.wine.builds.join(&wine.name)))
|
||||
.with_prefix(&config.game.wine.prefix)
|
||||
.with_loader(WineLoader::Current)
|
||||
.with_arch(WineArch::Win64);
|
||||
|
||||
if let Err(err) = wine.update_prefix::<&str>(None) {
|
||||
tracing::error!("Failed to create wine prefix");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wine-prefix-update-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
sender.input(AppMsg::DisableButtons(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Ok(None) => {
|
||||
tracing::error!("Failed to get selected wine executable");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("failed-get-selected-wine"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to get selected wine executable: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("failed-get-selected-wine"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
71
src/ui/main/download_diff.rs
Normal file
71
src/ui/main/download_diff.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use relm4::{
|
||||
prelude::*,
|
||||
Sender
|
||||
};
|
||||
|
||||
use gtk::glib::clone;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
use anime_launcher_sdk::anime_game_core::installer::diff::VersionDiff;
|
||||
|
||||
use crate::*;
|
||||
use crate::i18n::*;
|
||||
use crate::ui::components::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn download_diff(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>, diff: VersionDiff) {
|
||||
sender.input(AppMsg::SetDownloading(true));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
let result = diff.install_to_by(config.game.path, config.launcher.temp, clone!(@strong sender => move |state| {
|
||||
match &state {
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
tracing::error!("Downloading failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("downloading-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError(err) => {
|
||||
tracing::error!("Unpacking failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("unpacking-failed"),
|
||||
description: Some(err.clone())
|
||||
});
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state));
|
||||
}));
|
||||
|
||||
let mut perform_on_download_needed = true;
|
||||
|
||||
if let Err(err) = result {
|
||||
tracing::error!("Downloading failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("downloading-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
// Don't try to download something after state updating
|
||||
// because we just failed to do it
|
||||
perform_on_download_needed = false;
|
||||
}
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: false
|
||||
});
|
||||
});
|
||||
}
|
111
src/ui/main/download_wine.rs
Normal file
111
src/ui/main/download_wine.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
use relm4::{
|
||||
prelude::*,
|
||||
Sender
|
||||
};
|
||||
|
||||
use gtk::glib::clone;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
use anime_launcher_sdk::components::wine;
|
||||
|
||||
use crate::*;
|
||||
use crate::i18n::*;
|
||||
use crate::ui::components::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) {
|
||||
let mut config = config::get().unwrap();
|
||||
|
||||
match wine::get_downloaded(&CONFIG.components.path, &config.game.wine.builds) {
|
||||
Ok(downloaded) => {
|
||||
// Select downloaded version
|
||||
if !downloaded.is_empty() {
|
||||
config.game.wine.selected = Some(downloaded[0].versions[0].name.clone());
|
||||
|
||||
config::update(config);
|
||||
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
}
|
||||
|
||||
// Or download new one if none is available
|
||||
else {
|
||||
let latest = wine::Version::latest(&CONFIG.components.path).expect("Failed to get latest wine version");
|
||||
|
||||
// Choose selected wine version or use latest available one
|
||||
let wine = match &config.game.wine.selected {
|
||||
Some(version) => match wine::Version::find_in(&config.components.path, version) {
|
||||
Ok(Some(version)) => version,
|
||||
_ => latest
|
||||
}
|
||||
|
||||
None => latest
|
||||
};
|
||||
|
||||
// Download wine version
|
||||
match Installer::new(wine.uri) {
|
||||
Ok(mut installer) => {
|
||||
if let Some(temp_folder) = &config.launcher.temp {
|
||||
installer.temp_folder = temp_folder.to_path_buf();
|
||||
}
|
||||
|
||||
sender.input(AppMsg::SetDownloading(true));
|
||||
|
||||
std::thread::spawn(clone!(@strong sender => move || {
|
||||
#[allow(unused_must_use)]
|
||||
installer.install(&config.game.wine.builds, clone!(@strong sender => move |state| {
|
||||
match &state {
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
tracing::error!("Downloading failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("downloading-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError(err) => {
|
||||
tracing::error!("Unpacking failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("unpacking-failed"),
|
||||
description: Some(err.clone())
|
||||
});
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state));
|
||||
}));
|
||||
|
||||
config.game.wine.selected = Some(wine.name.clone());
|
||||
|
||||
config::update(config);
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
Err(err) => sender.input(AppMsg::Toast {
|
||||
title: tr("wine-install-failed"),
|
||||
description: Some(err.to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => sender.input(AppMsg::Toast {
|
||||
title: tr("downloaded-wine-list-failed"),
|
||||
description: Some(err.to_string())
|
||||
})
|
||||
}
|
||||
}
|
21
src/ui/main/launch.rs
Normal file
21
src/ui/main/launch.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use relm4::prelude::*;
|
||||
|
||||
use crate::i18n::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn launch(sender: ComponentSender<App>) {
|
||||
sender.input(AppMsg::HideWindow);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = anime_launcher_sdk::game::run() {
|
||||
tracing::error!("Failed to launch game: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-launching-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
sender.input(AppMsg::ShowWindow);
|
||||
});
|
||||
}
|
|
@ -10,13 +10,16 @@ use adw::prelude::*;
|
|||
|
||||
use gtk::glib::clone;
|
||||
|
||||
mod repair_game;
|
||||
mod apply_patch;
|
||||
mod download_wine;
|
||||
mod create_prefix;
|
||||
mod download_diff;
|
||||
mod launch;
|
||||
|
||||
use anime_launcher_sdk::config::launcher::LauncherStyle;
|
||||
use anime_launcher_sdk::states::LauncherState;
|
||||
use anime_launcher_sdk::wincompatlib::prelude::*;
|
||||
use anime_launcher_sdk::components::loader::ComponentsLoader;
|
||||
use anime_launcher_sdk::components::wine;
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::*;
|
||||
use crate::i18n::*;
|
||||
|
@ -31,6 +34,7 @@ relm4::new_stateless_action!(LauncherFolder, WindowActionGroup, "launcher_folder
|
|||
relm4::new_stateless_action!(GameFolder, WindowActionGroup, "game_folder");
|
||||
relm4::new_stateless_action!(ConfigFile, WindowActionGroup, "config_file");
|
||||
relm4::new_stateless_action!(DebugFile, WindowActionGroup, "debug_file");
|
||||
relm4::new_stateless_action!(WishUrl, WindowActionGroup, "wish_url");
|
||||
|
||||
relm4::new_stateless_action!(About, WindowActionGroup, "about");
|
||||
|
||||
|
@ -58,6 +62,9 @@ pub enum AppMsg {
|
|||
/// Needed for chained executions (e.g. update one voice after another)
|
||||
perform_on_download_needed: bool,
|
||||
|
||||
/// Automatically start patch applying if possible and needed
|
||||
apply_patch_if_needed: bool,
|
||||
|
||||
/// Show status gathering progress page
|
||||
show_status_page: bool
|
||||
},
|
||||
|
@ -66,9 +73,13 @@ pub enum AppMsg {
|
|||
/// was retrieved from the API
|
||||
SetGameDiff(Option<VersionDiff>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest patch version
|
||||
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
|
||||
/// was retrieved from remote repos
|
||||
SetPatch(Option<Patch>),
|
||||
SetUnityPlayerPatch(Option<UnityPlayerPatch>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest xlua patch version
|
||||
/// was retrieved from remote repos
|
||||
SetXluaPatch(Option<XluaPatch>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the launcher state was chosen
|
||||
SetLauncherState(Option<LauncherState>),
|
||||
|
@ -80,7 +91,6 @@ pub enum AppMsg {
|
|||
DisableButtons(bool),
|
||||
|
||||
OpenPreferences,
|
||||
ClosePreferences,
|
||||
RepairGame,
|
||||
|
||||
PredownloadUpdate,
|
||||
|
@ -110,6 +120,10 @@ impl SimpleComponent for App {
|
|||
&tr("debug-file") => DebugFile,
|
||||
},
|
||||
|
||||
section! {
|
||||
&tr("wish-url") => WishUrl
|
||||
},
|
||||
|
||||
section! {
|
||||
&tr("about") => About
|
||||
}
|
||||
|
@ -313,7 +327,7 @@ impl SimpleComponent for App {
|
|||
set_sensitive: match model.state.as_ref() {
|
||||
Some(LauncherState::PredownloadAvailable { game, voices }) => {
|
||||
let config = config::get().unwrap();
|
||||
let temp = config.launcher.temp.unwrap_or_else(|| PathBuf::from("/tmp"));
|
||||
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
|
||||
|
||||
let downloaded = temp.join(game.file_name().unwrap()).exists() &&
|
||||
voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists());
|
||||
|
@ -328,7 +342,7 @@ impl SimpleComponent for App {
|
|||
set_css_classes: match model.state.as_ref() {
|
||||
Some(LauncherState::PredownloadAvailable { game, voices }) => {
|
||||
let config = config::get().unwrap();
|
||||
let temp = config.launcher.temp.unwrap_or_else(|| PathBuf::from("/tmp"));
|
||||
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
|
||||
|
||||
let downloaded = temp.join(game.file_name().unwrap()).exists() &&
|
||||
voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists());
|
||||
|
@ -356,17 +370,18 @@ impl SimpleComponent for App {
|
|||
gtk::Button {
|
||||
#[watch]
|
||||
set_label: &match model.state {
|
||||
Some(LauncherState::Launch) => tr("launch"),
|
||||
Some(LauncherState::PredownloadAvailable { .. }) => tr("launch"),
|
||||
Some(LauncherState::PatchAvailable(_)) => tr("apply-patch"),
|
||||
Some(LauncherState::WineNotInstalled) => tr("download-wine"),
|
||||
Some(LauncherState::PrefixNotExists) => tr("create-prefix"),
|
||||
Some(LauncherState::VoiceUpdateAvailable(_)) => tr("update"),
|
||||
Some(LauncherState::VoiceOutdated(_)) => tr("update"),
|
||||
Some(LauncherState::VoiceNotInstalled(_)) => tr("download"),
|
||||
Some(LauncherState::GameUpdateAvailable(_)) => tr("update"),
|
||||
Some(LauncherState::GameOutdated(_)) => tr("update"),
|
||||
Some(LauncherState::GameNotInstalled(_)) => tr("download"),
|
||||
Some(LauncherState::Launch) => tr("launch"),
|
||||
Some(LauncherState::PredownloadAvailable { .. }) => tr("launch"),
|
||||
Some(LauncherState::UnityPlayerPatchAvailable(_)) => tr("apply-patch"),
|
||||
Some(LauncherState::XluaPatchAvailable(_)) => tr("apply-patch"),
|
||||
Some(LauncherState::WineNotInstalled) => tr("download-wine"),
|
||||
Some(LauncherState::PrefixNotExists) => tr("create-prefix"),
|
||||
Some(LauncherState::VoiceUpdateAvailable(_)) => tr("update"),
|
||||
Some(LauncherState::VoiceOutdated(_)) => tr("update"),
|
||||
Some(LauncherState::VoiceNotInstalled(_)) => tr("download"),
|
||||
Some(LauncherState::GameUpdateAvailable(_)) => tr("update"),
|
||||
Some(LauncherState::GameOutdated(_)) => tr("update"),
|
||||
Some(LauncherState::GameNotInstalled(_)) => tr("download"),
|
||||
|
||||
None => String::from("...")
|
||||
},
|
||||
|
@ -376,13 +391,14 @@ impl SimpleComponent for App {
|
|||
Some(LauncherState::GameOutdated { .. }) |
|
||||
Some(LauncherState::VoiceOutdated(_)) => false,
|
||||
|
||||
Some(LauncherState::PatchAvailable(patch)) => match patch {
|
||||
Patch::NotAvailable |
|
||||
Patch::Outdated { .. } |
|
||||
Patch::Preparation { .. } => false,
|
||||
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
|
||||
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
|
||||
PatchStatus::NotAvailable |
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } => false,
|
||||
|
||||
Patch::Testing { .. } |
|
||||
Patch::Available { .. } => true
|
||||
PatchStatus::Testing { .. } |
|
||||
PatchStatus::Available { .. } => true
|
||||
},
|
||||
|
||||
Some(_) => true,
|
||||
|
@ -395,13 +411,14 @@ impl SimpleComponent for App {
|
|||
Some(LauncherState::GameOutdated { .. }) |
|
||||
Some(LauncherState::VoiceOutdated(_)) => &["warning"],
|
||||
|
||||
Some(LauncherState::PatchAvailable(patch)) => match patch {
|
||||
Patch::NotAvailable |
|
||||
Patch::Outdated { .. } |
|
||||
Patch::Preparation { .. } => &["error"],
|
||||
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
|
||||
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
|
||||
PatchStatus::NotAvailable |
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } => &["error"],
|
||||
|
||||
Patch::Testing { .. } => &["warning"],
|
||||
Patch::Available { .. } => &["suggested-action"]
|
||||
PatchStatus::Testing { .. } => &["warning"],
|
||||
PatchStatus::Available { .. } => &["suggested-action"]
|
||||
},
|
||||
|
||||
Some(_) => &["suggested-action"],
|
||||
|
@ -414,11 +431,12 @@ impl SimpleComponent for App {
|
|||
Some(LauncherState::GameOutdated { .. }) |
|
||||
Some(LauncherState::VoiceOutdated(_)) => tr("main-window--version-outdated-tooltip"),
|
||||
|
||||
Some(LauncherState::PatchAvailable(patch)) => match patch {
|
||||
Patch::NotAvailable => tr("main-window--patch-unavailable-tooltip"),
|
||||
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
|
||||
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
|
||||
PatchStatus::NotAvailable => tr("main-window--patch-unavailable-tooltip"),
|
||||
|
||||
Patch::Outdated { .. } |
|
||||
Patch::Preparation { .. } => tr("main-window--patch-outdated-tooltip"),
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } => tr("main-window--patch-outdated-tooltip"),
|
||||
|
||||
_ => String::new()
|
||||
},
|
||||
|
@ -507,7 +525,7 @@ impl SimpleComponent for App {
|
|||
|
||||
let widgets = view_output!();
|
||||
|
||||
let about_dialog_broker: MessageBroker<AboutDialog> = MessageBroker::new();
|
||||
let about_dialog_broker: MessageBroker<AboutDialogMsg> = MessageBroker::new();
|
||||
|
||||
unsafe {
|
||||
MAIN_WINDOW = Some(widgets.main_window.clone());
|
||||
|
@ -572,6 +590,64 @@ impl SimpleComponent for App {
|
|||
}
|
||||
})));
|
||||
|
||||
group.add_action::<WishUrl>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
|
||||
std::thread::spawn(clone!(@strong sender => move || {
|
||||
let web_cache = CONFIG.game.path
|
||||
.join(unsafe { anime_launcher_sdk::anime_game_core::genshin::consts::DATA_FOLDER_NAME })
|
||||
.join("webCaches/Cache/Cache_Data/data_2");
|
||||
|
||||
if !web_cache.exists() {
|
||||
tracing::error!("Couldn't find wishes URL: cache file doesn't exist");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wish-url-search-failed"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
match std::fs::read(&web_cache) {
|
||||
Ok(web_cache) => {
|
||||
let web_cache = String::from_utf8_lossy(&web_cache);
|
||||
|
||||
// https://webstatic-sea.[ho-yo-ver-se].com/[ge-nsh-in]/event/e20190909gacha-v2/index.html?......
|
||||
if let Some(url) = web_cache.lines().rev().find(|line| line.contains("gacha-v2/index.html")) {
|
||||
let url_begin_pos = url.find("https://").unwrap();
|
||||
let url_end_pos = url_begin_pos + url[url_begin_pos..].find("\0\0\0\0").unwrap();
|
||||
|
||||
if let Err(err) = open::that(format!("{}#/log", &url[url_begin_pos..url_end_pos])) {
|
||||
tracing::error!("Failed to open wishes URL: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wish-url-opening-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
tracing::error!("Couldn't find wishes URL: no url found");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wish-url-search-failed"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Couldn't find wishes URL: failed to open cache file: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wish-url-search-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
})));
|
||||
|
||||
group.add_action::<About>(&RelmAction::new_stateless(move |_| {
|
||||
about_dialog_broker.send(AboutDialogMsg::Show);
|
||||
}));
|
||||
|
@ -608,9 +684,9 @@ impl SimpleComponent for App {
|
|||
let components = ComponentsLoader::new(&CONFIG.components.path);
|
||||
|
||||
match components.is_sync(&CONFIG.components.servers) {
|
||||
Ok(true) => (),
|
||||
Ok(Some(_)) => (),
|
||||
|
||||
Ok(false) => {
|
||||
Ok(None) => {
|
||||
for host in &CONFIG.components.servers {
|
||||
match components.sync(host) {
|
||||
Ok(true) => {
|
||||
|
@ -620,6 +696,8 @@ impl SimpleComponent for App {
|
|||
title: tr("components-index-updated"),
|
||||
description: None
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(false) => continue,
|
||||
|
@ -646,6 +724,86 @@ impl SimpleComponent for App {
|
|||
}
|
||||
}
|
||||
|
||||
// Update initial patch status
|
||||
|
||||
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-patch-status")))));
|
||||
|
||||
// Sync local patch repo
|
||||
let patch = Patch::new(&CONFIG.patch.path);
|
||||
|
||||
match patch.is_sync(&CONFIG.patch.servers) {
|
||||
Ok(Some(_)) => (),
|
||||
|
||||
Ok(None) => {
|
||||
for server in &CONFIG.patch.servers {
|
||||
match patch.sync(server) {
|
||||
Ok(true) => break,
|
||||
|
||||
Ok(false) => {
|
||||
tracing::error!("Failed to sync patch folder with remote: {server}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-sync-failed"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to sync patch folder with remote: {server}: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-sync-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to compare local patch folder with remote: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-state-check-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get main UnityPlayer patch status
|
||||
sender.input(AppMsg::SetUnityPlayerPatch(match patch.unity_player_patch() {
|
||||
Ok(patch) => Some(patch),
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to fetch unity player patch info: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-info-fetching-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
}));
|
||||
|
||||
// Get additional xlua patch status
|
||||
sender.input(AppMsg::SetXluaPatch(match patch.xlua_patch() {
|
||||
Ok(patch) => Some(patch),
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to fetch xlua patch info: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-info-fetching-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
}));
|
||||
|
||||
tracing::info!("Updated patch status");
|
||||
|
||||
// Update initial game version status
|
||||
|
||||
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-game-version")))));
|
||||
|
@ -666,29 +824,10 @@ impl SimpleComponent for App {
|
|||
|
||||
tracing::info!("Updated game version status");
|
||||
|
||||
// Update initial patch status
|
||||
|
||||
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-patch-status")))));
|
||||
|
||||
sender.input(AppMsg::SetPatch(match Patch::try_fetch(&CONFIG.patch.servers, None) {
|
||||
Ok(patch) => Some(patch),
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to fetch patch info: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-info-fetching-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
None
|
||||
}
|
||||
}));
|
||||
|
||||
tracing::info!("Updated patch status");
|
||||
|
||||
// Update launcher state
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
|
||||
|
@ -708,7 +847,7 @@ impl SimpleComponent for App {
|
|||
|
||||
match msg {
|
||||
// TODO: make function from this message like with toast
|
||||
AppMsg::UpdateLauncherState { perform_on_download_needed, show_status_page } => {
|
||||
AppMsg::UpdateLauncherState { perform_on_download_needed, apply_patch_if_needed, show_status_page } => {
|
||||
if show_status_page {
|
||||
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state")))));
|
||||
} else {
|
||||
|
@ -755,19 +894,22 @@ impl SimpleComponent for App {
|
|||
} else {
|
||||
self.disabled_buttons = false;
|
||||
}
|
||||
|
||||
if perform_on_download_needed {
|
||||
if let Some(state) = state {
|
||||
match state {
|
||||
LauncherState::VoiceUpdateAvailable(_) |
|
||||
LauncherState::VoiceNotInstalled(_) |
|
||||
LauncherState::GameUpdateAvailable(_) |
|
||||
LauncherState::GameNotInstalled(_) => {
|
||||
sender.input(AppMsg::PerformAction);
|
||||
}
|
||||
|
||||
_ => ()
|
||||
|
||||
if let Some(state) = state {
|
||||
match state {
|
||||
LauncherState::VoiceUpdateAvailable(_) |
|
||||
LauncherState::VoiceNotInstalled(_) |
|
||||
LauncherState::GameUpdateAvailable(_) |
|
||||
LauncherState::GameNotInstalled(_) if perform_on_download_needed => {
|
||||
sender.input(AppMsg::PerformAction);
|
||||
}
|
||||
|
||||
LauncherState::UnityPlayerPatchAvailable(_) |
|
||||
LauncherState::XluaPatchAvailable(_) if apply_patch_if_needed => {
|
||||
sender.input(AppMsg::PerformAction);
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -778,8 +920,13 @@ impl SimpleComponent for App {
|
|||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
AppMsg::SetPatch(patch) => unsafe {
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetPatch(patch));
|
||||
AppMsg::SetUnityPlayerPatch(patch) => unsafe {
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetUnityPlayerPatch(patch));
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
AppMsg::SetXluaPatch(patch) => unsafe {
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetXluaPatch(patch));
|
||||
}
|
||||
|
||||
AppMsg::SetLauncherState(state) => {
|
||||
|
@ -803,162 +950,15 @@ impl SimpleComponent for App {
|
|||
}
|
||||
|
||||
AppMsg::OpenPreferences => unsafe {
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().show();
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().present();
|
||||
}
|
||||
|
||||
AppMsg::ClosePreferences => unsafe {
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().hide();
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
AppMsg::RepairGame => {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
let progress_bar_input = self.progress_bar.sender().clone();
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("verifying-files"))));
|
||||
|
||||
self.downloading = true;
|
||||
|
||||
std::thread::spawn(move || {
|
||||
match repairer::try_get_integrity_files(None) {
|
||||
Ok(mut files) => {
|
||||
// Add voiceovers files
|
||||
let game = Game::new(&config.game.path);
|
||||
|
||||
if let Ok(voiceovers) = game.get_voice_packages() {
|
||||
for package in voiceovers {
|
||||
if let Ok(mut voiceover_files) = repairer::try_get_voice_integrity_files(package.locale(), None) {
|
||||
files.append(&mut voiceover_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
|
||||
|
||||
let mut total = 0;
|
||||
|
||||
for file in &files {
|
||||
total += file.size;
|
||||
}
|
||||
|
||||
let median_size = total / config.launcher.repairer.threads;
|
||||
let mut i = 0;
|
||||
|
||||
let (verify_sender, verify_receiver) = std::sync::mpsc::channel();
|
||||
|
||||
for _ in 0..config.launcher.repairer.threads {
|
||||
let mut thread_files = Vec::new();
|
||||
let mut thread_files_size = 0;
|
||||
|
||||
while i < files.len() {
|
||||
thread_files.push(files[i].clone());
|
||||
|
||||
thread_files_size += files[i].size;
|
||||
i += 1;
|
||||
|
||||
if thread_files_size >= median_size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let game_path = config.game.path.clone();
|
||||
let thread_sender = verify_sender.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for file in thread_files {
|
||||
let status = if config.launcher.repairer.fast {
|
||||
file.fast_verify(&game_path)
|
||||
} else {
|
||||
file.verify(&game_path)
|
||||
};
|
||||
|
||||
thread_sender.send((file, status)).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We have [config.launcher.repairer.threads] copies of this sender + the original one
|
||||
// receiver will return Err when all the senders will be dropped.
|
||||
// [config.launcher.repairer.threads] senders will be dropped when threads will finish verifying files
|
||||
// but this one will live as long as current thread exists so we should drop it manually
|
||||
drop(verify_sender);
|
||||
|
||||
let mut broken = Vec::new();
|
||||
let mut processed = 0;
|
||||
|
||||
while let Ok((file, status)) = verify_receiver.recv() {
|
||||
processed += file.size;
|
||||
|
||||
if !status {
|
||||
broken.push(file);
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(processed, total));
|
||||
}
|
||||
|
||||
if !broken.is_empty() {
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("repairing-files"))));
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
|
||||
|
||||
tracing::warn!("Found broken files:\n{}", broken.iter().fold(String::new(), |acc, file| acc + &format!("- {}\n", file.path.to_string_lossy())));
|
||||
|
||||
let total = broken.len() as f64;
|
||||
|
||||
let is_patch_applied = match Patch::try_fetch(config.patch.servers, anime_launcher_sdk::consts::PATCH_FETCHING_TIMEOUT) {
|
||||
Ok(patch) => patch.is_applied(&config.game.path).unwrap_or(true),
|
||||
Err(_) => true
|
||||
};
|
||||
|
||||
tracing::debug!("Patch status: {}", is_patch_applied);
|
||||
|
||||
fn should_ignore(path: &Path) -> bool {
|
||||
for part in ["UnityPlayer.dll", "xlua.dll", "crashreport.exe", "upload_crash.exe", "vulkan-1.dll"] {
|
||||
if path.ends_with(part) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
for (i, file) in broken.into_iter().enumerate() {
|
||||
if !is_patch_applied || !should_ignore(&file.path) {
|
||||
tracing::debug!("Repairing: {}", file.path.to_string_lossy());
|
||||
|
||||
if let Err(err) = file.repair(&config.game.path) {
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-file-repairing-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
tracing::error!("Failed to repair game file: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(i as u64, total as u64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to get inregrity failes: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("integrity-files-getting-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
});
|
||||
}
|
||||
AppMsg::RepairGame => repair_game::repair_game(sender, self.progress_bar.sender().to_owned()),
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
AppMsg::PredownloadUpdate => {
|
||||
if let Some(LauncherState::PredownloadAvailable { game, mut voices }) = self.state.clone() {
|
||||
let tmp = config::get().unwrap().launcher.temp.unwrap_or_else(|| PathBuf::from("/tmp"));
|
||||
let tmp = config::get().unwrap().launcher.temp.unwrap_or_else(std::env::temp_dir);
|
||||
|
||||
self.downloading = true;
|
||||
|
||||
|
@ -991,6 +991,7 @@ impl SimpleComponent for App {
|
|||
sender.input(AppMsg::SetDownloading(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
});
|
||||
|
@ -999,301 +1000,22 @@ impl SimpleComponent for App {
|
|||
|
||||
AppMsg::PerformAction => unsafe {
|
||||
match self.state.as_ref().unwrap_unchecked() {
|
||||
LauncherState::PatchAvailable(Patch::NotAvailable) |
|
||||
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) |
|
||||
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) |
|
||||
LauncherState::PredownloadAvailable { .. } |
|
||||
LauncherState::Launch => {
|
||||
sender.input(AppMsg::HideWindow);
|
||||
LauncherState::Launch => launch::launch(sender),
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = anime_launcher_sdk::game::run() {
|
||||
tracing::error!("Failed to launch game: {err}");
|
||||
LauncherState::UnityPlayerPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
|
||||
LauncherState::XluaPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-launching-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
LauncherState::WineNotInstalled => download_wine::download_wine(sender, self.progress_bar.sender().to_owned()),
|
||||
|
||||
sender.input(AppMsg::ShowWindow);
|
||||
});
|
||||
}
|
||||
|
||||
LauncherState::PatchAvailable(patch) => {
|
||||
match patch.to_owned() {
|
||||
Patch::NotAvailable |
|
||||
Patch::Outdated { .. } |
|
||||
Patch::Preparation { .. } => unreachable!(),
|
||||
|
||||
Patch::Testing { version, host, .. } |
|
||||
Patch::Available { version, host, .. } => {
|
||||
self.disabled_buttons = true;
|
||||
|
||||
let config = config::get().unwrap();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let applier = PatchApplier::new(&config.patch.path);
|
||||
|
||||
let mut synced = false;
|
||||
|
||||
match applier.is_sync_with(&host) {
|
||||
Ok(true) => synced = true,
|
||||
|
||||
Ok(false) => {
|
||||
match applier.sync(&host) {
|
||||
Ok(true) => synced = true,
|
||||
|
||||
Ok(false) => {
|
||||
tracing::error!("Failed to sync patch folder with remote: {host}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-sync-failed"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to sync patch folder with remote: {host}: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-sync-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to compare local patch folder with remote: {host}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("patch-state-check-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if synced {
|
||||
if let Err(err) = applier.apply(&config.game.path, version, config.patch.root) {
|
||||
tracing::error!("Failed to patch the game using remote: {host}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-patching-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sender.input(AppMsg::DisableButtons(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LauncherState::WineNotInstalled => {
|
||||
let mut config = config::get().unwrap();
|
||||
|
||||
match wine::get_downloaded(&CONFIG.components.path, &config.game.wine.builds) {
|
||||
Ok(downloaded) => {
|
||||
// Select downloaded version
|
||||
if !downloaded.is_empty() {
|
||||
config.game.wine.selected = Some(downloaded[0].versions[0].name.clone());
|
||||
|
||||
config::update(config);
|
||||
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
}
|
||||
|
||||
// Or download new one if none is available
|
||||
else {
|
||||
let latest = wine::Version::latest(&CONFIG.components.path).expect("Failed to get latest wine version");
|
||||
|
||||
// Choose selected wine version or use latest available one
|
||||
let wine = match &config.game.wine.selected {
|
||||
Some(version) => match wine::Version::find_in(&config.components.path, version) {
|
||||
Ok(Some(version)) => version,
|
||||
_ => latest
|
||||
}
|
||||
|
||||
None => latest
|
||||
};
|
||||
|
||||
// Download wine version
|
||||
match Installer::new(wine.uri) {
|
||||
Ok(mut installer) => {
|
||||
if let Some(temp_folder) = &config.launcher.temp {
|
||||
installer.temp_folder = temp_folder.to_path_buf();
|
||||
}
|
||||
|
||||
installer.downloader
|
||||
.set_downloading_speed(config.launcher.speed_limit)
|
||||
.expect("Failed to set downloading speed limit");
|
||||
|
||||
let progress_bar_input = self.progress_bar.sender().clone();
|
||||
|
||||
self.downloading = true;
|
||||
|
||||
std::thread::spawn(clone!(@strong sender => move || {
|
||||
#[allow(unused_must_use)]
|
||||
installer.install(&config.game.wine.builds, clone!(@strong sender => move |state| {
|
||||
match &state {
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
tracing::error!("Downloading failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("downloading-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError(err) => {
|
||||
tracing::error!("Unpacking failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("unpacking-failed"),
|
||||
description: Some(err.clone())
|
||||
});
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state));
|
||||
}));
|
||||
|
||||
config.game.wine.selected = Some(wine.name.clone());
|
||||
|
||||
config::update(config);
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
Err(err) => self.toast(tr("wine-install-failed"), Some(err.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => self.toast(tr("downloaded-wine-list-failed"), Some(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
LauncherState::PrefixNotExists => {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
match config.get_selected_wine() {
|
||||
Ok(Some(wine)) => {
|
||||
sender.input(AppMsg::DisableButtons(true));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let wine = wine
|
||||
.to_wine(config.components.path, Some(config.game.wine.builds.join(&wine.name)))
|
||||
.with_prefix(&config.game.wine.prefix)
|
||||
.with_loader(WineLoader::Current)
|
||||
.with_arch(WineArch::Win64);
|
||||
|
||||
if let Err(err) = wine.update_prefix::<&str>(None) {
|
||||
tracing::error!("Failed to create wine prefix");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wine-prefix-update-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
sender.input(AppMsg::DisableButtons(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Ok(None) => {
|
||||
tracing::error!("Failed to get selected wine executable");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("failed-get-selected-wine"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to get selected wine executable: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("failed-get-selected-wine"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
LauncherState::PrefixNotExists => create_prefix::create_prefix(sender),
|
||||
|
||||
LauncherState::VoiceUpdateAvailable(diff) |
|
||||
LauncherState::VoiceNotInstalled(diff) |
|
||||
LauncherState::GameUpdateAvailable(diff) |
|
||||
LauncherState::GameNotInstalled(diff) => {
|
||||
self.downloading = true;
|
||||
|
||||
let progress_bar_input = self.progress_bar.sender().clone();
|
||||
|
||||
// TODO: add speed limit
|
||||
std::thread::spawn(clone!(@strong diff => move || {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
let result = diff.install_to_by(config.game.path, config.launcher.temp, clone!(@strong sender => move |state| {
|
||||
match &state {
|
||||
InstallerUpdate::DownloadingError(err) => {
|
||||
tracing::error!("Downloading failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("downloading-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
InstallerUpdate::UnpackingError(err) => {
|
||||
tracing::error!("Unpacking failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("unpacking-failed"),
|
||||
description: Some(err.clone())
|
||||
});
|
||||
}
|
||||
|
||||
_ => ()
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state));
|
||||
}));
|
||||
|
||||
if let Err(err) = result {
|
||||
tracing::error!("Downloading failed: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("downloading-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: true,
|
||||
show_status_page: false
|
||||
});
|
||||
}));
|
||||
}
|
||||
LauncherState::GameNotInstalled(diff) => download_diff::download_diff(sender, self.progress_bar.sender().to_owned(), diff.to_owned()),
|
||||
|
||||
LauncherState::VoiceOutdated(_) |
|
||||
LauncherState::GameOutdated(_) => ()
|
||||
|
@ -1301,11 +1023,11 @@ impl SimpleComponent for App {
|
|||
}
|
||||
|
||||
AppMsg::HideWindow => unsafe {
|
||||
MAIN_WINDOW.as_ref().unwrap_unchecked().hide();
|
||||
MAIN_WINDOW.as_ref().unwrap_unchecked().set_visible(false);
|
||||
}
|
||||
|
||||
AppMsg::ShowWindow => unsafe {
|
||||
MAIN_WINDOW.as_ref().unwrap_unchecked().show();
|
||||
MAIN_WINDOW.as_ref().unwrap_unchecked().present();
|
||||
}
|
||||
|
||||
AppMsg::Toast { title, description } => self.toast(title, description)
|
||||
|
@ -1317,7 +1039,7 @@ impl App {
|
|||
pub fn toast<T: AsRef<str>>(&mut self, title: T, description: Option<T>) {
|
||||
let toast = adw::Toast::new(title.as_ref());
|
||||
|
||||
toast.set_timeout(5);
|
||||
toast.set_timeout(4);
|
||||
|
||||
if let Some(description) = description {
|
||||
toast.set_button_label(Some(&tr("details")));
|
||||
|
@ -1340,10 +1062,10 @@ impl App {
|
|||
});
|
||||
|
||||
toast.connect_button_clicked(move |_| {
|
||||
dialog.show();
|
||||
dialog.present();
|
||||
});
|
||||
}
|
||||
|
||||
self.toast_overlay.add_toast(&toast);
|
||||
self.toast_overlay.add_toast(toast);
|
||||
}
|
||||
}
|
154
src/ui/main/repair_game.rs
Normal file
154
src/ui/main/repair_game.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use relm4::{
|
||||
prelude::*,
|
||||
Sender
|
||||
};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
|
||||
use crate::*;
|
||||
use crate::i18n::*;
|
||||
use crate::ui::components::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("verifying-files"))));
|
||||
sender.input(AppMsg::SetDownloading(true));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
match repairer::try_get_integrity_files(None) {
|
||||
Ok(mut files) => {
|
||||
// Add voiceovers files
|
||||
let game = Game::new(&config.game.path);
|
||||
|
||||
if let Ok(voiceovers) = game.get_voice_packages() {
|
||||
for package in voiceovers {
|
||||
if let Ok(mut voiceover_files) = repairer::try_get_voice_integrity_files(package.locale(), None) {
|
||||
files.append(&mut voiceover_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
|
||||
|
||||
let mut total = 0;
|
||||
|
||||
for file in &files {
|
||||
total += file.size;
|
||||
}
|
||||
|
||||
let median_size = total / config.launcher.repairer.threads;
|
||||
let mut i = 0;
|
||||
|
||||
let (verify_sender, verify_receiver) = std::sync::mpsc::channel();
|
||||
|
||||
for _ in 0..config.launcher.repairer.threads {
|
||||
let mut thread_files = Vec::new();
|
||||
let mut thread_files_size = 0;
|
||||
|
||||
while i < files.len() {
|
||||
thread_files.push(files[i].clone());
|
||||
|
||||
thread_files_size += files[i].size;
|
||||
i += 1;
|
||||
|
||||
if thread_files_size >= median_size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let game_path = config.game.path.clone();
|
||||
let thread_sender = verify_sender.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for file in thread_files {
|
||||
let status = if config.launcher.repairer.fast {
|
||||
file.fast_verify(&game_path)
|
||||
} else {
|
||||
file.verify(&game_path)
|
||||
};
|
||||
|
||||
thread_sender.send((file, status)).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We have [config.launcher.repairer.threads] copies of this sender + the original one
|
||||
// receiver will return Err when all the senders will be dropped.
|
||||
// [config.launcher.repairer.threads] senders will be dropped when threads will finish verifying files
|
||||
// but this one will live as long as current thread exists so we should drop it manually
|
||||
drop(verify_sender);
|
||||
|
||||
let mut broken = Vec::new();
|
||||
let mut processed = 0;
|
||||
|
||||
while let Ok((file, status)) = verify_receiver.recv() {
|
||||
processed += file.size;
|
||||
|
||||
if !status {
|
||||
broken.push(file);
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(processed, total));
|
||||
}
|
||||
|
||||
if !broken.is_empty() {
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("repairing-files"))));
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
|
||||
|
||||
tracing::warn!("Found broken files:\n{}", broken.iter().fold(String::new(), |acc, file| acc + &format!("- {}\n", file.path.to_string_lossy())));
|
||||
|
||||
let total = broken.len() as f64;
|
||||
|
||||
// TODO: properly handle xlua patch
|
||||
let is_patch_applied = UnityPlayerPatch::from_folder(&config.patch.path).unwrap()
|
||||
.is_applied(&config.game.path).unwrap();
|
||||
|
||||
tracing::debug!("Patch status: {}", is_patch_applied);
|
||||
|
||||
fn should_ignore(path: &Path) -> bool {
|
||||
for part in ["UnityPlayer.dll", "xlua.dll", "crashreport.exe", "upload_crash.exe", "vulkan-1.dll"] {
|
||||
if path.ends_with(part) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
for (i, file) in broken.into_iter().enumerate() {
|
||||
if !is_patch_applied || !should_ignore(&file.path) {
|
||||
tracing::debug!("Repairing: {}", file.path.to_string_lossy());
|
||||
|
||||
if let Err(err) = file.repair(&config.game.path) {
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-file-repairing-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
tracing::error!("Failed to repair game file: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(i as u64, total as u64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to get inregrity failes: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("integrity-files-getting-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
});
|
||||
}
|
|
@ -523,7 +523,7 @@ impl SimpleAsyncComponent for EnhancementsApp {
|
|||
}
|
||||
|
||||
EnhancementsAppMsg::OpenGamescope => {
|
||||
self.gamescope.widget().show();
|
||||
self.gamescope.widget().present();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use relm4::factory::AsyncFactoryVecDeque;
|
||||
use relm4::prelude::*;
|
||||
use relm4::component::*;
|
||||
use relm4::factory::*;
|
||||
|
|
|
@ -106,7 +106,8 @@ pub struct GeneralApp {
|
|||
dxvk_components: AsyncController<ComponentsList<GeneralAppMsg>>,
|
||||
|
||||
game_diff: Option<VersionDiff>,
|
||||
patch: Option<Patch>,
|
||||
unity_player_patch: Option<UnityPlayerPatch>,
|
||||
xlua_patch: Option<XluaPatch>,
|
||||
|
||||
style: LauncherStyle,
|
||||
|
||||
|
@ -129,11 +130,15 @@ pub enum GeneralAppMsg {
|
|||
/// was retrieved from the API
|
||||
SetGameDiff(Option<VersionDiff>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest patch version
|
||||
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
|
||||
/// was retrieved from remote repos
|
||||
SetPatch(Option<Patch>),
|
||||
SetUnityPlayerPatch(Option<UnityPlayerPatch>),
|
||||
|
||||
// If one ever wich to change it to accept VoiceLocale
|
||||
/// Supposed to be called automatically on app's run when the latest xlua patch version
|
||||
/// was retrieved from remote repos
|
||||
SetXluaPatch(Option<XluaPatch>),
|
||||
|
||||
// If one ever wish to change it to accept VoiceLocale
|
||||
// I'd recommend to use clone!(@strong self.locale as locale => move |_| { .. })
|
||||
// in the VoicePackage component
|
||||
AddVoicePackage(DynamicIndex),
|
||||
|
@ -368,31 +373,32 @@ impl SimpleAsyncComponent for GeneralApp {
|
|||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: &tr("patch-version"),
|
||||
set_title: &tr("player-patch-version"),
|
||||
set_subtitle: &tr("player-patch-version-description"),
|
||||
|
||||
add_suffix = >k::Label {
|
||||
#[watch]
|
||||
set_text: &match model.patch.as_ref() {
|
||||
Some(patch) => match patch {
|
||||
Patch::NotAvailable => tr("patch-not-available"),
|
||||
Patch::Outdated { current, .. } => tr_args("patch-outdated", [("current", current.to_string().into())]),
|
||||
Patch::Preparation { .. } => tr("patch-preparation"),
|
||||
Patch::Testing { version, .. } |
|
||||
Patch::Available { version, .. } => version.to_string()
|
||||
set_text: &match model.unity_player_patch.as_ref() {
|
||||
Some(patch) => match patch.status() {
|
||||
PatchStatus::NotAvailable => tr("patch-not-available"),
|
||||
PatchStatus::Outdated { current, .. } => tr_args("patch-outdated", [("current", current.to_string().into())]),
|
||||
PatchStatus::Preparation { .. } => tr("patch-preparation"),
|
||||
PatchStatus::Testing { version, .. } |
|
||||
PatchStatus::Available { version, .. } => version.to_string()
|
||||
}
|
||||
|
||||
None => String::from("?")
|
||||
},
|
||||
|
||||
#[watch]
|
||||
set_css_classes: match model.patch.as_ref() {
|
||||
Some(patch) => match patch {
|
||||
Patch::NotAvailable => &["error"],
|
||||
Patch::Outdated { .. } |
|
||||
Patch::Preparation { .. } |
|
||||
Patch::Testing { .. } => &["warning"],
|
||||
Patch::Available { .. } => unsafe {
|
||||
if let Ok(true) = model.patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) {
|
||||
set_css_classes: match model.unity_player_patch.as_ref() {
|
||||
Some(patch) => match patch.status() {
|
||||
PatchStatus::NotAvailable => &["error"],
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } |
|
||||
PatchStatus::Testing { .. } => &["warning"],
|
||||
PatchStatus::Available { .. } => unsafe {
|
||||
if let Ok(true) = model.unity_player_patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) {
|
||||
&["success"]
|
||||
} else {
|
||||
&["warning"]
|
||||
|
@ -404,20 +410,20 @@ impl SimpleAsyncComponent for GeneralApp {
|
|||
},
|
||||
|
||||
#[watch]
|
||||
set_tooltip_text: Some(&match model.patch.as_ref() {
|
||||
Some(patch) => match patch {
|
||||
Patch::NotAvailable => tr("patch-not-available-tooltip"),
|
||||
Patch::Outdated { current, latest, .. } => tr_args("patch-outdated-tooltip", [
|
||||
set_tooltip_text: Some(&match model.unity_player_patch.as_ref() {
|
||||
Some(patch) => match patch.status() {
|
||||
PatchStatus::NotAvailable => tr("patch-not-available-tooltip"),
|
||||
PatchStatus::Outdated { current, latest, .. } => tr_args("patch-outdated-tooltip", [
|
||||
("current", current.to_string().into()),
|
||||
("latest", latest.to_string().into())
|
||||
]),
|
||||
Patch::Preparation { .. } => tr("patch-preparation-tooltip"),
|
||||
Patch::Testing { .. } => tr("patch-testing-tooltip"),
|
||||
Patch::Available { .. } => unsafe {
|
||||
if let Ok(true) = model.patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) {
|
||||
PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"),
|
||||
PatchStatus::Testing { .. } => tr("patch-testing-tooltip"),
|
||||
PatchStatus::Available { .. } => unsafe {
|
||||
if let Ok(true) = model.unity_player_patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) {
|
||||
String::new()
|
||||
} else {
|
||||
tr("patch-testing-tooltip")
|
||||
tr("patch-not-applied-tooltip")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -425,6 +431,113 @@ impl SimpleAsyncComponent for GeneralApp {
|
|||
None => String::new()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: &tr("xlua-patch-version"),
|
||||
set_subtitle: &tr("xlua-patch-version-description"),
|
||||
|
||||
add_suffix = >k::Label {
|
||||
#[watch]
|
||||
set_text: &match model.xlua_patch.as_ref() {
|
||||
Some(patch) => match patch.status() {
|
||||
PatchStatus::NotAvailable => tr("patch-not-available"),
|
||||
PatchStatus::Outdated { current, .. } => tr_args("patch-outdated", [("current", current.to_string().into())]),
|
||||
PatchStatus::Preparation { .. } => tr("patch-preparation"),
|
||||
PatchStatus::Testing { version, .. } |
|
||||
PatchStatus::Available { version, .. } => version.to_string()
|
||||
}
|
||||
|
||||
None => String::from("?")
|
||||
},
|
||||
|
||||
#[watch]
|
||||
set_css_classes: match model.xlua_patch.as_ref() {
|
||||
Some(patch) => match patch.status() {
|
||||
PatchStatus::NotAvailable => &["error"],
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } |
|
||||
PatchStatus::Testing { .. } => &["warning"],
|
||||
PatchStatus::Available { .. } => unsafe {
|
||||
if let Ok(true) = model.xlua_patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) {
|
||||
&["success"]
|
||||
} else {
|
||||
&["warning"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None => &[]
|
||||
},
|
||||
|
||||
#[watch]
|
||||
set_tooltip_text: Some(&match model.xlua_patch.as_ref() {
|
||||
Some(patch) => match patch.status() {
|
||||
PatchStatus::NotAvailable => tr("patch-not-available-tooltip"),
|
||||
PatchStatus::Outdated { current, latest, .. } => tr_args("patch-outdated-tooltip", [
|
||||
("current", current.to_string().into()),
|
||||
("latest", latest.to_string().into())
|
||||
]),
|
||||
PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"),
|
||||
PatchStatus::Testing { .. } => tr("patch-testing-tooltip"),
|
||||
PatchStatus::Available { .. } => unsafe {
|
||||
if let Ok(true) = model.xlua_patch.as_ref().unwrap_unchecked().is_applied(&CONFIG.game.path) {
|
||||
String::new()
|
||||
} else {
|
||||
tr("patch-not-applied-tooltip")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None => String::new()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
adw::ActionRow {
|
||||
set_title: &tr("apply-xlua-patch"),
|
||||
|
||||
add_suffix = >k::Switch {
|
||||
set_valign: gtk::Align::Center,
|
||||
|
||||
set_state: CONFIG.patch.apply_xlua,
|
||||
|
||||
connect_state_notify[sender] => move |switch| {
|
||||
if is_ready() {
|
||||
#[allow(unused_must_use)]
|
||||
if let Ok(mut config) = config::get() {
|
||||
config.patch.apply_xlua = switch.state();
|
||||
|
||||
config::update(config);
|
||||
|
||||
sender.output(PreferencesAppMsg::UpdateLauncherState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: &tr("ask-superuser-permissions"),
|
||||
set_subtitle: &tr("ask-superuser-permissions-description"),
|
||||
|
||||
add_suffix = >k::Switch {
|
||||
set_valign: gtk::Align::Center,
|
||||
|
||||
set_state: CONFIG.patch.root,
|
||||
|
||||
connect_state_notify => |switch| {
|
||||
if is_ready() {
|
||||
if let Ok(mut config) = config::get() {
|
||||
config.patch.root = switch.state();
|
||||
|
||||
config::update(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -614,7 +727,8 @@ impl SimpleAsyncComponent for GeneralApp {
|
|||
.forward(sender.input_sender(), std::convert::identity),
|
||||
|
||||
game_diff: None,
|
||||
patch: None,
|
||||
unity_player_patch: None,
|
||||
xlua_patch: None,
|
||||
|
||||
style: CONFIG.launcher.style,
|
||||
|
||||
|
@ -661,8 +775,12 @@ impl SimpleAsyncComponent for GeneralApp {
|
|||
self.game_diff = diff;
|
||||
}
|
||||
|
||||
GeneralAppMsg::SetPatch(patch) => {
|
||||
self.patch = patch;
|
||||
GeneralAppMsg::SetUnityPlayerPatch(patch) => {
|
||||
self.unity_player_patch = patch;
|
||||
}
|
||||
|
||||
GeneralAppMsg::SetXluaPatch(patch) => {
|
||||
self.xlua_patch = patch;
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
|
@ -805,15 +923,15 @@ impl SimpleAsyncComponent for GeneralApp {
|
|||
|
||||
self.selected_dxvk_version = if let Ok(Some(selected)) = CONFIG.get_selected_dxvk() {
|
||||
let mut index = 0;
|
||||
|
||||
|
||||
for (i, version) in self.downloaded_dxvk_versions.iter().enumerate() {
|
||||
if version.name == selected.name {
|
||||
index = i;
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
index as u32
|
||||
}
|
||||
|
||||
|
|
|
@ -28,9 +28,13 @@ pub enum PreferencesAppMsg {
|
|||
/// was retrieved from the API
|
||||
SetGameDiff(Option<VersionDiff>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest patch version
|
||||
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
|
||||
/// was retrieved from remote repos
|
||||
SetPatch(Option<Patch>),
|
||||
SetUnityPlayerPatch(Option<UnityPlayerPatch>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest xlua patch version
|
||||
/// was retrieved from remote repos
|
||||
SetXluaPatch(Option<XluaPatch>),
|
||||
|
||||
SetLauncherStyle(LauncherStyle),
|
||||
|
||||
|
@ -125,8 +129,13 @@ impl SimpleAsyncComponent for PreferencesApp {
|
|||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
PreferencesAppMsg::SetPatch(patch) => {
|
||||
self.general.sender().send(GeneralAppMsg::SetPatch(patch));
|
||||
PreferencesAppMsg::SetUnityPlayerPatch(patch) => {
|
||||
self.general.sender().send(GeneralAppMsg::SetUnityPlayerPatch(patch));
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
PreferencesAppMsg::SetXluaPatch(patch) => {
|
||||
self.general.sender().send(GeneralAppMsg::SetXluaPatch(patch));
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
|
@ -138,6 +147,7 @@ impl SimpleAsyncComponent for PreferencesApp {
|
|||
PreferencesAppMsg::UpdateLauncherState => {
|
||||
sender.output(Self::Output::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: false
|
||||
});
|
||||
}
|
||||
|
@ -152,7 +162,7 @@ impl SimpleAsyncComponent for PreferencesApp {
|
|||
PreferencesAppMsg::Toast { title, description } => unsafe {
|
||||
let toast = adw::Toast::new(&title);
|
||||
|
||||
toast.set_timeout(5);
|
||||
toast.set_timeout(4);
|
||||
|
||||
if let Some(description) = description {
|
||||
toast.set_button_label(Some(&tr("details")));
|
||||
|
@ -171,11 +181,11 @@ impl SimpleAsyncComponent for PreferencesApp {
|
|||
});
|
||||
|
||||
toast.connect_button_clicked(move |_| {
|
||||
dialog.show();
|
||||
dialog.present();
|
||||
});
|
||||
}
|
||||
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().add_toast(&toast);
|
||||
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().add_toast(toast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue