From 3c7eba4d79732cf0a0af936d7dacfaeafd173570 Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Sun, 5 Mar 2023 00:16:17 +0200 Subject: [PATCH] feat: added game repairing function --- CHANGELOG.md | 14 +++ assets/locales/de/errors.ftl | 3 + assets/locales/de/main.ftl | 2 + assets/locales/en/errors.ftl | 3 + assets/locales/en/main.ftl | 2 + assets/locales/ru/errors.ftl | 3 + assets/locales/ru/main.ftl | 2 + src/ui/main.rs | 167 ++++++++++++++++++++++++++++++++-- src/ui/preferences/general.rs | 16 +++- src/ui/preferences/main.rs | 8 ++ 10 files changed, 209 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a1a09..489793a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ 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/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- Added game repairing function + +### Fixed + +- Forced `format_lang` to return regions for language codes + +### Changed + +- Changed default language from en to en-us + ## 3.0.1 ### Fixed diff --git a/assets/locales/de/errors.ftl b/assets/locales/de/errors.ftl index 5a4e09d..36cacfb 100644 --- a/assets/locales/de/errors.ftl +++ b/assets/locales/de/errors.ftl @@ -8,6 +8,9 @@ failed-get-selected-wine = Die ausgewählte Wine version konnte nicht abgerufen downloading-failed = Herunterladen fehlgeschlagen unpacking-failed = Entpacken fehlgeschlagen +game-file-repairing-error = Failed to repair game file +integrity-files-getting-error = Failed to get integrity files + background-downloading-failed = Download des Hintergrundbildes fehlgeschlagen config-update-error = Speichern der Konfiguration fehlgeschlagen wine-prefix-update-failed = Aktualisierung des wine prefix fehlgeschlagen diff --git a/assets/locales/de/main.ftl b/assets/locales/de/main.ftl index 634a8c5..e46dce3 100644 --- a/assets/locales/de/main.ftl +++ b/assets/locales/de/main.ftl @@ -35,6 +35,8 @@ loading-launcher-state--patch = Launcher status am laden: Verifizierung des inst checking-free-space = Überprüfe Freien Speicherplatz downloading = Lade Herunter unpacking = Entpacken +verifying-files = Verifying files +repairing-files = Repairing files launch = Starten diff --git a/assets/locales/en/errors.ftl b/assets/locales/en/errors.ftl index 83fbbe7..94bf473 100644 --- a/assets/locales/en/errors.ftl +++ b/assets/locales/en/errors.ftl @@ -8,6 +8,9 @@ failed-get-selected-wine = Failed to get selected wine version downloading-failed = Downloading failed unpacking-failed = Unpacking failed +game-file-repairing-error = Failed to repair game file +integrity-files-getting-error = Failed to get integrity files + background-downloading-failed = Failed to download background picture config-update-error = Failed to save config wine-prefix-update-failed = Failed to update wine prefix diff --git a/assets/locales/en/main.ftl b/assets/locales/en/main.ftl index ba6342e..ee3c5f8 100644 --- a/assets/locales/en/main.ftl +++ b/assets/locales/en/main.ftl @@ -35,6 +35,8 @@ loading-launcher-state--patch = Loading launcher state: verifying installed patc checking-free-space = Checking free space downloading = Downloading unpacking = Unpacking +verifying-files = Verifying files +repairing-files = Repairing files launch = Launch diff --git a/assets/locales/ru/errors.ftl b/assets/locales/ru/errors.ftl index 39cce4d..a487a0a 100644 --- a/assets/locales/ru/errors.ftl +++ b/assets/locales/ru/errors.ftl @@ -8,6 +8,9 @@ failed-get-selected-wine = Не удалось найти выбранную в downloading-failed = Ошибка загрузки unpacking-failed = Ошибка распаковки +game-file-repairing-error = Не удалось починить игровой файл +integrity-files-getting-error = Не удалось получить верные данные о файлах игры + background-downloading-failed = Не удалось загрузить фоновое изображение config-update-error = Ошибка сохранения настроек wine-prefix-update-failed = Ошибка обновления префикса Wine diff --git a/assets/locales/ru/main.ftl b/assets/locales/ru/main.ftl index 696a3de..6d1efe9 100644 --- a/assets/locales/ru/main.ftl +++ b/assets/locales/ru/main.ftl @@ -42,6 +42,8 @@ loading-launcher-state--patch = Загрузка статуса лаунчера checking-free-space = Проверка свободного места downloading = Загрузка unpacking = Распаковка +verifying-files = Проверка файлов +repairing-files = Починка файлов launch = Запустить diff --git a/src/ui/main.rs b/src/ui/main.rs index 934a028..d8dd26a 100644 --- a/src/ui/main.rs +++ b/src/ui/main.rs @@ -15,6 +15,8 @@ use anime_launcher_sdk::states::LauncherState; use anime_launcher_sdk::wincompatlib::prelude::*; use anime_launcher_sdk::components::wine; +use std::path::Path; + use crate::*; use crate::i18n::*; use crate::ui::components::*; @@ -73,11 +75,13 @@ pub enum AppMsg { SetLauncherStyle(LauncherStyle), SetLoadingStatus(Option>), - OpenPreferences, - ClosePreferences, SetDownloading(bool), DisableButtons(bool), + OpenPreferences, + ClosePreferences, + RepairGame, + PredownloadUpdate, PerformAction, @@ -739,6 +743,14 @@ impl SimpleComponent for App { self.style = style; } + AppMsg::SetDownloading(state) => { + self.downloading = state; + } + + AppMsg::DisableButtons(state) => { + self.disabled_buttons = state; + } + AppMsg::OpenPreferences => unsafe { PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().show(); } @@ -747,12 +759,151 @@ impl SimpleComponent for App { PREFERENCES_WINDOW.as_ref().unwrap_unchecked().widget().hide(); } - AppMsg::SetDownloading(state) => { - self.downloading = state; - } + #[allow(unused_must_use)] + AppMsg::RepairGame => { + let config = config::get().unwrap(); - AppMsg::DisableButtons(state) => { - self.disabled_buttons = state; + 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) { + let err: std::io::Error = err.into(); + + 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)); + }); } #[allow(unused_must_use)] @@ -784,6 +935,8 @@ impl SimpleComponent for App { description: Some(err.to_string()) }); + tracing::error!("Failed to predownload update: {err}"); + break; } } diff --git a/src/ui/preferences/general.rs b/src/ui/preferences/general.rs index 95ddba3..49f15f9 100644 --- a/src/ui/preferences/general.rs +++ b/src/ui/preferences/general.rs @@ -138,6 +138,8 @@ pub enum GeneralAppMsg { RemoveVoicePackage(DynamicIndex), SetVoicePackageSensitivity(DynamicIndex, bool), + RepairGame, + UpdateLauncherStyle(LauncherStyle), WineRecommendedOnly(bool), @@ -293,16 +295,17 @@ impl SimpleAsyncComponent for GeneralApp { set_title: &tr("game-voiceovers") }, - // TODO for 3.1.0 - /*gtk::Box { + gtk::Box { set_orientation: gtk::Orientation::Horizontal, set_spacing: 8, set_margin_top: 16, gtk::Button { - set_label: &tr("repair-game") + set_label: &tr("repair-game"), + + connect_clicked => GeneralAppMsg::RepairGame } - }*/ + } }, add = &adw::PreferencesGroup { @@ -656,6 +659,11 @@ impl SimpleAsyncComponent for GeneralApp { } } + #[allow(unused_must_use)] + GeneralAppMsg::RepairGame => { + sender.output(Self::Output::RepairGame); + } + #[allow(unused_must_use)] GeneralAppMsg::UpdateLauncherStyle(style) => { if style == LauncherStyle::Classic && !KEEP_BACKGROUND_FILE.exists() { diff --git a/src/ui/preferences/main.rs b/src/ui/preferences/main.rs index 298eba1..ec641d6 100644 --- a/src/ui/preferences/main.rs +++ b/src/ui/preferences/main.rs @@ -35,6 +35,7 @@ pub enum PreferencesAppMsg { SetLauncherStyle(LauncherStyle), UpdateLauncherState, + RepairGame, Toast { title: String, @@ -141,6 +142,13 @@ impl SimpleAsyncComponent for PreferencesApp { }); } + #[allow(unused_must_use)] + PreferencesAppMsg::RepairGame => unsafe { + PREFERENCES_WINDOW.as_ref().unwrap_unchecked().close(); + + sender.output(Self::Output::RepairGame); + } + PreferencesAppMsg::Toast { title, description } => unsafe { let toast = adw::Toast::new(&title);