- added "Repair game" button
- added `repairer` field to settings file
- updated core library

From previous commits:
- made preparations for environment settings
- now launcher hides when you launch the game
- now `Config::try_get_wine_executable` can return `Some("wine")`
- removed old wine and dxvk versions;
  added new Wine-GE-Proton and GE-Proton builds
This commit is contained in:
Observer KRypt0n_ 2022-08-01 20:04:32 +02:00
parent 21ee39ad17
commit 4a83100b7e
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
6 changed files with 214 additions and 7 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "anime-game-launcher"
version = "0.5.2"
version = "0.5.3"
description = "Anime Game launcher"
authors = ["Nikita Podvirnyy <suimin.tu.mu.ga.mi@gmail.com>"]
license = "GPL-3.0"

@ -1 +1 @@
Subproject commit e040b40c0598c48630d7a06110a4aaccbcea6c53
Subproject commit 5abbea2f1152d25c2855c43d55133a3edc414bc0

View file

@ -26,6 +26,16 @@ Adw.PreferencesPage page {
title: "Game voiceovers";
subtitle: "Select voice packages used in game";
}
Gtk.Box {
orientation: horizontal;
spacing: 8;
margin-top: 16;
Gtk.Button repair_game {
label: "Repair game";
}
}
}
Adw.PreferencesGroup {

View file

@ -181,14 +181,31 @@ impl Config {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Launcher {
pub language: String,
pub temp: Option<String>
pub temp: Option<String>,
pub repairer: Repairer
}
impl Default for Launcher {
fn default() -> Self {
Self {
language: String::from("en-us"),
temp: launcher_dir()
temp: launcher_dir(),
repairer: Repairer::default()
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Repairer {
pub threads: u8,
pub fast: bool
}
impl Default for Repairer {
fn default() -> Self {
Self {
threads: 4,
fast: false
}
}
}

View file

@ -131,6 +131,7 @@ pub enum Actions {
OpenPreferencesPage,
PreferencesGoBack,
PerformButtonEvent,
RepairGame,
ShowProgressBar,
UpdateProgress { fraction: Rc<f64>, title: Rc<String> },
HideProgressBar,
@ -224,7 +225,10 @@ impl App {
receiver.attach(None, move |action| {
// Some debug output
println!("[main] [update] action: {:?}", action);
match &action {
Actions::UpdateProgress { .. } => (),
action => println!("[main] [update] action: {:?}", action)
}
match action {
Actions::OpenPreferencesPage => {
@ -401,8 +405,6 @@ impl App {
config::update(config);
this.widgets.progress_bar.hide();
this.update_state();
}
}
@ -518,6 +520,165 @@ impl App {
}
}
Actions::RepairGame => {
match config::get() {
Ok(config) => {
let this = this.clone();
std::thread::spawn(move || {
match repairer::try_get_integrity_files() {
Ok(mut files) => {
// Add voiceovers files
let game = Game::new(&config.game.path);
if let Ok(voiceovers) = game.get_voice_packages() {
for package in voiceovers {
if let Ok(mut voiceover_files) = repairer::try_get_voice_integrity_files(package.locale()) {
files.append(&mut voiceover_files);
}
}
}
this.update(Actions::ShowProgressBar).unwrap();
this.update(Actions::UpdateProgress {
fraction: Rc::new(0.0),
title: Rc::new(String::from("Verifying files: 0%"))
}).unwrap();
const VERIFIER_THREADS_NUM: u64 = 4;
let mut total = 0;
for file in &files {
total += file.size;
}
let median_size = total / VERIFIER_THREADS_NUM;
let mut i = 0;
let (sender, receiver) = std::sync::mpsc::channel();
for _ in 0..VERIFIER_THREADS_NUM {
let mut thread_files = Vec::new();
let mut thread_files_size = 0;
while i < files.len() {
thread_files.push(files[i].clone());
thread_files_size += files[i].size;
i += 1;
if thread_files_size >= median_size {
break;
}
}
let game_path = config.game.path.clone();
let thread_sender = sender.clone();
std::thread::spawn(move || {
for file in thread_files {
let status = if config.launcher.repairer.fast {
file.fast_verify(&game_path)
} else {
file.verify(&game_path)
};
thread_sender.send((file, status)).unwrap();
}
});
}
// We have VERIFIER_THREADS_NUM copies of this sender + the original one
// receiver will return Err when all the senders will be dropped.
// VERIFIER_THREADS_NUM senders will be dropped when threads will finish verifying files
// but this one will live as long as current thread exists so we should drop it manually
drop(sender);
let mut broken = Vec::new();
let mut processed = 0;
while let Ok((file, status)) = receiver.recv() {
processed += file.size;
if !status {
broken.push(file);
}
let progress = processed as f64 / total as f64;
this.update(Actions::UpdateProgress {
fraction: Rc::new(progress),
title: Rc::new(format!("Verifying files: {:.2}%", progress * 100.0))
}).unwrap();
}
if broken.len() > 0 {
this.update(Actions::UpdateProgress {
fraction: Rc::new(0.0),
title: Rc::new(String::from("Repairing files: 0%"))
}).unwrap();
println!("Found broken files:");
for file in &broken {
println!(" - {}", file.path);
}
let total = broken.len() as f64;
let is_patch_applied = match Patch::try_fetch(config.patch.servers) {
Ok(patch) => patch.is_applied(&config.game.path).unwrap_or(true),
Err(_) => true
};
println!("Patch status: {}", is_patch_applied);
fn should_ignore(path: &str) -> bool {
for part in ["UnityPlayer.dll", "xlua.dll", "crashreport.exe", "upload_crash.exe", "vulkan-1.dll"] {
if path.contains(part) {
return true;
}
}
false
}
for (i, file) in broken.into_iter().enumerate() {
if !is_patch_applied || !should_ignore(&file.path) {
if let Err(err) = file.repair(&config.game.path) {
this.update(Actions::ToastError(Rc::new((
String::from("Failed to repair game file"), err
)))).unwrap();
}
}
let progress = i as f64 / total;
this.update(Actions::UpdateProgress {
fraction: Rc::new(progress),
title: Rc::new(format!("Repairing files: {:.2}%", progress * 100.0))
}).unwrap();
}
}
this.update(Actions::HideProgressBar).unwrap();
},
Err(err) => {
this.update(Actions::ToastError(Rc::new((
String::from("Failed to get integrity files"), err
)))).unwrap();
this.update(Actions::HideProgressBar).unwrap();
}
}
});
},
Err(err) => this.toast_error("Failed to load config", err)
}
}
Actions::ShowProgressBar => {
this.widgets.progress_bar.show();
}
@ -567,6 +728,8 @@ impl App {
pub fn set_state(&self, state: LauncherState) {
println!("[main] [set_state] state: {:?}", &state);
self.widgets.progress_bar.hide();
self.widgets.launch_game.set_tooltip_text(None);
self.widgets.launch_game.set_sensitive(true);

View file

@ -33,6 +33,8 @@ pub struct AppWidgets {
pub voiceovers_row: adw::ExpanderRow,
pub voieover_components: Rc<Vec<VoiceoverRow>>,
pub repair_game: gtk::Button,
pub game_version: gtk::Label,
pub patch_version: gtk::Label,
@ -62,6 +64,8 @@ impl AppWidgets {
voiceovers_row: get_object(&builder, "voiceovers_row")?,
voieover_components: Default::default(),
repair_game: get_object(&builder, "repair_game")?,
game_version: get_object(&builder, "game_version")?,
patch_version: get_object(&builder, "patch_version")?,
@ -160,6 +164,7 @@ impl AppWidgets {
#[derive(Debug, Clone, glib::Downgrade)]
pub enum Actions {
VoiceoverPerformAction(Rc<usize>),
RepairGame,
DxvkPerformAction(Rc<usize>),
WinePerformAction(Rc<(usize, usize)>),
UpdateDxvkComboRow,
@ -226,6 +231,8 @@ impl App {
/// Add default events and values to the widgets
fn init_events(self) -> Self {
self.widgets.repair_game.connect_clicked(Actions::RepairGame.into_fn(&self));
// Voiceover download/delete button event
for (i, row) in (&*self.widgets.voieover_components).into_iter().enumerate() {
row.button.connect_clicked(clone!(@weak self as this => move |_| {
@ -325,6 +332,16 @@ impl App {
println!("[general page] [update] action: {:?}", &action);
match action {
Actions::RepairGame => {
let option = (&*this.app).take();
this.app.set(option.clone());
let app = option.unwrap();
app.update(super::main::Actions::PreferencesGoBack).unwrap();
app.update(super::main::Actions::RepairGame).unwrap();
}
Actions::VoiceoverPerformAction(i) => {
let component = this.widgets.voieover_components[*i].clone();