diff --git a/CHANGELOG.md b/CHANGELOG.md index 2db8eaa..571ed0d 100644 --- a/CHANGELOG.md +++ b/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
-[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 diff --git a/Cargo.lock b/Cargo.lock index b53ecb1..5bf421c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 4471b21..61c8e2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/anime-launcher-sdk b/anime-launcher-sdk index f244a20..19ceddc 160000 --- a/anime-launcher-sdk +++ b/anime-launcher-sdk @@ -1 +1 @@ -Subproject commit f244a20b42dd1cf43ae7955d2a26c0b33b2ccdbe +Subproject commit 19ceddca82367514b5c806cfbc94f527ccd75167 diff --git a/assets/locales/de/errors.ftl b/assets/locales/de/errors.ftl index a3bc53f..75f49c4 100644 --- a/assets/locales/de/errors.ftl +++ b/assets/locales/de/errors.ftl @@ -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 diff --git a/assets/locales/de/general.ftl b/assets/locales/de/general.ftl index f86f54c..640ed50 100644 --- a/assets/locales/de/general.ftl +++ b/assets/locales/de/general.ftl @@ -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 diff --git a/assets/locales/de/main.ftl b/assets/locales/de/main.ftl index 6b1dcad..f58f4fb 100644 --- a/assets/locales/de/main.ftl +++ b/assets/locales/de/main.ftl @@ -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 diff --git a/assets/locales/en/errors.ftl b/assets/locales/en/errors.ftl index a6a1663..cb76fc4 100644 --- a/assets/locales/en/errors.ftl +++ b/assets/locales/en/errors.ftl @@ -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 diff --git a/assets/locales/en/general.ftl b/assets/locales/en/general.ftl index 7b8e33d..17e715d 100644 --- a/assets/locales/en/general.ftl +++ b/assets/locales/en/general.ftl @@ -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 diff --git a/assets/locales/en/main.ftl b/assets/locales/en/main.ftl index d33b85e..8f921d1 100644 --- a/assets/locales/en/main.ftl +++ b/assets/locales/en/main.ftl @@ -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 diff --git a/assets/locales/es/errors.ftl b/assets/locales/es/errors.ftl index dac5e30..4d9b9c4 100755 --- a/assets/locales/es/errors.ftl +++ b/assets/locales/es/errors.ftl @@ -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 diff --git a/assets/locales/es/general.ftl b/assets/locales/es/general.ftl index 40be18a..0d23d2d 100755 --- a/assets/locales/es/general.ftl +++ b/assets/locales/es/general.ftl @@ -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 diff --git a/assets/locales/es/main.ftl b/assets/locales/es/main.ftl index cac6f6c..975fa7f 100755 --- a/assets/locales/es/main.ftl +++ b/assets/locales/es/main.ftl @@ -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 diff --git a/assets/locales/fr/errors.ftl b/assets/locales/fr/errors.ftl index 8969e3a..5a442f8 100644 --- a/assets/locales/fr/errors.ftl +++ b/assets/locales/fr/errors.ftl @@ -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é diff --git a/assets/locales/fr/general.ftl b/assets/locales/fr/general.ftl index c41a152..3384f70 100644 --- a/assets/locales/fr/general.ftl +++ b/assets/locales/fr/general.ftl @@ -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 diff --git a/assets/locales/fr/main.ftl b/assets/locales/fr/main.ftl index 427b9ac..2244e86 100644 --- a/assets/locales/fr/main.ftl +++ b/assets/locales/fr/main.ftl @@ -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 diff --git a/assets/locales/ru/errors.ftl b/assets/locales/ru/errors.ftl index af6486e..aeae56b 100644 --- a/assets/locales/ru/errors.ftl +++ b/assets/locales/ru/errors.ftl @@ -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 = Ошибка загрузки diff --git a/assets/locales/ru/general.ftl b/assets/locales/ru/general.ftl index bfd5cbe..40bb9f0 100644 --- a/assets/locales/ru/general.ftl +++ b/assets/locales/ru/general.ftl @@ -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 = Только рекомендуемое diff --git a/assets/locales/ru/main.ftl b/assets/locales/ru/main.ftl index ae90fd4..eea1e75 100644 --- a/assets/locales/ru/main.ftl +++ b/assets/locales/ru/main.ftl @@ -12,6 +12,7 @@ launcher-folder = Папка лаунчера game-folder = Папка игры config-file = Файл настроек debug-file = Журнал отладки +wish-url = История молитв about = О программе diff --git a/assets/locales/tr/errors.ftl b/assets/locales/tr/errors.ftl index 03f853a..ac99d69 100644 --- a/assets/locales/tr/errors.ftl +++ b/assets/locales/tr/errors.ftl @@ -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 diff --git a/assets/locales/tr/general.ftl b/assets/locales/tr/general.ftl index 1a0377b..0b4a02b 100644 --- a/assets/locales/tr/general.ftl +++ b/assets/locales/tr/general.ftl @@ -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 diff --git a/assets/locales/tr/main.ftl b/assets/locales/tr/main.ftl index a69b079..67c0e45 100644 --- a/assets/locales/tr/main.ftl +++ b/assets/locales/tr/main.ftl @@ -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 diff --git a/assets/locales/zh-cn/errors.ftl b/assets/locales/zh-cn/errors.ftl index 096f0ce..bd415ce 100644 --- a/assets/locales/zh-cn/errors.ftl +++ b/assets/locales/zh-cn/errors.ftl @@ -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 = 下载失败 diff --git a/assets/locales/zh-cn/general.ftl b/assets/locales/zh-cn/general.ftl index 4d98bf1..a3b002d 100644 --- a/assets/locales/zh-cn/general.ftl +++ b/assets/locales/zh-cn/general.ftl @@ -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 = 仅显示推荐版本 diff --git a/assets/locales/zh-cn/main.ftl b/assets/locales/zh-cn/main.ftl index 76a8f08..823989c 100644 --- a/assets/locales/zh-cn/main.ftl +++ b/assets/locales/zh-cn/main.ftl @@ -12,6 +12,7 @@ launcher-folder = 启动器文件夹 game-folder = 游戏文件夹 config-file = 配置文件 debug-file = 调试文件 +wish-url = Open wishes about = 关于 diff --git a/src/main.rs b/src/main.rs index deb6c33..aa46156 100644 --- a/src/main.rs +++ b/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::(()); } @@ -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::(()); } } diff --git a/src/ui/about.rs b/src/ui/about.rs index f179815..7446fd2 100644 --- a/src/ui/about.rs +++ b/src/ui/about.rs @@ -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 } diff --git a/src/ui/components/version.rs b/src/ui/components/version.rs index f46c436..c794dab 100644 --- a/src/ui/components/version.rs +++ b/src/ui/components/version.rs @@ -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(); diff --git a/src/ui/first_run/default_paths.rs b/src/ui/first_run/default_paths.rs index 329d843..3c5d7a6 100644 --- a/src/ui/first_run/default_paths.rs +++ b/src/ui/first_run/default_paths.rs @@ -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!(); diff --git a/src/ui/first_run/download_components.rs b/src/ui/first_run/download_components.rs index 2b2cf0b..6d18701 100644 --- a/src/ui/first_run/download_components.rs +++ b/src/ui/first_run/download_components.rs @@ -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 { +fn get_installer(uri: &str, temp: Option<&PathBuf>) -> anyhow::Result { 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(); diff --git a/src/ui/first_run/main.rs b/src/ui/first_run/main.rs index 7342010..c2d0fc2 100644 --- a/src/ui/first_run/main.rs +++ b/src/ui/first_run/main.rs @@ -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); } } } diff --git a/src/ui/main/apply_patch.rs b/src/ui/main/apply_patch.rs new file mode 100644 index 0000000..6cecf8c --- /dev/null +++ b/src/ui/main/apply_patch.rs @@ -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(sender: ComponentSender, 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 + }); + }); + } + } +} diff --git a/src/ui/main/create_prefix.rs b/src/ui/main/create_prefix.rs new file mode 100644 index 0000000..30ea025 --- /dev/null +++ b/src/ui/main/create_prefix.rs @@ -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) { + 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()) + }); + } + } +} diff --git a/src/ui/main/download_diff.rs b/src/ui/main/download_diff.rs new file mode 100644 index 0000000..5aa8859 --- /dev/null +++ b/src/ui/main/download_diff.rs @@ -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, progress_bar_input: Sender, 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 + }); + }); +} diff --git a/src/ui/main/download_wine.rs b/src/ui/main/download_wine.rs new file mode 100644 index 0000000..d813764 --- /dev/null +++ b/src/ui/main/download_wine.rs @@ -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, progress_bar_input: Sender) { + 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()) + }) + } +} diff --git a/src/ui/main/launch.rs b/src/ui/main/launch.rs new file mode 100644 index 0000000..5f4f3d1 --- /dev/null +++ b/src/ui/main/launch.rs @@ -0,0 +1,21 @@ +use relm4::prelude::*; + +use crate::i18n::*; +use super::{App, AppMsg}; + +pub fn launch(sender: ComponentSender) { + 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); + }); +} diff --git a/src/ui/main.rs b/src/ui/main/mod.rs similarity index 57% rename from src/ui/main.rs rename to src/ui/main/mod.rs index 9370e20..174090d 100644 --- a/src/ui/main.rs +++ b/src/ui/main/mod.rs @@ -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), - /// 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), + SetUnityPlayerPatch(Option), + + /// Supposed to be called automatically on app's run when the latest xlua patch version + /// was retrieved from remote repos + SetXluaPatch(Option), /// Supposed to be called automatically on app's run when the launcher state was chosen SetLauncherState(Option), @@ -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 = MessageBroker::new(); + let about_dialog_broker: MessageBroker = MessageBroker::new(); unsafe { MAIN_WINDOW = Some(widgets.main_window.clone()); @@ -572,6 +590,64 @@ impl SimpleComponent for App { } }))); + group.add_action::(&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::(&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>(&mut self, title: T, description: Option) { 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); } } diff --git a/src/ui/main/repair_game.rs b/src/ui/main/repair_game.rs new file mode 100644 index 0000000..d48719b --- /dev/null +++ b/src/ui/main/repair_game.rs @@ -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, progress_bar_input: Sender) { + 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)); + }); +} diff --git a/src/ui/preferences/enhancements.rs b/src/ui/preferences/enhancements.rs index 13ce900..4c00617 100644 --- a/src/ui/preferences/enhancements.rs +++ b/src/ui/preferences/enhancements.rs @@ -523,7 +523,7 @@ impl SimpleAsyncComponent for EnhancementsApp { } EnhancementsAppMsg::OpenGamescope => { - self.gamescope.widget().show(); + self.gamescope.widget().present(); } } } diff --git a/src/ui/preferences/environment.rs b/src/ui/preferences/environment.rs index e7cbde6..19d168d 100644 --- a/src/ui/preferences/environment.rs +++ b/src/ui/preferences/environment.rs @@ -1,4 +1,3 @@ -use relm4::factory::AsyncFactoryVecDeque; use relm4::prelude::*; use relm4::component::*; use relm4::factory::*; diff --git a/src/ui/preferences/general.rs b/src/ui/preferences/general.rs index e27c0b5..79b0cce 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -106,7 +106,8 @@ pub struct GeneralApp { dxvk_components: AsyncController>, game_diff: Option, - patch: Option, + unity_player_patch: Option, + xlua_patch: Option, style: LauncherStyle, @@ -129,11 +130,15 @@ pub enum GeneralAppMsg { /// was retrieved from the API SetGameDiff(Option), - /// 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), + SetUnityPlayerPatch(Option), - // 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), + + // 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 } diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index 1eef4fc..1d232a3 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -28,9 +28,13 @@ pub enum PreferencesAppMsg { /// was retrieved from the API SetGameDiff(Option), - /// 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), + SetUnityPlayerPatch(Option), + + /// Supposed to be called automatically on app's run when the latest xlua patch version + /// was retrieved from remote repos + SetXluaPatch(Option), 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); } } }