Init commit

This commit is contained in:
Observer KRypt0n_ 2022-11-11 18:29:16 +02:00
parent e3a24db1df
commit e7d356da5c
No known key found for this signature in database
GPG key ID: 844DA47BA25FE1E2
27 changed files with 1596 additions and 0 deletions

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
/target
# Added by cargo
#
# already existing elements were commented out
#/target
/Cargo.lock

3
.gitmodules vendored Normal file
View 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
View 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
View 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

@ -0,0 +1 @@
Subproject commit 73d3644761bef06cfc16e4e4bc4f9b9af3c50139

37
src/config/game/dxvk.rs Normal file
View 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
}
}
}
}

View 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
}
}
}

View 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
}
}
}
}

View 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
}
}
}
}

View 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()
}
}
}

View 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
}
}
}
}

View 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
}
}
}
}

View 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
}
}
}
}

View 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()
}
}

View 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")
])
}
}
}
}
}

View 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
View 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
View 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
}
}
}
}

View 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
}
}
}

View 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))
}
}

View 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
View 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
}
}
}
}

View 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
View 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
View 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
View 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
View 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"))
}