diff --git a/src/config/schema_blanks/wine/mod.rs b/src/config/schema_blanks/wine/mod.rs index 5c96fd3..2f24a02 100644 --- a/src/config/schema_blanks/wine/mod.rs +++ b/src/config/schema_blanks/wine/mod.rs @@ -1,9 +1,12 @@ pub mod wine_lang; pub mod wine_sync; +pub mod wine_drives; pub mod virtual_desktop; pub mod shared_libraries; pub mod prelude { + pub use super::wine_drives::*; + pub use super::wine_lang::WineLang; pub use super::wine_sync::WineSync; pub use super::virtual_desktop::VirtualDesktop; @@ -21,6 +24,7 @@ macro_rules! config_impl_wine_schema { pub sync: WineSync, pub language: WineLang, pub borderless: bool, + pub drives: WineDrives, pub virtual_desktop: VirtualDesktop, pub shared_libraries: SharedLibraries } @@ -37,6 +41,7 @@ macro_rules! config_impl_wine_schema { sync: WineSync::default(), language: WineLang::default(), borderless: false, + drives: WineDrives::default(), virtual_desktop: VirtualDesktop::default(), shared_libraries: SharedLibraries::default() } @@ -84,6 +89,10 @@ macro_rules! config_impl_wine_schema { .and_then(|value| value.as_bool()) .unwrap_or(default.borderless), + drives: value.get("drives") + .map(WineDrives::from) + .unwrap_or(default.drives), + virtual_desktop: value.get("virtual_desktop") .map(VirtualDesktop::from) .unwrap_or(default.virtual_desktop), diff --git a/src/config/schema_blanks/wine/wine_drives.rs b/src/config/schema_blanks/wine/wine_drives.rs new file mode 100644 index 0000000..4787944 --- /dev/null +++ b/src/config/schema_blanks/wine/wine_drives.rs @@ -0,0 +1,156 @@ +use std::path::{Path, PathBuf}; +use std::collections::HashMap; + +use serde::{Serialize, Deserialize}; +use serde_json::Value as JsonValue; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum AllowedDrives { + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z +} + +impl AllowedDrives { + #[inline] + pub fn list() -> &'static [Self] { + &[ + Self::A, Self::B, Self::C, Self::D, Self::E, Self::F, Self::G, Self::H, Self::I, Self::J, Self::K, Self::L, Self::M, + Self::N, Self::O, Self::P, Self::Q, Self::R, Self::S, Self::T, Self::U, Self::V, Self::W, Self::X, Self::Y, Self::Z + ] + } + + /// Get unix drive name + /// + /// ``` + /// assert_eq!(AllowedDrives::F, "f:"); + /// ``` + pub fn to_drive(&self) -> &str { + match self { + Self::A => "a:", + Self::B => "b:", + Self::C => "c:", + Self::D => "d:", + Self::E => "e:", + Self::F => "f:", + Self::G => "g:", + Self::H => "h:", + Self::I => "i:", + Self::J => "j:", + Self::K => "k:", + Self::L => "l:", + Self::M => "m:", + Self::N => "n:", + Self::O => "o:", + Self::P => "p:", + Self::Q => "q:", + Self::R => "r:", + Self::S => "s:", + Self::T => "t:", + Self::U => "u:", + Self::V => "v:", + Self::W => "w:", + Self::X => "x:", + Self::Y => "y:", + Self::Z => "z:" + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct WineDrives { + /// Symlink prefix's `drive_c` folder to the `c:` folder in the `dosdevices` + pub drive_c: bool, + + /// If `Some`, then symlink game folder to the given folder in the `dosdevices` + pub game_folder: Option, + + /// Symlink paths to the given letters in the `dosdevices` + pub map_folders: HashMap +} + +impl WineDrives { + /// Automatically map all the configured folders + pub fn map_folders(&self, game_folder: impl Into, prefix_folder: impl Into) -> anyhow::Result<()> { + let mut drives = self.map_folders.clone(); + + let game_folder = game_folder.into(); + let prefix_folder = prefix_folder.into(); + + if self.drive_c { + drives.insert(AllowedDrives::C, prefix_folder.join("drive_c")); + } + + if let Some(drive) = self.game_folder { + drives.insert(drive, game_folder); + } + + for (drive, path) in drives { + Self::map_folder(&prefix_folder, drive, path)?; + } + + Ok(()) + } + + /// Map specific folder + pub fn map_folder(prefix_folder: impl AsRef, drive: AllowedDrives, symlink_folder: impl AsRef) -> anyhow::Result<()> { + let drive_folder = prefix_folder.as_ref() + .join("dosdevices") + .join(drive.to_drive()); + + if drive_folder.exists() { + std::fs::remove_file(&drive_folder)?; + } + + std::os::unix::fs::symlink(symlink_folder.as_ref(), drive_folder)?; + + Ok(()) + } +} + +impl Default for WineDrives { + #[inline] + fn default() -> Self { + Self { + drive_c: true, + game_folder: Some(AllowedDrives::G), + map_folders: HashMap::new() + } + } +} + +impl From<&JsonValue> for WineDrives { + fn from(value: &JsonValue) -> Self { + let default = Self::default(); + + Self { + drive_c: value.get("drive_c") + .and_then(JsonValue::as_bool) + .unwrap_or(default.drive_c), + + game_folder: value.get("game_folder") + .and_then(|value| serde_json::from_value(value.clone()).ok()) + .unwrap_or(default.game_folder), + + map_folders: match value.get("map_folders") { + Some(value) => match value.as_object() { + Some(values) => { + let mut drives = HashMap::new(); + + for (drive, path) in values { + let drive = serde_json::from_str::(drive); + let path = path.as_str(); + + if let (Ok(drive), Some(path)) = (drive, path) { + drives.insert(drive, PathBuf::from(path)); + } + } + + drives + }, + None => default.map_folders + }, + None => default.map_folders + }, + } + } +} diff --git a/src/games/genshin/game.rs b/src/games/genshin/game.rs index 34f8429..0a42efc 100644 --- a/src/games/genshin/game.rs +++ b/src/games/genshin/game.rs @@ -10,6 +10,11 @@ use crate::components::wine::Bundle as WineBundle; use crate::config::ConfigExt; use crate::genshin::config::Config; +use crate::config::schema_blanks::prelude::{ + WineDrives, + AllowedDrives +}; + use crate::genshin::consts; #[cfg(feature = "fps-unlocker")] @@ -131,6 +136,16 @@ pub fn run() -> anyhow::Result<()> { )?; } + // Prepare wine prefix drives + + config.game.wine.drives.map_folders(&folders.game, &folders.prefix)?; + + // Workaround for sandboxing feature + if config.sandbox.enabled { + WineDrives::map_folder(&folders.prefix, AllowedDrives::C, "../drive_c")?; + WineDrives::map_folder(&folders.prefix, AllowedDrives::Z, "/")?; + } + // Prepare bash -c '' // %command% = %bash_command% %windows_command% %launch_args% diff --git a/src/games/honkai/game.rs b/src/games/honkai/game.rs index 2cd26f6..8a93ace 100644 --- a/src/games/honkai/game.rs +++ b/src/games/honkai/game.rs @@ -8,6 +8,11 @@ use crate::components::wine::Bundle as WineBundle; use crate::config::ConfigExt; use crate::honkai::config::Config; +use crate::config::schema_blanks::prelude::{ + WineDrives, + AllowedDrives +}; + use crate::honkai::consts; #[cfg(feature = "discord-rpc")] @@ -74,6 +79,16 @@ pub fn run() -> anyhow::Result<()> { return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}")); } + // Prepare wine prefix drives + + config.game.wine.drives.map_folders(&folders.game, &folders.prefix)?; + + // Workaround for sandboxing feature + if config.sandbox.enabled { + WineDrives::map_folder(&folders.prefix, AllowedDrives::C, "../drive_c")?; + WineDrives::map_folder(&folders.prefix, AllowedDrives::Z, "/")?; + } + // Prepare bash -c '' // %command% = %bash_command% %windows_command% %launch_args% diff --git a/src/games/pgr/game.rs b/src/games/pgr/game.rs index fbf79be..8972123 100644 --- a/src/games/pgr/game.rs +++ b/src/games/pgr/game.rs @@ -8,6 +8,11 @@ use crate::components::wine::Bundle as WineBundle; use crate::config::ConfigExt; use crate::pgr::config::Config; +use crate::config::schema_blanks::prelude::{ + WineDrives, + AllowedDrives +}; + use crate::pgr::consts; #[cfg(feature = "discord-rpc")] @@ -70,6 +75,16 @@ pub fn run() -> anyhow::Result<()> { return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}")); } + // Prepare wine prefix drives + + config.game.wine.drives.map_folders(&folders.game, &folders.prefix)?; + + // Workaround for sandboxing feature + if config.sandbox.enabled { + WineDrives::map_folder(&folders.prefix, AllowedDrives::C, "../drive_c")?; + WineDrives::map_folder(&folders.prefix, AllowedDrives::Z, "/")?; + } + // Prepare bash -c '' // %command% = %bash_command% %windows_command% %launch_args% diff --git a/src/games/star_rail/game.rs b/src/games/star_rail/game.rs index e8aa043..8c105bd 100644 --- a/src/games/star_rail/game.rs +++ b/src/games/star_rail/game.rs @@ -8,6 +8,11 @@ use crate::components::wine::Bundle as WineBundle; use crate::config::ConfigExt; use crate::star_rail::config::Config; +use crate::config::schema_blanks::prelude::{ + WineDrives, + AllowedDrives +}; + use crate::star_rail::consts; #[cfg(feature = "discord-rpc")] @@ -74,6 +79,16 @@ pub fn run() -> anyhow::Result<()> { return Err(anyhow::anyhow!("Telemetry server is not disabled: {server}")); } + // Prepare wine prefix drives + + config.game.wine.drives.map_folders(&folders.game, &folders.prefix)?; + + // Workaround for sandboxing feature + if config.sandbox.enabled { + WineDrives::map_folder(&folders.prefix, AllowedDrives::C, "../drive_c")?; + WineDrives::map_folder(&folders.prefix, AllowedDrives::Z, "/")?; + } + // Prepare bash -c '' // %command% = %bash_command% %windows_command% %launch_args%