Several changes

- added new wine versions
- added automatic default folder creation
- added `latest` methods for DXVK/Wine versions
- added `wine_prefix` mod with `WinePrefix` struct to manage what do you think what
- spent lots of time trying to make the launcher
  download default wine version,
  create prefix and apply DXVK
  but it just pauses actions flow after
  ~400 KB of downloaded wine version progress
This commit is contained in:
Observer KRypt0n_ 2022-07-26 23:02:43 +02:00
parent daac4c8ff0
commit 744f234acd
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
7 changed files with 394 additions and 59 deletions

View file

@ -3,6 +3,45 @@
"title": "Wine-GE-Proton", "title": "Wine-GE-Proton",
"subtitle": null, "subtitle": null,
"versions": [ "versions": [
{
"family": "Wine-GE-Proton",
"name": "lutris-GE-Proton7-22-x86_64",
"title": "Wine-GE-Proton 7-22",
"uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/GE-Proton7-22/wine-lutris-GE-Proton7-22-x86_64.tar.xz",
"files": {
"wine": "bin/wine64",
"wineserver": "bin/wineserver",
"wineboot": "bin/wineboot",
"winecfg": "lib64/wine/x86_64-windows/winecfg.exe"
},
"recommended": true
},
{
"family": "Wine-GE-Proton",
"name": "lutris-GE-Proton7-20-x86_64",
"title": "Wine-GE-Proton 7-20",
"uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/GE-Proton7-20/wine-lutris-GE-Proton7-20-x86_64.tar.xz",
"files": {
"wine": "bin/wine64",
"wineserver": "bin/wineserver",
"wineboot": "bin/wineboot",
"winecfg": "lib64/wine/x86_64-windows/winecfg.exe"
},
"recommended": true
},
{
"family": "Wine-GE-Proton",
"name": "lutris-GE-Proton7-18-x86_64",
"title": "Wine-GE-Proton 7-18",
"uri": "https://github.com/GloriousEggroll/wine-ge-custom/releases/download/GE-Proton7-18/wine-lutris-GE-Proton7-18-x86_64.tar.xz",
"files": {
"wine": "bin/wine64",
"wineserver": "bin/wineserver",
"wineboot": "bin/wineboot",
"winecfg": "lib64/wine/x86_64-windows/winecfg.exe"
},
"recommended": true
},
{ {
"family": "Wine-GE-Proton", "family": "Wine-GE-Proton",
"name": "lutris-GE-Proton7-16-x86_64", "name": "lutris-GE-Proton7-16-x86_64",
@ -204,6 +243,32 @@
"title": "GE-Proton", "title": "GE-Proton",
"subtitle": "This version includes its own DXVK builds and you can use DXVK_ASYNC variable", "subtitle": "This version includes its own DXVK builds and you can use DXVK_ASYNC variable",
"versions": [ "versions": [
{
"family": "GE-Proton",
"name": "GE-Proton7-26",
"title": "GE-Proton 7-26",
"uri": "https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton7-26/GE-Proton7-26.tar.gz",
"files": {
"wine": "files/bin/wine64",
"wineserver": "files/bin/wineserver",
"wineboot": "files/bin/wineboot",
"winecfg": "files/lib64/wine/x86_64-windows/winecfg.exe"
},
"recommended": true
},
{
"family": "GE-Proton",
"name": "GE-Proton7-24",
"title": "GE-Proton 7-24",
"uri": "https://github.com/GloriousEggroll/proton-ge-custom/releases/download/GE-Proton7-24/GE-Proton7-24.tar.gz",
"files": {
"wine": "files/bin/wine64",
"wineserver": "files/bin/wineserver",
"wineboot": "files/bin/wineboot",
"winecfg": "files/lib64/wine/x86_64-windows/winecfg.exe"
},
"recommended": true
},
{ {
"family": "GE-Proton", "family": "GE-Proton",
"name": "GE-Proton7-20", "name": "GE-Proton7-20",
@ -431,6 +496,19 @@
"title": "Lutris", "title": "Lutris",
"subtitle": null, "subtitle": null,
"versions": [ "versions": [
{
"family": "Lutris",
"name": "lutris-7.2-2-x86_64",
"title": "Lutris 7.2-2",
"uri": "https://github.com/lutris/wine/releases/download/lutris-wine-7.2-2/wine-lutris-7.2-2-x86_64.tar.xz",
"files": {
"wine": "bin/wine64",
"wineserver": "bin/wineserver",
"wineboot": "bin/wineboot",
"winecfg": "lib64/wine/x86_64-windows/winecfg.exe"
},
"recommended": true
},
{ {
"family": "Lutris", "family": "Lutris",
"name": "lutris-7.2-x86_64", "name": "lutris-7.2-x86_64",

View file

@ -61,6 +61,10 @@ pub struct Version {
} }
impl Version { impl Version {
pub fn latest() -> Result<Self, serde_json::Error> {
Ok(List::get()?.vanilla[0].clone())
}
pub fn is_downloaded_in<T: ToString>(&self, folder: T) -> bool { pub fn is_downloaded_in<T: ToString>(&self, folder: T) -> bool {
std::path::Path::new(&format!("{}/{}", folder.to_string(), self.name)).exists() std::path::Path::new(&format!("{}/{}", folder.to_string(), self.name)).exists()
} }

View file

@ -4,4 +4,5 @@ pub mod tasks;
pub mod game; pub mod game;
pub mod dxvk; pub mod dxvk;
pub mod wine; pub mod wine;
pub mod wine_prefix;
pub mod launcher; pub mod launcher;

View file

@ -53,6 +53,10 @@ pub struct Version {
} }
impl Version { impl Version {
pub fn latest() -> Result<Self, serde_json::Error> {
Ok(List::get()?[0].versions[0].clone())
}
pub fn is_downloaded_in<T: ToString>(&self, folder: T) -> bool { pub fn is_downloaded_in<T: ToString>(&self, folder: T) -> bool {
std::path::Path::new(&format!("{}/{}", folder.to_string(), self.name)).exists() std::path::Path::new(&format!("{}/{}", folder.to_string(), self.name)).exists()
} }

51
src/lib/wine_prefix.rs Normal file
View file

@ -0,0 +1,51 @@
use std::path::Path;
use std::process::Command;
pub struct WinePrefix {
pub path: String
}
impl WinePrefix {
pub fn new<T: ToString>(path: T) -> Self {
Self { path: path.to_string() }
}
pub fn exists(&self) -> bool {
Path::new(&format!("{}/drive_c", self.path)).exists()
}
fn wineboot<T: ToString>(&self, runners_folder: T, runner: super::wine::Version, command: &str) -> std::io::Result<()> {
let runners_folder = runners_folder.to_string();
let wineboot = format!("{}/{}", &runners_folder, runner.files.wineboot);
let wineserver = format!("{}/{}", &runners_folder, runner.files.wineserver);
Command::new(wineboot)
.env("WINESERVER", wineserver)
.env("WINEPREFIX", &self.path)
.arg(command)
.output()?;
Ok(())
}
pub fn update<T: ToString>(&self, runners_folder: T, runner: super::wine::Version) -> std::io::Result<()> {
self.wineboot(runners_folder, runner, "-u")
}
pub fn end<T: ToString>(&self, runners_folder: T, runner: super::wine::Version) -> std::io::Result<()> {
self.wineboot(runners_folder, runner, "-e")
}
pub fn kill<T: ToString>(&self, runners_folder: T, runner: super::wine::Version) -> std::io::Result<()> {
self.wineboot(runners_folder, runner, "-k")
}
pub fn restart<T: ToString>(&self, runners_folder: T, runner: super::wine::Version) -> std::io::Result<()> {
self.wineboot(runners_folder, runner, "-r")
}
pub fn shutdown<T: ToString>(&self, runners_folder: T, runner: super::wine::Version) -> std::io::Result<()> {
self.wineboot(runners_folder, runner, "-s")
}
}

View file

@ -27,6 +27,13 @@ async fn main() {
// FIXME: doesn't work? // FIXME: doesn't work?
set_application_name("An Anime Game Launcher"); set_application_name("An Anime Game Launcher");
// Create default launcher folder if needed
let launcher_dir = lib::consts::launcher_dir().unwrap();
if !std::path::Path::new(&launcher_dir).exists() {
std::fs::create_dir_all(launcher_dir).expect("Failed to create default launcher dir");
}
// Create app // Create app
let application = gtk::Application::new( let application = gtk::Application::new(
Some(APP_ID), Some(APP_ID),

View file

@ -6,6 +6,7 @@ use gtk4::glib::clone;
use std::rc::Rc; use std::rc::Rc;
use std::cell::Cell; use std::cell::Cell;
use std::io::Error;
use anime_game_core::prelude::*; use anime_game_core::prelude::*;
@ -18,6 +19,36 @@ use crate::lib::config;
use crate::lib::game; use crate::lib::game;
use crate::lib::tasks; use crate::lib::tasks;
use crate::lib::launcher::states::LauncherState; use crate::lib::launcher::states::LauncherState;
use crate::lib::wine_prefix::WinePrefix;
use crate::lib::wine::Version as WineVersion;
use crate::lib::dxvk::Version as DxvkVersion;
fn prettify_bytes(bytes: u64) -> String {
if bytes > 1024 * 1024 * 1024 {
format!("{:.2} GB", bytes as f64 / 1024.0 / 1024.0 / 1024.0)
}
else if bytes > 1024 * 1024 {
format!("{:.2} MB", bytes as f64 / 1024.0 / 1024.0)
}
else if bytes > 1024 {
format!("{:.2} KB", bytes as f64 / 1024.0)
}
else {
format!("{:.2} B", bytes)
}
}
fn toast_error(app: &App, msg: &str, err: Error) {
app.update(Actions::ToastError(Rc::new((
String::from(msg), err
)))).unwrap();
app.update(Actions::HideProgressBar).unwrap();
app.update_state();
}
/// This structure is used to describe widgets used in application /// This structure is used to describe widgets used in application
/// ///
@ -104,6 +135,7 @@ impl AppWidgets {
format!("GTK version: {}.{}.{}", gtk::major_version(), gtk::minor_version(), gtk::micro_version()), format!("GTK version: {}.{}.{}", gtk::major_version(), gtk::minor_version(), gtk::micro_version()),
format!("Libadwaita version: {}.{}.{}", adw::major_version(), adw::minor_version(), adw::micro_version()), format!("Libadwaita version: {}.{}.{}", adw::major_version(), adw::minor_version(), adw::micro_version()),
format!("Pango version: {}", gtk::pango::version_string().unwrap_or("?".into())), format!("Pango version: {}", gtk::pango::version_string().unwrap_or("?".into())),
format!("Cairo version: {}", gtk::cairo::version_string()),
].join("\n"))); ].join("\n")));
// Add preferences page to the leaflet // Add preferences page to the leaflet
@ -122,9 +154,11 @@ pub enum Actions {
PreferencesGoBack, PreferencesGoBack,
PerformButtonEvent, PerformButtonEvent,
DownloadDiff(Rc<VersionDiff>), DownloadDiff(Rc<VersionDiff>),
UpdateProgressByState(Rc<(InstallerUpdate, Option<String>)>),
ShowProgressBar, ShowProgressBar,
UpdateProgress { fraction: Rc<f64>, title: Rc<String> }, UpdateProgress { fraction: Rc<f64>, title: Rc<String> },
HideProgressBar HideProgressBar,
ToastError(Rc<(String, Error)>)
} }
impl Actions { impl Actions {
@ -211,7 +245,9 @@ impl App {
receiver.attach(None, move |action| { receiver.attach(None, move |action| {
// Some debug output // Some debug output
match &action { match &action {
Actions::UpdateProgress { .. } => (), // Actions::UpdateProgress { .. } |
// Actions::UpdateProgressByState(_) => (),
action => println!("[main] [update] action: {:?}", action) action => println!("[main] [update] action: {:?}", action)
} }
@ -266,76 +302,156 @@ impl App {
Actions::DownloadDiff(diff) => { Actions::DownloadDiff(diff) => {
match config::get() { match config::get() {
Ok(config) => { Ok(mut config) => {
fn to_gb(bytes: u64) -> f64 {
(bytes as f64 / 1024.0 / 1024.0 / 1024.0 * 100.0).ceil() / 100.0
}
let diff = (*diff).clone(); let diff = (*diff).clone();
let this = this.clone(); let this = this.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
let this = this.clone(); let this = this.clone();
diff.install_to_by(config.game.path, config.launcher.temp, move |state| { this.update(Actions::ShowProgressBar).unwrap();
match state {
InstallerUpdate::DownloadingStarted(_) => {
this.update(Actions::ShowProgressBar).unwrap();
this.update(Actions::UpdateProgress { // Download wine version if not installed
fraction: Rc::new(0.0), match WineVersion::latest() {
title: Rc::new(String::from("Downloading...")) Ok(wine) => match Installer::new(wine.uri) {
}).unwrap(); Ok(mut installer) => {
} let (send, recv) = std::sync::mpsc::channel();
let wine_title = wine.title.clone();
InstallerUpdate::DownloadingProgress(curr, total) => { installer.install(&config.game.wine.builds, clone!(@strong this => move |state| {
// To reduce amount of action requests match state {
if curr % 10000 < 200 { InstallerUpdate::UnpackingFinished => {
let progress = curr as f64 / total as f64; send.send(true).unwrap();
}
this.update(Actions::UpdateProgress { InstallerUpdate::DownloadingError(_) |
fraction: Rc::new(progress), InstallerUpdate::UnpackingError => {
title: Rc::new(format!( send.send(false).unwrap();
"Downloading: {:.2}% ({} of {} GB)", }
progress * 100.0,
to_gb(curr), _ => ()
to_gb(total) }
))
}).unwrap(); this.update(Actions::UpdateProgressByState(Rc::new((state, Some(wine_title.clone()))))).unwrap();
}));
// Block thread until downloading finished
if recv.recv().unwrap() {
config.game.wine.selected = Some(wine.name);
config::update(config.clone());
} }
else {
println!("I'm tired, Boss!");
return;
}
},
Err(err) => {
toast_error(&this, "Failed to init wine version installer", err.into());
return;
} }
},
Err(err) => {
toast_error(&this, "Failed to load wine versions list", err.into());
InstallerUpdate::UnpackingStarted(_) => { return;
this.update(Actions::UpdateProgress {
fraction: Rc::new(0.0),
title: Rc::new(String::from("Unpacking..."))
}).unwrap();
}
InstallerUpdate::UnpackingProgress(curr, total) => {
let progress = curr as f64 / total as f64;
this.update(Actions::UpdateProgress {
fraction: Rc::new(progress),
title: Rc::new(format!(
"Unpacking: {:.2}% ({} of {} GB)",
progress * 100.0,
to_gb(curr),
to_gb(total)
))
}).unwrap();
}
InstallerUpdate::DownloadingFinished => (),
InstallerUpdate::UnpackingFinished => {
this.update(Actions::HideProgressBar).unwrap();
this.update_state();
}
InstallerUpdate::DownloadingError(err) => this.toast_error("Failed to download game", err),
InstallerUpdate::UnpackingError => this.toast_error("Failed to unpack game", "?")
} }
}
// Create prefix if needed
let prefix = WinePrefix::new(&config.game.wine.prefix);
if !prefix.exists() {
this.update(Actions::UpdateProgress {
fraction: Rc::new(0.0),
title: Rc::new(String::from("Creating prefix..."))
}).unwrap();
match config.try_get_selected_wine_info() {
Some(wine_version) => {
if let Err(err) = prefix.update(&config.game.wine.builds, wine_version) {
toast_error(&this, "Failed to create wineprefix", err);
return;
}
},
None => return
}
}
// Download and apply DXVK if not installed
match DxvkVersion::latest() {
Ok(dxvk) => match Installer::new(&dxvk.uri) {
Ok(mut installer) => {
let (send, recv) = std::sync::mpsc::channel();
let dxvk_title = dxvk.name.clone();
installer.install(&config.game.dxvk.builds, clone!(@strong this => move |state| {
match state {
InstallerUpdate::UnpackingFinished => {
send.send(true).unwrap();
}
InstallerUpdate::DownloadingError(_) |
InstallerUpdate::UnpackingError => {
send.send(false).unwrap();
}
_ => ()
}
this.update(Actions::UpdateProgressByState(Rc::new((state, Some(dxvk_title.clone()))))).unwrap();
}));
// Block thread until downloading finished
if recv.recv().unwrap() {
config.game.dxvk.selected = Some(dxvk.name.clone());
config::update(config.clone());
}
else {
return;
}
// Apply DXVK
this.update(Actions::UpdateProgress {
fraction: Rc::new(100.0),
title: Rc::new(String::from("Applying DXVK..."))
}).unwrap();
match dxvk.apply(&config.game.dxvk.builds, &config.game.wine.prefix) {
Ok(_) => {
config.game.dxvk.selected = Some(dxvk.name);
config::update(config.clone());
},
Err(err) => {
toast_error(&this, "Failed to apply DXVK", err);
return;
}
}
},
Err(err) => {
toast_error(&this, "Failed to init wine version installer", err.into());
return;
}
},
Err(err) => {
toast_error(&this, "Failed to load wine versions list", err.into());
return;
}
}
// Download diff
diff.install_to_by(config.game.path, config.launcher.temp, move |state| {
this.update(Actions::UpdateProgressByState(Rc::new((state, None)))).unwrap();
}).unwrap(); }).unwrap();
}); });
}, },
@ -347,6 +463,74 @@ impl App {
} }
} }
Actions::UpdateProgressByState(state) => {
// let (state, suffix) = (&*state).clone();
match &state.0 {
InstallerUpdate::DownloadingStarted(_) => {
this.update(Actions::UpdateProgress {
fraction: Rc::new(0.0),
title: Rc::new(String::from("Downloading..."))
}).unwrap();
}
InstallerUpdate::DownloadingProgress(curr, total) => {
// To reduce amount of action requests
// if curr % 10000 < 200 {
let progress = *curr as f64 / *total as f64;
this.update(Actions::UpdateProgress {
fraction: Rc::new(progress),
title: Rc::new(format!(
"Downloading{}: {:.2}% ({} of {})",
if let Some(suffix) = &state.1 { format!(" {}", suffix) } else { String::new() },
progress * 100.0,
prettify_bytes(*curr),
prettify_bytes(*total)
))
}).unwrap();
// }
}
InstallerUpdate::UnpackingStarted(_) => {
this.update(Actions::UpdateProgress {
fraction: Rc::new(0.0),
title: Rc::new(String::from("Unpacking..."))
}).unwrap();
}
InstallerUpdate::UnpackingProgress(curr, total) => {
let progress = *curr as f64 / *total as f64;
this.update(Actions::UpdateProgress {
fraction: Rc::new(progress),
title: Rc::new(format!(
"Unpacking{}: {:.2}% ({} of {})",
if let Some(suffix) = &state.1 { format!(" {}", suffix) } else { String::new() },
progress * 100.0,
prettify_bytes(*curr),
prettify_bytes(*total)
))
}).unwrap();
}
InstallerUpdate::DownloadingFinished => (),
InstallerUpdate::UnpackingFinished => {
this.update(Actions::HideProgressBar).unwrap();
this.update_state();
}
InstallerUpdate::DownloadingError(err) => {
toast_error(&this, "Failed to download", err.clone().into());
}
InstallerUpdate::UnpackingError => {
toast_error(&this, "Failed to unpack", Error::last_os_error());
}
}
}
Actions::ShowProgressBar => { Actions::ShowProgressBar => {
this.widgets.progress_bar.set_text(None); this.widgets.progress_bar.set_text(None);
this.widgets.progress_bar.set_fraction(0.0); this.widgets.progress_bar.set_fraction(0.0);
@ -364,6 +548,12 @@ impl App {
this.widgets.launch_game_group.show(); this.widgets.launch_game_group.show();
this.widgets.progress_bar_group.hide(); this.widgets.progress_bar_group.hide();
} }
Actions::ToastError(toast) => {
let (msg, err) = (toast.0.clone(), toast.1.to_string());
this.toast_error(msg, err);
}
} }
glib::Continue(true) glib::Continue(true)