proton_launch/command/
run.rs

1use std::{path::PathBuf, str::FromStr};
2
3use crate::{paths::Paths, proton::ProtonVersion, steam::SteamData};
4
5use super::{Runnable, RunnableError, RunnableResult};
6
7#[derive(Debug, Clone)]
8#[cfg_attr(feature = "commandline", derive(clap::Args))]
9pub struct Run {
10    /// Optional path to the exe of the game
11    /// If not specified, the first part of the [ARGS] will be used as the exe
12    exe: Option<PathBuf>,
13
14    /// Args to pass to the game directly
15    /// If the exe is not specified, the first part of the [ARGS] will be used as the exe
16    /// The rest of the [ARGS] will be passed to the game
17    #[cfg_attr(feature = "commandline", clap(last = true))]
18    args: Vec<String>,
19
20    /// Optional save name to use
21    /// If not specified, the game exe without the extension will be used
22    #[cfg_attr(feature = "commandline", clap(short, long))]
23    save_name: Option<String>,
24
25    /// Optional proton version to use
26    #[cfg_attr(feature = "commandline", clap(short, long))]
27    proton: Option<ProtonVersion>,
28
29    /// Run the game in the same directory as the exe.
30    /// Some games need this since they use relative paths, this includes some Unity games.
31    /// This does require write access to the game directory, since a dxvk cache will be created there.
32    ///
33    #[cfg_attr(feature = "commandline", clap(long))]
34    here: bool,
35}
36
37impl Run {
38    fn get_exe_and_args(&self) -> Result<(PathBuf, &[String]), RunnableError> {
39        if let Some(exe) = &self.exe {
40            Ok((exe.clone(), &self.args))
41        } else {
42            if let Some((exe, args)) = self.args.split_first() {
43                Ok((PathBuf::from_str(exe.as_str()).unwrap(), args))
44            } else {
45                Err(RunnableError::NoExe)
46            }
47        }
48    }
49}
50
51impl Runnable for Run {
52    fn run(&self, paths: &Paths, steam_data: &SteamData) -> RunnableResult<()> {
53        let selected_proton = self.proton.filter(|p| p.is_installed(steam_data));
54        if let Some(handpicked) = self.proton {
55            if selected_proton.is_none() {
56                return Err(RunnableError::SelectedProtonNotInstalled(handpicked));
57            }
58        }
59        let selected_proton = selected_proton.or_else(|| ProtonVersion::best_installed(steam_data));
60
61        let (exe, args) = self.get_exe_and_args()?;
62
63        if let Some(selected) = selected_proton {
64            let save_name = self
65                .save_name
66                .as_deref()
67                .unwrap_or_else(|| exe.file_stem().unwrap().to_str().unwrap());
68            let proton_path = selected.get_path(steam_data).expect("You somehow managed to delete the selected proton version while running this command");
69            let proton_command = proton_path.join("proton");
70
71            println!("Launching {} with {}", exe.display(), selected);
72
73            let compat_dir = paths.compat_dir(save_name);
74            let run_dir = if self.here {
75                exe.parent().unwrap().to_path_buf()
76            } else {
77                paths.run_dir(save_name)
78            };
79
80            let mut command = std::process::Command::new(proton_command);
81            command.env("STEAM_COMPAT_CLIENT_INSTALL_PATH", &steam_data.path);
82            command.env("STEAM_COMPAT_DATA_PATH", compat_dir);
83            command.current_dir(run_dir);
84            command.arg("run");
85            command.arg(exe);
86            command.args(args);
87
88            let res = command.spawn().map_err(RunnableError::SpawnError)?.wait()?;
89            println!("Exited with status {}", res);
90            Ok(())
91        } else {
92            Err(RunnableError::NoProtonAtAll)
93        }
94    }
95}