mirror of
https://github.com/an-anime-team/sleepy-launcher.git
synced 2024-11-22 21:02:44 +03:00
feat(ui): initial xlua patch implementation
This commit is contained in:
parent
ddc122d631
commit
907361df7b
19 changed files with 751 additions and 441 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 78bb7082472d882d68574dd05ce3c392ab7c387a
|
||||
Subproject commit 26cc05815b405943faa258b59a3afd4c568b0afb
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = Только рекомендуемое
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = 仅显示推荐版本
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
40
src/ui/main/apply_patch.rs
Normal file
40
src/ui/main/apply_patch.rs
Normal file
|
@ -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<T: PatchExt + Send + Sync + 'static>(sender: ComponentSender<App>, patch: T) {
|
||||
match patch.status() {
|
||||
PatchStatus::NotAvailable |
|
||||
PatchStatus::Outdated { .. } |
|
||||
PatchStatus::Preparation { .. } => unreachable!(),
|
||||
|
||||
PatchStatus::Testing { .. } |
|
||||
PatchStatus::Available { .. } => {
|
||||
sender.input(AppMsg::DisableButtons(true));
|
||||
|
||||
let config = config::get().unwrap();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
59
src/ui/main/create_prefix.rs
Normal file
59
src/ui/main/create_prefix.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use relm4::prelude::*;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
use anime_launcher_sdk::wincompatlib::prelude::*;
|
||||
|
||||
use crate::i18n::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn create_prefix(sender: ComponentSender<App>) {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
match config.get_selected_wine() {
|
||||
Ok(Some(wine)) => {
|
||||
sender.input(AppMsg::DisableButtons(true));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let wine = wine
|
||||
.to_wine(config.components.path, Some(config.game.wine.builds.join(&wine.name)))
|
||||
.with_prefix(&config.game.wine.prefix)
|
||||
.with_loader(WineLoader::Current)
|
||||
.with_arch(WineArch::Win64);
|
||||
|
||||
if let Err(err) = wine.update_prefix::<&str>(None) {
|
||||
tracing::error!("Failed to create wine prefix");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("wine-prefix-update-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
sender.input(AppMsg::DisableButtons(false));
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Ok(None) => {
|
||||
tracing::error!("Failed to get selected wine executable");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("failed-get-selected-wine"),
|
||||
description: None
|
||||
});
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to get selected wine executable: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("failed-get-selected-wine"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
66
src/ui/main/download_diff.rs
Normal file
66
src/ui/main/download_diff.rs
Normal file
|
@ -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<App>, progress_bar_input: Sender<ProgressBarMsg>, 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
|
||||
});
|
||||
});
|
||||
}
|
115
src/ui/main/download_wine.rs
Normal file
115
src/ui/main/download_wine.rs
Normal file
|
@ -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<App>, progress_bar_input: Sender<ProgressBarMsg>) {
|
||||
let mut config = config::get().unwrap();
|
||||
|
||||
match wine::get_downloaded(&CONFIG.components.path, &config.game.wine.builds) {
|
||||
Ok(downloaded) => {
|
||||
// Select downloaded version
|
||||
if !downloaded.is_empty() {
|
||||
config.game.wine.selected = Some(downloaded[0].versions[0].name.clone());
|
||||
|
||||
config::update(config);
|
||||
|
||||
sender.input(AppMsg::UpdateLauncherState {
|
||||
perform_on_download_needed: false,
|
||||
apply_patch_if_needed: false,
|
||||
show_status_page: true
|
||||
});
|
||||
}
|
||||
|
||||
// Or download new one if none is available
|
||||
else {
|
||||
let latest = wine::Version::latest(&CONFIG.components.path).expect("Failed to get latest wine version");
|
||||
|
||||
// Choose selected wine version or use latest available one
|
||||
let wine = match &config.game.wine.selected {
|
||||
Some(version) => match wine::Version::find_in(&config.components.path, version) {
|
||||
Ok(Some(version)) => version,
|
||||
_ => latest
|
||||
}
|
||||
|
||||
None => latest
|
||||
};
|
||||
|
||||
// Download wine version
|
||||
match Installer::new(wine.uri) {
|
||||
Ok(mut installer) => {
|
||||
if let Some(temp_folder) = &config.launcher.temp {
|
||||
installer.temp_folder = temp_folder.to_path_buf();
|
||||
}
|
||||
|
||||
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())
|
||||
})
|
||||
}
|
||||
}
|
21
src/ui/main/launch.rs
Normal file
21
src/ui/main/launch.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use relm4::prelude::*;
|
||||
|
||||
use crate::i18n::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
pub fn launch(sender: ComponentSender<App>) {
|
||||
sender.input(AppMsg::HideWindow);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
if let Err(err) = anime_launcher_sdk::game::run() {
|
||||
tracing::error!("Failed to launch game: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-launching-failed"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
|
||||
sender.input(AppMsg::ShowWindow);
|
||||
});
|
||||
}
|
|
@ -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<VersionDiff>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest patch version
|
||||
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
|
||||
/// was retrieved from remote repos
|
||||
SetUnityPlayerPatch(Option<UnityPlayerPatch>),
|
||||
|
||||
// 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<XluaPatch>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the launcher state was chosen
|
||||
SetLauncherState(Option<LauncherState>),
|
||||
|
@ -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(_) => ()
|
154
src/ui/main/repair_game.rs
Normal file
154
src/ui/main/repair_game.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use relm4::{
|
||||
prelude::*,
|
||||
Sender
|
||||
};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use anime_launcher_sdk::config;
|
||||
|
||||
use crate::*;
|
||||
use crate::i18n::*;
|
||||
use crate::ui::components::*;
|
||||
use super::{App, AppMsg};
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) {
|
||||
let config = config::get().unwrap();
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("verifying-files"))));
|
||||
sender.input(AppMsg::SetDownloading(true));
|
||||
|
||||
std::thread::spawn(move || {
|
||||
match repairer::try_get_integrity_files(None) {
|
||||
Ok(mut files) => {
|
||||
// Add voiceovers files
|
||||
let game = Game::new(&config.game.path);
|
||||
|
||||
if let Ok(voiceovers) = game.get_voice_packages() {
|
||||
for package in voiceovers {
|
||||
if let Ok(mut voiceover_files) = repairer::try_get_voice_integrity_files(package.locale(), None) {
|
||||
files.append(&mut voiceover_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
|
||||
|
||||
let mut total = 0;
|
||||
|
||||
for file in &files {
|
||||
total += file.size;
|
||||
}
|
||||
|
||||
let median_size = total / config.launcher.repairer.threads;
|
||||
let mut i = 0;
|
||||
|
||||
let (verify_sender, verify_receiver) = std::sync::mpsc::channel();
|
||||
|
||||
for _ in 0..config.launcher.repairer.threads {
|
||||
let mut thread_files = Vec::new();
|
||||
let mut thread_files_size = 0;
|
||||
|
||||
while i < files.len() {
|
||||
thread_files.push(files[i].clone());
|
||||
|
||||
thread_files_size += files[i].size;
|
||||
i += 1;
|
||||
|
||||
if thread_files_size >= median_size {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let game_path = config.game.path.clone();
|
||||
let thread_sender = verify_sender.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
for file in thread_files {
|
||||
let status = if config.launcher.repairer.fast {
|
||||
file.fast_verify(&game_path)
|
||||
} else {
|
||||
file.verify(&game_path)
|
||||
};
|
||||
|
||||
thread_sender.send((file, status)).unwrap();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We have [config.launcher.repairer.threads] copies of this sender + the original one
|
||||
// receiver will return Err when all the senders will be dropped.
|
||||
// [config.launcher.repairer.threads] senders will be dropped when threads will finish verifying files
|
||||
// but this one will live as long as current thread exists so we should drop it manually
|
||||
drop(verify_sender);
|
||||
|
||||
let mut broken = Vec::new();
|
||||
let mut processed = 0;
|
||||
|
||||
while let Ok((file, status)) = verify_receiver.recv() {
|
||||
processed += file.size;
|
||||
|
||||
if !status {
|
||||
broken.push(file);
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(processed, total));
|
||||
}
|
||||
|
||||
if !broken.is_empty() {
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("repairing-files"))));
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
|
||||
|
||||
tracing::warn!("Found broken files:\n{}", broken.iter().fold(String::new(), |acc, file| acc + &format!("- {}\n", file.path.to_string_lossy())));
|
||||
|
||||
let total = broken.len() as f64;
|
||||
|
||||
// TODO: properly handle xlua patch
|
||||
let is_patch_applied = UnityPlayerPatch::from_folder(&config.patch.path).unwrap()
|
||||
.is_applied(&config.game.path).unwrap();
|
||||
|
||||
tracing::debug!("Patch status: {}", is_patch_applied);
|
||||
|
||||
fn should_ignore(path: &Path) -> bool {
|
||||
for part in ["UnityPlayer.dll", "xlua.dll", "crashreport.exe", "upload_crash.exe", "vulkan-1.dll"] {
|
||||
if path.ends_with(part) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
for (i, file) in broken.into_iter().enumerate() {
|
||||
if !is_patch_applied || !should_ignore(&file.path) {
|
||||
tracing::debug!("Repairing: {}", file.path.to_string_lossy());
|
||||
|
||||
if let Err(err) = file.repair(&config.game.path) {
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("game-file-repairing-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
|
||||
tracing::error!("Failed to repair game file: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_input.send(ProgressBarMsg::UpdateProgress(i as u64, total as u64));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to get inregrity failes: {err}");
|
||||
|
||||
sender.input(AppMsg::Toast {
|
||||
title: tr("integrity-files-getting-error"),
|
||||
description: Some(err.to_string())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sender.input(AppMsg::SetDownloading(false));
|
||||
});
|
||||
}
|
|
@ -107,6 +107,7 @@ pub struct GeneralApp {
|
|||
|
||||
game_diff: Option<VersionDiff>,
|
||||
unity_player_patch: Option<UnityPlayerPatch>,
|
||||
xlua_patch: Option<XluaPatch>,
|
||||
|
||||
style: LauncherStyle,
|
||||
|
||||
|
@ -129,10 +130,14 @@ pub enum GeneralAppMsg {
|
|||
/// was retrieved from the API
|
||||
SetGameDiff(Option<VersionDiff>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest patch version
|
||||
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
|
||||
/// was retrieved from remote repos
|
||||
SetUnityPlayerPatch(Option<UnityPlayerPatch>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest xlua patch version
|
||||
/// was retrieved from remote repos
|
||||
SetXluaPatch(Option<XluaPatch>),
|
||||
|
||||
// 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()) {
|
||||
|
|
|
@ -28,10 +28,14 @@ pub enum PreferencesAppMsg {
|
|||
/// was retrieved from the API
|
||||
SetGameDiff(Option<VersionDiff>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest patch version
|
||||
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
|
||||
/// was retrieved from remote repos
|
||||
SetUnityPlayerPatch(Option<UnityPlayerPatch>),
|
||||
|
||||
/// Supposed to be called automatically on app's run when the latest xlua patch version
|
||||
/// was retrieved from remote repos
|
||||
SetXluaPatch(Option<XluaPatch>),
|
||||
|
||||
SetLauncherStyle(LauncherStyle),
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue