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

View file

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

View file

@ -1,13 +1,13 @@
use relm4::prelude::*;
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::genshin::consts::launcher_dir;
use anime_launcher_sdk::star_rail::states::LauncherState;
use anime_launcher_sdk::star_rail::consts::launcher_dir;
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::filter::*;
@ -22,7 +22,7 @@ pub mod ui;
use ui::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_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
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`
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()));
// Set game edition
CONFIG.launcher.edition.select();
// Set UI language
let lang = CONFIG.launcher.language.parse().expect("Wrong language format used in config");
@ -189,16 +186,15 @@ fn main() {
match state {
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;
}
LauncherState::PredownloadAvailable { .. } |
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) => {
LauncherState::MainPatchAvailable(MainPatch { status: PatchStatus::NotAvailable, .. }) => {
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;
}

View file

@ -9,7 +9,7 @@ use gtk::glib::clone;
use anime_launcher_sdk::anime_game_core::prelude::*;
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;

View file

@ -21,9 +21,7 @@ pub struct DefaultPathsApp {
runners: PathBuf,
dxvks: PathBuf,
prefix: PathBuf,
game_global: PathBuf,
game_china: PathBuf,
fps_unlocker: PathBuf,
game: PathBuf,
components: PathBuf,
patch: PathBuf,
temp: PathBuf
@ -35,9 +33,7 @@ pub enum Folders {
Runners,
DXVK,
Prefix,
GameGlobal,
GameChina,
FpsUnlocker,
Game,
Components,
Patch,
Temp
@ -151,36 +147,14 @@ impl SimpleAsyncComponent for DefaultPathsApp {
},
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_activatable: true,
#[watch]
set_subtitle: model.game_global.to_str().unwrap(),
set_subtitle: model.game.to_str().unwrap(),
connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::GameGlobal)
},
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)
connect_activated => DefaultPathsAppMsg::ChoosePath(Folders::Game)
},
adw::ActionRow {
@ -298,9 +272,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
runners: CONFIG.game.wine.builds.clone(),
dxvks: CONFIG.game.dxvk.builds.clone(),
prefix: CONFIG.game.wine.prefix.clone(),
game_global: CONFIG.game.path.global.clone(),
game_china: CONFIG.game.path.china.clone(),
fps_unlocker: CONFIG.game.enhancements.fps_unlocker.path.clone(),
game: CONFIG.game.path.clone(),
components: CONFIG.components.path.clone(),
patch: CONFIG.patch.path.clone(),
@ -330,28 +302,24 @@ impl SimpleAsyncComponent for DefaultPathsApp {
match folder {
Folders::Launcher => {
self.runners = result.join("runners");
self.dxvks = result.join("dxvks");
self.prefix = result.join("prefix");
self.game_global = result.join(concat!("Ge", "nshi", "n Imp", "act"));
self.game_china = result.join(concat!("Yu", "anS", "hen"));
self.fps_unlocker = result.join("fps-unlocker");
self.components = result.join("components");
self.patch = result.join("patch");
self.temp = result.clone();
self.runners = result.join("runners");
self.dxvks = result.join("dxvks");
self.prefix = result.join("prefix");
self.game = result.join(concat!("Hon", "kai Sta", "r Rail"));
self.components = result.join("components");
self.patch = result.join("patch");
self.temp = result.clone();
self.launcher = result;
}
Folders::Runners => self.runners = result,
Folders::DXVK => self.dxvks = result,
Folders::Prefix => self.prefix = result,
Folders::GameGlobal => self.game_global = result,
Folders::GameChina => self.game_china = result,
Folders::FpsUnlocker => self.fps_unlocker = result,
Folders::Components => self.components = result,
Folders::Patch => self.patch = result,
Folders::Temp => self.temp = result
Folders::Runners => self.runners = result,
Folders::DXVK => self.dxvks = result,
Folders::Prefix => self.prefix = result,
Folders::Game => self.game = result,
Folders::Components => self.components = result,
Folders::Patch => self.patch = result,
Folders::Temp => self.temp = result
}
}
}
@ -371,12 +339,9 @@ impl SimpleAsyncComponent for DefaultPathsApp {
(old_config.game.wine.builds, &self.runners),
(old_config.game.dxvk.builds, &self.dxvks),
(old_config.game.wine.prefix, &self.prefix),
(old_config.game.path.global, &self.game_global),
(old_config.game.path.china, &self.game_china),
(old_config.game.path, &self.game),
(old_config.components.path, &self.components),
(old_config.patch.path, &self.patch),
(old_config.game.enhancements.fps_unlocker.path, &self.fps_unlocker)
(old_config.patch.path, &self.patch)
];
#[allow(clippy::expect_fun_call)]
@ -400,7 +365,7 @@ impl SimpleAsyncComponent for DefaultPathsApp {
}
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.dxvk.builds = self.dxvks.clone();
config.game.wine.prefix = self.prefix.clone();
config.game.path.global = self.game_global.clone();
config.game.path.china = self.game_china.clone();
config.game.path = self.game.clone();
config.components.path = self.components.clone();
config.patch.path = self.patch.clone();
config.launcher.temp = Some(self.temp.clone());
config.game.enhancements.fps_unlocker.path = self.fps_unlocker.clone();
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::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::star_rail::config::Config;
use std::path::PathBuf;

View file

@ -13,7 +13,6 @@ use super::welcome::*;
use super::tos_warning::*;
use super::dependencies::*;
use super::default_paths::*;
use super::select_voiceovers::*;
use super::download_components::*;
use super::finish::*;
@ -26,7 +25,6 @@ pub struct FirstRunApp {
tos_warning: AsyncController<TosWarningApp>,
dependencies: AsyncController<DependenciesApp>,
default_paths: AsyncController<DefaultPathsApp>,
select_voiceovers: AsyncController<SelectVoiceoversApp>,
download_components: AsyncController<DownloadComponentsApp>,
finish: AsyncController<FinishApp>,
@ -44,7 +42,6 @@ pub enum FirstRunAppMsg {
ScrollToTosWarning,
ScrollToDependencies,
ScrollToDefaultPaths,
ScrollToSelectVoiceovers,
ScrollToDownloadComponents,
ScrollToFinish,
@ -104,7 +101,6 @@ impl SimpleComponent for FirstRunApp {
append = model.tos_warning.widget(),
append = model.dependencies.widget(),
append = model.default_paths.widget(),
append = model.select_voiceovers.widget(),
append = model.download_components.widget(),
append = model.finish.widget(),
},
@ -148,10 +144,6 @@ impl SimpleComponent for FirstRunApp {
.launch(false)
.forward(sender.input_sender(), std::convert::identity),
select_voiceovers: SelectVoiceoversApp::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity),
download_components: DownloadComponentsApp::builder()
.launch(())
.forward(sender.input_sender(), std::convert::identity),
@ -209,12 +201,6 @@ impl SimpleComponent for FirstRunApp {
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 => {
// Update 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 dependencies;
pub mod default_paths;
pub mod select_voiceovers;
pub mod download_components;
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 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() {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } |
PatchStatus::Preparation { .. } => unreachable!(),
PatchStatus::Outdated { .. } => unreachable!(),
PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => {
@ -19,7 +18,7 @@ pub fn apply_patch<T: PatchExt + Send + Sync + 'static>(sender: ComponentSender<
std::thread::spawn(move || {
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");
sender.input(AppMsg::Toast {

View file

@ -3,7 +3,7 @@ use relm4::prelude::*;
use anime_launcher_sdk::wincompatlib::prelude::*;
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 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 || {
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 {
DiffUpdate::InstallerUpdate(InstallerUpdate::DownloadingError(err)) => {
tracing::error!("Downloading failed: {err}");

View file

@ -7,7 +7,7 @@ pub fn launch(sender: ComponentSender<App>) {
sender.input(AppMsg::HideWindow);
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}");
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 create_prefix;
mod download_diff;
mod migrate_folder;
mod launch;
use anime_launcher_sdk::components::loader::ComponentsLoader;
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::genshin::consts::*;
use anime_launcher_sdk::star_rail::states::*;
use anime_launcher_sdk::star_rail::consts::*;
use crate::*;
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!(ConfigFile, WindowActionGroup, "config_file");
relm4::new_stateless_action!(DebugFile, WindowActionGroup, "debug_file");
relm4::new_stateless_action!(WishUrl, WindowActionGroup, "wish_url");
relm4::new_stateless_action!(About, WindowActionGroup, "about");
@ -80,13 +78,9 @@ pub enum AppMsg {
/// was retrieved from the API
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
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>),
SetMainPatch(Option<MainPatch>),
/// Supposed to be called automatically on app's run when the launcher state was chosen
SetLauncherState(Option<LauncherState>),
@ -127,10 +121,6 @@ impl SimpleComponent for App {
&tr("debug-file") => DebugFile,
},
section! {
&tr("wish-url") => WishUrl
},
section! {
&tr("about") => About
}
@ -311,17 +301,13 @@ impl SimpleComponent for App {
#[watch]
set_tooltip_text: Some(&tr_args("predownload-update", [
("version", match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, .. }) => game.latest().to_string(),
Some(LauncherState::PredownloadAvailable(game)) => game.latest().to_string(),
_ => String::from("?")
}.into()),
("size", match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => {
let mut size = game.size().unwrap_or((0, 0)).0;
for voice in voices {
size += voice.size().unwrap_or((0, 0)).0;
}
Some(LauncherState::PredownloadAvailable(game)) => {
let size = game.size().unwrap_or((0, 0)).0;
prettify_bytes(size)
}
@ -335,14 +321,11 @@ impl SimpleComponent for App {
#[watch]
set_sensitive: match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => {
Some(LauncherState::PredownloadAvailable(game)) => {
let config = Config::get().unwrap();
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
let downloaded = temp.join(game.file_name().unwrap()).exists() &&
voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists());
!downloaded
!temp.join(game.file_name().unwrap()).exists()
}
_ => false
@ -350,14 +333,11 @@ impl SimpleComponent for App {
#[watch]
set_css_classes: match model.state.as_ref() {
Some(LauncherState::PredownloadAvailable { game, voices }) => {
Some(LauncherState::PredownloadAvailable(game)) => {
let config = Config::get().unwrap();
let temp = config.launcher.temp.unwrap_or_else(std::env::temp_dir);
let downloaded = temp.join(game.file_name().unwrap()).exists() &&
voices.iter().all(|voice| temp.join(voice.file_name().unwrap()).exists());
if downloaded {
if temp.join(game.file_name().unwrap()).exists() {
&["success"]
} else {
&["warning"]
@ -380,33 +360,25 @@ impl SimpleComponent for App {
gtk::Button {
#[watch]
set_label: &match model.state {
Some(LauncherState::Launch) => tr("launch"),
Some(LauncherState::PredownloadAvailable { .. }) => tr("launch"),
Some(LauncherState::FolderMigrationRequired { .. }) => tr("migrate-folders"),
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"),
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::GameUpdateAvailable(_)) => tr("update"),
Some(LauncherState::GameOutdated(_)) => tr("update"),
Some(LauncherState::GameNotInstalled(_)) => tr("download"),
None => String::from("...")
},
#[watch]
set_sensitive: !model.disabled_buttons && match &model.state {
Some(LauncherState::GameOutdated { .. }) |
Some(LauncherState::VoiceOutdated(_)) => false,
Some(LauncherState::GameOutdated { .. }) => false,
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } |
PatchStatus::Preparation { .. } => false,
PatchStatus::Outdated { .. } => false,
PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => true
@ -419,14 +391,11 @@ impl SimpleComponent for App {
#[watch]
set_css_classes: match &model.state {
Some(LauncherState::GameOutdated { .. }) |
Some(LauncherState::VoiceOutdated(_)) => &["warning"],
Some(LauncherState::GameOutdated { .. }) => &["warning"],
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } |
PatchStatus::Preparation { .. } => &["error"],
PatchStatus::Outdated { .. } => &["error"],
PatchStatus::Testing { .. } => &["warning"],
PatchStatus::Available { .. } => &["suggested-action"]
@ -439,17 +408,11 @@ impl SimpleComponent for App {
#[watch]
set_tooltip_text: Some(&match &model.state {
Some(LauncherState::GameOutdated { .. }) |
Some(LauncherState::VoiceOutdated(_)) => tr("main-window--version-outdated-tooltip"),
Some(LauncherState::GameOutdated { .. }) => tr("main-window--version-outdated-tooltip"),
Some(LauncherState::FolderMigrationRequired { .. }) => tr("migrate-folders-tooltip"),
Some(LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status, .. })) |
Some(LauncherState::XluaPatchAvailable(XluaPatch { status, .. })) => match status {
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable => tr("main-window--patch-unavailable-tooltip"),
PatchStatus::Outdated { .. } |
PatchStatus::Preparation { .. } => tr("main-window--patch-outdated-tooltip"),
PatchStatus::Outdated { .. } => tr("main-window--patch-outdated-tooltip"),
_ => String::new()
},
@ -567,8 +530,8 @@ impl SimpleComponent for App {
group.add_action::<GameFolder>(&RelmAction::new_stateless(clone!(@strong sender => move |_| {
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(),
Ok(config) => config.game.path,
Err(_) => CONFIG.game.path.clone()
};
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 |_| {
about_dialog_broker.send(AboutDialogMsg::Show);
}));
@ -783,28 +686,12 @@ impl SimpleComponent for App {
}
}
// Get main UnityPlayer patch status
sender.input(AppMsg::SetUnityPlayerPatch(match patch.unity_player_patch() {
// Get the main patch status
sender.input(AppMsg::SetMainPatch(match patch.main_patch() {
Ok(patch) => Some(patch),
Err(err) => {
tracing::error!("Failed to fetch unity player 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}");
tracing::error!("Failed to fetch main patch info: {err}");
sender.input(AppMsg::Toast {
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")))));
}
StateUpdating::Voice(locale) => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr_args("loading-launcher-state--voice", [
("locale", locale.to_name().to_owned().into())
])))));
}
StateUpdating::Patch => {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state--patch")))));
}
@ -905,18 +786,15 @@ impl SimpleComponent for App {
} else {
self.disabled_buttons = false;
}
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 => {
LauncherState::MainPatchAvailable(_) if apply_patch_if_needed => {
sender.input(AppMsg::PerformAction);
}
@ -931,13 +809,8 @@ impl SimpleComponent for App {
}
#[allow(unused_must_use)]
AppMsg::SetUnityPlayerPatch(patch) => unsafe {
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::SetMainPatch(patch) => unsafe {
PREFERENCES_WINDOW.as_ref().unwrap_unchecked().sender().send(PreferencesAppMsg::SetMainPatch(patch));
}
AppMsg::SetLauncherState(state) => {
@ -968,7 +841,7 @@ impl SimpleComponent for App {
#[allow(unused_must_use)]
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);
self.downloading = true;
@ -977,26 +850,18 @@ impl SimpleComponent for App {
progress_bar_input.send(ProgressBarMsg::UpdateCaption(Some(tr("downloading"))));
let mut diffs: Vec<VersionDiff> = vec![game];
diffs.append(&mut voices);
std::thread::spawn(move || {
for mut diff in diffs {
let result = diff.download_in(&tmp, clone!(@strong progress_bar_input => move |curr, total| {
progress_bar_input.send(ProgressBarMsg::UpdateProgress(curr, total));
}));
let result = game.download_in(&tmp, clone!(@strong progress_bar_input => move |curr, total| {
progress_bar_input.send(ProgressBarMsg::UpdateProgress(curr, total));
}));
if let Err(err) = result {
sender.input(AppMsg::Toast {
title: tr("downloading-failed"),
description: Some(err.to_string())
});
if let Err(err) = result {
sender.input(AppMsg::Toast {
title: tr("downloading-failed"),
description: Some(err.to_string())
});
tracing::error!("Failed to predownload update: {err}");
break;
}
tracing::error!("Failed to predownload update: {err}");
}
sender.input(AppMsg::SetDownloading(false));
@ -1011,28 +876,18 @@ impl SimpleComponent for App {
AppMsg::PerformAction => unsafe {
match self.state.as_ref().unwrap_unchecked() {
LauncherState::UnityPlayerPatchAvailable(UnityPlayerPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::XluaPatchAvailable(XluaPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::MainPatchAvailable(MainPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::PredownloadAvailable { .. } |
LauncherState::Launch => launch::launch(sender),
LauncherState::FolderMigrationRequired { from, to, cleanup_folder } =>
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::MainPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
LauncherState::WineNotInstalled => download_wine::download_wine(sender, self.progress_bar.sender().to_owned()),
LauncherState::PrefixNotExists => create_prefix::create_prefix(sender),
LauncherState::VoiceUpdateAvailable(diff) |
LauncherState::VoiceNotInstalled(diff) |
LauncherState::GameUpdateAvailable(diff) |
LauncherState::GameNotInstalled(diff) =>
download_diff::download_diff(sender, self.progress_bar.sender().to_owned(), diff.to_owned()),
LauncherState::VoiceOutdated(_) |
LauncherState::GameOutdated(_) => ()
}
}

View file

@ -21,19 +21,7 @@ pub fn repair_game(sender: ComponentSender<App>, progress_bar_input: Sender<Prog
std::thread::spawn(move || {
match repairer::try_get_integrity_files(None) {
Ok(mut 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);
}
}
}
Ok(files) => {
progress_bar_input.send(ProgressBarMsg::UpdateProgress(0, 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();
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 {
let status = if config.launcher.repairer.fast {
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 player_patch = UnityPlayerPatch::from_folder(&config.patch.path).unwrap()
.is_applied(&game_path).unwrap();
let main_patch = MainPatch::from_folder(&config.patch.path).unwrap()
.is_applied(&config.game.path).unwrap();
let xlua_patch = XluaPatch::from_folder(&config.patch.path).unwrap()
.is_applied(&game_path).unwrap();
tracing::debug!("Patch status: {main_patch}");
tracing::debug!("Patches status: player({player_patch}), xlua({xlua_patch})");
fn should_ignore(path: &Path, player_patch: bool, xlua_patch: bool) -> bool {
// Files managed by launch.bat file
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"] {
fn should_ignore(path: &Path, main_patch: bool) -> bool {
// Main patch related files
if main_patch {
for part in ["StarRailBase.dll", "UnityPlayer.dll"] {
if path.ends_with(part) {
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() {
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());
if let Err(err) = file.repair(&game_path) {
if let Err(err) = file.repair(&config.game.path) {
sender.input(AppMsg::Toast {
title: tr("game-file-repairing-error"),
description: Some(err.to_string())

View file

@ -4,7 +4,7 @@ use relm4::component::*;
use adw::prelude::*;
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::*;
@ -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 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::*;

View file

@ -1,16 +1,10 @@
use relm4::prelude::*;
use relm4::component::*;
use relm4::factory::{
AsyncFactoryVecDeque,
AsyncFactoryComponent,
AsyncFactorySender
};
use gtk::prelude::*;
use adw::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::*;
@ -18,10 +12,8 @@ use anime_launcher_sdk::components::*;
use anime_launcher_sdk::components::wine::WincompatlibWine;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::genshin::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::genshin::env_emulation::Environment;
use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::star_rail::config::schema::launcher::LauncherStyle;
use super::main::PreferencesAppMsg;
use crate::ui::migrate_installation::MigrateInstallationApp;
@ -30,93 +22,13 @@ use crate::ui::components::*;
use crate::i18n::*;
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 {
voice_packages: AsyncFactoryVecDeque<VoicePackageComponent>,
migrate_installation: Controller<MigrateInstallationApp>,
wine_components: AsyncController<ComponentsList<GeneralAppMsg>>,
dxvk_components: AsyncController<ComponentsList<GeneralAppMsg>>,
game_diff: Option<VersionDiff>,
unity_player_patch: Option<UnityPlayerPatch>,
xlua_patch: Option<XluaPatch>,
main_patch: Option<MainPatch>,
style: LauncherStyle,
@ -141,18 +53,7 @@ pub enum GeneralAppMsg {
/// 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 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),
SetMainPatch(Option<MainPatch>),
OpenMigrateInstallation,
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 {
set_orientation: gtk::Orientation::Horizontal,
set_spacing: 8,
@ -462,11 +290,10 @@ impl SimpleAsyncComponent for GeneralApp {
add_suffix = &gtk::Label {
#[watch]
set_text: &match model.unity_player_patch.as_ref() {
set_text: &match model.main_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()
}
@ -475,19 +302,20 @@ impl SimpleAsyncComponent for GeneralApp {
},
#[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() {
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(),
Ok(config) => config.game.path,
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"]
} else {
&["warning"]
@ -499,93 +327,21 @@ impl SimpleAsyncComponent for GeneralApp {
},
#[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() {
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(),
Ok(config) => config.game.path,
Err(_) => CONFIG.game.path.clone(),
};
if let Ok(true) = model.unity_player_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) {
if let Ok(true) = model.main_patch.as_ref().unwrap_unchecked().is_applied(path) {
String::new()
} else {
tr("patch-not-applied-tooltip")
@ -600,29 +356,6 @@ impl SimpleAsyncComponent for GeneralApp {
},
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 {
set_title: &tr("ask-superuser-permissions"),
set_subtitle: &tr("ask-superuser-permissions-description"),
@ -835,9 +568,7 @@ impl SimpleAsyncComponent for GeneralApp {
) -> AsyncComponentParts<Self> {
tracing::info!("Initializing general settings");
let mut model = Self {
voice_packages: AsyncFactoryVecDeque::new(adw::ExpanderRow::new(), sender.input_sender()),
let model = Self {
migrate_installation: MigrateInstallationApp::builder()
.launch(())
.detach(),
@ -905,8 +636,7 @@ impl SimpleAsyncComponent for GeneralApp {
.forward(sender.input_sender(), std::convert::identity),
game_diff: None,
unity_player_patch: None,
xlua_patch: None,
main_patch: None,
style: CONFIG.launcher.style,
@ -931,15 +661,6 @@ impl SimpleAsyncComponent for GeneralApp {
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!();
AsyncComponentParts { model, widgets }
@ -953,69 +674,8 @@ impl SimpleAsyncComponent for GeneralApp {
self.game_diff = diff;
}
GeneralAppMsg::SetUnityPlayerPatch(patch) => {
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()) {
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::SetMainPatch(patch) => {
self.main_patch = patch;
}
GeneralAppMsg::OpenMigrateInstallation => unsafe {

View file

@ -5,11 +5,11 @@ use gtk::prelude::*;
use adw::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::genshin::config::Config;
use anime_launcher_sdk::genshin::config::schema::launcher::LauncherStyle;
use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::star_rail::config::schema::launcher::LauncherStyle;
use crate::i18n::tr;
@ -33,13 +33,9 @@ pub enum PreferencesAppMsg {
/// was retrieved from the API
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
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>),
SetMainPatch(Option<MainPatch>),
SetLauncherStyle(LauncherStyle),
@ -138,13 +134,8 @@ impl SimpleAsyncComponent for PreferencesApp {
}
#[allow(unused_must_use)]
PreferencesAppMsg::SetUnityPlayerPatch(patch) => {
self.general.sender().send(GeneralAppMsg::SetUnityPlayerPatch(patch));
}
#[allow(unused_must_use)]
PreferencesAppMsg::SetXluaPatch(patch) => {
self.general.sender().send(GeneralAppMsg::SetXluaPatch(patch));
PreferencesAppMsg::SetMainPatch(patch) => {
self.general.sender().send(GeneralAppMsg::SetMainPatch(patch));
}
#[allow(unused_must_use)]