loadsmith_loader/args.rs
1use std::{collections::HashMap, ffi::OsString, fmt::Debug, path::Path, path::PathBuf};
2
3/// Arguments, environment variables, and an optional wrapper binary to use
4/// when launching a game with a mod loader.
5///
6/// Build one using the builder methods ([`arg`](LaunchArgs::arg),
7/// [`env`](LaunchArgs::env), [`wrapper`](LaunchArgs::wrapper)), then call
8/// [`apply`](LaunchArgs::apply) to configure a
9/// [`std::process::Command`].
10///
11/// # Examples
12///
13/// ```rust
14/// use loadsmith_loader::LaunchArgs;
15/// use std::process::Command;
16///
17/// let args = LaunchArgs::new()
18/// .arg("--doorstop-enable")
19/// .arg("true")
20/// .env("DOORSTOP_ENABLE", "TRUE");
21///
22/// let mut cmd = Command::new("game.exe");
23/// args.apply(&mut cmd);
24///
25/// assert_eq!(cmd.get_args().collect::<Vec<_>>(), ["--doorstop-enable", "true"]);
26/// ```
27#[derive(Debug, Clone, PartialEq, Eq, Default)]
28pub struct LaunchArgs {
29 args: Vec<OsString>,
30 env: HashMap<OsString, OsString>,
31 wrapper: Option<PathBuf>,
32}
33
34impl LaunchArgs {
35 /// Creates an empty set of launch arguments.
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 /// Appends a positional argument.
41 pub fn arg(mut self, arg: impl Into<OsString>) -> Self {
42 self.args.push(arg.into());
43 self
44 }
45
46 /// Sets an environment variable.
47 pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Self {
48 self.env.insert(key.into(), value.into());
49 self
50 }
51
52 /// Sets a wrapper binary (e.g. `wine` or `proton`) that the command
53 /// should be launched through.
54 pub fn wrapper(mut self, wrapper: impl Into<PathBuf>) -> Self {
55 self.wrapper = Some(wrapper.into());
56 self
57 }
58
59 /// Returns the positional arguments.
60 pub fn get_args(&self) -> &[OsString] {
61 &self.args
62 }
63
64 /// Returns the environment variables.
65 pub fn get_env(&self) -> &HashMap<OsString, OsString> {
66 &self.env
67 }
68
69 /// Returns the wrapper binary path, if set.
70 pub fn get_wrapper(&self) -> Option<&Path> {
71 self.wrapper.as_deref()
72 }
73
74 /// Applies these arguments, environment, and optional wrapper to a
75 /// [`std::process::Command`].
76 ///
77 /// When a wrapper is set the original command's program and arguments
78 /// become sub-arguments of the wrapper.
79 pub fn apply(self, command: &mut std::process::Command) {
80 if let Some(wrapper) = self.wrapper {
81 let mut new_command = std::process::Command::new(wrapper);
82 new_command
83 .arg(command.get_program())
84 .args(command.get_args());
85
86 for (key, value) in command.get_envs() {
87 if let Some(value) = value {
88 new_command.env(key, value);
89 } else {
90 new_command.env_remove(key);
91 }
92 }
93
94 *command = new_command;
95 }
96
97 for arg in self.args {
98 command.arg(arg);
99 }
100
101 for (key, value) in self.env {
102 command.env(key, value);
103 }
104 }
105}