- 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]`
This commit is contained in:
Observer KRypt0n_ 2023-03-08 14:21:23 +02:00
parent f6daddc4bc
commit 2ff3ad0122
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
7 changed files with 360 additions and 171 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "anime-launcher-sdk"
version = "0.3.6"
version = "0.4.0"
authors = ["Nikita Podvirnyy <suimin.tu.mu.ga.mi@gmail.com>"]
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 }

View file

@ -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<Version>
}
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<T: Into<PathBuf>, F: AsRef<str>>(components: T, name: F) -> anyhow::Result<Option<Self>> {
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<String, String>
}
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<T: Into<PathBuf>, F: AsRef<str>>(components: T, name: F) -> anyhow::Result<Option<Self>> {
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<T: Into<PathBuf>>(&self, components: T) -> anyhow::Result<Option<Group>> {
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<T: Into<PathBuf>>(&self, folder: T) -> bool {
@ -62,23 +146,18 @@ pub fn get_groups<T: Into<PathBuf>>(components: T) -> anyhow::Result<Vec<Group>>
}
/// List downloaded dxvk versions in some specific folder
pub fn get_downloaded<T: Into<PathBuf>>(components: T, folder: T) -> anyhow::Result<Vec<Version>> {
pub fn get_downloaded<T: Into<PathBuf>>(components: T, folder: T) -> anyhow::Result<Vec<Group>> {
let mut downloaded = Vec::new();
let list = get_groups(components)?
.into_iter()
.flat_map(|group| group.versions)
.collect::<Vec<Version>>();
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);
}
}

View file

@ -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<Vec<wine::Group>> {
tracing::debug!("Getting wine versions");
let components = serde_json::from_str::<serde_json::Value>(&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::<serde_json::Value>(&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::<wine::Version>(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<Vec<dxvk::Group>> {
tracing::debug!("Getting dxvk versions");
let components = serde_json::from_str::<serde_json::Value>(&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::<serde_json::Value>(&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::<dxvk::Version>(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<Vec<wine::Group>> {
tracing::debug!("Getting wine versions");
let components = serde_json::from_str::<serde_json::Value>(&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::<serde_json::Value>(&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::<wine::Version>(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<Vec<dxvk::Group>> {
tracing::debug!("Getting dxvk versions");
let components = serde_json::from_str::<serde_json::Value>(&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::<serde_json::Value>(&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::<dxvk::Version>(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)
}
}

View file

@ -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<Version>
}
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<T: Into<PathBuf>, F: AsRef<str>>(components: T, name: F) -> anyhow::Result<Option<Self>> {
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<String, String>
}
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<T: Into<PathBuf>, F: AsRef<str>>(components: T, name: F) -> anyhow::Result<Option<Self>> {
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<T: Into<PathBuf>>(&self, components: T) -> anyhow::Result<Option<Group>> {
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<T: Into<PathBuf>>(&self, folder: T) -> bool {
@ -72,27 +163,20 @@ pub fn get_groups<T: Into<PathBuf>>(components: T) -> anyhow::Result<Vec<Group>>
}
/// List downloaded wine versions in some specific folder
pub fn get_downloaded<T: Into<PathBuf>>(components: T, folder: T) -> anyhow::Result<Vec<Version>> {
pub fn get_downloaded<T: Into<PathBuf>>(components: T, folder: T) -> anyhow::Result<Vec<Group>> {
let mut downloaded = Vec::new();
let list = get_groups(components)?
.into_iter()
.flat_map(|group| group.versions)
.collect::<Vec<Version>>();
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)
}

View file

@ -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<Option<WineVersion>> {
/// 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<Option<wine::Version>> {
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<Option<DxvkVersion>> {
pub fn get_selected_dxvk(&self) -> anyhow::Result<Option<dxvk::Version>> {
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)
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}