Init commit
This commit is contained in:
parent
e3a24db1df
commit
e7d356da5c
27 changed files with 1596 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/target
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
#
|
||||||
|
# already existing elements were commented out
|
||||||
|
|
||||||
|
#/target
|
||||||
|
/Cargo.lock
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "anime-game-core"]
|
||||||
|
path = anime-game-core
|
||||||
|
url = https://github.com/an-anime-team/anime-game-core
|
26
Cargo.toml
Normal file
26
Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "anime-launcher-sdk"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikita Podvirnyy <suimin.tu.mu.ga.mi@gmail.com>"]
|
||||||
|
license = "GPL-3.0"
|
||||||
|
readme = "README.md"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anime-game-core = { path = "anime-game-core", features = ["genshin", "all", "static"] }
|
||||||
|
|
||||||
|
anyhow = "1.0"
|
||||||
|
dirs = "4.0.0"
|
||||||
|
|
||||||
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
states = []
|
||||||
|
config = ["dep:serde", "dep:serde_json"]
|
||||||
|
components = []
|
||||||
|
runner = []
|
||||||
|
fps-unlocker = []
|
||||||
|
|
||||||
|
default = ["all"]
|
||||||
|
all = ["states", "config", "components", "runner", "fps-unlocker"]
|
20
README.md
Normal file
20
README.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Anime Launcher SDK
|
||||||
|
|
||||||
|
## Project goals
|
||||||
|
|
||||||
|
* Unify backends for [gtk](https://github.com/an-anime-team/an-anime-game-launcher-gtk) and [tauri](https://github.com/an-anime-team/an-anime-game-launcher-tauri) launchers so they will have same functionality;
|
||||||
|
* Remove excess code from gtk launcher and prepare it for relm4 rewrite;
|
||||||
|
* Prepare codebase for tauri rewrite;
|
||||||
|
|
||||||
|
## Current progress (12.5%)
|
||||||
|
|
||||||
|
| Status | Feature | Description |
|
||||||
|
| :-: | - | - |
|
||||||
|
| ❌ | states | Getting current launcher's state (update available, etc.) |
|
||||||
|
| ✅ | config | Work with config file |
|
||||||
|
| ❌ | components | Work with components needed to run the game |
|
||||||
|
| ❌ | | List Wine and DXVK versions |
|
||||||
|
| ❌ | | Download, delete and select wine |
|
||||||
|
| ❌ | | Download, delete, select and apply DXVK |
|
||||||
|
| ❌ | runner | Run the game |
|
||||||
|
| ❌ | fps-unlocker | Support of FPS unlocker. Manage its config, download, use in game runner |
|
1
anime-game-core
Submodule
1
anime-game-core
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 73d3644761bef06cfc16e4e4bc4f9b9af3c50139
|
37
src/config/game/dxvk.rs
Normal file
37
src/config/game/dxvk.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::launcher_dir;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Dxvk {
|
||||||
|
pub builds: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Dxvk {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builds: launcher_dir.join("dxvks")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Dxvk {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
builds: match value.get("builds") {
|
||||||
|
Some(value) => match value.as_str() {
|
||||||
|
Some(value) => PathBuf::from(value),
|
||||||
|
None => default.builds
|
||||||
|
},
|
||||||
|
None => default.builds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
src/config/game/enhancements/fps_unlocker/config/fps.rs
Normal file
65
src/config/game/enhancements/fps_unlocker/config/fps.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Fps {
|
||||||
|
/// 90
|
||||||
|
Ninety,
|
||||||
|
|
||||||
|
/// 120
|
||||||
|
HundredTwenty,
|
||||||
|
|
||||||
|
/// 144
|
||||||
|
HundredFourtyFour,
|
||||||
|
|
||||||
|
/// 165
|
||||||
|
HundredSixtyFive,
|
||||||
|
|
||||||
|
/// 180
|
||||||
|
HundredEighty,
|
||||||
|
|
||||||
|
/// 200
|
||||||
|
TwoHundred,
|
||||||
|
|
||||||
|
/// 240
|
||||||
|
TwoHundredFourty,
|
||||||
|
|
||||||
|
Custom(u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fps {
|
||||||
|
pub fn list() -> Vec<Self> {
|
||||||
|
vec![
|
||||||
|
Self::Ninety,
|
||||||
|
Self::HundredTwenty,
|
||||||
|
Self::HundredFourtyFour,
|
||||||
|
Self::HundredSixtyFive,
|
||||||
|
Self::HundredEighty,
|
||||||
|
Self::TwoHundred,
|
||||||
|
Self::TwoHundredFourty
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_num(fps: u64) -> Self {
|
||||||
|
match fps {
|
||||||
|
90 => Self::Ninety,
|
||||||
|
120 => Self::HundredTwenty,
|
||||||
|
144 => Self::HundredFourtyFour,
|
||||||
|
165 => Self::HundredSixtyFive,
|
||||||
|
180 => Self::HundredEighty,
|
||||||
|
200 => Self::TwoHundred,
|
||||||
|
240 => Self::TwoHundredFourty,
|
||||||
|
num => Self::Custom(num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_num(&self) -> u64 {
|
||||||
|
match self {
|
||||||
|
Self::Ninety => 90,
|
||||||
|
Self::HundredTwenty => 120,
|
||||||
|
Self::HundredFourtyFour => 144,
|
||||||
|
Self::HundredSixtyFive => 165,
|
||||||
|
Self::HundredEighty => 180,
|
||||||
|
Self::TwoHundred => 200,
|
||||||
|
Self::TwoHundredFourty => 240,
|
||||||
|
Self::Custom(num) => *num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/config/game/enhancements/fps_unlocker/config/mod.rs
Normal file
55
src/config/game/enhancements/fps_unlocker/config/mod.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
pub mod fps;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::fps::Fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub fps: u64,
|
||||||
|
pub power_saving: bool,
|
||||||
|
pub fullscreen: bool,
|
||||||
|
pub priority: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
fps: 120,
|
||||||
|
power_saving: false,
|
||||||
|
fullscreen: false,
|
||||||
|
priority: 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Config {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fps: match value.get("fps") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.fps),
|
||||||
|
None => default.fps
|
||||||
|
},
|
||||||
|
|
||||||
|
power_saving: match value.get("power_saving") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.power_saving),
|
||||||
|
None => default.power_saving
|
||||||
|
},
|
||||||
|
|
||||||
|
fullscreen: match value.get("fullscreen") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.fullscreen),
|
||||||
|
None => default.fullscreen
|
||||||
|
},
|
||||||
|
|
||||||
|
priority: match value.get("priority") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.priority),
|
||||||
|
None => default.priority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
src/config/game/enhancements/fps_unlocker/mod.rs
Normal file
61
src/config/game/enhancements/fps_unlocker/mod.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::launcher_dir;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::config::Config;
|
||||||
|
|
||||||
|
pub use super::config::prelude::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct FpsUnlocker {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub enabled: bool,
|
||||||
|
pub config: Config
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FpsUnlocker {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: launcher_dir.join("fps-unlocker"),
|
||||||
|
enabled: false,
|
||||||
|
config: Config::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for FpsUnlocker {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: match value.get("path") {
|
||||||
|
Some(value) => match value.as_str() {
|
||||||
|
Some(value) => PathBuf::from(value),
|
||||||
|
None => default.path
|
||||||
|
},
|
||||||
|
None => default.path
|
||||||
|
},
|
||||||
|
|
||||||
|
enabled: match value.get("enabled") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.enabled),
|
||||||
|
None => default.enabled
|
||||||
|
},
|
||||||
|
|
||||||
|
config: match value.get("config") {
|
||||||
|
Some(value) => Config::from(value),
|
||||||
|
None => default.config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/config/game/enhancements/fsr.rs
Normal file
53
src/config/game/enhancements/fsr.rs
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/config/game/enhancements/gamescope/framerate.rs
Normal file
26
src/config/game/enhancements/gamescope/framerate.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
156
src/config/game/enhancements/gamescope/mod.rs
Normal file
156
src/config/game/enhancements/gamescope/mod.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
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 fsr: bool,
|
||||||
|
pub nis: bool,
|
||||||
|
pub window_type: WindowType
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gamescope {
|
||||||
|
pub fn get_command(&self) -> Option<String> {
|
||||||
|
// 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 FSR support
|
||||||
|
if self.fsr {
|
||||||
|
gamescope += " -U";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set NIS (Nvidia Image Scaling) support
|
||||||
|
if self.nis {
|
||||||
|
gamescope += " -Y";
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
fsr: false,
|
||||||
|
nis: 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
|
||||||
|
},
|
||||||
|
|
||||||
|
fsr: match value.get("fsr") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.fsr),
|
||||||
|
None => default.fsr
|
||||||
|
},
|
||||||
|
|
||||||
|
nis: match value.get("nis") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.nis),
|
||||||
|
None => default.nis
|
||||||
|
},
|
||||||
|
|
||||||
|
window_type: match value.get("window_type") {
|
||||||
|
Some(value) => WindowType::from(value),
|
||||||
|
None => default.window_type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/config/game/enhancements/gamescope/size.rs
Normal file
26
src/config/game/enhancements/gamescope/size.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/config/game/enhancements/gamescope/window_type.rs
Normal file
20
src/config/game/enhancements/gamescope/window_type.rs
Normal file
|
@ -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_default()
|
||||||
|
}
|
||||||
|
}
|
72
src/config/game/enhancements/hud.rs
Normal file
72
src/config/game/enhancements/hud.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum HUD {
|
||||||
|
None,
|
||||||
|
DXVK,
|
||||||
|
MangoHUD
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HUD {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for HUD {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
serde_json::from_value(value.clone()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for HUD {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(Self::None),
|
||||||
|
1 => Ok(Self::DXVK),
|
||||||
|
2 => Ok(Self::MangoHUD),
|
||||||
|
_ => Err(String::from("Failed to convert number to HUD enum"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
|
impl Into<u32> for HUD {
|
||||||
|
fn into(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::None => 0,
|
||||||
|
Self::DXVK => 1,
|
||||||
|
Self::MangoHUD => 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HUD {
|
||||||
|
/// Get environment variables corresponding to used wine hud
|
||||||
|
pub fn get_env_vars(&self, config: &Config) -> HashMap<&str, &str> {
|
||||||
|
match self {
|
||||||
|
Self::None => HashMap::new(),
|
||||||
|
Self::DXVK => HashMap::from([
|
||||||
|
("DXVK_HUD", "fps,frametimes,version,gpuload")
|
||||||
|
]),
|
||||||
|
Self::MangoHUD => {
|
||||||
|
// Don't show mangohud if gamescope is enabled
|
||||||
|
// otherwise it'll be doubled
|
||||||
|
if config.game.enhancements.gamescope.enabled {
|
||||||
|
HashMap::new()
|
||||||
|
} else {
|
||||||
|
HashMap::from([
|
||||||
|
("MANGOHUD", "1")
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
src/config/game/enhancements/mod.rs
Normal file
61
src/config/game/enhancements/mod.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
pub mod fsr;
|
||||||
|
pub mod hud;
|
||||||
|
pub mod fps_unlocker;
|
||||||
|
pub mod gamescope;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::gamescope::prelude::*;
|
||||||
|
pub use super::fps_unlocker::prelude::*;
|
||||||
|
|
||||||
|
pub use super::Enhancements;
|
||||||
|
pub use super::fsr::Fsr;
|
||||||
|
pub use super::hud::HUD;
|
||||||
|
pub use super::fps_unlocker::FpsUnlocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Enhancements {
|
||||||
|
pub fsr: Fsr,
|
||||||
|
pub gamemode: bool,
|
||||||
|
pub hud: HUD,
|
||||||
|
pub fps_unlocker: FpsUnlocker,
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
fps_unlocker: match value.get("fps_unlocker") {
|
||||||
|
Some(value) => FpsUnlocker::from(value),
|
||||||
|
None => default.fps_unlocker
|
||||||
|
},
|
||||||
|
|
||||||
|
gamescope: match value.get("gamescope") {
|
||||||
|
Some(value) => Gamescope::from(value),
|
||||||
|
None => default.gamescope
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
src/config/game/mod.rs
Normal file
131
src/config/game/mod.rs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::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: PathBuf,
|
||||||
|
pub voices: Vec<String>,
|
||||||
|
pub wine: prelude::Wine,
|
||||||
|
pub dxvk: prelude::Dxvk,
|
||||||
|
pub enhancements: prelude::Enhancements,
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
pub command: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Game {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: launcher_dir.join("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) => match value.as_str() {
|
||||||
|
Some(value) => PathBuf::from(value),
|
||||||
|
None => default.path
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
src/config/game/wine/mod.rs
Normal file
104
src/config/game/wine/mod.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::launcher_dir;
|
||||||
|
|
||||||
|
pub mod wine_sync;
|
||||||
|
pub mod wine_lang;
|
||||||
|
pub mod virtual_desktop;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::Wine;
|
||||||
|
pub use super::wine_sync::WineSync;
|
||||||
|
pub use super::wine_lang::WineLang;
|
||||||
|
pub use super::virtual_desktop::VirtualDesktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Wine {
|
||||||
|
pub prefix: PathBuf,
|
||||||
|
pub builds: PathBuf,
|
||||||
|
pub selected: Option<String>,
|
||||||
|
pub sync: WineSync,
|
||||||
|
pub language: WineLang,
|
||||||
|
pub borderless: bool,
|
||||||
|
pub virtual_desktop: VirtualDesktop
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Wine {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
prefix: launcher_dir.join("game"),
|
||||||
|
builds: launcher_dir.join("runners"),
|
||||||
|
selected: None,
|
||||||
|
sync: WineSync::default(),
|
||||||
|
language: WineLang::default(),
|
||||||
|
borderless: false,
|
||||||
|
virtual_desktop: VirtualDesktop::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Wine {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
prefix: match value.get("prefix") {
|
||||||
|
Some(value) => match value.as_str() {
|
||||||
|
Some(value) => PathBuf::from(value),
|
||||||
|
None => default.prefix
|
||||||
|
},
|
||||||
|
None => default.prefix
|
||||||
|
},
|
||||||
|
|
||||||
|
builds: match value.get("builds") {
|
||||||
|
Some(value) => match value.as_str() {
|
||||||
|
Some(value) => PathBuf::from(value),
|
||||||
|
None => default.builds
|
||||||
|
},
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
borderless: match value.get("borderless") {
|
||||||
|
Some(value) => value.as_bool().unwrap_or(default.borderless),
|
||||||
|
None => default.borderless
|
||||||
|
},
|
||||||
|
|
||||||
|
virtual_desktop: match value.get("virtual_desktop") {
|
||||||
|
Some(value) => VirtualDesktop::from(value),
|
||||||
|
None => default.virtual_desktop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
src/config/game/wine/virtual_desktop.rs
Normal file
60
src/config/game/wine/virtual_desktop.rs
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::config::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub struct VirtualDesktop {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub width: u64,
|
||||||
|
pub height: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VirtualDesktop {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
enabled: false,
|
||||||
|
width: 1920,
|
||||||
|
height: 1080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for VirtualDesktop {
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualDesktop {
|
||||||
|
pub fn get_resolution(&self) -> Resolution {
|
||||||
|
Resolution::from_pair(self.width, self.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_command(&self) -> Option<String> {
|
||||||
|
if self.enabled {
|
||||||
|
Some(format!("explorer /desktop=animegame,{}x{}", self.width, self.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/config/game/wine/wine_lang.rs
Normal file
86
src/config/game/wine/wine_lang.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
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_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
|
impl Into<u32> 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<Self> {
|
||||||
|
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"
|
||||||
|
})])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for WineLang {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&format!("{:?}", self))
|
||||||
|
}
|
||||||
|
}
|
64
src/config/game/wine/wine_sync.rs
Normal file
64
src/config/game/wine/wine_sync.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||||
|
pub enum WineSync {
|
||||||
|
None,
|
||||||
|
ESync,
|
||||||
|
FSync,
|
||||||
|
Futex2
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WineSync {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::FSync
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for WineSync {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
serde_json::from_value(value.clone()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for WineSync {
|
||||||
|
type Error = String;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(Self::None),
|
||||||
|
1 => Ok(Self::ESync),
|
||||||
|
2 => Ok(Self::FSync),
|
||||||
|
3 => Ok(Self::Futex2),
|
||||||
|
|
||||||
|
_ => Err(String::from("Failed to convert number to WineSync enum"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::from_over_into)]
|
||||||
|
impl Into<u32> for WineSync {
|
||||||
|
fn into(self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Self::None => 0,
|
||||||
|
Self::ESync => 1,
|
||||||
|
Self::FSync => 2,
|
||||||
|
Self::Futex2 => 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WineSync {
|
||||||
|
/// Get environment variables corresponding to used wine sync
|
||||||
|
pub fn get_env_vars(&self) -> HashMap<&str, &str> {
|
||||||
|
HashMap::from([(match self {
|
||||||
|
Self::None => return HashMap::new(),
|
||||||
|
|
||||||
|
Self::ESync => "WINEESYNC",
|
||||||
|
Self::FSync => "WINEFSYNC",
|
||||||
|
Self::Futex2 => "WINEFSYNC_FUTEX2"
|
||||||
|
}, "1")])
|
||||||
|
}
|
||||||
|
}
|
125
src/config/launcher/mod.rs
Normal file
125
src/config/launcher/mod.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use anime_game_core::genshin::consts::GameEdition as CoreGameEdition;
|
||||||
|
|
||||||
|
use crate::launcher_dir;
|
||||||
|
|
||||||
|
pub mod repairer;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::Launcher;
|
||||||
|
pub use super::repairer::Repairer;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum GameEdition {
|
||||||
|
Global,
|
||||||
|
China
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GameEdition {
|
||||||
|
fn default() -> Self {
|
||||||
|
let locale = match std::env::var("LC_ALL") {
|
||||||
|
Ok(locale) => locale,
|
||||||
|
Err(_) => match std::env::var("LC_MESSAGES") {
|
||||||
|
Ok(locale) => locale,
|
||||||
|
Err(_) => match std::env::var("LANG") {
|
||||||
|
Ok(locale) => locale,
|
||||||
|
Err(_) => return Self::Global
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if locale.len() > 4 && &locale[..5].to_lowercase() == "zh_cn" {
|
||||||
|
Self::China
|
||||||
|
} else {
|
||||||
|
Self::Global
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GameEdition> for CoreGameEdition {
|
||||||
|
fn from(edition: GameEdition) -> Self {
|
||||||
|
match edition {
|
||||||
|
GameEdition::Global => CoreGameEdition::Global,
|
||||||
|
GameEdition::China => CoreGameEdition::China
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CoreGameEdition> for GameEdition {
|
||||||
|
fn from(edition: CoreGameEdition) -> Self {
|
||||||
|
match edition {
|
||||||
|
CoreGameEdition::Global => Self::Global,
|
||||||
|
CoreGameEdition::China => Self::China
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Launcher {
|
||||||
|
pub language: String,
|
||||||
|
pub temp: Option<PathBuf>,
|
||||||
|
pub speed_limit: u64,
|
||||||
|
pub repairer: Repairer,
|
||||||
|
pub edition: GameEdition
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Launcher {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
language: String::from("en-us"),
|
||||||
|
temp: launcher_dir(),
|
||||||
|
speed_limit: 0,
|
||||||
|
repairer: Repairer::default(),
|
||||||
|
edition: GameEdition::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(PathBuf::from(value)),
|
||||||
|
None => default.temp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => default.temp
|
||||||
|
},
|
||||||
|
|
||||||
|
speed_limit: match value.get("speed_limit") {
|
||||||
|
Some(value) => value.as_u64().unwrap_or(default.speed_limit),
|
||||||
|
None => default.speed_limit
|
||||||
|
},
|
||||||
|
|
||||||
|
repairer: match value.get("repairer") {
|
||||||
|
Some(value) => Repairer::from(value),
|
||||||
|
None => default.repairer
|
||||||
|
},
|
||||||
|
|
||||||
|
edition: match value.get("edition") {
|
||||||
|
Some(value) => serde_json::from_value(value.clone()).unwrap_or(default.edition),
|
||||||
|
None => default.edition
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/config/launcher/repairer.rs
Normal file
35
src/config/launcher/repairer.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
src/config/mod.rs
Normal file
150
src/config/mod.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::config_file;
|
||||||
|
|
||||||
|
pub mod launcher;
|
||||||
|
pub mod game;
|
||||||
|
pub mod patch;
|
||||||
|
pub mod resolution;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
pub use super::launcher::prelude::*;
|
||||||
|
pub use super::game::prelude::*;
|
||||||
|
|
||||||
|
pub use super::patch::Patch;
|
||||||
|
pub use super::resolution::Resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
use prelude::*;
|
||||||
|
|
||||||
|
static mut CONFIG: Option<Config> = None;
|
||||||
|
|
||||||
|
/// Get config data
|
||||||
|
///
|
||||||
|
/// This method will load config from file once and store it into the memory.
|
||||||
|
/// If you know that the config file was updated - you should run `get_raw` method
|
||||||
|
/// that always loads config directly from the file. This will also update in-memory config
|
||||||
|
pub fn get() -> anyhow::Result<Config> {
|
||||||
|
unsafe {
|
||||||
|
match &CONFIG {
|
||||||
|
Some(config) => Ok(config.clone()),
|
||||||
|
None => get_raw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get config data
|
||||||
|
///
|
||||||
|
/// This method will always load data directly from the file and update in-memory config
|
||||||
|
pub fn get_raw() -> anyhow::Result<Config> {
|
||||||
|
match config_file() {
|
||||||
|
Some(path) => {
|
||||||
|
// Try to read config if the file exists
|
||||||
|
if Path::new(&path).exists() {
|
||||||
|
let mut file = File::open(path)?;
|
||||||
|
let mut json = String::new();
|
||||||
|
|
||||||
|
file.read_to_string(&mut json)?;
|
||||||
|
|
||||||
|
match serde_json::from_str(&json) {
|
||||||
|
Ok(config) => {
|
||||||
|
let config = Config::from(&config);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
CONFIG = Some(config.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
},
|
||||||
|
Err(err) => Err(anyhow::anyhow!("Failed to decode data from json format: {}", err.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise create default config file
|
||||||
|
else {
|
||||||
|
update_raw(Config::default())?;
|
||||||
|
|
||||||
|
Ok(Config::default())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Err(anyhow::anyhow!("Failed to get config file path"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update in-memory config data
|
||||||
|
///
|
||||||
|
/// Use `update_raw` if you want to update config file itself
|
||||||
|
pub fn update(config: Config) {
|
||||||
|
unsafe {
|
||||||
|
CONFIG = Some(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update config file
|
||||||
|
///
|
||||||
|
/// This method will also update in-memory config data
|
||||||
|
pub fn update_raw(config: Config) -> anyhow::Result<()> {
|
||||||
|
update(config.clone());
|
||||||
|
|
||||||
|
match config_file() {
|
||||||
|
Some(path) => {
|
||||||
|
let mut file = File::create(&path)?;
|
||||||
|
|
||||||
|
match serde_json::to_string_pretty(&config) {
|
||||||
|
Ok(json) => {
|
||||||
|
file.write_all(json.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(err) => Err(anyhow::anyhow!("Failed to encode data into json format: {}", err.to_string()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => Err(anyhow::anyhow!("Failed to get config file path"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update config file from the in-memory saved config
|
||||||
|
pub fn flush() -> anyhow::Result<()> {
|
||||||
|
unsafe {
|
||||||
|
match &CONFIG {
|
||||||
|
Some(config) => update_raw(config.clone()),
|
||||||
|
None => Err(anyhow::anyhow!("Config wasn't loaded into the memory"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct Config {
|
||||||
|
pub launcher: Launcher,
|
||||||
|
pub game: Game,
|
||||||
|
pub patch: Patch
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&JsonValue> for Config {
|
||||||
|
fn from(value: &JsonValue) -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
launcher: match value.get("launcher") {
|
||||||
|
Some(value) => Launcher::from(value),
|
||||||
|
None => default.launcher
|
||||||
|
},
|
||||||
|
|
||||||
|
game: match value.get("game") {
|
||||||
|
Some(value) => Game::from(value),
|
||||||
|
None => default.game
|
||||||
|
},
|
||||||
|
|
||||||
|
patch: match value.get("patch") {
|
||||||
|
Some(value) => Patch::from(value),
|
||||||
|
None => default.patch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
src/config/patch.rs
Normal file
69
src/config/patch.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
|
use crate::launcher_dir;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Patch {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub servers: Vec<String>,
|
||||||
|
pub root: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Patch {
|
||||||
|
fn default() -> Self {
|
||||||
|
let launcher_dir = launcher_dir().expect("Failed to get launcher dir");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
path: launcher_dir.join("patch"),
|
||||||
|
servers: vec![
|
||||||
|
"https://notabug.org/Krock/dawn".to_string(),
|
||||||
|
"https://codespace.gay/Maroxy/dawnin".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) => match value.as_str() {
|
||||||
|
Some(value) => PathBuf::from(value),
|
||||||
|
None => default.path
|
||||||
|
},
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/config/resolution.rs
Normal file
63
src/config/resolution.rs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Resolution {
|
||||||
|
// qHD; 960x540
|
||||||
|
MiniHD,
|
||||||
|
|
||||||
|
// 1280x720
|
||||||
|
HD,
|
||||||
|
|
||||||
|
// 1920x1080
|
||||||
|
FullHD,
|
||||||
|
|
||||||
|
// 2560x1440
|
||||||
|
QuadHD,
|
||||||
|
|
||||||
|
// 3840x2160
|
||||||
|
UltraHD,
|
||||||
|
|
||||||
|
Custom(u64, u64)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolution {
|
||||||
|
pub fn list() -> Vec<Self> {
|
||||||
|
vec![
|
||||||
|
Self::MiniHD,
|
||||||
|
Self::HD,
|
||||||
|
Self::FullHD,
|
||||||
|
Self::QuadHD,
|
||||||
|
Self::UltraHD
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_pair(width: u64, height: u64) -> Self {
|
||||||
|
for res in Self::list() {
|
||||||
|
let pair = res.get_pair();
|
||||||
|
|
||||||
|
if pair.0 == width && pair.1 == height {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::Custom(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_pair(&self) -> (u64, u64) {
|
||||||
|
match self {
|
||||||
|
Self::MiniHD => (960, 540),
|
||||||
|
Self::HD => (1280, 720),
|
||||||
|
Self::FullHD => (1920, 1080),
|
||||||
|
Self::QuadHD => (2560, 1440),
|
||||||
|
Self::UltraHD => (3840, 2160),
|
||||||
|
|
||||||
|
Self::Custom(w, h) => (*w, *h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Resolution {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let (w, h) = self.get_pair();
|
||||||
|
|
||||||
|
f.write_str(&format!("{w}x{h}"))
|
||||||
|
}
|
||||||
|
}
|
18
src/lib.rs
Normal file
18
src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(feature = "config")]
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
/// Get default launcher dir path
|
||||||
|
///
|
||||||
|
/// `$HOME/.local/share/anime-game-launcher`
|
||||||
|
pub fn launcher_dir() -> Option<PathBuf> {
|
||||||
|
dirs::data_dir().map(|dir| dir.join("anime-game-launcher"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get default config file path
|
||||||
|
///
|
||||||
|
/// `$HOME/.local/share/anime-game-launcher/config.json`
|
||||||
|
pub fn config_file() -> Option<PathBuf> {
|
||||||
|
launcher_dir().map(|dir| dir.join("config.json"))
|
||||||
|
}
|
Loading…
Reference in a new issue