From bf31327532ca017b01ef4ea22f0f4a358838ad14 Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Thu, 4 Aug 2022 11:47:57 +0200 Subject: [PATCH] Reworked work with config file - now missing fields will be automatically filled; excess fields - removed. thanks to new code structure I can easily create new fields or rename old ones - improved `WineLang` enum; now launcher loads languages list dynamically from this enum so I can easily add support for new languages --- assets/ui/preferences/enhancements.blp | 15 - src/lib/config/game/dxvk.rs | 48 +++ src/lib/config/game/enhancements/fsr.rs | 53 +++ .../game/enhancements/gamescope/framerate.rs | 26 ++ .../config/game/enhancements/gamescope/mod.rs | 149 +++++++++ .../game/enhancements/gamescope/size.rs | 26 ++ .../enhancements/gamescope/window_type.rs | 20 ++ src/lib/config/{ => game/enhancements}/hud.rs | 9 +- src/lib/config/game/enhancements/mod.rs | 52 +++ src/lib/config/game/mod.rs | 127 +++++++ src/lib/config/game/wine/mod.rs | 80 +++++ src/lib/config/game/wine/wine_lang.rs | 85 +++++ src/lib/config/{ => game/wine}/wine_sync.rs | 7 + src/lib/config/launcher/mod.rs | 62 ++++ src/lib/config/launcher/repairer.rs | 35 ++ src/lib/config/mod.rs | 312 ++---------------- src/lib/config/patch.rs | 66 ++++ src/lib/config/wine_lang.rs | 79 ----- src/lib/game.rs | 2 +- src/main.rs | 2 +- src/ui/preferences/enhancements.rs | 22 +- src/ui/preferences/gamescope.rs | 18 +- 22 files changed, 902 insertions(+), 393 deletions(-) create mode 100644 src/lib/config/game/dxvk.rs create mode 100644 src/lib/config/game/enhancements/fsr.rs create mode 100644 src/lib/config/game/enhancements/gamescope/framerate.rs create mode 100644 src/lib/config/game/enhancements/gamescope/mod.rs create mode 100644 src/lib/config/game/enhancements/gamescope/size.rs create mode 100644 src/lib/config/game/enhancements/gamescope/window_type.rs rename src/lib/config/{ => game/enhancements}/hud.rs (87%) create mode 100644 src/lib/config/game/enhancements/mod.rs create mode 100644 src/lib/config/game/mod.rs create mode 100644 src/lib/config/game/wine/mod.rs create mode 100644 src/lib/config/game/wine/wine_lang.rs rename src/lib/config/{ => game/wine}/wine_sync.rs (86%) create mode 100644 src/lib/config/launcher/mod.rs create mode 100644 src/lib/config/launcher/repairer.rs create mode 100644 src/lib/config/patch.rs delete mode 100644 src/lib/config/wine_lang.rs diff --git a/assets/ui/preferences/enhancements.blp b/assets/ui/preferences/enhancements.blp index f2df365..9afeae2 100644 --- a/assets/ui/preferences/enhancements.blp +++ b/assets/ui/preferences/enhancements.blp @@ -22,21 +22,6 @@ Adw.PreferencesPage page { Adw.ComboRow wine_lang { title: "Language"; subtitle: "Choose the language to use in wine environment. Can fix keyboard layout detection in-game"; - - model: Gtk.StringList { - strings [ - "System", - "English", - "German", - "Russian", - "Portuguese", - "French", - "Chinese", - "Spanish", - "Japanese", - "Korean" - ] - }; } } diff --git a/src/lib/config/game/dxvk.rs b/src/lib/config/game/dxvk.rs new file mode 100644 index 0000000..a566f0a --- /dev/null +++ b/src/lib/config/game/dxvk.rs @@ -0,0 +1,48 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +use crate::lib::consts::launcher_dir; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Dxvk { + pub builds: String, + pub selected: Option +} + +impl Default for Dxvk { + fn default() -> Self { + let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); + + Self { + builds: format!("{launcher_dir}/dxvks"), + selected: None + } + } +} + +impl From<&JsonValue> for Dxvk { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + builds: match value.get("builds") { + Some(value) => value.as_str().unwrap_or(&default.builds).to_string(), + None => default.builds + }, + + selected: match value.get("selected") { + Some(value) => { + if value.is_null() { + None + } else { + match value.as_str() { + Some(value) => Some(value.to_string()), + None => default.selected + } + } + }, + None => default.selected + } + } + } +} diff --git a/src/lib/config/game/enhancements/fsr.rs b/src/lib/config/game/enhancements/fsr.rs new file mode 100644 index 0000000..5b6794f --- /dev/null +++ b/src/lib/config/game/enhancements/fsr.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Fsr { + pub strength: u64, + pub enabled: bool +} + +impl Default for Fsr { + fn default() -> Self { + Self { + strength: 2, + enabled: false + } + } +} + +impl From<&JsonValue> for Fsr { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + strength: match value.get("strength") { + Some(value) => value.as_u64().unwrap_or(default.strength), + None => default.strength + }, + + enabled: match value.get("enabled") { + Some(value) => value.as_bool().unwrap_or(default.enabled), + None => default.enabled + } + } + } +} + +impl Fsr { + /// Get environment variables corresponding to used amd fsr options + pub fn get_env_vars(&self) -> HashMap<&str, String> { + if self.enabled { + HashMap::from([ + ("WINE_FULLSCREEN_FSR", String::from("1")), + ("WINE_FULLSCREEN_FSR_STRENGTH", self.strength.to_string()) + ]) + } + + else { + HashMap::new() + } + } +} diff --git a/src/lib/config/game/enhancements/gamescope/framerate.rs b/src/lib/config/game/enhancements/gamescope/framerate.rs new file mode 100644 index 0000000..35b7061 --- /dev/null +++ b/src/lib/config/game/enhancements/gamescope/framerate.rs @@ -0,0 +1,26 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] +pub struct Framerate { + pub focused: u64, + pub unfocused: u64 +} + +impl From<&JsonValue> for Framerate { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + focused: match value.get("focused") { + Some(value) => value.as_u64().unwrap_or(default.focused), + None => default.focused + }, + + unfocused: match value.get("unfocused") { + Some(value) => value.as_u64().unwrap_or(default.unfocused), + None => default.unfocused + } + } + } +} diff --git a/src/lib/config/game/enhancements/gamescope/mod.rs b/src/lib/config/game/enhancements/gamescope/mod.rs new file mode 100644 index 0000000..888496d --- /dev/null +++ b/src/lib/config/game/enhancements/gamescope/mod.rs @@ -0,0 +1,149 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +pub mod size; +pub mod framerate; +pub mod window_type; + +pub mod prelude { + pub use super::Gamescope; + pub use super::size::Size; + pub use super::framerate::Framerate; + pub use super::window_type::WindowType; +} + +use prelude::*; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct Gamescope { + pub enabled: bool, + pub game: Size, + pub gamescope: Size, + pub framerate: Framerate, + pub integer_scaling: bool, + pub nvidia_image_scaling: bool, + pub window_type: WindowType +} + +impl Gamescope { + pub fn get_command(&self, fsr_enabled: bool) -> Option { + // https://github.com/bottlesdevs/Bottles/blob/b908311348ed1184ead23dd76f9d8af41ff24082/src/backend/wine/winecommand.py#L478 + if self.enabled { + let mut gamescope = String::from("gamescope"); + + // Set window type + match self.window_type { + WindowType::Borderless => gamescope += " -b", + WindowType::Fullscreen => gamescope += " -f" + } + + // Set game width + if self.game.width > 0 { + gamescope += &format!(" -w {}", self.game.width); + } + + // Set game height + if self.game.height > 0 { + gamescope += &format!(" -h {}", self.game.height); + } + + // Set gamescope width + if self.gamescope.width > 0 { + gamescope += &format!(" -W {}", self.gamescope.width); + } + + // Set gamescope height + if self.gamescope.height > 0 { + gamescope += &format!(" -H {}", self.gamescope.height); + } + + // Set focused framerate limit + if self.framerate.focused > 0 { + gamescope += &format!(" -r {}", self.framerate.focused); + } + + // Set unfocused framerate limit + if self.framerate.unfocused > 0 { + gamescope += &format!(" -o {}", self.framerate.unfocused); + } + + // Set integer scaling + if self.integer_scaling { + gamescope += " -n"; + } + + // Set NIS (Nvidia Image Scaling) support + if self.nvidia_image_scaling { + gamescope += " -Y"; + } + + // Set FSR support (only if NIS is not enabled) + else if fsr_enabled { + gamescope += " -U"; + } + + Some(gamescope) + } + + else { + None + } + } +} + +impl Default for Gamescope { + fn default() -> Self { + Self { + enabled: false, + game: Size::default(), + gamescope: Size::default(), + framerate: Framerate::default(), + integer_scaling: true, + nvidia_image_scaling: false, + window_type: WindowType::default() + } + } +} + +impl From<&JsonValue> for Gamescope { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + enabled: match value.get("enabled") { + Some(value) => value.as_bool().unwrap_or(default.enabled), + None => default.enabled + }, + + game: match value.get("game") { + Some(value) => Size::from(value), + None => default.game + }, + + gamescope: match value.get("gamescope") { + Some(value) => Size::from(value), + None => default.gamescope + }, + + framerate: match value.get("framerate") { + Some(value) => Framerate::from(value), + None => default.framerate + }, + + integer_scaling: match value.get("integer_scaling") { + Some(value) => value.as_bool().unwrap_or(default.integer_scaling), + None => default.integer_scaling + }, + + nvidia_image_scaling: match value.get("nvidia_image_scaling") { + Some(value) => value.as_bool().unwrap_or(default.nvidia_image_scaling), + None => default.nvidia_image_scaling + }, + + window_type: match value.get("window_type") { + Some(value) => WindowType::from(value), + None => default.window_type + } + } + } +} diff --git a/src/lib/config/game/enhancements/gamescope/size.rs b/src/lib/config/game/enhancements/gamescope/size.rs new file mode 100644 index 0000000..fc63de0 --- /dev/null +++ b/src/lib/config/game/enhancements/gamescope/size.rs @@ -0,0 +1,26 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] +pub struct Size { + pub width: u64, + pub height: u64 +} + +impl From<&JsonValue> for Size { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + width: match value.get("width") { + Some(value) => value.as_u64().unwrap_or(default.width), + None => default.width + }, + + height: match value.get("height") { + Some(value) => value.as_u64().unwrap_or(default.height), + None => default.height + } + } + } +} diff --git a/src/lib/config/game/enhancements/gamescope/window_type.rs b/src/lib/config/game/enhancements/gamescope/window_type.rs new file mode 100644 index 0000000..0ce8d4c --- /dev/null +++ b/src/lib/config/game/enhancements/gamescope/window_type.rs @@ -0,0 +1,20 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum WindowType { + Borderless, + Fullscreen +} + +impl Default for WindowType { + fn default() -> Self { + Self::Borderless + } +} + +impl From<&JsonValue> for WindowType { + fn from(value: &JsonValue) -> Self { + serde_json::from_value(value.clone()).unwrap_or(Self::default()) + } +} diff --git a/src/lib/config/hud.rs b/src/lib/config/game/enhancements/hud.rs similarity index 87% rename from src/lib/config/hud.rs rename to src/lib/config/game/enhancements/hud.rs index 0636ba9..2d97419 100644 --- a/src/lib/config/hud.rs +++ b/src/lib/config/game/enhancements/hud.rs @@ -1,8 +1,9 @@ use std::collections::HashMap; use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; -use super::Config; +use crate::lib::config::Config; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum HUD { @@ -17,6 +18,12 @@ impl Default for HUD { } } +impl From<&JsonValue> for HUD { + fn from(value: &JsonValue) -> Self { + serde_json::from_value(value.clone()).unwrap_or(Self::default()) + } +} + impl TryFrom for HUD { type Error = String; diff --git a/src/lib/config/game/enhancements/mod.rs b/src/lib/config/game/enhancements/mod.rs new file mode 100644 index 0000000..b7f1f25 --- /dev/null +++ b/src/lib/config/game/enhancements/mod.rs @@ -0,0 +1,52 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +pub mod fsr; +pub mod hud; +pub mod gamescope; + +pub mod prelude { + pub use super::gamescope::prelude::*; + + pub use super::Enhancements; + pub use super::fsr::Fsr; + pub use super::hud::HUD; +} + +use prelude::*; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] +pub struct Enhancements { + pub fsr: Fsr, + pub gamemode: bool, + pub hud: HUD, + pub gamescope: Gamescope +} + +impl From<&JsonValue> for Enhancements { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + fsr: match value.get("fsr") { + Some(value) => Fsr::from(value), + None => default.fsr + }, + + gamemode: match value.get("gamemode") { + Some(value) => value.as_bool().unwrap_or(default.gamemode), + None => default.gamemode + }, + + hud: match value.get("hud") { + Some(value) => HUD::from(value), + None => default.hud + }, + + gamescope: match value.get("gamescope") { + Some(value) => Gamescope::from(value), + None => default.gamescope + } + } + } +} diff --git a/src/lib/config/game/mod.rs b/src/lib/config/game/mod.rs new file mode 100644 index 0000000..443b337 --- /dev/null +++ b/src/lib/config/game/mod.rs @@ -0,0 +1,127 @@ +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +use crate::lib::consts::launcher_dir; + +pub mod wine; +pub mod dxvk; +pub mod enhancements; + +pub mod prelude { + pub use super::enhancements::prelude::*; + pub use super::wine::prelude::*; + + pub use super::Game; + pub use super::dxvk::Dxvk; +} + +use prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Game { + pub path: String, + pub voices: Vec, + pub wine: prelude::Wine, + pub dxvk: prelude::Dxvk, + pub enhancements: prelude::Enhancements, + pub environment: HashMap, + pub command: Option +} + +impl Default for Game { + fn default() -> Self { + let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); + + Self { + path: format!("{launcher_dir}/game/drive_c/Program Files/Genshin Impact"), + voices: vec![ + String::from("en-us") + ], + wine: Wine::default(), + dxvk: Dxvk::default(), + enhancements: Enhancements::default(), + environment: HashMap::new(), + command: None + } + } +} + +impl From<&JsonValue> for Game { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + path: match value.get("path") { + Some(value) => value.as_str().unwrap_or(&default.path).to_string(), + None => default.path + }, + + voices: match value.get("voices") { + Some(value) => match value.as_array() { + Some(values) => { + let mut voices = Vec::new(); + + for value in values { + if let Some(voice) = value.as_str() { + voices.push(voice.to_string()); + } + } + + voices + }, + None => default.voices + }, + None => default.voices + }, + + wine: match value.get("wine") { + Some(value) => Wine::from(value), + None => default.wine + }, + + dxvk: match value.get("dxvk") { + Some(value) => Dxvk::from(value), + None => default.dxvk + }, + + enhancements: match value.get("enhancements") { + Some(value) => Enhancements::from(value), + None => default.enhancements + }, + + environment: match value.get("environment") { + Some(value) => match value.as_object() { + Some(values) => { + let mut vars = HashMap::new(); + + for (name, value) in values { + if let Some(value) = value.as_str() { + vars.insert(name.clone(), value.to_string()); + } + } + + vars + }, + None => default.environment + }, + None => default.environment + }, + + command: match value.get("command") { + Some(value) => { + if value.is_null() { + None + } else { + match value.as_str() { + Some(value) => Some(value.to_string()), + None => default.command + } + } + }, + None => default.command + } + } + } +} diff --git a/src/lib/config/game/wine/mod.rs b/src/lib/config/game/wine/mod.rs new file mode 100644 index 0000000..7b8a57e --- /dev/null +++ b/src/lib/config/game/wine/mod.rs @@ -0,0 +1,80 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +use crate::lib::consts::launcher_dir; + +pub mod wine_sync; +pub mod wine_lang; + +pub mod prelude { + pub use super::Wine; + pub use super::wine_sync::WineSync; + pub use super::wine_lang::WineLang; +} + +use prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Wine { + pub prefix: String, + pub builds: String, + pub selected: Option, + pub sync: WineSync, + pub language: WineLang +} + +impl Default for Wine { + fn default() -> Self { + let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); + + Self { + prefix: format!("{launcher_dir}/game"), + builds: format!("{launcher_dir}/runners"), + selected: None, + sync: WineSync::default(), + language: WineLang::default() + } + } +} + +impl From<&JsonValue> for Wine { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + prefix: match value.get("prefix") { + Some(value) => value.as_str().unwrap_or(&default.prefix).to_string(), + None => default.prefix + }, + + builds: match value.get("builds") { + Some(value) => value.as_str().unwrap_or(&default.builds).to_string(), + None => default.builds + }, + + selected: match value.get("selected") { + Some(value) => { + if value.is_null() { + None + } else { + match value.as_str() { + Some(value) => Some(value.to_string()), + None => default.selected + } + } + }, + None => default.selected + }, + + sync: match value.get("sync") { + Some(value) => WineSync::from(value), + None => default.sync + }, + + language: match value.get("language") { + Some(value) => WineLang::from(value), + None => default.language + } + } + } +} diff --git a/src/lib/config/game/wine/wine_lang.rs b/src/lib/config/game/wine/wine_lang.rs new file mode 100644 index 0000000..df69c1f --- /dev/null +++ b/src/lib/config/game/wine/wine_lang.rs @@ -0,0 +1,85 @@ +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum WineLang { + System, + English, + Russian, + German, + Portuguese, + Polish, + French, + Spanish, + Chinese, + Japanese, + Korean +} + +impl Default for WineLang { + fn default() -> Self { + Self::System + } +} + +impl From<&JsonValue> for WineLang { + fn from(value: &JsonValue) -> Self { + serde_json::from_value(value.clone()).unwrap_or(Self::default()) + } +} + +impl Into for WineLang { + fn into(self) -> String { + format!("{:?}", self) + } +} + +impl Into for WineLang { + fn into(self) -> u32 { + for (i, lang) in Self::list().into_iter().enumerate() { + if lang == self { + return i as u32; + } + } + + unreachable!() + } +} + +impl WineLang { + pub fn list() -> Vec { + vec![ + Self::System, + Self::English, + Self::Russian, + Self::German, + Self::Portuguese, + Self::Polish, + Self::French, + Self::Spanish, + Self::Chinese, + Self::Japanese, + Self::Korean + ] + } + + /// Get environment variables corresponding to used wine language + pub fn get_env_vars(&self) -> HashMap<&str, &str> { + HashMap::from([("LANG", match self { + Self::System => return HashMap::new(), + + Self::English => "en_US.UTF8", + Self::Russian => "ru_RU.UTF8", + Self::German => "de_DE.UTF8", + Self::Portuguese => "pt_PT.UTF8", + Self::Polish => "pl-PL.UTF8", + Self::French => "fr_FR.UTF8", + Self::Spanish => "es_ES.UTF8", + Self::Chinese => "zh_CN.UTF8", + Self::Japanese => "ja_JP.UTF8", + Self::Korean => "ko_KR.UTF8" + })]) + } +} diff --git a/src/lib/config/wine_sync.rs b/src/lib/config/game/wine/wine_sync.rs similarity index 86% rename from src/lib/config/wine_sync.rs rename to src/lib/config/game/wine/wine_sync.rs index 83c92cc..203aac9 100644 --- a/src/lib/config/wine_sync.rs +++ b/src/lib/config/game/wine/wine_sync.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum WineSync { @@ -16,6 +17,12 @@ impl Default for WineSync { } } +impl From<&JsonValue> for WineSync { + fn from(value: &JsonValue) -> Self { + serde_json::from_value(value.clone()).unwrap_or(Self::default()) + } +} + impl TryFrom for WineSync { type Error = String; diff --git a/src/lib/config/launcher/mod.rs b/src/lib/config/launcher/mod.rs new file mode 100644 index 0000000..2cc41f4 --- /dev/null +++ b/src/lib/config/launcher/mod.rs @@ -0,0 +1,62 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +use crate::lib::consts::launcher_dir; + +pub mod repairer; + +pub mod prelude { + pub use super::Launcher; + pub use super::repairer::Repairer; +} + +use prelude::*; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Launcher { + pub language: String, + pub temp: Option, + pub repairer: Repairer +} + +impl Default for Launcher { + fn default() -> Self { + Self { + language: String::from("en-us"), + temp: launcher_dir(), + repairer: Repairer::default() + } + } +} + +impl From<&JsonValue> for Launcher { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + language: match value.get("language") { + Some(value) => value.as_str().unwrap_or(&default.language).to_string(), + None => default.language + }, + + temp: match value.get("temp") { + Some(value) => { + if value.is_null() { + None + } else { + match value.as_str() { + Some(value) => Some(value.to_string()), + None => default.temp + } + } + }, + None => default.temp + }, + + repairer: match value.get("repairer") { + Some(value) => Repairer::from(value), + None => default.repairer + } + } + } +} diff --git a/src/lib/config/launcher/repairer.rs b/src/lib/config/launcher/repairer.rs new file mode 100644 index 0000000..1c6d2fb --- /dev/null +++ b/src/lib/config/launcher/repairer.rs @@ -0,0 +1,35 @@ +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Repairer { + pub threads: u64, + pub fast: bool +} + +impl Default for Repairer { + fn default() -> Self { + Self { + threads: 4, + fast: false + } + } +} + +impl From<&JsonValue> for Repairer { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + threads: match value.get("threads") { + Some(value) => value.as_u64().unwrap_or(default.threads), + None => default.threads + }, + + fast: match value.get("fast") { + Some(value) => value.as_bool().unwrap_or(default.fast), + None => default.fast + } + } + } +} diff --git a/src/lib/config/mod.rs b/src/lib/config/mod.rs index c1d844e..4a32224 100644 --- a/src/lib/config/mod.rs +++ b/src/lib/config/mod.rs @@ -1,10 +1,10 @@ -use std::collections::HashMap; use std::fs::File; use std::io::Read; use std::path::Path; use std::io::{Error, ErrorKind, Write}; use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; use crate::lib; use super::consts::*; @@ -13,13 +13,18 @@ use super::wine::{ List as WineList }; -mod hud; -mod wine_sync; -mod wine_lang; +pub mod launcher; +pub mod game; +pub mod patch; -pub use hud::HUD; -pub use wine_sync::WineSync; -pub use wine_lang::WineLang; +pub mod prelude { + pub use super::launcher::prelude::*; + pub use super::game::prelude::*; + + pub use super::patch::Patch; +} + +use prelude::*; static mut CONFIG: Option = None; @@ -50,8 +55,10 @@ pub fn get_raw() -> Result { file.read_to_string(&mut json)?; - match serde_json::from_str::(&json) { + match serde_json::from_str(&json) { Ok(config) => { + let config = Config::from(&config); + unsafe { CONFIG = Some(config.clone()); } @@ -179,288 +186,27 @@ impl Config { None => None } } - - pub fn get_gamescope_command(&self) -> Option { - // https://github.com/bottlesdevs/Bottles/blob/b908311348ed1184ead23dd76f9d8af41ff24082/src/backend/wine/winecommand.py#L478 - if self.game.enhancements.gamescope.enabled { - let mut gamescope = String::from("gamescope"); - - // Set window type - match self.game.enhancements.gamescope.window_type { - WindowType::Borderless => gamescope += " -b", - WindowType::Fullscreen => gamescope += " -f" - } - - // Set game width - if self.game.enhancements.gamescope.game.width > 0 { - gamescope += &format!(" -w {}", self.game.enhancements.gamescope.game.width); - } - - // Set game height - if self.game.enhancements.gamescope.game.height > 0 { - gamescope += &format!(" -h {}", self.game.enhancements.gamescope.game.height); - } - - // Set gamescope width - if self.game.enhancements.gamescope.gamescope.width > 0 { - gamescope += &format!(" -W {}", self.game.enhancements.gamescope.gamescope.width); - } - - // Set gamescope height - if self.game.enhancements.gamescope.gamescope.height > 0 { - gamescope += &format!(" -H {}", self.game.enhancements.gamescope.gamescope.height); - } - - // Set focused framerate limit - if self.game.enhancements.gamescope.framerate.focused > 0 { - gamescope += &format!(" -r {}", self.game.enhancements.gamescope.framerate.focused); - } - - // Set unfocused framerate limit - if self.game.enhancements.gamescope.framerate.unfocused > 0 { - gamescope += &format!(" -o {}", self.game.enhancements.gamescope.framerate.unfocused); - } - - // Set integer scaling - if self.game.enhancements.gamescope.integer_scaling { - gamescope += " -n"; - } - - // Set NIS (Nvidia Image Scaling) support - if self.game.enhancements.gamescope.nvidia_image_scaling { - gamescope += " -Y"; - } - - // Set FSR support (only if NIS is not enabled) - else if self.game.enhancements.fsr.enabled { - gamescope += " -U"; - } - - Some(gamescope) - } - - else { - None - } - } } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Launcher { - pub language: String, - pub temp: Option, - pub repairer: Repairer -} +impl From<&JsonValue> for Config { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); -impl Default for Launcher { - fn default() -> Self { Self { - language: String::from("en-us"), - 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 - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Patch { - pub path: String, - pub servers: Vec, - pub root: bool -} - -impl Default for Patch { - fn default() -> Self { - Self { - path: match launcher_dir() { - Some(dir) => format!("{}/patch", dir), - None => String::new() + launcher: match value.get("launcher") { + Some(value) => Launcher::from(value), + None => default.launcher }, - servers: vec![ - "https://notabug.org/Krock/dawn".to_string(), - "https://dev.kaifa.ch/Maroxy/dawn".to_string() - ], - // Disable root requirement for patching if we're running launcher in flatpak - root: !Path::new("/.flatpak-info").exists() - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Game { - pub path: String, - pub voices: Vec, - pub wine: Wine, - pub dxvk: Dxvk, - pub enhancements: Enhancements, - pub environment: HashMap, - pub command: Option -} - -impl Default for Game { - fn default() -> Self { - Self { - path: match launcher_dir() { - Some(dir) => format!("{}/game/drive_c/Program Files/Genshin Impact", dir), - None => String::new() + game: match value.get("game") { + Some(value) => Game::from(value), + None => default.game }, - voices: vec![ - String::from("en-us") - ], - wine: Wine::default(), - dxvk: Dxvk::default(), - enhancements: Enhancements::default(), - environment: HashMap::new(), - command: None + + patch: match value.get("patch") { + Some(value) => Patch::from(value), + None => default.patch + } } } } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Wine { - pub prefix: String, - pub builds: String, - pub selected: Option, - pub sync: WineSync, - pub language: WineLang -} - -impl Default for Wine { - fn default() -> Self { - Self { - prefix: match launcher_dir() { - Some(dir) => format!("{}/game", dir), - None => String::new() - }, - builds: match launcher_dir() { - Some(dir) => format!("{}/runners", dir), - None => String::new() - }, - selected: None, - sync: WineSync::default(), - language: WineLang::default() - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Dxvk { - pub builds: String, - pub selected: Option -} - -impl Default for Dxvk { - fn default() -> Self { - Self { - builds: match launcher_dir() { - Some(dir) => format!("{}/dxvks", dir), - None => String::new() - }, - selected: None - } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] -pub struct Enhancements { - pub fsr: Fsr, - pub gamemode: bool, - pub hud: HUD, - pub gamescope: Gamescope -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Fsr { - pub strength: u32, - pub enabled: bool -} - -impl Default for Fsr { - fn default() -> Self { - Self { - strength: 2, - enabled: false - } - } -} - -impl Fsr { - /// Get environment variables corresponding to used amd fsr options - pub fn get_env_vars(&self) -> HashMap<&str, String> { - if self.enabled { - HashMap::from([ - ("WINE_FULLSCREEN_FSR", String::from("1")), - ("WINE_FULLSCREEN_FSR_STRENGTH", self.strength.to_string()) - ]) - } - - else { - HashMap::new() - } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub struct Gamescope { - pub enabled: bool, - pub game: Size, - pub gamescope: Size, - pub framerate: Framerate, - pub integer_scaling: bool, - pub nvidia_image_scaling: bool, - pub window_type: WindowType -} - -impl Default for Gamescope { - fn default() -> Self { - Self { - enabled: false, - game: Size::default(), - gamescope: Size::default(), - framerate: Framerate::default(), - integer_scaling: true, - nvidia_image_scaling: false, - window_type: WindowType::default() - } - } -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] -pub struct Size { - pub width: u16, - pub height: u16 -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)] -pub struct Framerate { - pub focused: u16, - pub unfocused: u16 -} - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum WindowType { - Borderless, - Fullscreen -} - -impl Default for WindowType { - fn default() -> Self { - Self::Borderless - } -} diff --git a/src/lib/config/patch.rs b/src/lib/config/patch.rs new file mode 100644 index 0000000..4032a70 --- /dev/null +++ b/src/lib/config/patch.rs @@ -0,0 +1,66 @@ +use std::path::Path; + +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +use crate::lib::consts::launcher_dir; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Patch { + pub path: String, + pub servers: Vec, + pub root: bool +} + +impl Default for Patch { + fn default() -> Self { + let launcher_dir = launcher_dir().expect("Failed to get launcher dir"); + + Self { + path: format!("{launcher_dir}/patch"), + servers: vec![ + "https://notabug.org/Krock/dawn".to_string(), + "https://dev.kaifa.ch/Maroxy/dawn".to_string() + ], + + // Disable root requirement for patching if we're running launcher in flatpak + root: !Path::new("/.flatpak-info").exists() + } + } +} + +impl From<&JsonValue> for Patch { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + path: match value.get("path") { + Some(value) => value.as_str().unwrap_or(&default.path).to_string(), + None => default.path + }, + + servers: match value.get("servers") { + Some(value) => match value.as_array() { + Some(values) => { + let mut servers = Vec::new(); + + for value in values { + if let Some(server) = value.as_str() { + servers.push(server.to_string()); + } + } + + servers + }, + None => default.servers + }, + None => default.servers + }, + + root: match value.get("root") { + Some(value) => value.as_bool().unwrap_or(default.root), + None => default.root + } + } + } +} diff --git a/src/lib/config/wine_lang.rs b/src/lib/config/wine_lang.rs deleted file mode 100644 index 0f5ad70..0000000 --- a/src/lib/config/wine_lang.rs +++ /dev/null @@ -1,79 +0,0 @@ -use std::collections::HashMap; - -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] -pub enum WineLang { - System, - English, - German, - Russian, - Portuguese, - French, - Chinese, - Spanish, - Japanese, - Korean -} - -impl Default for WineLang { - fn default() -> Self { - Self::System - } -} - -impl TryFrom for WineLang { - type Error = String; - - fn try_from(value: u32) -> Result { - match value { - 0 => Ok(Self::System), - 1 => Ok(Self::English), - 2 => Ok(Self::German), - 3 => Ok(Self::Russian), - 4 => Ok(Self::Portuguese), - 5 => Ok(Self::French), - 6 => Ok(Self::Chinese), - 7 => Ok(Self::Spanish), - 8 => Ok(Self::Japanese), - 9 => Ok(Self::Korean), - _ => Err(String::from("Failed to convert number to WineLang enum")) - } - } -} - -impl Into for WineLang { - fn into(self) -> u32 { - match self { - WineLang::System => 0, - WineLang::English => 1, - WineLang::German => 2, - WineLang::Russian => 3, - WineLang::Portuguese => 4, - WineLang::French => 5, - WineLang::Chinese => 6, - WineLang::Spanish => 7, - WineLang::Japanese => 8, - WineLang::Korean => 9 - } - } -} - -impl WineLang { - /// Get environment variables corresponding to used wine language - pub fn get_env_vars(&self) -> HashMap<&str, &str> { - HashMap::from([("LANG", match self { - WineLang::System => return HashMap::new(), - - WineLang::English => "en_US.UTF8", - WineLang::German => "de_DE.UTF8", - WineLang::Russian => "ru_RU.UTF8", - WineLang::Portuguese => "pt_PT.UTF8", - WineLang::French => "fr_FR.UTF8", - WineLang::Chinese => "zh_CN.UTF8", - WineLang::Spanish => "es_ES.UTF8", - WineLang::Japanese => "ja_JP.UTF8", - WineLang::Korean => "ko_KR.UTF8" - })]) - } -} diff --git a/src/lib/game.rs b/src/lib/game.rs index 75955a9..070ad6e 100644 --- a/src/lib/game.rs +++ b/src/lib/game.rs @@ -99,7 +99,7 @@ pub fn run(debug: bool) -> std::io::Result<()> { } // gamescope -- - if let Some(gamescope) = config.get_gamescope_command() { + if let Some(gamescope) = config.game.enhancements.gamescope.get_command(config.game.enhancements.fsr.enabled) { bash_chain = format!("{gamescope} -- {bash_chain}"); } diff --git a/src/main.rs b/src/main.rs index 045567e..83be432 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,7 +48,7 @@ fn main() { ); // Create default launcher folder if needed - let launcher_dir = lib::consts::launcher_dir().unwrap(); + let launcher_dir = lib::consts::launcher_dir().expect("Failed to get launcher dir"); if !Path::new(&launcher_dir).exists() || Path::new(&format!("{}/.first-run", launcher_dir)).exists() { fs::create_dir_all(&launcher_dir).expect("Failed to create default launcher dir"); diff --git a/src/ui/preferences/enhancements.rs b/src/ui/preferences/enhancements.rs index 68474e0..b8c4d24 100644 --- a/src/ui/preferences/enhancements.rs +++ b/src/ui/preferences/enhancements.rs @@ -6,6 +6,7 @@ use gtk::glib::clone; use crate::lib; use crate::lib::config; +use crate::lib::config::prelude::*; use crate::ui::*; @@ -61,6 +62,17 @@ impl AppWidgets { gamescope_app: GamescopeApp::new(window)? }; + // Set availale wine languages + let model = gtk::StringList::new(&[]); + + for lang in WineLang::list() { + let lang: String = lang.into(); + + model.append(&lang); + } + + result.wine_lang.set_model(Some(&model)); + // Disable gamemode row if it's not available if !lib::is_available("gamemoderun") { result.gamemode_row.set_sensitive(false); @@ -108,7 +120,7 @@ impl App { // Wine sync selection self.widgets.sync_combo.connect_selected_notify(move |row| { if let Ok(mut config) = config::get() { - config.game.wine.sync = config::WineSync::try_from(row.selected()).unwrap(); + config.game.wine.sync = WineSync::try_from(row.selected()).unwrap(); config::update(config); } @@ -117,7 +129,7 @@ impl App { // Wine language selection self.widgets.wine_lang.connect_selected_notify(move |row| { if let Ok(mut config) = config::get() { - config.game.wine.language = config::WineLang::try_from(row.selected()).unwrap(); + config.game.wine.language = WineLang::list()[row.selected() as usize]; config::update(config); } @@ -126,7 +138,7 @@ impl App { // HUD selection self.widgets.hud_combo.connect_selected_notify(move |row| { if let Ok(mut config) = config::get() { - config.game.enhancements.hud = config::HUD::try_from(row.selected()).unwrap(); + config.game.enhancements.hud = HUD::try_from(row.selected()).unwrap(); config::update(config); } @@ -142,7 +154,7 @@ impl App { // Source: Bottles (https://github.com/bottlesdevs/Bottles/blob/22fa3573a13f4e9b9c429e4cdfe4ca29787a2832/src/ui/details-preferences.ui#L88) self.widgets.fsr_combo.connect_selected_notify(move |row| { if let Ok(mut config) = config::get() { - config.game.enhancements.fsr.strength = 5 - row.selected(); + config.game.enhancements.fsr.strength = 5 - row.selected() as u64; config::update(config); } @@ -207,7 +219,7 @@ impl App { self.widgets.hud_combo.set_selected(config.game.enhancements.hud.into()); // FSR strength selection - self.widgets.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength); + self.widgets.fsr_combo.set_selected(5 - config.game.enhancements.fsr.strength as u32); // FSR switching self.widgets.fsr_switcher.set_state(config.game.enhancements.fsr.enabled); diff --git a/src/ui/preferences/gamescope.rs b/src/ui/preferences/gamescope.rs index b5bc109..41677bb 100644 --- a/src/ui/preferences/gamescope.rs +++ b/src/ui/preferences/gamescope.rs @@ -3,8 +3,10 @@ use libadwaita::{self as adw, prelude::*}; use gtk::glib; -use crate::ui::get_object; use crate::lib::config; +use crate::lib::config::prelude::*; + +use crate::ui::*; /// This structure is used to describe widgets used in application /// @@ -174,9 +176,9 @@ impl App { if let Ok(mut config) = config::get() { config.game.enhancements.gamescope.window_type = if button.is_active() { - config::WindowType::Borderless + WindowType::Borderless } else { - config::WindowType::Fullscreen + WindowType::Fullscreen }; config::update(config); @@ -195,9 +197,9 @@ impl App { if let Ok(mut config) = config::get() { config.game.enhancements.gamescope.window_type = if button.is_active() { - config::WindowType::Fullscreen + WindowType::Fullscreen } else { - config::WindowType::Borderless + WindowType::Borderless }; config::update(config); @@ -214,7 +216,7 @@ impl App { status_page.set_description(Some("Loading gamescope...")); - fn set_text(widget: >k::Entry, value: u16) { + fn set_text(widget: >k::Entry, value: u64) { widget.set_text(&if value == 0 { String::new() } else { value.to_string() }); } @@ -231,8 +233,8 @@ impl App { self.widgets.nvidia_image_scaling.set_state(config.game.enhancements.gamescope.nvidia_image_scaling); match config.game.enhancements.gamescope.window_type { - config::WindowType::Borderless => self.widgets.borderless.set_active(true), - config::WindowType::Fullscreen => self.widgets.fullscreen.set_active(true) + WindowType::Borderless => self.widgets.borderless.set_active(true), + WindowType::Fullscreen => self.widgets.fullscreen.set_active(true) }; Ok(())