diff --git a/Cargo.lock b/Cargo.lock index f6519c3b1f..3500ef8714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8958,6 +8958,7 @@ dependencies = [ "serde_with", "sha1_smol", "sha2", + "shlex", "sqlx", "sysinfo", "tauri", diff --git a/Cargo.toml b/Cargo.toml index d95e9b6011..8c3724bed1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -138,6 +138,7 @@ serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde sha1 = "0.10.6" sha1_smol = { version = "1.0.1", features = ["std"] } sha2 = "0.10.9" +shlex = "1.3.0" spdx = "0.10.8" sqlx = { version = "0.8.6", default-features = false } sysinfo = { version = "0.35.2", default-features = false } diff --git a/packages/app-lib/Cargo.toml b/packages/app-lib/Cargo.toml index 85200eb258..5879bcb3fe 100644 --- a/packages/app-lib/Cargo.toml +++ b/packages/app-lib/Cargo.toml @@ -27,6 +27,7 @@ hashlink.workspace = true png.workspace = true bytemuck.workspace = true rgb.workspace = true +shlex.workspace = true chrono = { workspace = true, features = ["serde"] } daedalus.workspace = true diff --git a/packages/app-lib/src/api/profile/mod.rs b/packages/app-lib/src/api/profile/mod.rs index da12fe309b..0f5dec855d 100644 --- a/packages/app-lib/src/api/profile/mod.rs +++ b/packages/app-lib/src/api/profile/mod.rs @@ -664,7 +664,11 @@ async fn run_credentials( .filter(|hook_command| !hook_command.is_empty()); if let Some(hook) = pre_launch_hooks { // TODO: hook parameters - let mut cmd = hook.split(' '); + let mut cmd = shlex::split(hook) + .ok_or(crate::ErrorKind::LauncherError( + "Unable to parse pre-launch hook".to_string(), + ))? + .into_iter(); if let Some(command) = cmd.next() { let full_path = get_full_path(&profile.path).await?; let result = Command::new(command) diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index d39fd94a98..6e3c9cc4c6 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -563,7 +563,17 @@ pub async fn launch_minecraft( let args = version_info.arguments.clone().unwrap_or_default(); let mut command = match wrapper { Some(hook) => { - let mut command = Command::new(hook); + let mut hook = shlex::split(hook) + .ok_or(crate::ErrorKind::LauncherError( + "Unable to parse wrapper hook".to_string(), + ))? + .into_iter(); + let cmd = hook.next().ok_or(crate::ErrorKind::LauncherError( + "Empty wrapper hook".to_string(), + ))?; + + let mut command = Command::new(cmd); + command.args(hook); command.arg(&java_version.path); command } diff --git a/packages/app-lib/src/state/process.rs b/packages/app-lib/src/state/process.rs index a4727468c1..45ce995b70 100644 --- a/packages/app-lib/src/state/process.rs +++ b/packages/app-lib/src/state/process.rs @@ -711,7 +711,11 @@ impl Process { // We do not wait on the post exist command to finish running! We let it spawn + run on its own. // This behaviour may be changed in the future if let Some(hook) = post_exit_command { - let mut cmd = hook.split(' '); + let mut cmd = shlex::split(&hook) + .ok_or(crate::ErrorKind::LauncherError( + "Unable to parse post-exit hook".to_string(), + ))? + .into_iter(); if let Some(command) = cmd.next() { let mut command = Command::new(command); command.args(cmd).current_dir( diff --git a/packages/app-lib/src/state/profiles.rs b/packages/app-lib/src/state/profiles.rs index 7cf70a2850..6a523c036d 100644 --- a/packages/app-lib/src/state/profiles.rs +++ b/packages/app-lib/src/state/profiles.rs @@ -103,10 +103,11 @@ impl ProfileInstallStage { pub enum LauncherFeatureVersion { None, MigratedServerLastPlayTime, + MigratedLaunchHooks, } impl LauncherFeatureVersion { - pub const MOST_RECENT: Self = Self::MigratedServerLastPlayTime; + pub const MOST_RECENT: Self = Self::MigratedLaunchHooks; pub fn as_str(&self) -> &'static str { match *self { @@ -114,6 +115,7 @@ impl LauncherFeatureVersion { Self::MigratedServerLastPlayTime => { "migrated_server_last_play_time" } + Self::MigratedLaunchHooks => "migrated_launch_hooks", } } @@ -123,6 +125,7 @@ impl LauncherFeatureVersion { "migrated_server_last_play_time" => { Self::MigratedServerLastPlayTime } + "migrated_launch_hooks" => Self::MigratedLaunchHooks, _ => Self::None, } } @@ -782,6 +785,30 @@ impl Profile { self.launcher_feature_version = LauncherFeatureVersion::MigratedServerLastPlayTime; } + LauncherFeatureVersion::MigratedServerLastPlayTime => { + let q = shlex::Quoter::new().allow_nul(true); + + // Previously split by spaces + if let Some(pre_launch) = self.hooks.pre_launch.as_ref() { + self.hooks.pre_launch = + Some(q.join(pre_launch.split(' ')).unwrap()) + } + + // Previously treated as complete path to command + if let Some(wrapper) = self.hooks.wrapper.as_ref() { + self.hooks.wrapper = + Some(q.quote(wrapper).unwrap().to_string()) + } + + // Previously split by spaces + if let Some(post_exit) = self.hooks.post_exit.as_ref() { + self.hooks.post_exit = + Some(q.join(post_exit.split(' ')).unwrap()) + } + + self.launcher_feature_version = + LauncherFeatureVersion::MigratedLaunchHooks; + } LauncherFeatureVersion::MOST_RECENT => unreachable!( "LauncherFeatureVersion::MOST_RECENT was not updated" ),