proton-call-3.1.2/.cargo_vcs_info.json0000644000000001360000000000100132710ustar { "git": { "sha1": "07447994e43d93f751f1ed504a89b9c909f7edb6" }, "path_in_vcs": "" }proton-call-3.1.2/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000012251046102023000202760ustar 00000000000000--- name: Bug report about: Create a report for bugs title: '' labels: bug assignees: caverym --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. proton-call-3.1.2/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000011411046102023000213260ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: caverym --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. proton-call-3.1.2/.github/funding.yml000064400000000000000000000000411046102023000155710ustar 00000000000000# github: caverym ko_fi: caverym proton-call-3.1.2/.github/workflows/rust.yml000064400000000000000000000004741046102023000172030ustar 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.1.2/.gitignore000064400000000000000000000002761046102023000140560ustar 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.1.2/CODE_OF_CONDUCT.md000064400000000000000000000121471046102023000146650ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at averylapine@gmail.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. proton-call-3.1.2/COMPLETED000064400000000000000000000017441046102023000133260ustar 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.1.2/Cargo.lock0000644000000045730000000000100112550ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "jargon-args" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e1a4272819cbb3e7d0290885b457d2f2df46dbc9c4f4511ef83797c7f688a7" [[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.1.2" dependencies = [ "bincode", "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.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276" 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.1.2/Cargo.toml0000644000000021200000000000100112620ustar # 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.1.2" 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" [profile.release] opt-level = "z" lto = true codegen-units = 1 [dependencies.bincode] version = "1.3.3" [dependencies.jargon-args] version = "0.2.5" [dependencies.lliw] version = "0.2.0" [dependencies.serde] version = "1.0.132" features = ["derive"] [dependencies.toml] version = "0.5.8" proton-call-3.1.2/Cargo.toml.orig000064400000000000000000000010211046102023000147420ustar 00000000000000[package] name = "proton-call" version = "3.1.2" 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.8" jargon-args = "0.2.5" lliw = "0.2.0" serde = { version = "1.0.132", features = ["derive"] } bincode = "1.3.3" proton-call-3.1.2/LICENSE000064400000000000000000000020551046102023000130700ustar 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.1.2/README.md000064400000000000000000000077661046102023000133600ustar 00000000000000# Proton Caller logo Proton-Caller Run any Windows program through [Valve's Proton](https://github.com/ValveSoftware/Proton). [![Packaging status](https://repology.org/badge/vertical-allrepos/proton-caller.svg)](https://repology.org/project/proton-caller/versions) [Usage](https://github.com/caverym/Proton-Caller#usage) [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 path 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/" ``` ## Runtime: Proton Caller 3.1.0 added support for Steam's runtimes and their options. Selecting a runtime can be done by using `-R Soldier/Sniper/Default/BattleEye` On Proton versions 5 and newer, runtime Soldier is selected automatically The runtime options can be selected using *multiple* `-o` available options: ``` log, // PROTON_LOG wined3d, // PROTON_USE_WINED3D nod3d11, // PROTON_NO_D3D11 nod3d10, // PROTON_NO_D3D10 noesync, // PROTON_NO_ESYNC nofsync, // PROTON_NO_FSYNC enablenvapi, // PROTON_ENABLE_NVAPI } ``` More about these options can be found in Proton's manual. ## 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-based Distributions: #### Based on Debian 12+ or Ubuntu 22.04+: `sudo apt install proton-caller` #### Based on Ubuntu 20.04-21.10: ``` sudo add-apt-repository ppa:benthetechguy/proton-caller sudo apt install proton-caller ``` #### Other: A `.deb` file is available for download at the [releases](https://github.com/caverym/proton-caller/releases) page. ### RPM-based Distributions: A `.rpm` file is available for download at the [releases](https://github.com/caverym/proton-caller/releases) page. There is also a [Copr](https://developer.fedoraproject.org/deployment/copr/about.html) repository available for Fedora 34+ users: ``` sudo dnf copr enable benthetechguy/proton-caller sudo dnf install proton-caller ``` ### 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 ``` ## Credits [logo](logo.svg) by [Maro_Does_Art](https://twitter.com/Maro_Does_Art) on twitter proton-call-3.1.2/Space Engine.desktop000064400000000000000000000003601046102023000156740ustar 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.1.2/TODO000064400000000000000000000015141046102023000125520ustar 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.1.2/logo.svg000064400000000000000000005153721046102023000135570ustar 00000000000000 proton-call-3.1.2/proton.conf000064400000000000000000000002211046102023000142440ustar 00000000000000data = "/home/avery/Documents/Proton/env/" steam = "/home/avery/.steam/steam/" # optional common = "/home/avery/.steam/steam/steamapps/common/" proton-call-3.1.2/src/config.rs000064400000000000000000000071661046102023000144750ustar 00000000000000extern crate serde; extern crate toml; use 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: String = format!("{}/proton.conf", val); return Ok(PathBuf::from(path)); } match var("HOME") { Ok(var) => Ok(PathBuf::from(format!("{}/.config/proton.conf", var))), Err(_) => throw!(Kind::Environment, "XDG_CONFIG_HOME / HOME missing"), } } #[inline] /// 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] #[inline] /// 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] #[inline] /// Returns the in use steam directory pub fn steam(&self) -> PathBuf { self.steam.clone() } #[must_use] #[inline] /// 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.1.2/src/error.rs000064400000000000000000000110221046102023000143430ustar 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, /// for when Index failes an action with cache IndexCache, /// for when parsing RuntimeOption fails, ParseRuntimeOpt, /// for when steam runtime version is missing RuntimeMissing, } 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", Kind::IndexCache => "failed read/write to cache", Kind::ParseRuntimeOpt => "failed parsing runtime option", Kind::RuntimeMissing => "failed to find Runtime", } ) } } proton-call-3.1.2/src/index.rs000064400000000000000000000121651046102023000143320ustar 00000000000000use crate::error::{Error, Kind}; use crate::{pass, throw, Version}; use lliw::Fg::LightYellow as Yellow; use lliw::Reset; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::ffi::OsString; use std::fmt::{Display, Formatter}; use std::fs::{DirEntry, File, OpenOptions}; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; /// Index type to Index Proton versions in common #[derive(Debug, Serialize, Deserialize)] pub struct Index { dir: PathBuf, inner: HashMap, } 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.inner { str = format!("{}\nProton {}: {}", str, version, path.display()); } 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(), inner: HashMap::new(), }; idx.load()?; Ok(idx) } #[must_use] #[inline] /// Returns the number of Indexed Protons pub fn len(&self) -> usize { self.inner.len() } #[must_use] #[inline] /// Returns true if Index is empty pub fn is_empty(&self) -> bool { self.inner.is_empty() } #[must_use] #[inline] /// Retrieves the path of the requested Proton version pub fn get(&self, version: &Version) -> Option { self.inner.get(version).map(std::clone::Clone::clone) } fn cache_location() -> Result { use std::env::var; if let Ok(val) = var("HOME") { let path = format!("{}/.cache/proton/index", val); return Ok(PathBuf::from(path)); } throw!(Kind::Environment, "$HOME does not exist") } fn open_cache() -> Result { let path: PathBuf = Self::cache_location()?; if let Some(parent) = path.parent() { if !parent.exists() { if let Err(e) = std::fs::create_dir(parent) { throw!(Kind::IndexCache, "{}", e); } } } match OpenOptions::new() .read(true) .write(true) .create(true) .open(path) { Ok(cache) => pass!(cache), Err(e) => throw!(Kind::IndexCache, "{}", e), } } fn load(&mut self) -> Result<(), Error> { if let Err(e) = self._load() { eprintln!("{}warning{}: {}\nreindexing...", Yellow, Reset, e); self.index()?; if let Err(e) = self.save() { eprintln!("{}warning:{} {}\n", Yellow, Reset, e); } } Ok(()) } fn _load(&mut self) -> Result<(), Error> { let cache: File = Self::open_cache()?; self.read_index(cache)?; Ok(()) } fn read_index(&mut self, mut f: File) -> Result<(), Error> { let mut buf: Vec = Vec::new(); if let Err(e) = f.read_to_end(&mut buf) { throw!(Kind::IndexCache, "{}", e); } self.inner = match bincode::deserialize::(&buf) { Ok(c) => c.inner, Err(_) => throw!(Kind::IndexCache, "can't deserialize"), }; Ok(()) } fn save(&self) -> Result<(), Error> { let mut cache: File = Self::open_cache()?; let bytes: Vec = match bincode::serialize(self) { Ok(b) => b, Err(e) => throw!(Kind::IndexCache, "{}", e), }; if let Err(e) = cache.write(&bytes) { throw!(Kind::IndexCache, "{}", e); } Ok(()) } // TODO: Refactor and optimize `index` method. /// Indexes Proton versions /// # Errors /// An error is returned when the function cannot read the common directory pub 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.inner.insert(version, entry_path); } } } } } else { throw!(Kind::IndexReadDir, "can not read common dir"); } pass!() } } proton-call-3.1.2/src/lib.rs000064400000000000000000000116211046102023000137650ustar 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 runtime; mod runtime_options; mod version; /// Contains the `Error` and `ErrorKind` types pub mod error; pub use config::Config; use error::{Error, Kind}; pub use index::Index; pub use runtime::RunTimeVersion; use runtime::Runtime; pub use runtime_options::RuntimeOption; 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, options: Vec, compat: PathBuf, steam: PathBuf, runtime: Option, common: PathBuf, } impl Proton { #[must_use] /// Creates a new instance of `Proton` pub fn new( version: Version, path: PathBuf, program: PathBuf, args: Vec, options: Vec, compat: PathBuf, steam: PathBuf, runtime: Option, common: PathBuf, ) -> Proton { Proton { version, path, program, args, options, compat, steam, runtime, common, } .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!() } fn gen_options(&self) -> Vec<(String, String)> { let mut opts = Vec::new(); for opt in &self.options { opts.insert(opts.len(), (opt.to_string(), "1".to_string())) } opts } /// 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()?; // check one for runtimes if let Some(runtime) = self.runtime { let runtime = Runtime::from_proton(runtime, self)?; return runtime.execute(); } // check two for runtimes match self.version { Version::Mainline(maj, _) => { if maj >= 5 { let runtime = Runtime::from_proton(RunTimeVersion::Soldier, self)?; return runtime.execute() } }, Version::Experimental => { let runtime = Runtime::from_proton(RunTimeVersion::Soldier, self)?; return runtime.execute() } _ => {}, } self.execute() } /// Executes Proton fn execute(self) -> Result { use std::process::{Child, Command}; let envs: Vec<(String, String)> = self.gen_options(); println!( "Running Proton {} for {} with:\n{:#?}", self.version, self.program.to_string_lossy(), envs, ); let mut child: Child = match Command::new(&self.path) .arg("run") .arg(&self.program) .args(&self.args) .env("STEAM_COMPAT_DATA_PATH", &self.compat) .env("STEAM_COMPAT_CLIENT_INSTALL_PATH", &self.steam) .envs(envs) .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.1.2/src/main.rs000064400000000000000000000157771046102023000141630ustar 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 ``` */ extern crate jargon_args; extern crate lliw; use proton_call::error::{Error, Kind}; use proton_call::{pass, throw, Config, Index, Proton, RunTimeVersion, RuntimeOption, 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, custom: Option, options: Vec, args: Vec, runtime_version: Option, } /// 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; // args.insert(args.len(), "--index".to_string()); 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 if parser.contains(["-a", "--add"]) { todo!("command") } else { let config: Config = Config::open()?; let mut args = Args { program: parser.result_arg(["-r", "--run"])?, version: parser.option_arg(["-p", "--proton"]).unwrap_or_default(), custom: parser.option_arg(["-c", "--custom"]), runtime_version: parser.option_arg::(["-R", "--runtime"]), options: Vec::new(), args: Vec::new(), }; let (options, argv) = if parser.contains(["-o", "--options"]) { let mut opts: Vec = Vec::new(); if parser.contains(["-l", "--log"]) { opts.insert(opts.len(), RuntimeOption::log) } let finish = parser.finish(); let mut arv = Vec::new(); for arg in finish { if let Ok(opt) = arg.parse::() { opts.insert(opts.len(), opt) } else { arv.insert(arv.len(), arg) } } (opts, arv) } else { let mut opts: Vec = Vec::new(); if parser.contains(["-l", "--log"]) { opts.insert(opts.len(), RuntimeOption::log) } (opts, parser.finish()) }; args.options = options; args.args = argv; 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(()) } fn get_proton_path(index: &mut Index, version: Version) -> Result { if let Some(path) = index.get(&version) { return Ok(path); } eprintln!( "{}info:{} Proton {} not found, reindexing...", lliw::Fg::Blue, lliw::Reset, version ); index.index()?; index.get(&version).ok_or_else(|| { Error::new( Kind::ProtonMissing, format!("Proton {} does not exist", version), ) }) } /// Runs caller in normal mode, running indexed Proton versions fn normal_mode(config: &Config, args: Args) -> Result { let mut index: Index = Index::new(&config.common())?; let proton_path: PathBuf = get_proton_path(&mut index, args.version)?; let proton: Proton = Proton::new( args.version, proton_path, args.program, args.args, args.options, config.data(), config.steam(), args.runtime_version, config.common(), ); 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.options, config.data(), config.steam(), args.runtime_version, config.common(), ); 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 -o, --options [OPTIONS] Pass options to Runtime -p, --proton [VERSION] Use Proton VERSION from `common` -r, --run EXE Run EXE in proton -R, --runtime [VERSION] Use runtime VERSION -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.1.2/src/runtime.rs000064400000000000000000000060111046102023000146770ustar 00000000000000use std::{ convert::Infallible, fmt::Display, path::{Path, PathBuf}, process::ExitStatus, str::FromStr, }; use crate::{ error::{Error, Kind}, pass, throw, Proton, }; #[derive(Debug)] pub struct Runtime { version: RunTimeVersion, path: PathBuf, proton: Proton, } impl Runtime { pub fn from_proton(version: RunTimeVersion, proton: Proton) -> Result { Ok(Self { version, path: Self::find(&proton.common, version)?, proton, }) } pub fn execute(self) -> Result { use std::process::{Child, Command}; let envs: Vec<(String, String)> = self.proton.gen_options(); let mut child: Child = match Command::new(&self.path) .arg(&self.proton.path) .arg("runinprefix") .arg(&self.proton.program) .args(&self.proton.args) .env("STEAM_COMPAT_DATA_PATH", &self.proton.compat) .env("STEAM_COMPAT_CLIENT_INSTALL_PATH", &self.proton.steam) .envs(envs) .spawn() { Ok(child) => child, Err(e) => throw!(Kind::ProtonExit, "{}", e), }; let status: ExitStatus = match child.wait() { Ok(e) => e, Err(e) => throw!(Kind::ProtonWait, "'{}': {}", child.id(), e), }; pass!(status) } pub fn find(common: &Path, version: RunTimeVersion) -> Result { let tmp = format!("{}/{}/run", common.display(), version); let path = PathBuf::from(tmp); if path.exists() { pass!(path) } else { throw!(Kind::RuntimeMissing, "{}", version) } } } /// Enum to represet Steam runtime versions #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum RunTimeVersion { /// Default version of Steam's runtime Default, /// Sniper version of Steam's runtime Sniper, /// Soldier version of Steam's runtime Soldier, /// BattleEye version of Steam's runtime BattleEye, /// EasyAntiCheat version of Steam's runtime EasyAntiCheat, } impl Display for RunTimeVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RunTimeVersion::Default => write!(f, "SteamLinuxRuntime"), RunTimeVersion::Sniper => write!(f, "SteamLinuxRuntime_sniper"), RunTimeVersion::Soldier => write!(f, "SteamLinuxRuntime_soldier"), RunTimeVersion::BattleEye => write!(f, "Proton BattlEye Runtime"), RunTimeVersion::EasyAntiCheat => write!(f, "Proton EasyAntiCheat Runtime"), } } } impl FromStr for RunTimeVersion { type Err = Infallible; fn from_str(s: &str) -> Result { Ok(match s { "default" => Self::Default, "soldier" => Self::Soldier, "sniper" => Self::Sniper, "battleeye" => Self::BattleEye, "eac" | "easyanticheat" => Self::EasyAntiCheat, _ => Self::Default, }) } } proton-call-3.1.2/src/runtime_options.rs000064400000000000000000000044101046102023000164530ustar 00000000000000use std::{ fmt::{Debug, Display}, str::FromStr, }; use crate::{error::Kind, throw}; /// Runtime options define at https://github.com/ValveSoftware/Proton/tree/proton_6.3-rc#runtime-config-options #[allow(non_camel_case_types)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum RuntimeOption { /// Convenience method for dumping a useful debug log log, // PROTON_LOG /// Use OpenGL-based wined3d instead of Vulkan-based DXVK for d3d11, d3d10, and d3d9. wined3d, // PROTON_USE_WINED3D /// Disable `d3d11.dll`, for d3d11 games which can fall back to and run better with d3d9. nod3d11, // PROTON_NO_D3D11 /// Disable `d3d10.dll` and `dxgi.dll`, for d3d10 games which can fall back to and run better with d3d9. nod3d10, // PROTON_NO_D3D10 /// Do not use eventfd-based in-process synchronization primitives. noesync, // PROTON_NO_ESYNC /// Do not use futex-based in-process synchronization primitives. (Automatically disabled on systems with no `FUTEX_WAIT_MULTIPLE` support.) nofsync, // PROTON_NO_FSYNC /// Enable NVIDIA's NVAPI GPU support library. enablenvapi, // PROTON_ENABLE_NVAPI } impl Display for RuntimeOption { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let opt = match self { RuntimeOption::log => "PROTON_LOG", RuntimeOption::wined3d => "PROTON_USE_WINED3D", RuntimeOption::nod3d11 => "PROTON_NO_D3D11", RuntimeOption::nod3d10 => "PROTON_NO_D3D10", RuntimeOption::noesync => "PROTON_NO_ESYNC", RuntimeOption::nofsync => "PROTON_NO_FSYNC", RuntimeOption::enablenvapi => "PROTON_ENABLE_NVAPI", }; write!(f, "{}", opt) } } impl FromStr for RuntimeOption { type Err = crate::Error; fn from_str(s: &str) -> Result { match s { "log" => Ok(Self::log), "wined3d" => Ok(Self::wined3d), "nod3d11" => Ok(Self::nod3d11), "nod3d10" => Ok(Self::nod3d10), "noesync" => Ok(Self::noesync), "nofsync" => Ok(Self::nofsync), "enablenvapi" | "nvapi" => Ok(Self::enablenvapi), _ => throw!(Kind::ParseRuntimeOpt, "{} is not a runtime option", s), } } } proton-call-3.1.2/src/version.rs000064400000000000000000000037171046102023000147130ustar 00000000000000use crate::{pass, throw, Error, Kind}; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::ffi::OsStr; use std::fmt::{Display, Formatter}; use std::hash::Hash; use std::path::Path; use std::str::FromStr; /// Version type to handle Proton Versions #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize, Hash)] 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(7, 0) } } impl Version { #[must_use] /// Creates a new `Version::Mainline` instance pub fn new(major: u8, minor: u8) -> Version { Version::Mainline(major, minor) } #[must_use] /// Converts path to custon proton version into Version enum pub fn from_custom(name: &Path) -> Version { let name_osstr: &OsStr = name.file_name().unwrap_or_else(|| OsStr::new("custom")); let name_str: Cow = name_osstr.to_string_lossy(); let version_str: &str = name_str.split(' ').last().unwrap_or("custom"); version_str.parse().unwrap_or(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), } } }