proton_call/
lib.rs

1#![forbid(unsafe_code)]
2#![forbid(missing_docs)]
3#![forbid(unstable_features)]
4#![forbid(missing_fragment_specifier)]
5#![warn(clippy::all, clippy::pedantic)]
6
7/*!
8# Proton Caller API
9
10This defines the internal API used in `proton-call` to run Proton
11*/
12
13mod config;
14mod index;
15mod runtime;
16mod runtime_options;
17mod version;
18
19/// Contains the `Error` and `ErrorKind` types
20pub mod error;
21
22pub use config::Config;
23use error::{Error, Kind};
24pub use index::Index;
25pub use runtime::RunTimeVersion;
26use runtime::Runtime;
27pub use runtime_options::RuntimeOption;
28use std::borrow::Cow;
29use std::fs::create_dir;
30pub use version::Version;
31
32use std::path::PathBuf;
33use std::process::ExitStatus;
34
35/// Type to handle executing Proton
36#[derive(Debug)]
37pub struct Proton {
38    version: Version,
39    path: PathBuf,
40    program: PathBuf,
41    args: Vec<String>,
42    options: Vec<RuntimeOption>,
43    compat: PathBuf,
44    steam: PathBuf,
45    runtime: Option<RunTimeVersion>,
46    common: PathBuf,
47}
48
49impl Proton {
50    #[must_use]
51    /// Creates a new instance of `Proton`
52    pub fn new(
53        version: Version,
54        path: PathBuf,
55        program: PathBuf,
56        args: Vec<String>,
57        options: Vec<RuntimeOption>,
58        compat: PathBuf,
59        steam: PathBuf,
60        runtime: Option<RunTimeVersion>,
61        common: PathBuf,
62    ) -> Proton {
63        Proton {
64            version,
65            path,
66            program,
67            args,
68            options,
69            compat,
70            steam,
71            runtime,
72            common,
73        }
74        .update_path()
75    }
76
77    /// Appends the executable to the path
78    fn update_path(mut self) -> Proton {
79        let str: Cow<str> = self.path.to_string_lossy();
80        let str: String = format!("{}/proton", str);
81        self.path = PathBuf::from(str);
82        self
83    }
84
85    fn create_p_dir(&mut self) -> Result<(), Error> {
86        let name: Cow<str> = self.compat.to_string_lossy();
87        let newdir: PathBuf = PathBuf::from(format!("{}/Proton {}", name, self.version));
88
89        if !newdir.exists() {
90            if let Err(e) = create_dir(&newdir) {
91                throw!(Kind::ProtonDir, "failed to create Proton directory: {}", e);
92            }
93        }
94
95        self.compat = newdir;
96
97        pass!()
98    }
99
100    fn check_proton(&self) -> Result<(), Error> {
101        if !self.path.exists() {
102            throw!(Kind::ProtonMissing, "{}", self.version);
103        }
104
105        pass!()
106    }
107
108    fn check_program(&self) -> Result<(), Error> {
109        if !self.program.exists() {
110            throw!(Kind::ProgramMissing, "{}", self.program.to_string_lossy());
111        }
112
113        pass!()
114    }
115
116    fn gen_options(&self) -> Vec<(String, String)> {
117        let mut opts = Vec::new();
118        for opt in &self.options {
119            opts.insert(opts.len(), (opt.to_string(), "1".to_string()))
120        }
121        opts
122    }
123
124    /// Changes `compat` path to the version of Proton in use, creates the directory if doesn't already exist
125    ///
126    /// # Errors
127    ///
128    /// Will fail on:
129    /// * Creating a Proton compat env directory fails
130    /// * Executing Proton fails
131    pub fn run(mut self) -> Result<ExitStatus, Error> {
132        self.create_p_dir()?;
133        self.check_proton()?;
134        self.check_program()?;
135
136        // check one for runtimes
137        if let Some(runtime) = self.runtime {
138            let runtime = Runtime::from_proton(runtime, self)?;
139            return runtime.execute();
140        }
141
142        // check two for runtimes
143        match self.version {
144            Version::Mainline(maj, _) => {
145                if maj >= 5 {
146                    let runtime = Runtime::from_proton(RunTimeVersion::Soldier, self)?;
147                    return runtime.execute()
148                }
149            },
150            Version::Experimental => {
151                let runtime = Runtime::from_proton(RunTimeVersion::Soldier, self)?;
152                return runtime.execute()
153            }
154            _ => {},
155        }
156
157        self.execute()
158    }
159
160    /// Executes Proton
161    fn execute(self) -> Result<ExitStatus, Error> {
162        use std::process::{Child, Command};
163
164        let envs: Vec<(String, String)> = self.gen_options();
165
166        println!(
167            "Running Proton {} for {} with:\n{:#?}",
168            self.version,
169            self.program.to_string_lossy(),
170            envs,
171        );
172
173        let mut child: Child = match Command::new(&self.path)
174            .arg("run")
175            .arg(&self.program)
176            .args(&self.args)
177            .env("STEAM_COMPAT_DATA_PATH", &self.compat)
178            .env("STEAM_COMPAT_CLIENT_INSTALL_PATH", &self.steam)
179            .envs(envs)
180            .spawn()
181        {
182            Ok(c) => c,
183            Err(e) => throw!(Kind::ProtonSpawn, "{}\nDebug:\n{:#?}", e, self),
184        };
185
186        let status: ExitStatus = match child.wait() {
187            Ok(e) => e,
188            Err(e) => throw!(Kind::ProtonWait, "'{}': {}", child.id(), e),
189        };
190
191        pass!(status)
192    }
193}