From 2ff3ad0122704ea88880a80ae6a29a9e8833a1ba Mon Sep 17 00:00:00 2001 From: Observer KRypt0n_ Date: Wed, 8 Mar 2023 14:21:23 +0200 Subject: [PATCH] 0.4.0 - added `Group::find_in`, `Version::find_in` and `Version::find_group` methods to both `wine` and `dxvk` mods - `get_downloaded` now returns list of groups with downloaded versions instead of just a single list - added `features` property to `Group` structs of `wine` and `dxvk` - added support of `[group].features.env` variables to `game::run` - `ComponentsLoader` now caches `get_wine[dxvk]_versions` output - `Config::try_get_selected_wine[dxvk]_info` renamed to `Config::get_selected_wine[dxvk]` --- Cargo.toml | 3 +- src/components/dxvk.rs | 107 +++++++++++++--- src/components/loader.rs | 256 +++++++++++++++++++++------------------ src/components/wine.rs | 116 +++++++++++++++--- src/config/mod.rs | 30 ++--- src/game.rs | 17 ++- src/states/mod.rs | 2 +- 7 files changed, 360 insertions(+), 171 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a25d9a..cef3569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anime-launcher-sdk" -version = "0.3.6" +version = "0.4.0" authors = ["Nikita Podvirnyy "] license = "GPL-3.0" readme = "README.md" @@ -12,6 +12,7 @@ anime-game-core = { path = "anime-game-core", features = ["genshin", "all", "sta anyhow = "1.0" dirs = "4.0.0" tracing = "0.1" +cached = { version = "0.42", features = ["proc_macro"] } serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } diff --git a/src/components/dxvk.rs b/src/components/dxvk.rs index 5b1555b..ecd8bc0 100644 --- a/src/components/dxvk.rs +++ b/src/components/dxvk.rs @@ -1,17 +1,75 @@ use std::path::PathBuf; +use std::collections::HashMap; use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; use wincompatlib::prelude::*; use super::loader::ComponentsLoader; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Group { pub name: String, pub title: String, + pub features: Features, pub versions: Vec } +impl Group { + /// Find dxvk group with given name in components index + /// + /// This method will also check all version names within this group, so both `vanilla` and `dxvk-1.10.3` will work + pub fn find_in, F: AsRef>(components: T, name: F) -> anyhow::Result> { + let name = name.as_ref(); + + for group in get_groups(components)? { + if group.name == name || group.versions.iter().any(move |version| version.name == name) { + return Ok(Some(group)); + } + } + + Ok(None) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Features { + pub env: HashMap +} + +impl Default for Features { + fn default() -> Self { + Self { + env: HashMap::new() + } + } +} + +impl From<&JsonValue> for Features { + fn from(value: &JsonValue) -> Self { + let mut default = Self::default(); + + Self { + env: match value.get("env") { + Some(value) => { + if let Some(object) = value.as_object() { + for (key, value) in object { + if let Some(value) = value.as_str() { + default.env.insert(key.to_string(), value.to_string()); + } else { + default.env.insert(key.to_string(), value.to_string()); + } + } + } + + default.env + }, + None => default.env + } + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Version { pub name: String, @@ -25,6 +83,32 @@ impl Version { Ok(get_groups(components)?[0].versions[0].clone()) } + /// Find dxvk version with given name in components index + pub fn find_in, F: AsRef>(components: T, name: F) -> anyhow::Result> { + let name = name.as_ref(); + + for group in get_groups(components)? { + if let Some(version) = group.versions.into_iter().find(move |version| version.name == name || version.version == name) { + return Ok(Some(version)); + } + } + + Ok(None) + } + + /// Find dxvk group current version belongs to + pub fn find_group>(&self, components: T) -> anyhow::Result> { + let name = self.name.as_str(); + + for group in get_groups(components)? { + if group.versions.iter().any(move |version| version.name == name || version.version == name) { + return Ok(Some(group)); + } + } + + Ok(None) + } + /// Check is current dxvk downloaded in specified folder #[inline] pub fn is_downloaded_in>(&self, folder: T) -> bool { @@ -62,23 +146,18 @@ pub fn get_groups>(components: T) -> anyhow::Result> } /// List downloaded dxvk versions in some specific folder -pub fn get_downloaded>(components: T, folder: T) -> anyhow::Result> { +pub fn get_downloaded>(components: T, folder: T) -> anyhow::Result> { let mut downloaded = Vec::new(); - let list = get_groups(components)? - .into_iter() - .flat_map(|group| group.versions) - .collect::>(); + let folder: PathBuf = folder.into(); - for entry in folder.into().read_dir()? { - let name = entry?.file_name(); + for mut group in get_groups(components)? { + group.versions = group.versions.into_iter() + .filter(|version| folder.join(&version.name).exists()) + .collect(); - for version in &list { - if name == version.name.as_str() { - downloaded.push(version.clone()); - - break; - } + if !group.versions.is_empty() { + downloaded.push(group); } } diff --git a/src/components/loader.rs b/src/components/loader.rs index 7e06312..f818576 100644 --- a/src/components/loader.rs +++ b/src/components/loader.rs @@ -4,6 +4,144 @@ use crate::anime_game_core::traits::git_sync::RemoteGitSync; use super::wine; use super::dxvk; +/// Try to get wine versions from components index +#[tracing::instrument(level = "debug", ret)] +#[cached::proc_macro::cached(key = "PathBuf", convert = r##"{ index.to_path_buf() }"##, result)] +pub fn get_wine_versions(index: &Path) -> anyhow::Result> { + tracing::debug!("Getting wine versions"); + + let components = serde_json::from_str::(&std::fs::read_to_string(index.join("components.json"))?)?; + + match components.get("wine") { + Some(wine) => match wine.as_array() { + Some(groups) => { + let mut wine_groups = Vec::with_capacity(groups.len()); + + for group in groups { + let name = match group.get("name") { + Some(name) => match name.as_str() { + Some(name) => name.to_string(), + None => anyhow::bail!("Wrong components index structure: wine group's name entry must be a string") + } + + None => anyhow::bail!("Wrong components index structure: wine group's name not found") + }; + + let title = match group.get("title") { + Some(title) => match title.as_str() { + Some(title) => title.to_string(), + None => anyhow::bail!("Wrong components index structure: wine group's title entry must be a string") + } + + None => anyhow::bail!("Wrong components index structure: wine group's title not found") + }; + + let versions = serde_json::from_str::(&std::fs::read_to_string(index.join("wine").join(format!("{name}.json")))?)?; + + let mut wine_versions = Vec::new(); + + match versions.as_array() { + Some(versions) => { + for version in versions { + wine_versions.push(serde_json::from_value::(version.to_owned())?); + } + } + + None => anyhow::bail!("Wrong components index structure: wine versions must be a list") + } + + let features = match group.get("features") { + Some(features) => features.into(), + None => wine::Features::default() + }; + + wine_groups.push(wine::Group { + name, + title, + features, + versions: wine_versions + }); + } + + Ok(wine_groups) + } + + None => anyhow::bail!("Wrong components index structure: wine entry must be a list") + } + + None => anyhow::bail!("Wrong components index structure: wine entry not found") + } +} + +/// Try to get dxvk versions from components index +#[tracing::instrument(level = "debug", ret)] +#[cached::proc_macro::cached(key = "PathBuf", convert = r##"{ index.to_path_buf() }"##, result)] +pub fn get_dxvk_versions(index: &Path) -> anyhow::Result> { + tracing::debug!("Getting dxvk versions"); + + let components = serde_json::from_str::(&std::fs::read_to_string(index.join("components.json"))?)?; + + match components.get("dxvk") { + Some(dxvk) => match dxvk.as_array() { + Some(groups) => { + let mut dxvk_groups = Vec::with_capacity(groups.len()); + + for group in groups { + let name = match group.get("name") { + Some(name) => match name.as_str() { + Some(name) => name.to_string(), + None => anyhow::bail!("Wrong components index structure: dxvk group's name entry must be a string") + } + + None => anyhow::bail!("Wrong components index structure: dxvk group's name not found") + }; + + let title = match group.get("title") { + Some(title) => match title.as_str() { + Some(title) => title.to_string(), + None => anyhow::bail!("Wrong components index structure: dxvk group's title entry must be a string") + } + + None => anyhow::bail!("Wrong components index structure: dxvk group's title not found") + }; + + let versions = serde_json::from_str::(&std::fs::read_to_string(index.join("dxvk").join(format!("{name}.json")))?)?; + + let mut dxvk_versions = Vec::new(); + + match versions.as_array() { + Some(versions) => { + for version in versions { + dxvk_versions.push(serde_json::from_value::(version.to_owned())?); + } + } + + None => anyhow::bail!("Wrong components index structure: wine versions must be a list") + } + + let features = match group.get("features") { + Some(features) => features.into(), + None => dxvk::Features::default() + }; + + dxvk_groups.push(dxvk::Group { + name, + title, + features, + versions: dxvk_versions + }); + } + + Ok(dxvk_groups) + } + + None => anyhow::bail!("Wrong components index structure: wine entry must be a list") + } + + None => anyhow::bail!("Wrong components index structure: wine entry not found") + } +} + #[derive(Debug)] pub struct ComponentsLoader { folder: PathBuf @@ -22,127 +160,13 @@ impl ComponentsLoader { } } - /// Try to get wine versions from components index - #[tracing::instrument(level = "debug", ret)] pub fn get_wine_versions(&self) -> anyhow::Result> { - tracing::debug!("Getting wine versions"); - - let components = serde_json::from_str::(&std::fs::read_to_string(self.folder.join("components.json"))?)?; - - match components.get("wine") { - Some(wine) => match wine.as_array() { - Some(groups) => { - let mut wine_groups = Vec::with_capacity(groups.len()); - - for group in groups { - let name = match group.get("name") { - Some(name) => match name.as_str() { - Some(name) => name.to_string(), - None => anyhow::bail!("Wrong components index structure: wine group's name entry must be a string") - } - - None => anyhow::bail!("Wrong components index structure: wine group's name not found") - }; - - let title = match group.get("title") { - Some(title) => match title.as_str() { - Some(title) => title.to_string(), - None => anyhow::bail!("Wrong components index structure: wine group's title entry must be a string") - } - - None => anyhow::bail!("Wrong components index structure: wine group's title not found") - }; - - let versions = serde_json::from_str::(&std::fs::read_to_string(self.folder.join("wine").join(format!("{name}.json")))?)?; - - let mut wine_versions = Vec::new(); - - match versions.as_array() { - Some(versions) => { - for version in versions { - wine_versions.push(serde_json::from_value::(version.to_owned())?); - } - } - - None => anyhow::bail!("Wrong components index structure: wine versions must be a list") - } - - wine_groups.push(wine::Group { - name, - title, - versions: wine_versions - }); - } - - Ok(wine_groups) - } - - None => anyhow::bail!("Wrong components index structure: wine entry must be a list") - } - - None => anyhow::bail!("Wrong components index structure: wine entry not found") - } + get_wine_versions(&self.folder) } /// Try to get dxvk versions from components index #[tracing::instrument(level = "debug", ret)] pub fn get_dxvk_versions(&self) -> anyhow::Result> { - tracing::debug!("Getting dxvk versions"); - - let components = serde_json::from_str::(&std::fs::read_to_string(self.folder.join("components.json"))?)?; - - match components.get("dxvk") { - Some(dxvk) => match dxvk.as_array() { - Some(groups) => { - let mut dxvk_groups = Vec::with_capacity(groups.len()); - - for group in groups { - let name = match group.get("name") { - Some(name) => match name.as_str() { - Some(name) => name.to_string(), - None => anyhow::bail!("Wrong components index structure: dxvk group's name entry must be a string") - } - - None => anyhow::bail!("Wrong components index structure: dxvk group's name not found") - }; - - let title = match group.get("title") { - Some(title) => match title.as_str() { - Some(title) => title.to_string(), - None => anyhow::bail!("Wrong components index structure: dxvk group's title entry must be a string") - } - - None => anyhow::bail!("Wrong components index structure: dxvk group's title not found") - }; - - let versions = serde_json::from_str::(&std::fs::read_to_string(self.folder.join("dxvk").join(format!("{name}.json")))?)?; - - let mut dxvk_versions = Vec::new(); - - match versions.as_array() { - Some(versions) => { - for version in versions { - dxvk_versions.push(serde_json::from_value::(version.to_owned())?); - } - } - - None => anyhow::bail!("Wrong components index structure: wine versions must be a list") - } - - dxvk_groups.push(dxvk::Group { - name, - title, - versions: dxvk_versions - }); - } - - Ok(dxvk_groups) - } - - None => anyhow::bail!("Wrong components index structure: wine entry must be a list") - } - - None => anyhow::bail!("Wrong components index structure: wine entry not found") - } + get_dxvk_versions(&self.folder) } } diff --git a/src/components/wine.rs b/src/components/wine.rs index a7bacd6..d92b76f 100644 --- a/src/components/wine.rs +++ b/src/components/wine.rs @@ -1,17 +1,82 @@ use std::path::PathBuf; +use std::collections::HashMap; use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; use wincompatlib::prelude::*; use super::loader::ComponentsLoader; -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Group { pub name: String, pub title: String, + pub features: Features, pub versions: Vec } +impl Group { + /// Find wine group with given name in components index + /// + /// This method will also check all version names within this group, so both `wine-ge-proton` and `lutris-GE-Proton7-37-x86_64` will work + pub fn find_in, F: AsRef>(components: T, name: F) -> anyhow::Result> { + let name = name.as_ref(); + + for group in get_groups(components)? { + if group.name == name || group.versions.iter().any(move |version| version.name == name) { + return Ok(Some(group)); + } + } + + Ok(None) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Features { + pub need_dxvk: bool, + pub env: HashMap +} + +impl Default for Features { + fn default() -> Self { + Self { + need_dxvk: true, + env: HashMap::new() + } + } +} + +impl From<&JsonValue> for Features { + fn from(value: &JsonValue) -> Self { + let mut default = Self::default(); + + Self { + need_dxvk: match value.get("need_dxvk") { + Some(value) => value.as_bool().unwrap_or(default.need_dxvk), + None => default.need_dxvk + }, + + env: match value.get("env") { + Some(value) => { + if let Some(object) = value.as_object() { + for (key, value) in object { + if let Some(value) = value.as_str() { + default.env.insert(key.to_string(), value.to_string()); + } else { + default.env.insert(key.to_string(), value.to_string()); + } + } + } + + default.env + }, + None => default.env + } + } + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Version { pub name: String, @@ -35,6 +100,32 @@ impl Version { Ok(get_groups(components)?[0].versions[0].clone()) } + /// Find wine version with given name in components index + pub fn find_in, F: AsRef>(components: T, name: F) -> anyhow::Result> { + let name = name.as_ref(); + + for group in get_groups(components)? { + if let Some(version) = group.versions.into_iter().find(move |version| version.name == name) { + return Ok(Some(version)); + } + } + + Ok(None) + } + + /// Find wine group current version belongs to + pub fn find_group>(&self, components: T) -> anyhow::Result> { + let name = self.name.as_str(); + + for group in get_groups(components)? { + if group.versions.iter().any(move |version| version.name == name) { + return Ok(Some(group)); + } + } + + Ok(None) + } + /// Check is current wine downloaded in specified folder #[inline] pub fn is_downloaded_in>(&self, folder: T) -> bool { @@ -72,27 +163,20 @@ pub fn get_groups>(components: T) -> anyhow::Result> } /// List downloaded wine versions in some specific folder -pub fn get_downloaded>(components: T, folder: T) -> anyhow::Result> { +pub fn get_downloaded>(components: T, folder: T) -> anyhow::Result> { let mut downloaded = Vec::new(); - let list = get_groups(components)? - .into_iter() - .flat_map(|group| group.versions) - .collect::>(); + let folder: PathBuf = folder.into(); - for entry in folder.into().read_dir()? { - let name = entry?.file_name(); + for mut group in get_groups(components)? { + group.versions = group.versions.into_iter() + .filter(|version| folder.join(&version.name).exists()) + .collect(); - for version in &list { - if name == version.name.as_str() { - downloaded.push(version.clone()); - - break; - } + if !group.versions.is_empty() { + downloaded.push(group); } } - downloaded.sort_by(|a, b| b.name.partial_cmp(&a.name).unwrap()); - Ok(downloaded) } diff --git a/src/config/mod.rs b/src/config/mod.rs index 8b76531..0a2405d 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -190,22 +190,22 @@ impl From<&JsonValue> for Config { } #[cfg(feature = "components")] -use crate::components::wine::{self, Version as WineVersion}; +use crate::components::wine; #[cfg(feature = "components")] -use crate::components::dxvk::{self, Version as DxvkVersion}; +use crate::components::dxvk; #[cfg(feature = "components")] impl Config { - pub fn try_get_selected_wine_info(&self) -> anyhow::Result> { + /// Try to get selected wine version + /// + /// Returns: + /// 1) `Ok(Some(..))` if version selected and found + /// 2) `Ok(None)` if version wasn't found, so likely too old or just incorrect + /// 3) `Err(..)` if failed to get selected wine version + pub fn get_selected_wine(&self) -> anyhow::Result> { match &self.game.wine.selected { - Some(selected) => { - Ok(wine::get_groups(&self.components.path)? - .iter() - .flat_map(|group| group.versions.clone()) - .find(|version| version.name.eq(selected))) - } - + Some(selected) => wine::Version::find_in(&self.components.path, selected), None => Ok(None) } } @@ -216,15 +216,9 @@ impl Config { /// 1) `Ok(Some(..))` if version was found /// 2) `Ok(None)` if version wasn't found, so too old or dxvk is not applied /// 3) `Err(..)` if failed to get applied dxvk version, likely because wrong prefix path specified - pub fn try_get_selected_dxvk_info(&self) -> anyhow::Result> { + pub fn get_selected_dxvk(&self) -> anyhow::Result> { match wincompatlib::dxvk::Dxvk::get_version(&self.game.wine.prefix)? { - Some(version) => { - Ok(dxvk::get_groups(&self.components.path)? - .iter() - .flat_map(|group| group.versions.clone()) - .find(move |dxvk| dxvk.version == version)) - } - + Some(version) => dxvk::Version::find_in(&self.components.path, version), None => Ok(None) } } diff --git a/src/game.rs b/src/game.rs index a7fe967..abc609b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -24,7 +24,7 @@ pub fn run() -> anyhow::Result<()> { return Err(anyhow::anyhow!("Game is not installed")); } - let Some(wine) = config.try_get_selected_wine_info()? else { + let Some(wine) = config.get_selected_wine()? else { anyhow::bail!("Couldn't find wine executable"); }; @@ -127,10 +127,17 @@ pub fn run() -> anyhow::Result<()> { command.env("WINEARCH", "win64"); command.env("WINEPREFIX", &config.game.wine.prefix); - // Add DXVK_ASYNC=1 for dxvk-async builds automatically - if let Ok(Some(dxvk)) = &config.try_get_selected_dxvk_info() { - if dxvk.version.contains("async") { - command.env("DXVK_ASYNC", "1"); + // Add environment flags for selected wine + if let Ok(Some(wine )) = config.get_selected_wine() { + if let Ok(Some(group)) = wine.find_group(&config.components.path) { + command.envs(group.features.env); + } + } + + // Add environment flags for selected dxvk + if let Ok(Some(dxvk )) = config.get_selected_dxvk() { + if let Ok(Some(group)) = dxvk.find_group(&config.components.path) { + command.envs(group.features.env); } } diff --git a/src/states/mod.rs b/src/states/mod.rs index 4827e75..ccaddb3 100644 --- a/src/states/mod.rs +++ b/src/states/mod.rs @@ -148,7 +148,7 @@ impl LauncherState { // Check wine existence #[cfg(feature = "components")] { - if config.try_get_selected_wine_info()?.is_none() { + if config.get_selected_wine()?.is_none() { return Ok(Self::WineNotInstalled); } }