feat: added game repairing function

This commit is contained in:
Observer KRypt0n_ 2023-03-05 00:16:17 +02:00
parent e008a97822
commit 3c7eba4d79
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
10 changed files with 209 additions and 11 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -42,6 +42,8 @@ loading-launcher-state--patch = Загрузка статуса лаунчера
checking-free-space = Проверка свободного места
downloading = Загрузка
unpacking = Распаковка
verifying-files = Проверка файлов
repairing-files = Починка файлов
launch = Запустить

View file

@ -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<Option<String>>),
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();
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);
}
}
}
AppMsg::DisableButtons(state) => {
self.disabled_buttons = state;
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;
}
}

View file

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

View file

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