feat: integrated jadeite patch

This commit is contained in:
Observer KRypt0n_ 2023-06-10 22:10:24 +02:00
parent cd50870a0e
commit 21772ff735
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
11 changed files with 144 additions and 211 deletions

8
Cargo.lock generated
View file

@ -48,8 +48,8 @@ dependencies = [
[[package]]
name = "anime-game-core"
version = "1.11.8"
source = "git+https://github.com/an-anime-team/anime-game-core?tag=1.11.8#eaa07d40c321a6285ca81892d32b6e0f7b056fc6"
version = "1.12.0"
source = "git+https://github.com/an-anime-team/anime-game-core?tag=1.12.0#71dd691d1698c3f8d23744c364b24824c43a6e33"
dependencies = [
"anyhow",
"bzip2",
@ -73,8 +73,8 @@ dependencies = [
[[package]]
name = "anime-launcher-sdk"
version = "1.6.7"
source = "git+https://github.com/an-anime-team/anime-launcher-sdk?tag=1.6.7#60d800a23d2359d40aaf3b1d62e0b953e9c08062"
version = "1.7.0"
source = "git+https://github.com/an-anime-team/anime-launcher-sdk?tag=1.7.0#40708d6b1072e5699ffa46acbe200289adb34a05"
dependencies = [
"anime-game-core",
"anyhow",

View file

@ -17,8 +17,8 @@ glib-build-tools = "0.17"
[dependencies.anime-launcher-sdk]
git = "https://github.com/an-anime-team/anime-launcher-sdk"
tag = "1.6.7"
features = ["all", "star-rail"]
tag = "1.7.0"
features = ["all", "star-rail", "star-rail-patch"]
# path = "../anime-launcher-sdk" # ! for dev purposes only

View file

@ -197,7 +197,7 @@ fn main() {
}
LauncherState::PredownloadAvailable { .. } |
LauncherState::MainPatchAvailable(MainPatch { status: PatchStatus::NotAvailable, .. }) => {
LauncherState::PatchUpdateAvailable => {
if just_run_game {
anime_launcher_sdk::star_rail::game::run().expect("Failed to run the game");

View file

@ -1,45 +0,0 @@
use relm4::prelude::*;
use crate::*;
use crate::i18n::*;
use super::{App, AppMsg};
pub fn apply_patch(sender: ComponentSender<App>, patch: MainPatch) {
match patch.status() {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } => unreachable!(),
PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => {
sender.input(AppMsg::DisableButtons(true));
let config = Config::get().unwrap();
std::thread::spawn(move || {
let mut apply_patch_if_needed = true;
let game_path = config.game.path.for_edition(config.launcher.edition);
if let Err(err) = patch.apply(game_path, config.patch.root) {
tracing::error!("Failed to patch the game");
sender.input(AppMsg::Toast {
title: tr("game-patching-error"),
description: Some(err.to_string())
});
// Don't try to apply the patch after state updating
// because we just failed to do it
apply_patch_if_needed = false;
}
sender.input(AppMsg::DisableButtons(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed,
show_status_page: true
});
});
}
}
}

View file

@ -34,7 +34,6 @@ pub fn create_prefix(sender: ComponentSender<App>) {
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

@ -68,7 +68,6 @@ pub fn download_diff(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
sender.input(AppMsg::SetDownloading(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed,
apply_patch_if_needed: false,
show_status_page: false
});
});

View file

@ -25,7 +25,6 @@ pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed: false,
show_status_page: true
});
}
@ -89,7 +88,6 @@ pub fn download_wine(sender: ComponentSender<App>, progress_bar_input: Sender<Pr
sender.input(AppMsg::SetDownloading(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed: false,
show_status_page: true
});
}));

View file

@ -10,7 +10,7 @@ use adw::prelude::*;
use gtk::glib::clone;
mod apply_patch;
mod update_patch;
mod download_wine;
mod create_prefix;
mod download_diff;
@ -67,9 +67,6 @@ pub enum AppMsg {
/// Needed for chained executions (e.g. update one voice after another)
perform_on_download_needed: bool,
/// Automatically start patch applying if possible and needed
apply_patch_if_needed: bool,
/// Show status gathering progress page
show_status_page: bool
},
@ -80,7 +77,7 @@ pub enum AppMsg {
/// Supposed to be called automatically on app's run when the latest main patch version
/// was retrieved from remote repos
SetMainPatch(Option<MainPatch>),
SetMainPatch(Option<(Version, JadeitePatchStatusVariant)>),
/// Supposed to be called automatically on app's run when the launcher state was chosen
SetLauncherState(Option<LauncherState>),
@ -359,32 +356,36 @@ impl SimpleComponent for App {
#[watch]
set_icon_name: match &model.state {
Some(LauncherState::Launch) |
Some(LauncherState::PatchNotVerified) |
Some(LauncherState::PredownloadAvailable { .. }) => "media-playback-start-symbolic",
Some(LauncherState::PatchNotInstalled) |
Some(LauncherState::PatchUpdateAvailable) => "document-save-symbolic",
Some(LauncherState::WineNotInstalled) |
Some(LauncherState::PrefixNotExists) => "document-save-symbolic",
Some(LauncherState::GameUpdateAvailable(_)) |
Some(LauncherState::GameNotInstalled(_)) => "document-save-symbolic",
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } => "window-close-symbolic",
PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => "document-save-symbolic"
}
Some(LauncherState::GameOutdated(_)) |
Some(LauncherState::PatchBroken) |
Some(LauncherState::PatchUnsafe) |
None => "window-close-symbolic"
},
#[watch]
set_label: &match &model.state {
Some(LauncherState::Launch) |
Some(LauncherState::PatchNotVerified) |
Some(LauncherState::PredownloadAvailable { .. }) => tr("launch"),
Some(LauncherState::MainPatchAvailable(_)) => tr("apply-patch"),
// TODO: add localization
Some(LauncherState::PatchNotInstalled) |
Some(LauncherState::PatchUpdateAvailable) => String::from("Download patch"),
Some(LauncherState::PatchBroken) => String::from("Patch is broken"),
Some(LauncherState::PatchUnsafe) => String::from("Patch is unsafe"),
Some(LauncherState::WineNotInstalled) => tr("download-wine"),
Some(LauncherState::PrefixNotExists) => tr("create-prefix"),
@ -416,15 +417,9 @@ impl SimpleComponent for App {
#[watch]
set_sensitive: !model.disabled_buttons && match &model.state {
Some(LauncherState::GameOutdated { .. }) => false,
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } => false,
PatchStatus::Testing { .. } |
PatchStatus::Available { .. } => true
},
Some(LauncherState::GameOutdated { .. }) |
Some(LauncherState::PatchBroken) |
Some(LauncherState::PatchUnsafe) => false,
Some(_) => true,
@ -433,15 +428,11 @@ impl SimpleComponent for App {
#[watch]
set_css_classes: match &model.state {
Some(LauncherState::GameOutdated { .. }) => &["warning", "pill"],
Some(LauncherState::GameOutdated { .. }) |
Some(LauncherState::PatchNotVerified) => &["warning", "pill"],
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable |
PatchStatus::Outdated { .. } => &["error", "pill"],
PatchStatus::Testing { .. } => &["warning", "pill"],
PatchStatus::Available { .. } => &["suggested-action", "pill"]
},
Some(LauncherState::PatchBroken) |
Some(LauncherState::PatchUnsafe) => &["error", "pill"],
Some(_) => &["suggested-action", "pill"],
@ -452,12 +443,9 @@ impl SimpleComponent for App {
set_tooltip_text: Some(&match &model.state {
Some(LauncherState::GameOutdated { .. }) => tr("main-window--version-outdated-tooltip"),
Some(LauncherState::MainPatchAvailable(MainPatch { status, .. })) => match status {
PatchStatus::NotAvailable => tr("main-window--patch-unavailable-tooltip"),
PatchStatus::Outdated { .. } => tr("main-window--patch-outdated-tooltip"),
_ => String::new()
},
// TODO: add localization
Some(LauncherState::PatchBroken) => String::from("Current patch version is broken and doesn't work properly"),
Some(LauncherState::PatchUnsafe) => String::from("Current patch version is unsafe and should not be used"),
_ => String::new()
}),
@ -751,45 +739,31 @@ impl SimpleComponent for App {
// Update initial patch status
tasks.push(std::thread::spawn(clone!(@strong sender => move || {
// Sync local patch repo
let patch = Patch::new(&CONFIG.patch.path, CONFIG.launcher.edition);
match patch.is_sync(&CONFIG.patch.servers) {
Ok(Some(_)) => (),
Ok(None) => {
for server in &CONFIG.patch.servers {
match patch.sync(server) {
Ok(_) => break,
Err(err) => {
tracing::error!("Failed to sync patch folder with remote: {server}: {err}");
sender.input(AppMsg::Toast {
title: tr("patch-sync-failed"),
description: Some(err.to_string())
});
}
}
}
}
Err(err) => {
tracing::error!("Failed to compare local patch folder with remote: {err}");
sender.input(AppMsg::Toast {
title: tr("patch-state-check-failed"),
description: Some(err.to_string())
});
}
}
// Get main patch status
sender.input(AppMsg::SetMainPatch(match patch.main_patch() {
Ok(patch) => Some(patch),
sender.input(AppMsg::SetMainPatch(match jadeite::get_latest() {
Ok(latest) => match jadeite::get_metadata() {
Ok(metadata) => {
let status = GAME.get_version()
.map(|version| metadata.hsr.global.get_status(version))
.unwrap_or(metadata.hsr.global.status);
Some((latest.version, status))
}
Err(err) => {
tracing::error!("Failed to fetch patch metadata: {err}");
sender.input(AppMsg::Toast {
title: tr("patch-info-fetching-error"),
description: Some(err.to_string())
});
None
}
},
Err(err) => {
tracing::error!("Failed to fetch main patch info: {err}");
tracing::error!("Failed to fetch latest patch version: {err}");
sender.input(AppMsg::Toast {
title: tr("patch-info-fetching-error"),
@ -831,7 +805,6 @@ impl SimpleComponent for App {
// Update launcher state
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed: false,
show_status_page: true
});
@ -851,7 +824,7 @@ impl SimpleComponent for App {
match msg {
// TODO: make function from this message like with toast
AppMsg::UpdateLauncherState { perform_on_download_needed, apply_patch_if_needed, show_status_page } => {
AppMsg::UpdateLauncherState { perform_on_download_needed, show_status_page } => {
if show_status_page {
sender.input(AppMsg::SetLoadingStatus(Some(Some(tr("loading-launcher-state")))));
} else {
@ -898,10 +871,6 @@ impl SimpleComponent for App {
sender.input(AppMsg::PerformAction);
}
LauncherState::MainPatchAvailable(_) if apply_patch_if_needed => {
sender.input(AppMsg::PerformAction);
}
_ => ()
}
}
@ -969,7 +938,6 @@ impl SimpleComponent for App {
sender.input(AppMsg::SetDownloading(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed: false,
show_status_page: true
});
});
@ -978,11 +946,13 @@ impl SimpleComponent for App {
AppMsg::PerformAction => unsafe {
match self.state.as_ref().unwrap_unchecked() {
LauncherState::MainPatchAvailable(MainPatch { status: PatchStatus::NotAvailable, .. }) |
LauncherState::PatchNotVerified |
LauncherState::PredownloadAvailable { .. } |
LauncherState::Launch => launch::launch(sender),
LauncherState::MainPatchAvailable(patch) => apply_patch::apply_patch(sender, patch.to_owned()),
LauncherState::PatchNotInstalled |
LauncherState::PatchUpdateAvailable => update_patch::update_patch(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),
@ -990,7 +960,7 @@ impl SimpleComponent for App {
LauncherState::GameNotInstalled(diff) =>
download_diff::download_diff(sender, self.progress_bar.sender().to_owned(), diff.to_owned()),
LauncherState::GameOutdated(_) => ()
_ => ()
}
}

View file

@ -0,0 +1,64 @@
use relm4::{
prelude::*,
Sender
};
use gtk::glib::clone;
use crate::*;
use crate::i18n::*;
use crate::ui::components::*;
use super::{App, AppMsg};
pub fn update_patch(sender: ComponentSender<App>, progress_bar_input: Sender<ProgressBarMsg>) {
sender.input(AppMsg::SetDownloading(true));
let config = Config::get().unwrap();
std::thread::spawn(move || {
let result = jadeite::get_latest()
.and_then(|patch| patch.install(config.patch.path, clone!(@strong sender => move |state| {
match &state {
InstallerUpdate::DownloadingError(err) => {
tracing::error!("Downloading failed: {err}");
sender.input(AppMsg::Toast {
title: tr("downloading-failed"),
description: Some(err.to_string())
});
}
InstallerUpdate::UnpackingError(err) => {
tracing::error!("Unpacking failed: {err}");
sender.input(AppMsg::Toast {
title: tr("unpacking-failed"),
description: Some(err.clone())
});
}
_ => ()
}
#[allow(unused_must_use)] {
progress_bar_input.send(ProgressBarMsg::UpdateFromState(state.into()));
}
})));
if let Err(err) = result {
tracing::error!("Failed to download latest patch");
sender.input(AppMsg::Toast {
title: String::from("Failed to download latest patch"), // TODO: add localization
description: Some(err.to_string())
});
}
sender.input(AppMsg::SetDownloading(false));
sender.input(AppMsg::UpdateLauncherState {
perform_on_download_needed: false,
show_status_page: true
});
});
}

View file

@ -4,10 +4,11 @@ use relm4::component::*;
use gtk::prelude::*;
use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::star_rail::consts::GameEdition;
use anime_launcher_sdk::wincompatlib::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::star_rail::consts::GameEdition;
use anime_launcher_sdk::config::ConfigExt;
use anime_launcher_sdk::star_rail::config::Config;
use anime_launcher_sdk::star_rail::config::schema::launcher::LauncherStyle;
@ -27,7 +28,7 @@ pub struct GeneralApp {
components_page: AsyncController<ComponentsPage>,
game_diff: Option<VersionDiff>,
main_patch: Option<MainPatch>,
main_patch: Option<(Version, JadeitePatchStatusVariant)>,
style: LauncherStyle,
@ -42,7 +43,7 @@ pub enum GeneralAppMsg {
/// Supposed to be called automatically on app's run when the latest UnityPlayer patch version
/// was retrieved from remote repos
SetMainPatch(Option<MainPatch>),
SetMainPatch(Option<(Version, JadeitePatchStatusVariant)>),
UpdateDownloadedWine,
UpdateDownloadedDxvk,
@ -301,36 +302,17 @@ impl SimpleAsyncComponent for GeneralApp {
add_suffix = &gtk::Label {
#[watch]
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::Testing { version, .. } |
PatchStatus::Available { version, .. } => version.to_string()
}
Some((version, _)) => version.to_string(),
None => String::from("?")
},
#[watch]
set_css_classes: match model.main_patch.as_ref() {
Some(patch) => match patch.status() {
PatchStatus::NotAvailable => &["error"],
PatchStatus::Outdated { .. } |
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.main_patch.as_ref().unwrap_unchecked().is_applied(path) {
&["success"]
} else {
&["warning"]
}
}
Some((_, status)) => match status {
JadeitePatchStatusVariant::Verified => &["success"],
JadeitePatchStatusVariant::Unverified => &["warning"],
JadeitePatchStatusVariant::Broken => &["error"],
JadeitePatchStatusVariant::Unsafe => &["error"]
}
None => &[]
@ -338,25 +320,14 @@ impl SimpleAsyncComponent for GeneralApp {
#[watch]
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::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(),
};
Some((_, status)) => match status {
JadeitePatchStatusVariant::Unverified => tr("patch-testing-tooltip"),
if let Ok(true) = model.main_patch.as_ref().unwrap_unchecked().is_applied(path) {
String::new()
} else {
tr("patch-not-applied-tooltip")
}
}
// TODO: add localizaion
JadeitePatchStatusVariant::Broken => String::from("Current patch version doesn't work"),
JadeitePatchStatusVariant::Unsafe => String::from("Current patch version is unsafe to use"),
_ => String::new()
}
None => String::new()
@ -365,29 +336,6 @@ impl SimpleAsyncComponent for GeneralApp {
}
},
add = &adw::PreferencesGroup {
adw::ActionRow {
set_title: &tr("ask-superuser-permissions"),
set_subtitle: &tr("ask-superuser-permissions-description"),
add_suffix = &gtk::Switch {
set_valign: gtk::Align::Center,
set_state: CONFIG.patch.root,
connect_state_notify => |switch| {
if is_ready() {
if let Ok(mut config) = Config::get() {
config.patch.root = switch.state();
Config::update(config);
}
}
}
}
}
},
add = &adw::PreferencesGroup {
set_title: &tr("options"),

View file

@ -4,6 +4,7 @@ use relm4::component::*;
use gtk::prelude::*;
use adw::prelude::*;
use anime_launcher_sdk::anime_game_core::prelude::*;
use anime_launcher_sdk::anime_game_core::star_rail::prelude::*;
use anime_launcher_sdk::config::ConfigExt;
@ -30,7 +31,7 @@ pub enum PreferencesAppMsg {
/// Supposed to be called automatically on app's run when the latest main patch version
/// was retrieved from remote repos
SetMainPatch(Option<MainPatch>),
SetMainPatch(Option<(Version, JadeitePatchStatusVariant)>),
SetLauncherStyle(LauncherStyle),
@ -131,7 +132,6 @@ impl SimpleAsyncComponent for PreferencesApp {
PreferencesAppMsg::UpdateLauncherState => {
sender.output(Self::Output::UpdateLauncherState {
perform_on_download_needed: false,
apply_patch_if_needed: false,
show_status_page: false
});
}