proton-call-3.0.1/.cargo_vcs_info.json0000644000000001360000000000100132670ustar { "git": { "sha1": "9874d3baa505d9bec54f47512c79b9d8ce97a1cc" }, "path_in_vcs": "" }proton-call-3.0.1/.github/funding.yml000064400000000000000000000000410072674642500156170ustar 00000000000000# github: caverym ko_fi: caverym proton-call-3.0.1/.github/workflows/rust.yml000064400000000000000000000004740072674642500172310ustar 00000000000000name: Rust on: push: branches: [ master ] pull_request: branches: [ master ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose proton-call-3.0.1/.gitignore000064400000000000000000000002760072674642500141040ustar 00000000000000.project .idea dist build protoncall.spec __pycache__ .vscode boost libs CMakeLists.txt venv cmake-build-debug cmake-build-debug-coverage pc proton-call packaging # Added by cargo /target proton-call-3.0.1/COMPLETED000064400000000000000000000017440072674642500133540ustar 000000000000001. Check for executable existance A. Check for Proton B. Check for Windows executable This would keep the program from executing programs that do not exist on the drive. 2. Pass arguments to executable A. Pass arguments to Windows executable B. Dynamically change the amount of arguments passed to allow this feature to be added. Make it possible to launch programs which need specific command line arguments to execute using Proton Caller. This is needed because many games and game launchers DO NOT run without spcial arguments. Some may want to use this to also force games to run in different graphics APIs (OpenGL, Vulkan). 2. config file for paths and default A. include and use library to parse config files B. Have paths to steam's common C. Have path to a default version D. choose default version if none listed Remove the need for confusing and inconsistant environment variables proton-call-3.0.1/Cargo.lock0000644000000042230000000000100112430ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "jargon-args" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f822f93441a0df2080a5ca60d861b4cf4ea53e65dccddbe04a70e6630f9e8341" [[package]] name = "lliw" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d502c8bcc35a4f7ca9a7ffb7ac27b15ba30b1b92c2d69a1e4437e2635d73af7" [[package]] name = "proc-macro2" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] [[package]] name = "proton-call" version = "3.0.1" dependencies = [ "jargon-args", "lliw", "serde", "toml", ] [[package]] name = "quote" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "toml" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] [[package]] name = "unicode-xid" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" proton-call-3.0.1/Cargo.toml0000644000000020300000000000100112600ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "proton-call" version = "3.0.1" authors = ["Avery Murray "] description = "Run any Windows program through Valve's Proton" readme = "README.md" keywords = ["wine", "valve", "steam", "proton"] license = "MIT" repository = "https://github.com/caverym/proton-caller" resolver = "2" [profile.release] opt-level = "z" lto = true codegen-units = 1 [dependencies.jargon-args] version = "0.2.3" [dependencies.lliw] version = "0.2.0" [dependencies.serde] version = "1" features = ["derive"] [dependencies.toml] version = "0.5" proton-call-3.0.1/Cargo.toml.orig000064400000000000000000000007670072674642500150100ustar 00000000000000[package] name = "proton-call" version = "3.0.1" license = "MIT" authors = ["Avery Murray "] description = "Run any Windows program through Valve's Proton" repository = "https://github.com/caverym/proton-caller" readme = "README.md" keywords = ["wine", "valve", "steam", "proton"] edition = "2021" [profile.release] opt-level = 'z' lto = true codegen-units = 1 [dependencies] toml = "0.5" jargon-args = "0.2.3" lliw = "0.2.0" serde = { version = "1", features = ["derive"] } proton-call-3.0.1/LICENSE000064400000000000000000000020550072674642500131160ustar 00000000000000MIT License Copyright (c) 2021 Avery Murray Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. proton-call-3.0.1/README.md000064400000000000000000000051360072674642500133730ustar 00000000000000# Proton-Caller Run any Windows program through [Valve's Proton](https://github.com/ValveSoftware/Proton). [Usage](https://github.com/caverym/Proton-Caller#usage) Please create an issue if you want added features or have an issue. [FAQ](https://github.com/caverym/Proton-Caller/wiki/FAQ) ## Problem Reporting: Please create an issue on the [Github](https://github.com/caverym/Proton-Caller) page which lists: system, kernel version, game, shell, and if it is or isn't a Steam game – provide how you had installed it and where it is installed. Additionally provide screenshots of the shell. Try many methods to get it to work and describe what you did in your issue. ### Warning: if you are not using a release, use a release. ## Usage: Defaults to the latest version of Proton. ``` proton-call -r foo.exe ``` Defaults to the latest version of Proton, all extra arguments passed to the executable. ``` proton-call -r foo.exe --goes --to program ``` `--goes --to program` are passed to the proton / the program Uses specified version of Proton, any extra arguments will be passed to the executable. ``` proton-call -p 5.13 -r foo.exe ``` Uses custom version of Proton, give the past to directory, not the Proton executable itself. ``` proton-call -c '/path/to/Proton version' -r foo.exe ``` ## Config: Configuration files are extremely simple: `~/.config/proton.conf` Set your own path to `data` (any empty directory), `steam`, (the directory steam is installed in), and optionally `common` (steam's common directory). ``` data = "/home/avery/Documents/Proton/env/" steam = "/home/avery/.steam/steam/" # optional common = "/home/avery/.steam/steam/steamapps/common/" ``` ## Install: #### Arch Linux: [Proton-caller](https://aur.archlinux.org/packages/proton-caller) is available as a [package in the AUR](https://aur.archlinux.org/packages/proton-caller). #### Debian: A `.deb` file is available for download at the [releases](https://github.com/caverym/proton-caller/releases) page. #### Other Linux: An x86_64 Linux binary is available for download at the [releases](https://github.com/caverym/proton-caller/releases) page. #### Compile from source: ``` git clone https://github.com/caverym/proton-caller.git cd proton-caller cargo b --release --locked sudo install -Dm 755 target/release/proton-call /usr/bin/proton-call ``` ### Space Engine example: Make a .desktop launcher. [example file](Space%20Engine.desktop) ``` [Desktop Entry] Type=Application Name=Space Engine Comment=Space Engine Exec=proton-call --run SpaceEngine.exe Path=/home/avery/Documents/games/SpaceEngine/system Terminal=false StartupNotify=false ``` proton-call-3.0.1/Space Engine.desktop000064400000000000000000000003600072674642500157220ustar 00000000000000[Desktop Entry] Version=1.0 Type=Application Name=Space Engine Comment=Space Engine Exec=prime-run proton-call -r SpaceEngine.exe Icon=kstars_supernovae Path=/home/avery/Documents/games/SpaceEngine/system Terminal=false StartupNotify=false proton-call-3.0.1/TODO000064400000000000000000000015140072674642500126000ustar 00000000000000# TODO This is a list of To-Do actions for continual development for Proton Caller. If you believe features should be added to Proton Caller in the future, create a pull request and add it to this list, or an issue in the same format. They will be reviewed and added to this list if they are valid features to have. ## How to contribute to the list Keep each list item in an organized layout: X. Simple name for features A. Things included within the feature ... An explination for why the feature is needed, what it will be used for and any ideas on how to implement the feature. (and two empty lines below) Any feature requests that do not follow this layout will not be reviewed. Completed actions will be added to a separete file (COMPLETED) and explained in the release (and commit) they were added in. proton-call-3.0.1/proton.conf000064400000000000000000000002210072674642500142720ustar 00000000000000data = "/home/avery/Documents/Proton/env/" steam = "/home/avery/.steam/steam/" # optional common = "/home/avery/.steam/steam/steamapps/common/" proton-call-3.0.1/src/config.rs000064400000000000000000000070440072674642500145160ustar 00000000000000use crate::{ error::{Error, Kind}, throw, }; use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::path::PathBuf; /// Config type for parsing config files #[derive(Debug, serde::Deserialize)] pub struct Config { data: PathBuf, steam: PathBuf, common: Option, } impl Config { /// Opens and returns the user's config /// /// # Errors /// /// This function will fail if... /// * Can not read `XDG_CONFIG_HOME` or `HOME` from the environment /// * Can not open config file /// * Can not parse config into `Config` pub fn open() -> Result { use std::fs::File; use std::io::Read; // Get default config location let loc: PathBuf = Config::config_location()?; // Open the config file let mut file: File = match File::open(&loc) { Ok(f) => f, Err(e) => throw!(Kind::ConfigOpen, "{}", e), }; // Read the config into memory let mut buffer: Vec = Vec::new(); if let Err(e) = file.read_to_end(&mut buffer) { throw!(Kind::ConfigRead, "{}", e); } // Parse the config into `Config` let slice: &[u8] = buffer.as_slice(); let mut config: Config = toml::from_slice(slice)?; config.default_common(); Ok(config) } /// Finds one of the two default config locations /// /// # Errors /// /// Will only fail if `XDG_CONFIG_HOME` and `HOME` do not exist in environment pub fn config_location() -> Result { use std::env::var; if let Ok(val) = var("XDG_CONFIG_HOME") { let path = format!("{}/proton.conf", val); Ok(PathBuf::from(path)) } else if let Ok(val) = var("HOME") { let path = format!("{}/.config/proton.conf", val); Ok(PathBuf::from(path)) } else { throw!(Kind::Environment, "XDG_CONFIG_HOME / HOME missing") } } /// Sets a default common if not given by user fn default_common(&mut self) { if self.common.is_none() { let common: PathBuf = self._default_common(); self.common = Some(common); } } #[must_use] /// Generates a default common directory fn _default_common(&self) -> PathBuf { eprintln!("warning: using default common"); let steam: Cow = self.steam.to_string_lossy(); let common_str: String = format!("{}/steamapps/common/", steam); PathBuf::from(common_str) } #[must_use] /// Returns the in use common directory pub fn common(&self) -> PathBuf { if let Some(common) = &self.common { common.clone() } else { self._default_common() } } #[must_use] /// Returns the in use steam directory pub fn steam(&self) -> PathBuf { self.steam.clone() } #[must_use] /// Returns the in use compat data directory pub fn data(&self) -> PathBuf { self.data.clone() } } impl Display for Config { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let data: Cow = self.data.to_string_lossy(); let steam: Cow = self.steam.to_string_lossy(); let common: String = if let Some(common) = &self.common { common.to_string_lossy().to_string() } else { let pb: PathBuf = self._default_common(); pb.to_string_lossy().to_string() }; write!(f, "steam: {}\ndata: {}\ncommon: {}", steam, data, common) } } proton-call-3.0.1/src/error.rs000064400000000000000000000101700072674642500143740ustar 00000000000000use lliw::Fg::Red; use lliw::Reset; use std::fmt::{Display, Formatter}; use std::num::ParseIntError; /// Simple macro rapper for `Result::Ok(T)` #[macro_export] macro_rules! pass { () => { Ok(()) }; ($item:expr) => {{ Ok($item) }}; } /// Macro to throw an error, `Result::Err(e)` #[macro_export] macro_rules! throw { ($kind:expr, $fmt:literal) => ({ return $crate::error::_throw($kind, std::format!($fmt)) }); ($kind:expr, $fmt:literal, $($arg:tt)*) => ({ return $crate::error::_throw($kind, std::format!($fmt, $($arg)*)) }) } #[doc(hidden)] pub fn _throw(kind: Kind, inner: String) -> Result { Err(Error::new(kind, inner)) } /// Error type #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub struct Error { inner: String, // file: Option, kind: Kind, } impl Error { #[must_use] /// creates new instance of `Error` pub fn new(kind: Kind, inner: String) -> Error { Error { inner, kind } } #[must_use] /// returns Error kind pub fn kind(&self) -> Kind { self.kind } } impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}: {}{}", Red, self.kind, Reset, self.inner) } } impl From for Error { fn from(pie: ParseIntError) -> Self { Error::new(Kind::VersionParse, pie.to_string()) } } impl From for Error { fn from(te: toml::de::Error) -> Self { Error::new(Kind::ConfigParse, te.to_string()) } } impl From for Error { fn from(jae: jargon_args::Error) -> Self { match jae { jargon_args::Error::MissingArg(key) => { Error::new(Kind::ArgumentMissing, key.to_string()) } jargon_args::Error::Other(s) => Error::new(Kind::JargonInternal, s), } } } impl std::error::Error for Error {} /// Error Kinds #[repr(i32)] #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum Kind { /// for when something weird happens in the program Internal = 1, /// for when environment variables can't be read Environment, /// for when the config file fails to be opened ConfigOpen, /// for when the config file fails to be read ConfigRead, /// for when Toml fails to parse config ConfigParse, /// Pfor when creating a Proton directory fails ProtonDir, /// for when Proton fails to spawn ProtonSpawn, /// for when waiting for child process fails ProtonWait, /// for when Proton is not found ProtonMissing, /// for when requested program is not found ProgramMissing, /// for when Indexing fails to read common directory IndexReadDir, /// for when parsing a version number fails VersionParse, /// for when Proton exits with an error ProtonExit, /// for when a command line argument is missing ArgumentMissing, /// for when Jargon has an internal Error, JargonInternal, } impl Display for Kind { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { Kind::Internal => "internal error", Kind::Environment => "failed to read environment", Kind::ConfigOpen => "failed to open config", Kind::ConfigRead => "failed to read config", Kind::ConfigParse => "failed to parse config", Kind::ProtonDir => "failed to create Proton directory", Kind::ProtonSpawn => "failed to spawn Proton", Kind::ProtonWait => "failed to wait for Proton child", Kind::IndexReadDir => "failed to Index", Kind::VersionParse => "failed to parse version", Kind::ProtonMissing => "cannot find Proton", Kind::ProgramMissing => "cannot find program", Kind::ProtonExit => "proton exited with", Kind::ArgumentMissing => "missing command line argument", Kind::JargonInternal => "jargon args internal error", } ) } } proton-call-3.0.1/src/index.rs000064400000000000000000000053150072674642500143570ustar 00000000000000use crate::error::{Error, Kind}; use crate::{pass, throw, Version}; use lliw::Fg::LightYellow as Yellow; use lliw::Reset; use std::collections::BTreeMap; use std::ffi::OsString; use std::fmt::{Display, Formatter}; use std::fs::DirEntry; use std::path::{Path, PathBuf}; /// Index type to Index Proton versions in common #[derive(Debug)] pub struct Index { dir: PathBuf, map: BTreeMap, } impl Display for Index { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut str: String = format!( "Indexed Directory: {}\n\nIndexed {} Proton Versions:\n", self.dir.to_string_lossy(), self.len() ); for (version, path) in &self.map { str = format!("{}\nProton {} `{}`", str, version, path.to_string_lossy()); } write!(f, "{}", str) } } impl Index { /// Creates an index of Proton versions in given path /// /// # Errors /// /// Will fail if Indexing fails to read the directory pub fn new(index: &Path) -> Result { let mut idx = Index { dir: index.to_path_buf(), map: BTreeMap::new(), }; idx.index()?; Ok(idx) } #[must_use] /// Returns the number of Indexed Protons pub fn len(&self) -> usize { self.map.len() } #[must_use] /// Returns true if Index is empty pub fn is_empty(&self) -> bool { self.map.is_empty() } #[must_use] /// Retrieves the path of the requested Proton version pub fn get(&self, version: Version) -> Option { let path = self.map.get(&version)?; Some(path.clone()) } /// Indexes Proton versions fn index(&mut self) -> Result<(), Error> { if let Ok(rd) = self.dir.read_dir() { for result_entry in rd { let entry: DirEntry = if let Ok(e) = result_entry { e } else { eprintln!("{}warning:{} failed indexing a directory...", Yellow, Reset); continue; }; let entry_path: PathBuf = entry.path(); if entry_path.is_dir() { let name: OsString = entry.file_name(); let name: String = name.to_string_lossy().to_string(); if let Some(version_str) = name.split(' ').last() { if let Ok(version) = version_str.parse() { self.map.insert(version, entry_path); } } } } } else { throw!(Kind::IndexReadDir, "can not read common dir"); } pass!() } } proton-call-3.0.1/src/lib.rs000064400000000000000000000072100072674642500140120ustar 00000000000000#![forbid(unsafe_code)] #![forbid(missing_docs)] #![forbid(unstable_features)] #![forbid(missing_fragment_specifier)] #![warn(clippy::all, clippy::pedantic)] /*! # Proton Caller API This defines the internal API used in `proton-call` to run Proton */ mod config; mod index; mod version; /// Contains the `Error` and `ErrorKind` types pub mod error; pub use config::Config; use error::{Error, Kind}; pub use index::Index; use std::borrow::Cow; use std::fs::create_dir; pub use version::Version; use std::path::PathBuf; use std::process::ExitStatus; /// Type to handle executing Proton #[derive(Debug)] pub struct Proton { version: Version, path: PathBuf, program: PathBuf, args: Vec, log: bool, compat: PathBuf, steam: PathBuf, } impl Proton { #[must_use] /// Creates a new instance of `Proton` pub fn new( version: Version, path: PathBuf, program: PathBuf, args: Vec, log: bool, compat: PathBuf, steam: PathBuf, ) -> Proton { Proton { version, path, program, args, log, compat, steam, } .update_path() } /// Appends the executable to the path fn update_path(mut self) -> Proton { let str: Cow = self.path.to_string_lossy(); let str: String = format!("{}/proton", str); self.path = PathBuf::from(str); self } fn create_p_dir(&mut self) -> Result<(), Error> { let name: Cow = self.compat.to_string_lossy(); let newdir: PathBuf = PathBuf::from(format!("{}/Proton {}", name, self.version)); if !newdir.exists() { if let Err(e) = create_dir(&newdir) { throw!(Kind::ProtonDir, "failed to create Proton directory: {}", e); } } self.compat = newdir; pass!() } fn check_proton(&self) -> Result<(), Error> { if !self.path.exists() { throw!(Kind::ProtonMissing, "{}", self.version); } pass!() } fn check_program(&self) -> Result<(), Error> { if !self.program.exists() { throw!(Kind::ProgramMissing, "{}", self.program.to_string_lossy()); } pass!() } /// Changes `compat` path to the version of Proton in use, creates the directory if doesn't already exist /// /// # Errors /// /// Will fail on: /// * Creating a Proton compat env directory fails /// * Executing Proton fails pub fn run(mut self) -> Result { self.create_p_dir()?; self.check_proton()?; self.check_program()?; self.execute() } /// Executes Proton fn execute(self) -> Result { use std::process::{Child, Command}; println!( "Running Proton {} for {}", self.version, self.program.to_string_lossy() ); let log: &str = if self.log { "1" } else { "0" }; let mut child: Child = match Command::new(&self.path) .arg("run") .arg(&self.program) .args(&self.args) .env("PROTON_LOG", log) .env("STEAM_COMPAT_DATA_PATH", &self.compat) .env("STEAM_COMPAT_CLIENT_INSTALL_PATH", &self.steam) .spawn() { Ok(c) => c, Err(e) => throw!(Kind::ProtonSpawn, "{}\nDebug:\n{:#?}", e, self), }; let status: ExitStatus = match child.wait() { Ok(e) => e, Err(e) => throw!(Kind::ProtonWait, "'{}': {}", child.id(), e), }; pass!(status) } } proton-call-3.0.1/src/main.rs000064400000000000000000000123350072674642500141740ustar 00000000000000#![forbid(unsafe_code)] #![forbid(missing_docs)] #![warn(clippy::all, clippy::pedantic)] /*! # Proton Call Run any Windows program through [Valve's Proton](https://github.com/ValveSoftware/Proton). ## Usage: Defaults to the latest version of Proton. ``` proton-call -r foo.exe ``` Defaults to the latest version of Proton, all extra arguments passed to the executable. ``` proton-call -r foo.exe --goes --to program ``` `--goes --to program` are passed to the proton / the program Uses specified version of Proton, any extra arguments will be passed to the executable. ``` proton-call -p 5.13 -r foo.exe ``` Uses custom version of Proton, give the past to directory, not the Proton executable itself. ``` proton-call -c '/path/to/Proton version' -r foo.exe ``` */ use proton_call::error::{Error, Kind}; use proton_call::{pass, throw, Config, Index, Proton, Version}; use std::path::PathBuf; use std::process::exit; /// Type to handle and parse command line arguments with `Jargon` #[derive(Debug)] struct Args { program: PathBuf, version: Version, log: bool, custom: Option, args: Vec, } /// Main function which purely handles errors fn main() { let args: Vec = std::env::args().collect(); let program: String = args[0].split('/').last().unwrap_or(&args[0]).to_string(); if let Err(e) = proton_caller(args) { eprintln!("{}: {}", program, e); let code = e.kind() as i32; exit(code); } } /// Effective main function which parses arguments fn proton_caller(args: Vec) -> Result<(), Error> { use jargon_args::Jargon; let mut parser: Jargon = Jargon::from_vec(args); if parser.contains(["-h", "--help"]) { help(); } else if parser.contains(["-v", "--version"]) { version(); } else if parser.contains(["-i", "--index"]) { let config: Config = Config::open()?; let common_index = Index::new(&config.common())?; println!("{}", common_index); } else { let config: Config = Config::open()?; let args = Args { program: parser.result_arg(["-r", "--run"])?, version: parser.option_arg(["-p", "--proton"]).unwrap_or_default(), log: parser.contains(["-l", "--log"]), custom: parser.option_arg(["-c", "--custom"]), args: parser.finish(), }; let proton = if args.custom.is_some() { custom_mode(&config, args)? } else { normal_mode(&config, args)? }; let exit = proton.run()?; if !exit.success() { if let Some(code) = exit.code() { throw!(Kind::ProtonExit, "code: {}", code); } throw!(Kind::ProtonExit, "an error"); } } Ok(()) } /// Runs caller in normal mode, running indexed Proton versions fn normal_mode(config: &Config, args: Args) -> Result { let common_index: Index = Index::new(&config.common())?; let proton_path: PathBuf = match common_index.get(args.version) { Some(pp) => pp, None => throw!( Kind::ProtonMissing, "Proton {} does not exist", args.version ), }; let proton: Proton = Proton::new( args.version, proton_path, args.program, args.args, args.log, config.data(), config.steam(), ); pass!(proton) } /// Runs caller in custom mode, using a custom Proton path fn custom_mode(config: &Config, args: Args) -> Result { if let Some(custom) = args.custom { let proton: Proton = Proton::new( Version::from_custom(custom.as_path()), custom, args.program, args.args, args.log, config.data(), config.steam(), ); return pass!(proton); } throw!(Kind::Internal, "failed to run custom mode") } #[doc(hidden)] static HELP: &str = "\ Usage: proton-call [OPTIONS]... EXE [EXTRA]... Options: -c, --custom [PATH] Path to a directory containing Proton to use -h, --help View this help message -i, --index View an index of installed Proton versions -l, --log Pass PROTON_LOG variable to Proton -p, --proton [VERSION] Use Proton VERSION from `common` -r, --run EXE Run EXE in proton -v, --version View version information Config: The config file should be located at '$XDG_CONFIG_HOME/proton.conf' or '$HOME/.config/proton.conf' The config requires two values. Data: a location to any directory to contain Proton's runtime files. Steam: the directory to where steam is installed (the one which contains the steamapps directory). Common: the directory to where your proton versions are stored, usually Steam's steamapps/common directory. Example: data = \"/home/avery/Documents/Proton/env/\" steam = \"/home/avery/.steam/steam/\" common = \"/home/avery/.steam/steam/steamapps/common/\" "; #[doc(hidden)] fn help() { println!("{}", HELP); } #[doc(hidden)] fn version() { println!( "Proton Caller (proton-call) {} Copyright (C) 2021 {}", env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_AUTHORS") ); } proton-call-3.0.1/src/version.rs000064400000000000000000000035720072674642500147400ustar 00000000000000use crate::{pass, throw, Error, Kind}; use std::fmt::{Display, Formatter}; use std::path::Path; use std::str::FromStr; /// Version type to handle Proton Versions #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum Version { /// Two number version Mainline(u8, u8), /// Experimental version Experimental, /// Custom version (will be replaced by Mainline if possible) Custom, } impl Default for Version { fn default() -> Self { Version::Mainline(6, 3) } } impl Version { #[must_use] /// Creates a new `Version::Mainline` instance pub fn new(major: u8, minor: u8) -> Version { Version::Mainline(major, minor) } #[must_use] /// Tries parsing custom Proton path into `Version::Mainline` pub fn from_custom(name: &Path) -> Version { if let Some(n) = name.file_name() { let name_str = n.to_string_lossy().to_string(); if let Some(version_str) = name_str.split(' ').last() { if let Ok(version) = version_str.parse() { return version; } } } Version::Custom } } impl Display for Version { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Version::Mainline(mj, mn) => write!(f, "{}.{}", mj, mn), Version::Experimental => write!(f, "Experimental"), Version::Custom => write!(f, "Custom"), } } } impl FromStr for Version { type Err = Error; fn from_str(s: &str) -> Result { if s.to_ascii_lowercase() == "experimental" { return pass!(Version::Experimental); } match s.split('.').collect::>().as_slice() { [maj, min] => pass!(Version::new(maj.parse()?, min.parse()?)), _ => throw!(Kind::VersionParse, "'{}'", s), } } }