diff --git a/Cargo.lock b/Cargo.lock index 72a1fd8..c88ba6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,7 @@ dependencies = [ [[package]] name = "anime-game-core" -version = "1.4.0" +version = "1.4.2" dependencies = [ "anyhow", "bzip2", @@ -76,7 +76,7 @@ dependencies = [ [[package]] name = "anime-launcher-sdk" -version = "0.5.4" +version = "0.5.5" dependencies = [ "anime-game-core", "anyhow", diff --git a/anime-launcher-sdk b/anime-launcher-sdk index 78bb708..26cc058 160000 --- a/anime-launcher-sdk +++ b/anime-launcher-sdk @@ -1 +1 @@ -Subproject commit 78bb7082472d882d68574dd05ce3c392ab7c387a +Subproject commit 26cc05815b405943faa258b59a3afd4c568b0afb diff --git a/assets/locales/de/general.ftl b/assets/locales/de/general.ftl index f86f54c..593043b 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 = 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 = 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 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 = Ausgewählte version recommended-only = Nur empfohlene 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/es/general.ftl b/assets/locales/es/general.ftl index 40be18a..1877362 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 = 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 = 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 = 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 = Versión seleccionada recommended-only = Sólo recomendadas 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/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/tr/general.ftl b/assets/locales/tr/general.ftl index 1a0377b..b5a89bf 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 = 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 = 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 = 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 = Seçilmiş versiyon recommended-only = Sadece önerilenler 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/src/main.rs b/src/main.rs index ba5309f..4a10b1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -191,7 +191,8 @@ fn main() { } LauncherState::PredownloadAvailable { .. } | - LauncherState::MainPatchAvailable(UnityPlayerPatch { status: PatchStatus::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"); diff --git a/src/ui/main/apply_patch.rs b/src/ui/main/apply_patch.rs new file mode 100644 index 0000000..bc26327 --- /dev/null +++ b/src/ui/main/apply_patch.rs @@ -0,0 +1,40 @@ +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 || { + 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()) + }); + } + + sender.input(AppMsg::DisableButtons(false)); + sender.input(AppMsg::UpdateLauncherState { + perform_on_download_needed: false, + apply_patch_if_needed: true, + 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..f77923d --- /dev/null +++ b/src/ui/main/download_diff.rs @@ -0,0 +1,66 @@ +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)); + + // TODO: add speed limit + 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)); + })); + + 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, + 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..8853d45 --- /dev/null +++ b/src/ui/main/download_wine.rs @@ -0,0 +1,115 @@ +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(); + } + + installer.downloader + .set_downloading_speed(config.launcher.speed_limit) + .expect("Failed to set downloading speed limit"); + + 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 62% rename from src/ui/main.rs rename to src/ui/main/mod.rs index 89a3718..4052468 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::*; @@ -58,6 +61,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,11 +72,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 SetUnityPlayerPatch(Option), - // TODO: xlua patch status + /// 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), @@ -357,17 +365,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::MainPatchAvailable(_)) => 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("...") }, @@ -377,7 +386,8 @@ impl SimpleComponent for App { Some(LauncherState::GameOutdated { .. }) | Some(LauncherState::VoiceOutdated(_)) => false, - Some(LauncherState::MainPatchAvailable(UnityPlayerPatch { status, .. })) => match status { + Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) | + Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status { PatchStatus::NotAvailable | PatchStatus::Outdated { .. } | PatchStatus::Preparation { .. } => false, @@ -396,7 +406,8 @@ impl SimpleComponent for App { Some(LauncherState::GameOutdated { .. }) | Some(LauncherState::VoiceOutdated(_)) => &["warning"], - Some(LauncherState::MainPatchAvailable(UnityPlayerPatch { status, .. })) => match status { + Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) | + Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status { PatchStatus::NotAvailable | PatchStatus::Outdated { .. } | PatchStatus::Preparation { .. } => &["error"], @@ -415,7 +426,8 @@ impl SimpleComponent for App { Some(LauncherState::GameOutdated { .. }) | Some(LauncherState::VoiceOutdated(_)) => tr("main-window--version-outdated-tooltip"), - Some(LauncherState::MainPatchAvailable(UnityPlayerPatch { status, .. })) => match status { + Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) | + Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status { PatchStatus::NotAvailable => tr("main-window--patch-unavailable-tooltip"), PatchStatus::Outdated { .. } | @@ -713,8 +725,8 @@ impl SimpleComponent for App { } } - // Get main patch status - let main_patch = match patch.unity_player_patch() { + // Get main UnityPlayer patch status + sender.input(AppMsg::SetUnityPlayerPatch(match patch.unity_player_patch() { Ok(patch) => Some(patch), Err(err) => { @@ -727,15 +739,30 @@ impl SimpleComponent for App { None } - }; + })); - sender.input(AppMsg::SetUnityPlayerPatch(main_patch)); + // 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 launcher state sender.input(AppMsg::UpdateLauncherState { perform_on_download_needed: false, + apply_patch_if_needed: false, show_status_page: true }); @@ -755,7 +782,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 { @@ -802,19 +829,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); + } + + _ => () } } } @@ -829,6 +859,11 @@ impl SimpleComponent for App { 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) => { self.state = state; } @@ -853,149 +888,7 @@ impl SimpleComponent for App { PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().present(); } - #[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; - - // 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)); - }); - } + AppMsg::RepairGame => repair_game::repair_game(sender, self.progress_bar.sender().to_owned()), #[allow(unused_must_use)] AppMsg::PredownloadUpdate => { @@ -1033,6 +926,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 }); }); @@ -1041,260 +935,22 @@ impl SimpleComponent for App { AppMsg::PerformAction => unsafe { match self.state.as_ref().unwrap_unchecked() { - LauncherState::MainPatchAvailable(UnityPlayerPatch { status: PatchStatus::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::MainPatchAvailable(patch) => { - let patch = patch.to_owned(); - - match patch.status() { - PatchStatus::NotAvailable | - PatchStatus::Outdated { .. } | - PatchStatus::Preparation { .. } => unreachable!(), - - PatchStatus::Testing { .. } | - PatchStatus::Available { .. } => { - self.disabled_buttons = true; - - let config = config::get().unwrap(); - - std::thread::spawn(move || { - 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()) - }); - } - - 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(_) => () 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/general.rs b/src/ui/preferences/general.rs index 4312756..ed6101d 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -107,6 +107,7 @@ pub struct GeneralApp { game_diff: Option, unity_player_patch: Option, + xlua_patch: Option, style: LauncherStyle, @@ -129,10 +130,14 @@ 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 SetUnityPlayerPatch(Option), + /// 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 wich to change it to accept VoiceLocale // I'd recommend to use clone!(@strong self.locale as locale => move |_| { .. }) // in the VoicePackage component @@ -368,7 +373,8 @@ 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] @@ -417,7 +423,7 @@ impl SimpleAsyncComponent for GeneralApp { 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); + } + } + } + } } }, @@ -615,6 +728,7 @@ impl SimpleAsyncComponent for GeneralApp { game_diff: None, unity_player_patch: None, + xlua_patch: None, style: CONFIG.launcher.style, @@ -665,6 +779,10 @@ impl SimpleAsyncComponent for GeneralApp { self.unity_player_patch = patch; } + GeneralAppMsg::SetXluaPatch(patch) => { + self.xlua_patch = patch; + } + #[allow(unused_must_use)] GeneralAppMsg::AddVoicePackage(index) => { if let Some(package) = self.voice_packages.get(index.current_index()) { diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index a05c3be..40c0d02 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -28,10 +28,14 @@ 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 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), UpdateLauncherState, @@ -129,6 +133,11 @@ impl SimpleAsyncComponent for PreferencesApp { 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)] PreferencesAppMsg::SetLauncherStyle(style) => { sender.output(Self::Output::SetLauncherStyle(style)); @@ -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 }); }