feat: switch to shell-based command launching

note: needs error handling
This commit is contained in:
DecDuck
2025-04-02 20:04:14 +11:00
parent 91024e64e7
commit 0f4ce9b6f3
@@ -4,6 +4,7 @@ use std::{
io::{self, Error}, io::{self, Error},
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Child, Command, ExitStatus}, process::{Child, Command, ExitStatus},
str::FromStr,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
thread::spawn, thread::spawn,
}; };
@@ -74,24 +75,6 @@ impl ProcessManager<'_> {
} }
} }
fn process_command(
&self,
install_dir: &String,
command: Vec<String>,
) -> (PathBuf, Vec<String>) {
let root = &command[0];
let install_dir = Path::new(install_dir);
let absolute_exe = install_dir.join(root);
/*
let args = command_components[1..]
.iter()
.map(|v| v.to_string())
.collect();
*/
(absolute_exe, Vec::new())
}
pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> { pub fn kill_game(&mut self, game_id: String) -> Result<(), io::Error> {
match self.processes.get(&game_id) { match self.processes.get(&game_id) {
Some(child) => { Some(child) => {
@@ -218,37 +201,6 @@ impl ProcessManager<'_> {
.get(version_name) .get(version_name)
.ok_or(ProcessError::InvalidVersion)?; .ok_or(ProcessError::InvalidVersion)?;
let mut command: Vec<String> = Vec::new();
match game_status {
GameDownloadStatus::Installed {
version_name: _,
install_dir: _,
} => {
command.extend([game_version.launch_command.clone()]);
command.extend(game_version.launch_args.clone());
}
GameDownloadStatus::SetupRequired {
version_name: _,
install_dir: _,
} => {
command.extend([game_version.setup_command.clone()]);
command.extend(game_version.setup_args.clone());
}
_ => panic!("unreachable code"),
};
info!("Command: {:?}", &command);
let (command, args) = self.process_command(install_dir, command);
let target_current_dir = command.parent().unwrap().to_str().unwrap();
info!(
"launching process {} in {}",
command.to_str().unwrap(),
target_current_dir
);
let current_time = chrono::offset::Local::now(); let current_time = chrono::offset::Local::now();
let log_file = OpenOptions::new() let log_file = OpenOptions::new()
.write(true) .write(true)
@@ -284,19 +236,50 @@ impl ProcessManager<'_> {
.get(&(current_platform, target_platform)) .get(&(current_platform, target_platform))
.ok_or(ProcessError::InvalidPlatform)?; .ok_or(ProcessError::InvalidPlatform)?;
let launch_process = game_launcher let (launch, args) = match game_status {
.launch_process( GameDownloadStatus::Installed {
&meta, version_name: _,
command.to_string_lossy().to_string(), install_dir: _,
game_version, } => (&game_version.launch_command, &game_version.launch_args),
target_current_dir, GameDownloadStatus::SetupRequired {
log_file, version_name: _,
error_file, install_dir: _,
) } => (&game_version.setup_command, &game_version.setup_args),
.map_err(ProcessError::IOError)?; GameDownloadStatus::Remote {} => unreachable!("nuh uh"),
};
let launch = PathBuf::from_str(&install_dir).unwrap().join(launch);
let launch = launch.to_str().unwrap();
let launch_string = game_launcher.create_launch_process(
&meta,
launch.to_string(),
args.to_vec(),
game_version,
install_dir,
);
info!("launching process {} in {}", launch_string, install_dir);
#[cfg(target_os = "windows")]
let mut command = Command::new("cmd");
#[cfg(target_os = "windows")]
command.args(["/C", &launch_string]);
#[cfg(unix)]
let mut command: Command = Command::new("sh");
#[cfg(unix)]
command.arg("-c").arg(launch_string);
command
.stderr(error_file)
.stdout(log_file)
.current_dir(install_dir);
let child = command.spawn().map_err(ProcessError::IOError)?;
let launch_process_handle = let launch_process_handle =
Arc::new(SharedChild::new(launch_process).map_err(ProcessError::IOError)?); Arc::new(SharedChild::new(child).map_err(ProcessError::IOError)?);
db_lock db_lock
.applications .applications
@@ -341,49 +324,41 @@ pub enum Platform {
} }
pub trait ProcessHandler: Send + 'static { pub trait ProcessHandler: Send + 'static {
fn launch_process( fn create_launch_process(
&self, &self,
meta: &DownloadableMetadata, meta: &DownloadableMetadata,
launch_command: String, launch_command: String,
args: Vec<String>,
game_version: &GameVersion, game_version: &GameVersion,
current_dir: &str, current_dir: &str,
log_file: File, ) -> String;
error_file: File,
) -> Result<Child, Error>;
} }
struct NativeGameLauncher; struct NativeGameLauncher;
impl ProcessHandler for NativeGameLauncher { impl ProcessHandler for NativeGameLauncher {
fn launch_process( fn create_launch_process(
&self, &self,
_meta: &DownloadableMetadata, _meta: &DownloadableMetadata,
launch_command: String, launch_command: String,
args: Vec<String>,
game_version: &GameVersion, game_version: &GameVersion,
current_dir: &str, current_dir: &str,
log_file: File, ) -> String {
error_file: File, format!("\"{}\" {}", launch_command, args.join(" "))
) -> Result<Child, Error> {
Command::new(PathBuf::from(launch_command))
.current_dir(current_dir)
.stdout(log_file)
.stderr(error_file)
.args(game_version.launch_args.clone())
.spawn()
} }
} }
const UMU_LAUNCHER_EXECUTABLE: &str = "umu-run"; const UMU_LAUNCHER_EXECUTABLE: &str = "umu-run";
struct UMULauncher; struct UMULauncher;
impl ProcessHandler for UMULauncher { impl ProcessHandler for UMULauncher {
fn launch_process( fn create_launch_process(
&self, &self,
_meta: &DownloadableMetadata, _meta: &DownloadableMetadata,
launch_command: String, launch_command: String,
args: Vec<String>,
game_version: &GameVersion, game_version: &GameVersion,
_current_dir: &str, _current_dir: &str,
_log_file: File, ) -> String {
_error_file: File,
) -> Result<Child, Error> {
debug!("Game override: \"{:?}\"", &game_version.umu_id_override); debug!("Game override: \"{:?}\"", &game_version.umu_id_override);
let game_id = match &game_version.umu_id_override { let game_id = match &game_version.umu_id_override {
Some(game_override) => game_override Some(game_override) => game_override
@@ -392,11 +367,11 @@ impl ProcessHandler for UMULauncher {
.unwrap_or(game_override.clone()), .unwrap_or(game_override.clone()),
None => game_version.game_id.clone(), None => game_version.game_id.clone(),
}; };
info!("Game ID: {}", game_id); format!(
UmuCommandBuilder::new(UMU_LAUNCHER_EXECUTABLE, launch_command) "GAMEID={game_id} {umu} \"{launch}\" {args}",
.game_id(game_id) umu = UMU_LAUNCHER_EXECUTABLE,
.launch_args(game_version.launch_args.clone()) launch = launch_command,
.build() args = args.join(" ")
.spawn() )
} }
} }