feat: initial changes set

This commit is contained in:
Observer KRypt0n_ 2023-04-22 23:32:08 +02:00
parent 835cb63b88
commit 81e7b9c5a7
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
20 changed files with 137 additions and 1079 deletions

6
Cargo.lock generated
View file

@ -40,8 +40,7 @@ dependencies = [
[[package]] [[package]]
name = "anime-game-core" name = "anime-game-core"
version = "1.7.2" version = "1.7.4"
source = "git+https://github.com/an-anime-team/anime-game-core?tag=1.7.2#b367ea1303c9687217024902e37dbb4e24b05f37"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bzip2", "bzip2",
@ -65,8 +64,7 @@ dependencies = [
[[package]] [[package]]
name = "anime-launcher-sdk" name = "anime-launcher-sdk"
version = "1.0.10" version = "1.0.12"
source = "git+https://github.com/an-anime-team/anime-launcher-sdk?tag=1.0.10#204369158e57056dfc64fbff62fbe6a571034913"
dependencies = [ dependencies = [
"anime-game-core", "anime-game-core",
"anyhow", "anyhow",

View file

@ -16,11 +16,11 @@ opt-level = "s"
glib-build-tools = "0.17" glib-build-tools = "0.17"
[dependencies.anime-launcher-sdk] [dependencies.anime-launcher-sdk]
git = "https://github.com/an-anime-team/anime-launcher-sdk" # git = "https://github.com/an-anime-team/anime-launcher-sdk"
tag = "1.0.10" # tag = "1.0.10"
features = ["all", "genshin"] features = ["all", "star-rail"]
# path = "../anime-launcher-sdk" # ! for dev purposes only path = "../anime-launcher-sdk" # ! for dev purposes only
[dependencies] [dependencies]
relm4 = { version = "0.6.0-alpha.2", features = ["macros", "libadwaita"] } relm4 = { version = "0.6.0-alpha.2", features = ["macros", "libadwaita"] }

View file

@ -1,13 +1,13 @@
use relm4::prelude::*; use relm4::prelude::*;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::{Config, Schema}; use anime_launcher_sdk::star_rail::config::{Config, Schema};
use anime_launcher_sdk::genshin::states::LauncherState; use anime_launcher_sdk::star_rail::states::LauncherState;
use anime_launcher_sdk::genshin::consts::launcher_dir; use anime_launcher_sdk::star_rail::consts::launcher_dir;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::genshin::prelude::*; use anime_launcher_sdk::anime_game_core::star_rail::prelude::*;
use tracing_subscriber::prelude::*; use tracing_subscriber::prelude::*;
use tracing_subscriber::filter::*; use tracing_subscriber::filter::*;
@ -22,7 +22,7 @@ pub mod ui;
use ui::main::*; use ui::main::*;
use ui::first_run::main::*; use ui::first_run::main::*;
pub const APP_ID: &str = "moe.launcher.an-anime-game-launcher"; pub const APP_ID: &str = "moe.launcher.the-honkers-railway-launcher";
pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const APP_DEBUG: bool = cfg!(debug_assertions); pub const APP_DEBUG: bool = cfg!(debug_assertions);
@ -40,7 +40,7 @@ lazy_static::lazy_static! {
/// This one is used to prepare some launcher UI components on start /// This one is used to prepare some launcher UI components on start
pub static ref CONFIG: Schema = Config::get().expect("Failed to load config"); pub static ref CONFIG: Schema = Config::get().expect("Failed to load config");
pub static ref GAME: Game = Game::new(CONFIG.game.path.for_edition(CONFIG.launcher.edition)); pub static ref GAME: Game = Game::new(&CONFIG.game.path);
/// Path to launcher folder. Standard is `$HOME/.local/share/anime-game-launcher` /// Path to launcher folder. Standard is `$HOME/.local/share/anime-game-launcher`
pub static ref LAUNCHER_FOLDER: PathBuf = launcher_dir().expect("Failed to get launcher folder"); pub static ref LAUNCHER_FOLDER: PathBuf = launcher_dir().expect("Failed to get launcher folder");
@ -162,9 +162,6 @@ fn main() {
}} }}
", BACKGROUND_FILE.to_string_lossy())); ", BACKGROUND_FILE.to_string_lossy()));
// Set game edition
CONFIG.launcher.edition.select();
// Set UI language // Set UI language
let lang = CONFIG.launcher.language.parse().expect("Wrong language format used in config"); let lang = CONFIG.launcher.language.parse().expect("Wrong language format used in config");
@ -189,16 +186,15 @@ fn main() {
match state { match state {
LauncherState::Launch => { LauncherState::Launch => {
anime_launcher_sdk::genshin::game::run().expect("Failed to run the game"); anime_launcher_sdk::star_rail::game::run().expect("Failed to run the game");
return; return;
} }
LauncherState::PredownloadAvailable { .. } | LauncherState::PredownloadAvailable { .. } |
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) | LauncherState::MainPatchAvailable(MainPatch { status: PatchStatus::NotAvailable, .. }) => {
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) => {
if just_run_game { if just_run_game {
anime_launcher_sdk::genshin::game::run().expect("Failed to run the game"); anime_launcher_sdk::star_rail::game::run().expect("Failed to run the game");
return; return;
} }

View file

@ -9,7 +9,7 @@ use gtk::glib::clone;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use std::path::PathBuf; use std::path::PathBuf;

View file

@ -21,9 +21,7 @@ pub struct DefaultPathsApp {
runners: PathBuf, runners: PathBuf,
dxvks: PathBuf, dxvks: PathBuf,
prefix: PathBuf, prefix: PathBuf,
game_global: PathBuf, game: PathBuf,
game_china: PathBuf,
fps_unlocker: PathBuf,
components: PathBuf, components: PathBuf,
patch: PathBuf, patch: PathBuf,
temp: PathBuf temp: PathBuf
@ -35,9 +33,7 @@ pub enum Folders {
Runners, Runners,
DXVK, DXVK,
Prefix, Prefix,
GameGlobal, Game,
GameChina,
FpsUnlocker,
Components, Components,
Patch, Patch,
Temp Temp
@ -151,36 +147,14 @@ impl SimpleAsyncComponent for DefaultPathsApp {
}, },
adw::ActionRow { adw::ActionRow {
set_title: &tr("global-game-installation-folder"), set_title: &tr("global-game-installation-folder"), // FIXME: locale
set_icon_name: Some("folder-symbolic"), set_icon_name: Some("folder-symbolic"),
set_activatable: true, set_activatable: true,
#[watch] #[watch]
set_subtitle: model.game_global.to_str().unwrap(), set_subtitle: model.game.to_str().unwrap(),
connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::GameGlobal) connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Game)
},
adw::ActionRow {
set_title: &tr("chinese-game-installation-folder"),
set_icon_name: Some("folder-symbolic"),
set_activatable: true,
#[watch]
set_subtitle: model.game_china.to_str().unwrap(),
connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::GameChina)
},
adw::ActionRow {
set_title: &tr("fps-unlocker-folder"),
set_icon_name: Some("folder-symbolic"),
set_activatable: true,
#[watch]
set_subtitle: model.fps_unlocker.to_str().unwrap(),
connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::FpsUnlocker)
}, },
adw::ActionRow { adw::ActionRow {
@ -298,9 +272,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
runners: CONFIG.game.wine.builds.clone(), runners: CONFIG.game.wine.builds.clone(),
dxvks: CONFIG.game.dxvk.builds.clone(), dxvks: CONFIG.game.dxvk.builds.clone(),
prefix: CONFIG.game.wine.prefix.clone(), prefix: CONFIG.game.wine.prefix.clone(),
game_global: CONFIG.game.path.global.clone(), game: CONFIG.game.path.clone(),
game_china: CONFIG.game.path.china.clone(),
fps_unlocker: CONFIG.game.enhancements.fps_unlocker.path.clone(),
components: CONFIG.components.path.clone(), components: CONFIG.components.path.clone(),
patch: CONFIG.patch.path.clone(), patch: CONFIG.patch.path.clone(),
@ -333,9 +305,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
self.runners = result.join("runners"); self.runners = result.join("runners");
self.dxvks = result.join("dxvks"); self.dxvks = result.join("dxvks");
self.prefix = result.join("prefix"); self.prefix = result.join("prefix");
self.game_global = result.join(concat!("Ge", "nshi", "n Imp", "act")); self.game = result.join(concat!("Hon", "kai Sta", "r Rail"));
self.game_china = result.join(concat!("Yu", "anS", "hen"));
self.fps_unlocker = result.join("fps-unlocker");
self.components = result.join("components"); self.components = result.join("components");
self.patch = result.join("patch"); self.patch = result.join("patch");
self.temp = result.clone(); self.temp = result.clone();
@ -346,9 +316,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
Folders::Runners => self.runners = result, Folders::Runners => self.runners = result,
Folders::DXVK => self.dxvks = result, Folders::DXVK => self.dxvks = result,
Folders::Prefix => self.prefix = result, Folders::Prefix => self.prefix = result,
Folders::GameGlobal => self.game_global = result, Folders::Game => self.game = result,
Folders::GameChina => self.game_china = result,
Folders::FpsUnlocker => self.fps_unlocker = result,
Folders::Components => self.components = result, Folders::Components => self.components = result,
Folders::Patch => self.patch = result, Folders::Patch => self.patch = result,
Folders::Temp => self.temp = result Folders::Temp => self.temp = result
@ -371,12 +339,9 @@ impl SimpleAsyncComponent for DefaultPathsApp {
(old_config.game.wine.builds, &self.runners), (old_config.game.wine.builds, &self.runners),
(old_config.game.dxvk.builds, &self.dxvks), (old_config.game.dxvk.builds, &self.dxvks),
(old_config.game.wine.prefix, &self.prefix), (old_config.game.wine.prefix, &self.prefix),
(old_config.game.path.global, &self.game_global), (old_config.game.path, &self.game),
(old_config.game.path.china, &self.game_china),
(old_config.components.path, &self.components), (old_config.components.path, &self.components),
(old_config.patch.path, &self.patch), (old_config.patch.path, &self.patch)
(old_config.game.enhancements.fps_unlocker.path, &self.fps_unlocker)
]; ];
#[allow(clippy::expect_fun_call)] #[allow(clippy::expect_fun_call)]
@ -400,7 +365,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
} }
else { else {
sender.output(Self::Output::ScrollToSelectVoiceovers); sender.output(Self::Output::ScrollToDownloadComponents);
} }
} }
@ -434,14 +399,11 @@ impl DefaultPathsApp {
config.game.wine.builds = self.runners.clone(); config.game.wine.builds = self.runners.clone();
config.game.dxvk.builds = self.dxvks.clone(); config.game.dxvk.builds = self.dxvks.clone();
config.game.wine.prefix = self.prefix.clone(); config.game.wine.prefix = self.prefix.clone();
config.game.path.global = self.game_global.clone(); config.game.path = self.game.clone();
config.game.path.china = self.game_china.clone();
config.components.path = self.components.clone(); config.components.path = self.components.clone();
config.patch.path = self.patch.clone(); config.patch.path = self.patch.clone();
config.launcher.temp = Some(self.temp.clone()); config.launcher.temp = Some(self.temp.clone());
config.game.enhancements.fps_unlocker.path = self.fps_unlocker.clone();
Config::update_raw(config) Config::update_raw(config)
} }
} }

View file

@ -10,7 +10,7 @@ use anime_launcher_sdk::components::*;
use anime_launcher_sdk::components::wine::WincompatlibWine; use anime_launcher_sdk::components::wine::WincompatlibWine;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use std::path::PathBuf; use std::path::PathBuf;

View file

@ -13,7 +13,6 @@ use super::welcome::*;
use super::tos_warning::*; use super::tos_warning::*;
use super::dependencies::*; use super::dependencies::*;
use super::default_paths::*; use super::default_paths::*;
use super::select_voiceovers::*;
use super::download_components::*; use super::download_components::*;
use super::finish::*; use super::finish::*;
@ -26,7 +25,6 @@ pub struct FirstRunApp {
tos_warning: AsyncController<TosWarningApp>, tos_warning: AsyncController<TosWarningApp>,
dependencies: AsyncController<DependenciesApp>, dependencies: AsyncController<DependenciesApp>,
default_paths: AsyncController<DefaultPathsApp>, default_paths: AsyncController<DefaultPathsApp>,
select_voiceovers: AsyncController<SelectVoiceoversApp>,
download_components: AsyncController<DownloadComponentsApp>, download_components: AsyncController<DownloadComponentsApp>,
finish: AsyncController<FinishApp>, finish: AsyncController<FinishApp>,
@ -44,7 +42,6 @@ pub enum FirstRunAppMsg {
ScrollToTosWarning, ScrollToTosWarning,
ScrollToDependencies, ScrollToDependencies,
ScrollToDefaultPaths, ScrollToDefaultPaths,
ScrollToSelectVoiceovers,
ScrollToDownloadComponents, ScrollToDownloadComponents,
ScrollToFinish, ScrollToFinish,
@ -104,7 +101,6 @@ impl SimpleComponent for FirstRunApp {
append = model.tos_warning.widget(), append = model.tos_warning.widget(),
append = model.dependencies.widget(), append = model.dependencies.widget(),
append = model.default_paths.widget(), append = model.default_paths.widget(),
append = model.select_voiceovers.widget(),
append = model.download_components.widget(), append = model.download_components.widget(),
append = model.finish.widget(), append = model.finish.widget(),
}, },
@ -148,10 +144,6 @@ impl SimpleComponent for FirstRunApp {
.launch(false) .launch(false)
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
select_voiceovers: SelectVoiceoversApp::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity),
download_components: DownloadComponentsApp::builder() download_components: DownloadComponentsApp::builder()
.launch(()) .launch(())
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
@ -209,12 +201,6 @@ impl SimpleComponent for FirstRunApp {
self.carousel.scroll_to(self.default_paths.widget(), true); self.carousel.scroll_to(self.default_paths.widget(), true);
} }
FirstRunAppMsg::ScrollToSelectVoiceovers => {
self.title = tr("select-voice-packages");
self.carousel.scroll_to(self.select_voiceovers.widget(), true);
}
FirstRunAppMsg::ScrollToDownloadComponents => { FirstRunAppMsg::ScrollToDownloadComponents => {
// Update components index // Update components index
sender.input(FirstRunAppMsg::SetLoadingStatus(Some(Some(tr("updating-components-index"))))); sender.input(FirstRunAppMsg::SetLoadingStatus(Some(Some(tr("updating-components-index")))));

View file

@ -3,6 +3,5 @@ pub mod welcome;
pub mod tos_warning; pub mod tos_warning;
pub mod dependencies; pub mod dependencies;
pub mod default_paths; pub mod default_paths;
pub mod select_voiceovers;
pub mod download_components; pub mod download_components;
pub mod finish; pub mod finish;

View file

@ -1,179 +0,0 @@
use relm4::prelude::*;
use relm4::component::*;
use adw::prelude::*;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use crate::i18n::*;
use super::main::*;
pub struct SelectVoiceoversApp {
english: gtk::Switch,
japanese: gtk::Switch,
korean: gtk::Switch,
chinese: gtk::Switch
}
#[derive(Debug, Clone)]
pub enum SelectVoiceoversAppMsg {
Continue,
Exit
}
#[relm4::component(async, pub)]
impl SimpleAsyncComponent for SelectVoiceoversApp {
type Init = ();
type Input = SelectVoiceoversAppMsg;
type Output = FirstRunAppMsg;
view! {
adw::PreferencesPage {
set_hexpand: true,
add = &adw::PreferencesGroup {
set_valign: gtk::Align::Center,
set_vexpand: true,
gtk::Label {
set_label: &tr("select-voice-packages"),
add_css_class: "title-1"
}
},
add = &adw::PreferencesGroup {
set_valign: gtk::Align::Center,
set_vexpand: true,
adw::ActionRow {
set_title: &tr("english"),
#[local_ref]
add_suffix = english -> gtk::Switch {
set_valign: gtk::Align::Center,
set_state: true
}
},
adw::ActionRow {
set_title: &tr("japanese"),
#[local_ref]
add_suffix = japanese -> gtk::Switch {
set_valign: gtk::Align::Center
}
},
adw::ActionRow {
set_title: &tr("korean"),
#[local_ref]
add_suffix = korean -> gtk::Switch {
set_valign: gtk::Align::Center
}
},
adw::ActionRow {
set_title: &tr("chinese"),
#[local_ref]
add_suffix = chinese -> gtk::Switch {
set_valign: gtk::Align::Center
}
}
},
add = &adw::PreferencesGroup {
set_valign: gtk::Align::Center,
set_vexpand: true,
gtk::Box {
set_orientation: gtk::Orientation::Horizontal,
set_halign: gtk::Align::Center,
set_spacing: 8,
gtk::Button {
set_label: &tr("continue"),
set_css_classes: &["suggested-action", "pill"],
connect_clicked => SelectVoiceoversAppMsg::Continue
},
gtk::Button {
set_label: &tr("exit"),
add_css_class: "pill",
connect_clicked => SelectVoiceoversAppMsg::Exit
}
}
}
}
}
async fn init(
_init: Self::Init,
root: Self::Root,
_sender: AsyncComponentSender<Self>,
) -> AsyncComponentParts<Self> {
let model = Self {
english: gtk::Switch::new(),
japanese: gtk::Switch::new(),
korean: gtk::Switch::new(),
chinese: gtk::Switch::new()
};
let english = &model.english;
let japanese = &model.japanese;
let korean = &model.korean;
let chinese = &model.chinese;
let widgets = view_output!();
AsyncComponentParts { model, widgets }
}
async fn update(&mut self, msg: Self::Input, sender: AsyncComponentSender<Self>) {
match msg {
#[allow(unused_must_use)]
SelectVoiceoversAppMsg::Continue => {
match self.update_config() {
Ok(_) => sender.output(Self::Output::ScrollToDownloadComponents),
Err(err) => sender.output(Self::Output::Toast {
title: tr("config-update-error"),
description: Some(err.to_string())
})
};
}
SelectVoiceoversAppMsg::Exit => relm4::main_application().quit()
}
}
}
impl SelectVoiceoversApp {
pub fn update_config(&self) -> anyhow::Result<()> {
let mut config = Config::get()?;
config.game.voices = Vec::new();
if self.english.state() {
config.game.voices.push(String::from("en-us"));
}
if self.japanese.state() {
config.game.voices.push(String::from("ja-jp"));
}
if self.korean.state() {
config.game.voices.push(String::from("ko-kr"));
}
if self.chinese.state() {
config.game.voices.push(String::from("zh-cn"));
}
Config::update_raw(config)
}
}

View file

@ -4,11 +4,10 @@ use crate::*;
use crate::i18n::*; use crate::i18n::*;
use super::{App, AppMsg}; use super::{App, AppMsg};
pub fn apply_patch<T: PatchExt + Send + Sync + 'static>(sender: ComponentSender<App>, patch: T) { pub fn apply_patch(sender: ComponentSender<App>, patch: MainPatch) {
match patch.status() { match patch.status() {
PatchStatus::NotAvailable | PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } | PatchStatus::Outdated { .. } => unreachable!(),
PatchStatus::Preparation { .. } => unreachable!(),
PatchStatus::Testing { .. } | PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => { PatchStatus::Available { .. } => {
@ -19,7 +18,7 @@ pub fn apply_patch<T: PatchExt + Send + Sync + 'static>(sender: ComponentSender<
std::thread::spawn(move || { std::thread::spawn(move || {
let mut apply_patch_if_needed = true; let mut apply_patch_if_needed = true;
if let Err(err) = patch.apply(config.game.path.for_edition(config.launcher.edition), config.patch.root) { if let Err(err) = patch.apply(config.game.path, config.patch.root) {
tracing::error!("Failed to patch the game"); tracing::error!("Failed to patch the game");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {

View file

@ -3,7 +3,7 @@ use relm4::prelude::*;
use anime_launcher_sdk::wincompatlib::prelude::*; use anime_launcher_sdk::wincompatlib::prelude::*;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use crate::i18n::*; use crate::i18n::*;
use super::{App, AppMsg}; use super::{App, AppMsg};

View file

@ -17,9 +17,8 @@ pub fn download_diff(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
std::thread::spawn(move || { std::thread::spawn(move || {
let config = Config::get().unwrap(); let config = Config::get().unwrap();
let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf();
let result = diff.install_to_by(game_path, config.launcher.temp, clone!(@strong sender => move |state| { let result = diff.install_to_by(config.game.path, config.launcher.temp, clone!(@strong sender => move |state| {
match &state { match &state {
DiffUpdate::InstallerUpdate(InstallerUpdate::DownloadingError(err)) => { DiffUpdate::InstallerUpdate(InstallerUpdate::DownloadingError(err)) => {
tracing::error!("Downloading failed: {err}"); tracing::error!("Downloading failed: {err}");

View file

@ -7,7 +7,7 @@ pub fn launch(sender: ComponentSender<App>) {
sender.input(AppMsg::HideWindow); sender.input(AppMsg::HideWindow);
std::thread::spawn(move || { std::thread::spawn(move || {
if let Err(err) = anime_launcher_sdk::genshin::game::run() { if let Err(err) = anime_launcher_sdk::star_rail::game::run() {
tracing::error!("Failed to launch game: {err}"); tracing::error!("Failed to launch game: {err}");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {

View file

@ -1,25 +0,0 @@
use relm4::prelude::*;
use std::path::PathBuf;
use crate::*;
use super::{App, AppMsg};
pub fn migrate_folder(sender: ComponentSender<App>, from: PathBuf, to: PathBuf, cleanup_folder: Option<PathBuf>) {
sender.input(AppMsg::DisableButtons(true));
std::thread::spawn(move || {
move_folder::move_folder(&from, &to).expect("Failed to perform migration");
if let Some(cleanup_folder) = cleanup_folder {
std::fs::remove_dir_all(cleanup_folder).expect("Failed to remove cleanup folder");
}
sender.input(AppMsg::DisableButtons(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed: false,
show_status_page: true
});
});
}

View file

@ -15,18 +15,17 @@ mod apply_patch;
mod download_wine; mod download_wine;
mod create_prefix; mod create_prefix;
mod download_diff; mod download_diff;
mod migrate_folder;
mod launch; mod launch;
use anime_launcher_sdk::components::loader::ComponentsLoader; use anime_launcher_sdk::components::loader::ComponentsLoader;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle; use anime_launcher_sdk::star_rail::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::genshin::states::*; use anime_launcher_sdk::star_rail::states::*;
use anime_launcher_sdk::genshin::consts::*; use anime_launcher_sdk::star_rail::consts::*;
use crate::*; use crate::*;
use crate::i18n::*; use crate::i18n::*;
@ -41,7 +40,6 @@ relm4::new_stateless_action!(LauncherFolder, WindowActionGroup, "launcher_folder
relm4::new_stateless_action!(GameFolder, WindowActionGroup, "game_folder"); relm4::new_stateless_action!(GameFolder, WindowActionGroup, "game_folder");
relm4::new_stateless_action!(ConfigFile, WindowActionGroup, "config_file"); relm4::new_stateless_action!(ConfigFile, WindowActionGroup, "config_file");
relm4::new_stateless_action!(DebugFile, WindowActionGroup, "debug_file"); relm4::new_stateless_action!(DebugFile, WindowActionGroup, "debug_file");
relm4::new_stateless_action!(WishUrl, WindowActionGroup, "wish_url");
relm4::new_stateless_action!(About, WindowActionGroup, "about"); relm4::new_stateless_action!(About, WindowActionGroup, "about");
@ -80,13 +78,9 @@ pub enum AppMsg {
/// was retrieved from the API /// was retrieved from the API
SetGameDiff(Option<VersionDiff>), SetGameDiff(Option<VersionDiff>),
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version /// Supposed to be called automatically on app's run when the latest main patch version
/// was retrieved from remote repos /// was retrieved from remote repos
SetUnityPlayerPatch(Option<UnityPlayerPatch>), SetMainPatch(Option<MainPatch>),
/// 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 /// Supposed to be called automatically on app's run when the launcher state was chosen
SetLauncherState(Option<LauncherState>), SetLauncherState(Option<LauncherState>),
@ -127,10 +121,6 @@ impl SimpleComponent for App {
&tr("debug-file") => DebugFile, &tr("debug-file") => DebugFile,
}, },
section! {
&tr("wish-url") => WishUrl
},
section! { section! {
&tr("about") => About &tr("about") => About
} }
@ -311,17 +301,13 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_tooltip_text: Some(&tr_args("predownload-update", [ set_tooltip_text: Some(&tr_args("predownload-update", [
("version", match model.state.as_ref() { ("version", match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, .. }) => game.latest().to_string(), Some(LauncherState::PredownloadAvailable(game)) => game.latest().to_string(),
_ => String::from("?") _ => String::from("?")
}.into()), }.into()),
("size", match model.state.as_ref() { ("size", match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => { Some(LauncherState::PredownloadAvailable(game)) => {
let mut size = game.size().unwrap_or((0, 0)).0; let size = game.size().unwrap_or((0, 0)).0;
for voice in voices {
size += voice.size().unwrap_or((0, 0)).0;
}
prettify_bytes(size) prettify_bytes(size)
} }
@ -335,14 +321,11 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_sensitive: match model.state.as_ref() { set_sensitive: match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => { Some(LauncherState::PredownloadAvailable(game)) => {
let config = Config::get().unwrap(); let config = Config::get().unwrap();
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir); let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
let downloaded = temp.join(game.file_name().unwrap()).exists() && !temp.join(game.file_name().unwrap()).exists()
voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists());
!downloaded
} }
_ => false _ => false
@ -350,14 +333,11 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_css_classes: match model.state.as_ref() { set_css_classes: match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => { Some(LauncherState::PredownloadAvailable(game)) => {
let config = Config::get().unwrap(); let config = Config::get().unwrap();
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir); let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
let downloaded = temp.join(game.file_name().unwrap()).exists() && if temp.join(game.file_name().unwrap()).exists() {
voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists());
if downloaded {
&["success"] &["success"]
} else { } else {
&["warning"] &["warning"]
@ -382,14 +362,9 @@ impl SimpleComponent for App {
set_label: &match model.state { set_label: &match model.state {
Some(LauncherState::Launch) => tr("launch"), Some(LauncherState::Launch) => tr("launch"),
Some(LauncherState::PredownloadAvailable { .. }) => tr("launch"), Some(LauncherState::PredownloadAvailable { .. }) => tr("launch"),
Some(LauncherState::FolderMigrationRequired { .. }) => tr("migrate-folders"), Some(LauncherState::MainPatchAvailable(_)) => tr("apply-patch"),
Some(LauncherState::UnityPlayerPatchAvailable(_)) => tr("apply-patch"),
Some(LauncherState::XluaPatchAvailable(_)) => tr("apply-patch"),
Some(LauncherState::WineNotInstalled) => tr("download-wine"), Some(LauncherState::WineNotInstalled) => tr("download-wine"),
Some(LauncherState::PrefixNotExists) => tr("create-prefix"), 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::GameUpdateAvailable(_)) => tr("update"),
Some(LauncherState::GameOutdated(_)) => tr("update"), Some(LauncherState::GameOutdated(_)) => tr("update"),
Some(LauncherState::GameNotInstalled(_)) => tr("download"), Some(LauncherState::GameNotInstalled(_)) => tr("download"),
@ -399,14 +374,11 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_sensitive: !model.disabled_buttons && match &model.state { set_sensitive: !model.disabled_buttons && match &model.state {
Some(LauncherState::GameOutdated { .. }) | Some(LauncherState::GameOutdated { .. }) => false,
Some(LauncherState::VoiceOutdated(_)) => false,
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) | Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
PatchStatus::NotAvailable | PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } | PatchStatus::Outdated { .. } => false,
PatchStatus::Preparation { .. } => false,
PatchStatus::Testing { .. } | PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => true PatchStatus::Available { .. } => true
@ -419,14 +391,11 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_css_classes: match &model.state { set_css_classes: match &model.state {
Some(LauncherState::GameOutdated { .. }) | Some(LauncherState::GameOutdated { .. }) => &["warning"],
Some(LauncherState::VoiceOutdated(_)) => &["warning"],
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) | Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
PatchStatus::NotAvailable | PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } | PatchStatus::Outdated { .. } => &["error"],
PatchStatus::Preparation { .. } => &["error"],
PatchStatus::Testing { .. } => &["warning"], PatchStatus::Testing { .. } => &["warning"],
PatchStatus::Available { .. } => &["suggested-action"] PatchStatus::Available { .. } => &["suggested-action"]
@ -439,17 +408,11 @@ impl SimpleComponent for App {
#[watch] #[watch]
set_tooltip_text: Some(&match &model.state { set_tooltip_text: Some(&match &model.state {
Some(LauncherState::GameOutdated { .. }) | Some(LauncherState::GameOutdated { .. }) => tr("main-window--version-outdated-tooltip"),
Some(LauncherState::VoiceOutdated(_)) => tr("main-window--version-outdated-tooltip"),
Some(LauncherState::FolderMigrationRequired { .. }) => tr("migrate-folders-tooltip"), Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
PatchStatus::NotAvailable => tr("main-window--patch-unavailable-tooltip"), PatchStatus::NotAvailable => tr("main-window--patch-unavailable-tooltip"),
PatchStatus::Outdated { .. } => tr("main-window--patch-outdated-tooltip"),
PatchStatus::Outdated { .. } |
PatchStatus::Preparation { .. } => tr("main-window--patch-outdated-tooltip"),
_ => String::new() _ => String::new()
}, },
@ -567,8 +530,8 @@ impl SimpleComponent for App {
group.add_action::<GameFolder>(&RelmAction::new_stateless(clone!(@strong sender => move |_| { group.add_action::<GameFolder>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
let path = match Config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path,
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.clone()
}; };
if let Err(err) = open::that(path) { if let Err(err) = open::that(path) {
@ -605,66 +568,6 @@ impl SimpleComponent for App {
} }
}))); })));
group.add_action::<WishUrl>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
std::thread::spawn(clone!(@strong sender => move || {
let config = Config::get().unwrap_or_else(|_| CONFIG.clone());
let web_cache = config.game.path.for_edition(config.launcher.edition)
.join(config.launcher.edition.data_folder())
.join("webCaches/Cache/Cache_Data/data_2");
if !web_cache.exists() {
tracing::error!("Couldn't find wishes URL: cache file doesn't exist");
sender.input(AppMsg::Toast {
title: tr("wish-url-search-failed"),
description: None
});
}
else {
match std::fs::read(&web_cache) {
Ok(web_cache) => {
let web_cache = String::from_utf8_lossy(&web_cache);
// https://webstatic-sea.[ho-yo-ver-se].com/[ge-nsh-in]/event/e20190909gacha-v2/index.html?......
if let Some(url) = web_cache.lines().rev().find(|line| line.contains("gacha-v2/index.html")) {
let url_begin_pos = url.find("https://").unwrap();
let url_end_pos = url_begin_pos + url[url_begin_pos..].find("\0\0\0\0").unwrap();
if let Err(err) = open::that(format!("{}#/log", &url[url_begin_pos..url_end_pos])) {
tracing::error!("Failed to open wishes URL: {err}");
sender.input(AppMsg::Toast {
title: tr("wish-url-opening-error"),
description: Some(err.to_string())
});
}
}
else {
tracing::error!("Couldn't find wishes URL: no url found");
sender.input(AppMsg::Toast {
title: tr("wish-url-search-failed"),
description: None
});
}
}
Err(err) => {
tracing::error!("Couldn't find wishes URL: failed to open cache file: {err}");
sender.input(AppMsg::Toast {
title: tr("wish-url-search-failed"),
description: Some(err.to_string())
});
}
}
}
}));
})));
group.add_action::<About>(&RelmAction::new_stateless(move |_| { group.add_action::<About>(&RelmAction::new_stateless(move |_| {
about_dialog_broker.send(AboutDialogMsg::Show); about_dialog_broker.send(AboutDialogMsg::Show);
})); }));
@ -783,28 +686,12 @@ impl SimpleComponent for App {
} }
} }
// Get main UnityPlayer patch status // Get the main patch status
sender.input(AppMsg::SetUnityPlayerPatch(match patch.unity_player_patch() { sender.input(AppMsg::SetMainPatch(match patch.main_patch() {
Ok(patch) => Some(patch), Ok(patch) => Some(patch),
Err(err) => { Err(err) => {
tracing::error!("Failed to fetch unity player patch info: {err}"); tracing::error!("Failed to fetch main patch info: {err}");
sender.input(AppMsg::Toast {
title: tr("patch-info-fetching-error"),
description: Some(err.to_string())
});
None
}
}));
// Get additional xlua patch status
sender.input(AppMsg::SetXluaPatch(match patch.xlua_patch() {
Ok(patch) => Some(patch),
Err(err) => {
tracing::error!("Failed to fetch xlua patch info: {err}");
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr("patch-info-fetching-error"), title: tr("patch-info-fetching-error"),
@ -874,12 +761,6 @@ impl SimpleComponent for App {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--game"))))); sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--game")))));
} }
StateUpdating::Voice(locale) => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr_args("loading-launcher-state--voice", [
("locale", locale.to_name().to_owned().into())
])))));
}
StateUpdating::Patch => { StateUpdating::Patch => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--patch"))))); sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--patch")))));
} }
@ -908,15 +789,12 @@ impl SimpleComponent for App {
if let Some(state) = state { if let Some(state) = state {
match state { match state {
LauncherState::VoiceUpdateAvailable(_) |
LauncherState::VoiceNotInstalled(_) |
LauncherState::GameUpdateAvailable(_) | LauncherState::GameUpdateAvailable(_) |
LauncherState::GameNotInstalled(_) if perform_on_download_needed => { LauncherState::GameNotInstalled(_) if perform_on_download_needed => {
sender.input(AppMsg::PerformAction); sender.input(AppMsg::PerformAction);
} }
LauncherState::UnityPlayerPatchAvailable(_) | LauncherState::MainPatchAvailable(_) if apply_patch_if_needed => {
LauncherState::XluaPatchAvailable(_) if apply_patch_if_needed => {
sender.input(AppMsg::PerformAction); sender.input(AppMsg::PerformAction);
} }
@ -931,13 +809,8 @@ impl SimpleComponent for App {
} }
#[allow(unused_must_use)] #[allow(unused_must_use)]
AppMsg::SetUnityPlayerPatch(patch) => unsafe { AppMsg::SetMainPatch(patch) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetUnityPlayerPatch(patch)); PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetMainPatch(patch));
}
#[allow(unused_must_use)]
AppMsg::SetXluaPatch(patch) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetXluaPatch(patch));
} }
AppMsg::SetLauncherState(state) => { AppMsg::SetLauncherState(state) => {
@ -968,7 +841,7 @@ impl SimpleComponent for App {
#[allow(unused_must_use)] #[allow(unused_must_use)]
AppMsg::PredownloadUpdate => { AppMsg::PredownloadUpdate => {
if let Some(LauncherState::PredownloadAvailable { game, mut voices }) = self.state.clone() { if let Some(LauncherState::PredownloadAvailable(mut game)) = self.state.clone() {
let tmp = Config::get().unwrap().launcher.temp.unwrap_or_else(std::env::temp_dir); let tmp = Config::get().unwrap().launcher.temp.unwrap_or_else(std::env::temp_dir);
self.downloading = true; self.downloading = true;
@ -977,13 +850,8 @@ impl SimpleComponent for App {
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("downloading")))); progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("downloading"))));
let mut diffs: Vec<VersionDiff> = vec![game];
diffs.append(&mut voices);
std::thread::spawn(move || { std::thread::spawn(move || {
for mut diff in diffs { let result = game.download_in(&tmp, clone!(@strong progress_bar_input => move |curr, total| {
let result = diff.download_in(&tmp, clone!(@strong progress_bar_input => move |curr, total| {
progress_bar_input.send(ProgressBarMsg::UpdateProgress(curr, total)); progress_bar_input.send(ProgressBarMsg::UpdateProgress(curr, total));
})); }));
@ -994,9 +862,6 @@ impl SimpleComponent for App {
}); });
tracing::error!("Failed to predownload update: {err}"); tracing::error!("Failed to predownload update: {err}");
break;
}
} }
sender.input(AppMsg::SetDownloading(false)); sender.input(AppMsg::SetDownloading(false));
@ -1011,28 +876,18 @@ impl SimpleComponent for App {
AppMsg::PerformAction => unsafe { AppMsg::PerformAction => unsafe {
match self.state.as_ref().unwrap_unchecked() { match self.state.as_ref().unwrap_unchecked() {
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) | LauncherState::MainPatchAvailable(MainPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::PredownloadAvailable { .. } | LauncherState::PredownloadAvailable { .. } |
LauncherState::Launch => launch::launch(sender), LauncherState::Launch => launch::launch(sender),
LauncherState::FolderMigrationRequired { from, to, cleanup_folder } => LauncherState::MainPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
migrate_folder::migrate_folder(sender, from.to_owned(), to.to_owned(), cleanup_folder.to_owned()),
LauncherState::UnityPlayerPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
LauncherState::XluaPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
LauncherState::WineNotInstalled => download_wine::download_wine(sender, self.progress_bar.sender().to_owned()), LauncherState::WineNotInstalled => download_wine::download_wine(sender, self.progress_bar.sender().to_owned()),
LauncherState::PrefixNotExists => create_prefix::create_prefix(sender), LauncherState::PrefixNotExists => create_prefix::create_prefix(sender),
LauncherState::VoiceUpdateAvailable(diff) |
LauncherState::VoiceNotInstalled(diff) |
LauncherState::GameUpdateAvailable(diff) | LauncherState::GameUpdateAvailable(diff) |
LauncherState::GameNotInstalled(diff) => LauncherState::GameNotInstalled(diff) =>
download_diff::download_diff(sender, self.progress_bar.sender().to_owned(), diff.to_owned()), download_diff::download_diff(sender, self.progress_bar.sender().to_owned(), diff.to_owned()),
LauncherState::VoiceOutdated(_) |
LauncherState::GameOutdated(_) => () LauncherState::GameOutdated(_) => ()
} }
} }

View file

@ -21,19 +21,7 @@ pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<Prog
std::thread::spawn(move || { std::thread::spawn(move || {
match repairer::try_get_integrity_files(None) { match repairer::try_get_integrity_files(None) {
Ok(mut files) => { Ok(files) => {
// Add voiceovers files
let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf();
let game = Game::new(&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)); progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 0));
let mut total = 0; let mut total = 0;
@ -64,7 +52,7 @@ pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<Prog
let thread_sender = verify_sender.clone(); let thread_sender = verify_sender.clone();
std::thread::spawn(clone!(@strong game_path => move || { std::thread::spawn(clone!(@strong config.game.path as game_path => move || {
for file in thread_files { for file in thread_files {
let status = if config.launcher.repairer.fast { let status = if config.launcher.repairer.fast {
file.fast_verify(&game_path) file.fast_verify(&game_path)
@ -105,34 +93,15 @@ pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<Prog
let total = broken.len() as f64; let total = broken.len() as f64;
let player_patch = UnityPlayerPatch::from_folder(&config.patch.path).unwrap() let main_patch = MainPatch::from_folder(&config.patch.path).unwrap()
.is_applied(&game_path).unwrap(); .is_applied(&config.game.path).unwrap();
let xlua_patch = XluaPatch::from_folder(&config.patch.path).unwrap() tracing::debug!("Patch status: {main_patch}");
.is_applied(&game_path).unwrap();
tracing::debug!("Patches status: player({player_patch}), xlua({xlua_patch})"); fn should_ignore(path: &Path, main_patch: bool) -> bool {
// Main patch related files
fn should_ignore(path: &Path, player_patch: bool, xlua_patch: bool) -> bool { if main_patch {
// Files managed by launch.bat file for part in ["StarRailBase.dll", "UnityPlayer.dll"] {
for part in ["crashreport.exe", "upload_crash.exe"] {
if path.ends_with(part) {
return true;
}
}
// UnityPlayer patch related files
if player_patch {
for part in ["UnityPlayer.dll", "vulkan-1.dll"] {
if path.ends_with(part) {
return true;
}
}
}
// Xlua patch related files
if xlua_patch {
for part in ["xlua.dll", "mhypbase.dll"] {
if path.ends_with(part) { if path.ends_with(part) {
return true; return true;
} }
@ -143,10 +112,10 @@ pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<Prog
} }
for (i, file) in broken.into_iter().enumerate() { for (i, file) in broken.into_iter().enumerate() {
if !should_ignore(&file.path, player_patch, xlua_patch) { if !should_ignore(&file.path, main_patch) {
tracing::debug!("Repairing file: {}", file.path.to_string_lossy()); tracing::debug!("Repairing file: {}", file.path.to_string_lossy());
if let Err(err) = file.repair(&game_path) { if let Err(err) = file.repair(&config.game.path) {
sender.input(AppMsg::Toast { sender.input(AppMsg::Toast {
title: tr("game-file-repairing-error"), title: tr("game-file-repairing-error"),
description: Some(err.to_string()) description: Some(err.to_string())

View file

@ -4,7 +4,7 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::config::schema_blanks::prelude::*; use anime_launcher_sdk::config::schema_blanks::prelude::*;
@ -345,158 +345,6 @@ impl SimpleAsyncComponent for EnhancementsApp {
} }
} }
} }
},
add = &adw::PreferencesGroup {
set_title: &tr("fps-unlocker"),
adw::ComboRow {
set_title: &tr("enabled"),
set_subtitle: &tr("fps-unlocker-description"),
#[wrap(Some)]
set_model = &gtk::StringList::new(&[
"90",
"120",
"144",
"165",
"180",
"200",
"240",
&tr("custom")
]),
set_selected: match Fps::from_num(CONFIG.game.enhancements.fps_unlocker.config.fps) {
Fps::Ninety => 0,
Fps::HundredTwenty => 1,
Fps::HundredFourtyFour => 2,
Fps::HundredSixtyFive => 3,
Fps::HundredEighty => 4,
Fps::TwoHundred => 5,
Fps::TwoHundredFourty => 6,
Fps::Custom(_) => 7
},
connect_selected_notify => |row| {
if is_ready() && row.selected() < Fps::list().len() as u32 - 1 {
if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.fps = Fps::list()[row.selected() as usize].to_num();
Config::update(config);
}
}
},
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.game.enhancements.fps_unlocker.enabled,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.enabled = switch.state();
Config::update(config);
}
}
}
}
},
adw::ActionRow {
set_title: &tr("power-saving"),
set_subtitle: &tr("power-saving-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.game.enhancements.fps_unlocker.config.power_saving,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.power_saving = switch.state();
Config::update(config);
}
}
}
}
},
adw::ActionRow {
set_title: &tr("monitor"),
set_subtitle: &tr("monitor-description"),
add_suffix = &gtk::SpinButton {
set_valign: gtk::Align::Center,
set_adjustment: &gtk::Adjustment::new(1.0, 1.0, 10.0, 1.0, 1.0, 0.0),
set_value: CONFIG.game.enhancements.fps_unlocker.config.monitor as f64,
connect_changed => |row| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.monitor = row.value() as u64;
Config::update(config);
}
}
}
}
},
adw::ComboRow {
set_title: &tr("window-mode"),
#[wrap(Some)]
set_model = &gtk::StringList::new(&[
&tr("default"),
&tr("popup"),
&tr("fullscreen")
]),
set_selected: CONFIG.game.enhancements.fps_unlocker.config.window_mode.ordinal() as u32,
connect_selected_notify => |row| unsafe {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.window_mode = WindowMode::from_ordinal_unsafe(row.selected() as i8);
Config::update(config);
}
}
}
},
adw::ComboRow {
set_title: &tr("priority"),
set_subtitle: &tr("priority-description"),
#[wrap(Some)]
set_model = &gtk::StringList::new(&[
&tr("realtime"),
&tr("high"),
&tr("above-normal"),
&tr("normal"),
&tr("below-normal"),
&tr("low")
]),
set_selected: CONFIG.game.enhancements.fps_unlocker.config.priority as u32,
connect_selected_notify => |row| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.game.enhancements.fps_unlocker.config.priority = row.selected() as u64;
Config::update(config);
}
}
}
},
} }
} }
} }

View file

@ -4,7 +4,7 @@ use relm4::component::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::config::schema_blanks::prelude::*; use anime_launcher_sdk::config::schema_blanks::prelude::*;

View file

@ -1,16 +1,10 @@
use relm4::prelude::*; use relm4::prelude::*;
use relm4::component::*; use relm4::component::*;
use relm4::factory::{
AsyncFactoryVecDeque,
AsyncFactoryComponent,
AsyncFactorySender
};
use gtk::prelude::*; use gtk::prelude::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::genshin::consts::GameEdition;
use anime_launcher_sdk::wincompatlib::prelude::*; use anime_launcher_sdk::wincompatlib::prelude::*;
@ -18,10 +12,8 @@ use anime_launcher_sdk::components::*;
use anime_launcher_sdk::components::wine::WincompatlibWine; use anime_launcher_sdk::components::wine::WincompatlibWine;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::star_rail::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::genshin::env_emulation::Environment;
use super::main::PreferencesAppMsg; use super::main::PreferencesAppMsg;
use crate::ui::migrate_installation::MigrateInstallationApp; use crate::ui::migrate_installation::MigrateInstallationApp;
@ -30,93 +22,13 @@ use crate::ui::components::*;
use crate::i18n::*; use crate::i18n::*;
use crate::*; use crate::*;
#[derive(Debug)]
struct VoicePackageComponent {
locale: VoiceLocale,
installed: bool,
sensitive: bool
}
#[relm4::factory(async)]
impl AsyncFactoryComponent for VoicePackageComponent {
type Init = (VoiceLocale, bool);
type Input = GeneralAppMsg;
type Output = GeneralAppMsg;
type CommandOutput = ();
type ParentInput = GeneralAppMsg;
type ParentWidget = adw::ExpanderRow;
view! {
root = adw::ActionRow {
set_title: &tr(&self.locale.to_name().to_ascii_lowercase()),
add_suffix = &gtk::Button {
#[watch]
set_visible: self.installed,
#[watch]
set_sensitive: self.sensitive,
set_icon_name: "user-trash-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(GeneralAppMsg::RemoveVoicePackage(index.clone()));
}
},
add_suffix = &gtk::Button {
#[watch]
set_visible: !self.installed,
#[watch]
set_sensitive: self.sensitive,
set_icon_name: "document-save-symbolic",
add_css_class: "flat",
set_valign: gtk::Align::Center,
connect_clicked[sender, index] => move |_| {
sender.input(GeneralAppMsg::AddVoicePackage(index.clone()));
}
}
}
}
fn output_to_parent_input(output: Self::Output) -> Option<Self::ParentInput> {
Some(output)
}
async fn init_model(
init: Self::Init,
_index: &DynamicIndex,
_sender: AsyncFactorySender<Self>,
) -> Self {
Self {
locale: init.0,
installed: init.1,
sensitive: true
}
}
async fn update(&mut self, msg: Self::Input, sender: AsyncFactorySender<Self>) {
self.installed = !self.installed;
sender.output(msg);
}
}
pub struct GeneralApp { pub struct GeneralApp {
voice_packages: AsyncFactoryVecDeque<VoicePackageComponent>,
migrate_installation: Controller<MigrateInstallationApp>, migrate_installation: Controller<MigrateInstallationApp>,
wine_components: AsyncController<ComponentsList<GeneralAppMsg>>, wine_components: AsyncController<ComponentsList<GeneralAppMsg>>,
dxvk_components: AsyncController<ComponentsList<GeneralAppMsg>>, dxvk_components: AsyncController<ComponentsList<GeneralAppMsg>>,
game_diff: Option<VersionDiff>, game_diff: Option<VersionDiff>,
unity_player_patch: Option<UnityPlayerPatch>, main_patch: Option<MainPatch>,
xlua_patch: Option<XluaPatch>,
style: LauncherStyle, style: LauncherStyle,
@ -141,18 +53,7 @@ pub enum GeneralAppMsg {
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version /// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
/// was retrieved from remote repos /// was retrieved from remote repos
SetUnityPlayerPatch(Option<UnityPlayerPatch>), SetMainPatch(Option<MainPatch>),
/// 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 wish to change it to accept VoiceLocale
// I'd recommend to use clone!(@strong self.locale as locale => move |_| { .. })
// in the VoicePackage component
AddVoicePackage(DynamicIndex),
RemoveVoicePackage(DynamicIndex),
SetVoicePackageSensitivity(DynamicIndex, bool),
OpenMigrateInstallation, OpenMigrateInstallation,
RepairGame, RepairGame,
@ -306,79 +207,6 @@ impl SimpleAsyncComponent for GeneralApp {
} }
}, },
adw::ComboRow {
set_title: &tr("game-edition"),
set_model: Some(&gtk::StringList::new(&[
&tr("global"),
&tr("china")
])),
set_selected: match CONFIG.launcher.edition {
GameEdition::Global => 0,
GameEdition::China => 1
},
connect_selected_notify[sender] => move |row| {
if is_ready() {
#[allow(unused_must_use)]
if let Ok(mut config) = Config::get() {
config.launcher.edition = match row.selected() {
0 => GameEdition::Global,
1 => GameEdition::China,
_ => unreachable!()
};
// Select new game edition
config.launcher.edition.select();
Config::update(config);
sender.output(PreferencesAppMsg::UpdateLauncherState);
}
}
}
},
adw::ComboRow {
set_title: &tr("game-environment"),
set_subtitle: &tr("game-environment-description"),
set_model: Some(&gtk::StringList::new(&[
"PC",
"Android"
])),
set_selected: match CONFIG.launcher.environment {
Environment::PC => 0,
Environment::Android => 1,
_ => unreachable!()
},
connect_selected_notify => |row| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.launcher.environment = match row.selected() {
0 => Environment::PC,
1 => Environment::Android,
_ => unreachable!()
};
Config::update(config);
}
}
}
},
#[local_ref]
voice_packages -> adw::ExpanderRow {
set_title: &tr("game-voiceovers"),
set_subtitle: &tr("game-voiceovers-description")
},
gtk::Box { gtk::Box {
set_orientation: gtk::Orientation::Horizontal, set_orientation: gtk::Orientation::Horizontal,
set_spacing: 8, set_spacing: 8,
@ -462,11 +290,10 @@ impl SimpleAsyncComponent for GeneralApp {
add_suffix = &gtk::Label { add_suffix = &gtk::Label {
#[watch] #[watch]
set_text: &match model.unity_player_patch.as_ref() { set_text: &match model.main_patch.as_ref() {
Some(patch) => match patch.status() { Some(patch) => match patch.status() {
PatchStatus::NotAvailable => tr("patch-not-available"), PatchStatus::NotAvailable => tr("patch-not-available"),
PatchStatus::Outdated { current, .. } => tr_args("patch-outdated", [("current", current.to_string().into())]), PatchStatus::Outdated { current, .. } => tr_args("patch-outdated", [("current", current.to_string().into())]),
PatchStatus::Preparation { .. } => tr("patch-preparation"),
PatchStatus::Testing { version, .. } | PatchStatus::Testing { version, .. } |
PatchStatus::Available { version, .. } => version.to_string() PatchStatus::Available { version, .. } => version.to_string()
} }
@ -475,19 +302,20 @@ impl SimpleAsyncComponent for GeneralApp {
}, },
#[watch] #[watch]
set_css_classes: match model.unity_player_patch.as_ref() { set_css_classes: match model.main_patch.as_ref() {
Some(patch) => match patch.status() { Some(patch) => match patch.status() {
PatchStatus::NotAvailable => &["error"], PatchStatus::NotAvailable => &["error"],
PatchStatus::Outdated { .. } | PatchStatus::Outdated { .. } |
PatchStatus::Preparation { .. } |
PatchStatus::Testing { .. } => &["warning"], PatchStatus::Testing { .. } => &["warning"],
PatchStatus::Available { .. } => unsafe { PatchStatus::Available { .. } => unsafe {
let path = match Config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path,
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.clone(),
}; };
if let Ok(true) = model.unity_player_patch.as_ref().unwrap_unchecked().is_applied(path) { if let Ok(true) = model.main_patch.as_ref().unwrap_unchecked().is_applied(path) {
&["success"] &["success"]
} else { } else {
&["warning"] &["warning"]
@ -499,93 +327,21 @@ impl SimpleAsyncComponent for GeneralApp {
}, },
#[watch] #[watch]
set_tooltip_text: Some(&match model.unity_player_patch.as_ref() { set_tooltip_text: Some(&match model.main_patch.as_ref() {
Some(patch) => match patch.status() { Some(patch) => match patch.status() {
PatchStatus::NotAvailable => tr("patch-not-available-tooltip"), PatchStatus::NotAvailable => tr("patch-not-available-tooltip"),
PatchStatus::Outdated { current, latest, .. } => tr_args("patch-outdated-tooltip", [ PatchStatus::Outdated { current, latest, .. } => tr_args("patch-outdated-tooltip", [
("current", current.to_string().into()), ("current", current.to_string().into()),
("latest", latest.to_string().into()) ("latest", latest.to_string().into())
]), ]),
PatchStatus::Preparation { .. } => tr("patch-preparation-tooltip"),
PatchStatus::Testing { .. } => tr("patch-testing-tooltip"), PatchStatus::Testing { .. } => tr("patch-testing-tooltip"),
PatchStatus::Available { .. } => unsafe { PatchStatus::Available { .. } => unsafe {
let path = match Config::get() { let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(), Ok(config) => config.game.path,
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(), Err(_) => CONFIG.game.path.clone(),
}; };
if let Ok(true) = model.unity_player_patch.as_ref().unwrap_unchecked().is_applied(path) { if let Ok(true) = model.main_patch.as_ref().unwrap_unchecked().is_applied(path) {
String::new()
} else {
tr("patch-not-applied-tooltip")
}
}
}
None => String::new()
})
}
},
adw::ActionRow {
set_title: &tr("xlua-patch-version"),
set_subtitle: &tr("xlua-patch-version-description"),
add_suffix = &gtk::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 {
let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
};
if let Ok(true) = model.xlua_patch.as_ref().unwrap_unchecked().is_applied(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 {
let path = match Config::get() {
Ok(config) => config.game.path.for_edition(config.launcher.edition).to_path_buf(),
Err(_) => CONFIG.game.path.for_edition(CONFIG.launcher.edition).to_path_buf(),
};
if let Ok(true) = model.xlua_patch.as_ref().unwrap_unchecked().is_applied(path) {
String::new() String::new()
} else { } else {
tr("patch-not-applied-tooltip") tr("patch-not-applied-tooltip")
@ -600,29 +356,6 @@ impl SimpleAsyncComponent for GeneralApp {
}, },
add = &adw::PreferencesGroup { add = &adw::PreferencesGroup {
adw::ActionRow {
set_title: &tr("apply-xlua-patch"),
add_suffix = &gtk::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 { adw::ActionRow {
set_title: &tr("ask-superuser-permissions"), set_title: &tr("ask-superuser-permissions"),
set_subtitle: &tr("ask-superuser-permissions-description"), set_subtitle: &tr("ask-superuser-permissions-description"),
@ -835,9 +568,7 @@ impl SimpleAsyncComponent for GeneralApp {
) -> AsyncComponentParts<Self> { ) -> AsyncComponentParts<Self> {
tracing::info!("Initializing general settings"); tracing::info!("Initializing general settings");
let mut model = Self { let model = Self {
voice_packages: AsyncFactoryVecDeque::new(adw::ExpanderRow::new(), sender.input_sender()),
migrate_installation: MigrateInstallationApp::builder() migrate_installation: MigrateInstallationApp::builder()
.launch(()) .launch(())
.detach(), .detach(),
@ -905,8 +636,7 @@ impl SimpleAsyncComponent for GeneralApp {
.forward(sender.input_sender(), std::convert::identity), .forward(sender.input_sender(), std::convert::identity),
game_diff: None, game_diff: None,
unity_player_patch: None, main_patch: None,
xlua_patch: None,
style: CONFIG.launcher.style, style: CONFIG.launcher.style,
@ -931,15 +661,6 @@ impl SimpleAsyncComponent for GeneralApp {
selecting_dxvk_version: false selecting_dxvk_version: false
}; };
for package in VoiceLocale::list() {
model.voice_packages.guard().push_back((
*package,
CONFIG.game.voices.iter().any(|voice| VoiceLocale::from_str(voice) == Some(*package))
));
}
let voice_packages = model.voice_packages.widget();
let widgets = view_output!(); let widgets = view_output!();
AsyncComponentParts { model, widgets } AsyncComponentParts { model, widgets }
@ -953,69 +674,8 @@ impl SimpleAsyncComponent for GeneralApp {
self.game_diff = diff; self.game_diff = diff;
} }
GeneralAppMsg::SetUnityPlayerPatch(patch) => { GeneralAppMsg::SetMainPatch(patch) => {
self.unity_player_patch = patch; self.main_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()) {
if let Ok(mut config) = Config::get() {
if !config.game.voices.iter().any(|voice| VoiceLocale::from_str(voice) == Some(package.locale)) {
config.game.voices.push(package.locale.to_code().to_string());
Config::update(config);
sender.output(PreferencesAppMsg::UpdateLauncherState);
}
}
}
}
#[allow(unused_must_use)]
GeneralAppMsg::RemoveVoicePackage(index) => {
if let Some(package) = self.voice_packages.guard().get_mut(index.current_index()) {
if let Ok(mut config) = Config::get() {
package.sensitive = false;
config.game.voices.retain(|voice| VoiceLocale::from_str(voice) != Some(package.locale));
Config::update(config.clone());
let package = VoicePackage::with_locale(package.locale).unwrap();
let game_path = config.game.path.for_edition(config.launcher.edition).to_path_buf();
if package.is_installed_in(&game_path) {
std::thread::spawn(move || {
if let Err(err) = package.delete_in(game_path) {
tracing::error!("Failed to delete voice package: {:?}", package.locale());
sender.input(GeneralAppMsg::Toast {
title: tr("voice-package-deletion-error"),
description: Some(err.to_string())
});
}
sender.input(GeneralAppMsg::SetVoicePackageSensitivity(index, true));
sender.output(PreferencesAppMsg::UpdateLauncherState);
});
}
else {
sender.input(GeneralAppMsg::SetVoicePackageSensitivity(index, true));
}
}
}
}
GeneralAppMsg::SetVoicePackageSensitivity(index, sensitive) => {
if let Some(package) = self.voice_packages.guard().get_mut(index.current_index()) {
package.sensitive = sensitive;
}
} }
GeneralAppMsg::OpenMigrateInstallation => unsafe { GeneralAppMsg::OpenMigrateInstallation => unsafe {

View file

@ -5,11 +5,11 @@ use gtk::prelude::*;
use adw::prelude::*; use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*; use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::genshin::prelude::*; use anime_launcher_sdk::anime_game_core::star_rail::prelude::*;
use anime_launcher_sdk::config::ConfigExt; use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config; use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle; use anime_launcher_sdk::star_rail::config::schema::launcher::LauncherStyle;
use crate::i18n::tr; use crate::i18n::tr;
@ -33,13 +33,9 @@ pub enum PreferencesAppMsg {
/// was retrieved from the API /// was retrieved from the API
SetGameDiff(Option<VersionDiff>), SetGameDiff(Option<VersionDiff>),
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version /// Supposed to be called automatically on app's run when the latest main patch version
/// was retrieved from remote repos /// was retrieved from remote repos
SetUnityPlayerPatch(Option<UnityPlayerPatch>), SetMainPatch(Option<MainPatch>),
/// 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), SetLauncherStyle(LauncherStyle),
@ -138,13 +134,8 @@ impl SimpleAsyncComponent for PreferencesApp {
} }
#[allow(unused_must_use)] #[allow(unused_must_use)]
PreferencesAppMsg::SetUnityPlayerPatch(patch) => { PreferencesAppMsg::SetMainPatch(patch) => {
self.general.sender().send(GeneralAppMsg::SetUnityPlayerPatch(patch)); self.general.sender().send(GeneralAppMsg::SetMainPatch(patch));
}
#[allow(unused_must_use)]
PreferencesAppMsg::SetXluaPatch(patch) => {
self.general.sender().send(GeneralAppMsg::SetXluaPatch(patch));
} }
#[allow(unused_must_use)] #[allow(unused_must_use)]