1#![forbid(unsafe_code)]
2#![forbid(missing_docs)]
3#![forbid(unstable_features)]
4#![forbid(missing_fragment_specifier)]
5#![warn(clippy::all, clippy::pedantic)]
6
7mod config;
14mod index;
15mod runtime;
16mod runtime_options;
17mod version;
18
19pub 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#[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 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 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 pub fn run(mut self) -> Result<ExitStatus, Error> {
132 self.create_p_dir()?;
133 self.check_proton()?;
134 self.check_program()?;
135
136 if let Some(runtime) = self.runtime {
138 let runtime = Runtime::from_proton(runtime, self)?;
139 return runtime.execute();
140 }
141
142 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 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}