diff --git a/Cargo.toml b/Cargo.toml index 48f7416..91edb1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "anime-launcher-sdk" -version = "1.1.6" +version = "1.2.0" authors = ["Nikita Podvirnyy "] license = "GPL-3.0" readme = "README.md" @@ -40,10 +40,11 @@ components = ["dep:wincompatlib", "dep:lazy_static"] game = ["components", "config"] discord-rpc = ["dep:discord-rich-presence"] sandbox = [] +sessions = [] # Genshin-specific features environment-emulation = [] fps-unlocker = ["dep:md-5"] -all = ["states", "config", "components", "game", "discord-rpc", "sandbox", "environment-emulation", "fps-unlocker"] +all = ["states", "config", "components", "game", "discord-rpc", "sandbox", "sessions", "environment-emulation", "fps-unlocker"] default = ["all"] diff --git a/src/games/genshin/mod.rs b/src/games/genshin/mod.rs index e673290..a8b1abd 100644 --- a/src/games/genshin/mod.rs +++ b/src/games/genshin/mod.rs @@ -14,3 +14,6 @@ pub mod fps_unlocker; #[cfg(feature = "game")] pub mod game; + +#[cfg(feature = "sessions")] +pub mod sessions; diff --git a/src/games/genshin/sessions.rs b/src/games/genshin/sessions.rs new file mode 100644 index 0000000..66c874c --- /dev/null +++ b/src/games/genshin/sessions.rs @@ -0,0 +1,105 @@ +use serde::{Serialize, Deserialize}; + +use std::path::{Path, PathBuf}; + +use crate::sessions::{ + SessionsExt, + Sessions as SessionsDescriptor +}; + +use super::consts::launcher_dir; + +/// Get default sessions file path +/// +/// `$HOME/.local/share/anime-game-launcher/sessions.json` +#[inline] +pub fn sessions_file() -> anyhow::Result { + launcher_dir().map(|dir| dir.join("sessions.json")) +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SessionData { + // [Software\\miHoYo\\Genshin Impact] + pub game_reg: String, + + // [Software\\miHoYoSDK] + pub sdk_reg: String +} + +pub struct Sessions; + +impl SessionsExt for Sessions { + type SessionData = SessionData; + + fn get_sessions() -> anyhow::Result> { + let path = sessions_file()?; + + if !path.exists() { + tracing::warn!("Session file doesn't exist. Returning default value"); + + return Ok(SessionsDescriptor::default()); + } + + Ok(serde_json::from_slice(&std::fs::read(path)?)?) + } + + fn set_sessions(sessions: SessionsDescriptor) -> anyhow::Result<()> { + Ok(std::fs::write(sessions_file()?, serde_json::to_string_pretty(&sessions)?)?) + } + + fn update(name: String, prefix: impl AsRef) -> anyhow::Result<()> { + let mut sessions = Self::get_sessions()?; + + tracing::info!("Updating session '{name}' from prefix: {:?}", prefix.as_ref()); + + let mut new_session = Self::SessionData { + game_reg: String::new(), + sdk_reg: String::new() + }; + + for entry in std::fs::read_to_string(prefix.as_ref().join("user.reg"))?.split("\n\n") { + if entry.starts_with("[Software\\\\miHoYo\\\\Genshin Impact]") { + new_session.game_reg = entry.to_owned(); + } + + else if entry.starts_with("[Software\\\\miHoYoSDK]") { + new_session.sdk_reg = entry.to_owned(); + } + } + + sessions.sessions.insert(name, new_session); + + Self::set_sessions(sessions) + } + + fn apply(name: String, prefix: impl AsRef) -> anyhow::Result<()> { + let sessions = Self::get_sessions()?; + + let Some(session) = sessions.sessions.get(&name) else { + anyhow::bail!("Session with given name doesn't exist"); + }; + + tracing::info!("Applying session '{name}' to prefix: {:?}", prefix.as_ref()); + + let entries: String = std::fs::read_to_string(prefix.as_ref().join("user.reg"))? + .split("\n\n") + .map(|entry| { + let new_entry = if entry.starts_with("[Software\\\\miHoYo\\\\Genshin Impact]") { + session.game_reg.clone() + } + + else if entry.starts_with("[Software\\\\miHoYoSDK]") { + session.sdk_reg.clone() + } + + else { + entry.to_owned() + }; + + new_entry + "\n\n" + }) + .collect(); + + Ok(std::fs::write(prefix.as_ref().join("user.reg"), format!("{}\n", entries.trim_end()))?) + } +} diff --git a/src/games/honkai/mod.rs b/src/games/honkai/mod.rs index 6138c07..de66d04 100644 --- a/src/games/honkai/mod.rs +++ b/src/games/honkai/mod.rs @@ -8,3 +8,6 @@ pub mod states; #[cfg(feature = "game")] pub mod game; + +#[cfg(feature = "sessions")] +pub mod sessions; diff --git a/src/games/honkai/sessions.rs b/src/games/honkai/sessions.rs new file mode 100644 index 0000000..a475219 --- /dev/null +++ b/src/games/honkai/sessions.rs @@ -0,0 +1,105 @@ +use serde::{Serialize, Deserialize}; + +use std::path::{Path, PathBuf}; + +use crate::sessions::{ + SessionsExt, + Sessions as SessionsDescriptor +}; + +use super::consts::launcher_dir; + +/// Get default sessions file path +/// +/// `$HOME/.local/share/honkers-launcher/sessions.json` +#[inline] +pub fn sessions_file() -> anyhow::Result { + launcher_dir().map(|dir| dir.join("sessions.json")) +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SessionData { + // [Software\\miHoYo\\Honkai Impact 3rd] + pub game_reg: String, + + // [Software\\miHoYoSDK] + pub sdk_reg: String +} + +pub struct Sessions; + +impl SessionsExt for Sessions { + type SessionData = SessionData; + + fn get_sessions() -> anyhow::Result> { + let path = sessions_file()?; + + if !path.exists() { + tracing::warn!("Session file doesn't exist. Returning default value"); + + return Ok(SessionsDescriptor::default()); + } + + Ok(serde_json::from_slice(&std::fs::read(path)?)?) + } + + fn set_sessions(sessions: SessionsDescriptor) -> anyhow::Result<()> { + Ok(std::fs::write(sessions_file()?, serde_json::to_string_pretty(&sessions)?)?) + } + + fn update(name: String, prefix: impl AsRef) -> anyhow::Result<()> { + let mut sessions = Self::get_sessions()?; + + tracing::info!("Updating session '{name}' from prefix: {:?}", prefix.as_ref()); + + let mut new_session = Self::SessionData { + game_reg: String::new(), + sdk_reg: String::new() + }; + + for entry in std::fs::read_to_string(prefix.as_ref().join("user.reg"))?.split("\n\n") { + if entry.starts_with("[Software\\\\miHoYo\\\\Honkai Impact 3rd]") { + new_session.game_reg = entry.to_owned(); + } + + else if entry.starts_with("[Software\\\\miHoYoSDK]") { + new_session.sdk_reg = entry.to_owned(); + } + } + + sessions.sessions.insert(name, new_session); + + Self::set_sessions(sessions) + } + + fn apply(name: String, prefix: impl AsRef) -> anyhow::Result<()> { + let sessions = Self::get_sessions()?; + + let Some(session) = sessions.sessions.get(&name) else { + anyhow::bail!("Session with given name doesn't exist"); + }; + + tracing::info!("Applying session '{name}' to prefix: {:?}", prefix.as_ref()); + + let entries: String = std::fs::read_to_string(prefix.as_ref().join("user.reg"))? + .split("\n\n") + .map(|entry| { + let new_entry = if entry.starts_with("[Software\\\\miHoYo\\\\Honkai Impact 3rd]") { + session.game_reg.clone() + } + + else if entry.starts_with("[Software\\\\miHoYoSDK]") { + session.sdk_reg.clone() + } + + else { + entry.to_owned() + }; + + new_entry + "\n\n" + }) + .collect(); + + Ok(std::fs::write(prefix.as_ref().join("user.reg"), format!("{}\n", entries.trim_end()))?) + } +} diff --git a/src/games/star_rail/mod.rs b/src/games/star_rail/mod.rs index 6138c07..de66d04 100644 --- a/src/games/star_rail/mod.rs +++ b/src/games/star_rail/mod.rs @@ -8,3 +8,6 @@ pub mod states; #[cfg(feature = "game")] pub mod game; + +#[cfg(feature = "sessions")] +pub mod sessions; diff --git a/src/games/star_rail/sessions.rs b/src/games/star_rail/sessions.rs new file mode 100644 index 0000000..469b476 --- /dev/null +++ b/src/games/star_rail/sessions.rs @@ -0,0 +1,105 @@ +use serde::{Serialize, Deserialize}; + +use std::path::{Path, PathBuf}; + +use crate::sessions::{ + SessionsExt, + Sessions as SessionsDescriptor +}; + +use super::consts::launcher_dir; + +/// Get default sessions file path +/// +/// `$HOME/.local/share/honkers-railway-launcher/sessions.json` +#[inline] +pub fn sessions_file() -> anyhow::Result { + launcher_dir().map(|dir| dir.join("sessions.json")) +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SessionData { + // [Software\\Cognosphere\\Star Rail] + pub game_reg: String, + + // [Software\\miHoYoSDK] + pub sdk_reg: String +} + +pub struct Sessions; + +impl SessionsExt for Sessions { + type SessionData = SessionData; + + fn get_sessions() -> anyhow::Result> { + let path = sessions_file()?; + + if !path.exists() { + tracing::warn!("Session file doesn't exist. Returning default value"); + + return Ok(SessionsDescriptor::default()); + } + + Ok(serde_json::from_slice(&std::fs::read(path)?)?) + } + + fn set_sessions(sessions: SessionsDescriptor) -> anyhow::Result<()> { + Ok(std::fs::write(sessions_file()?, serde_json::to_string_pretty(&sessions)?)?) + } + + fn update(name: String, prefix: impl AsRef) -> anyhow::Result<()> { + let mut sessions = Self::get_sessions()?; + + tracing::info!("Updating session '{name}' from prefix: {:?}", prefix.as_ref()); + + let mut new_session = Self::SessionData { + game_reg: String::new(), + sdk_reg: String::new() + }; + + for entry in std::fs::read_to_string(prefix.as_ref().join("user.reg"))?.split("\n\n") { + if entry.starts_with("[Software\\\\Cognosphere\\\\Star Rail]") { + new_session.game_reg = entry.to_owned(); + } + + else if entry.starts_with("[Software\\\\miHoYoSDK]") { + new_session.sdk_reg = entry.to_owned(); + } + } + + sessions.sessions.insert(name, new_session); + + Self::set_sessions(sessions) + } + + fn apply(name: String, prefix: impl AsRef) -> anyhow::Result<()> { + let sessions = Self::get_sessions()?; + + let Some(session) = sessions.sessions.get(&name) else { + anyhow::bail!("Session with given name doesn't exist"); + }; + + tracing::info!("Applying session '{name}' to prefix: {:?}", prefix.as_ref()); + + let entries: String = std::fs::read_to_string(prefix.as_ref().join("user.reg"))? + .split("\n\n") + .map(|entry| { + let new_entry = if entry.starts_with("[Software\\\\Cognosphere\\\\Star Rail]") { + session.game_reg.clone() + } + + else if entry.starts_with("[Software\\\\miHoYoSDK]") { + session.sdk_reg.clone() + } + + else { + entry.to_owned() + }; + + new_entry + "\n\n" + }) + .collect(); + + Ok(std::fs::write(prefix.as_ref().join("user.reg"), format!("{}\n", entries.trim_end()))?) + } +} diff --git a/src/lib.rs b/src/lib.rs index a85801c..c480e79 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,9 @@ pub mod components; #[cfg(feature = "discord-rpc")] pub mod discord_rpc; +#[cfg(feature = "sessions")] +pub mod sessions; + pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Check if specified binary is available diff --git a/src/sessions.rs b/src/sessions.rs new file mode 100644 index 0000000..fb15e86 --- /dev/null +++ b/src/sessions.rs @@ -0,0 +1,74 @@ +use std::path::Path; +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Sessions { + pub current: Option, + pub sessions: HashMap +} + +impl Default for Sessions { + #[inline] + fn default() -> Self { + Self { + current: None, + sessions: HashMap::new() + } + } +} + +pub trait SessionsExt { + type SessionData; + + /// Get sessions descriptor + /// + /// If it doesn't exist, then default values will be returned + fn get_sessions() -> anyhow::Result>; + + /// Update sessions descriptor + fn set_sessions(sessions: Sessions) -> anyhow::Result<()>; + + /// Get current session name + fn get_current() -> anyhow::Result> { + Ok(Self::get_sessions()?.current) + } + + /// Set current session name + fn set_current(name: String) -> anyhow::Result<()> { + let mut sessions = Self::get_sessions()?; + + sessions.current = Some(name); + + Self::set_sessions(sessions) + } + + /// List available sessions + fn list() -> anyhow::Result> { + Ok(Self::get_sessions()?.sessions) + } + + /// Remove session with given name + /// + /// Sets current session to `None` if its name passed + fn remove(name: impl AsRef) -> anyhow::Result<()> { + let mut sessions = Self::get_sessions()?; + + if let Some(current) = &sessions.current { + if current == name.as_ref() { + sessions.current = None; + } + } + + sessions.sessions.remove(name.as_ref()); + + Self::set_sessions(sessions) + } + + /// Update saved session using files from the wine prefix + fn update(name: String, prefix: impl AsRef) -> anyhow::Result<()>; + + /// Apply saved session to the wine prefix + fn apply(name: String, prefix: impl AsRef) -> anyhow::Result<()>; +}