Merge pull request #113 from an-anime-team/next

Release 3.3.0
This commit is contained in:
Observer KRypt0n_ 2023-03-24 20:32:20 +02:00 committed by GitHub
commit 8a355f4af1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 1099 additions and 649 deletions

View file

@ -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
View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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é

View file

@ -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

View file

@ -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

View file

@ -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 = Ошибка загрузки

View file

@ -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 = Только рекомендуемое

View file

@ -12,6 +12,7 @@ launcher-folder = Папка лаунчера
game-folder = Папка игры
config-file = Файл настроек
debug-file = Журнал отладки
wish-url = История молитв
about = О программе

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 = 下载失败

View file

@ -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 = 仅显示推荐版本

View file

@ -12,6 +12,7 @@ launcher-folder = 启动器文件夹
game-folder = 游戏文件夹
config-file = 配置文件
debug-file = 调试文件
wish-url = Open wishes
about = 关于

View file

@ -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>(());
}
}

View file

@ -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
}

View file

@ -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();

View file

@ -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!();

View file

@ -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();

View file

@ -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);
}
}
}

View 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
});
});
}
}
}

View 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())
});
}
}
}

View 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
});
});
}

View 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
View 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);
});
}

View file

@ -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
View 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));
});
}

View file

@ -523,7 +523,7 @@ impl SimpleAsyncComponent for EnhancementsApp {
}
EnhancementsAppMsg::OpenGamescope => {
self.gamescope.widget().show();
self.gamescope.widget().present();
}
}
}

View file

@ -1,4 +1,3 @@
use relm4::factory::AsyncFactoryVecDeque;
use relm4::prelude::*;
use relm4::component::*;
use relm4::factory::*;

View file

@ -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 = &gtk::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 = &gtk::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 = &gtk::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 = &gtk::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
}

View file

@ -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);
}
}
}