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