From 092f2fa5dc23bd45e5f34dc0203e8534d6fe3ea5 Mon Sep 17 00:00:00 2001 From: Dimitri Belopopsky Date: Wed, 14 Apr 2021 22:26:41 +0200 Subject: [PATCH] First publish --- .gitignore | 1 + Cargo.lock | 247 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 24 +++++ README.md | 19 ++++ src/main.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 526 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..69c68a4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,247 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crossbeam-utils" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "dirs" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom 0.2.2", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom 0.1.16", + "redox_syscall", + "rust-argon2", +] + +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "steam_randomiser" +version = "0.1.0" +dependencies = [ + "dirs", + "rand", + "which", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "which" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +dependencies = [ + "either", + "libc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d55bd81 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "steam_randomiser" +version = "0.1.0" +authors = ["Dimitri Belopopsky "] +edition = "2018" +license = "MIT" +description = "A tool to search files" +readme = "README.md" +homepage = "https://github.com/ShadowMitia/steam_randomiser" +repository = "https://github.com/ShadowMitia/steam_randomiser" +keywords = ["cli", "steam", "random", "game"] +categories = ["command-line-utilities"] + + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dirs = "3.0.1" +rand = "0.8.3" +which = "4.1.0" + +[profile.release] +lto = true +codegen-units = 1 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..46e890c --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Steam Randomiser + +Don't know what to play on [Steam](https://store.steampowered.com/)? + +Just run `Steam Randomiser` and it will randomly choose from one of your **installed** games! + +Perfect when you don't know which game to play from all of those bundles you've bought all these years! + +## Usage + +Double click on the executable or run from a terminal. + +## Build + +```sh +git clone https://github.com/ShadowMitia/steam_randomiser.git +cd steam_randomiser +cargo build +``` diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2c9889f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,235 @@ +use std::{ + fs::DirEntry, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +use rand::seq::SliceRandom; + +const FLATPAK_APPLICATIONS_PATH: &str = ".var/app/com.valvesoftware.Steam/data/Steam"; +const VANILLA_APPLICATIONS_PATH_NIX: &str = r#".local/share/Steam"#; +const VANILLA_APPLICATIONS_PATH_WINDOWS: &str = r#"C:\Program Files (x86)\Steam"#; + +const MANIFEST_DIR: &str = "steamapps/"; + +fn generate_steam_rungame(id: &str) -> String { + format!("steam://rungameid/{}", id) +} + +fn is_proton(app_name: &str) -> bool { + if app_name.starts_with("Proton") { + let number = app_name.split(' ').collect::>(); + if number.len() > 1 { + let number = number[1].parse::(); + if number.unwrap_or(0.0) != 0.0 { + return true; + } + } + } + false +} + +fn is_blacklisted(app_name: &str) -> bool { + let steam_libs = [ + "Steamworks Common Redistributables", + "SteamVR", + "Proton Experimental", + ]; + + steam_libs.iter().any(|&b| b == app_name) + || app_name.ends_with("Soundtrack") // This **should** deal with downloaded albums + || is_proton(app_name) + || app_name.starts_with("Steam Linux Runtime") +} + +fn get_other_install_dirs(path: &Path) -> Vec { + let mut path = path.to_path_buf(); + path.push("libraryfolders.vdf"); + + let contents = std::fs::read_to_string(path); + let lines = contents.unwrap(); + + let mut libs = Vec::new(); + + // NOTE: I'm assuming that after the "TimeNextStatsReport" and "ContentStatsID" come the alternative paths + let lines = lines.lines().skip(2).collect::>(); + for line in lines.iter().take(lines.len() - 1) { + let path = line + .split('\t') + .filter(|s| !s.is_empty()) + .collect::>(); + + let tag = path[0].replace("\"", ""); + let path = path[1].trim().replace("\"", ""); + if tag.chars().all(|c| c.is_digit(10)) { + libs.push(path.to_string()); + } + } + libs +} + +fn get_games_from_manifest_in_path(path: &Path) -> Vec<(String, String)> { + let dir = std::fs::read_dir(path).unwrap(); + + let manifest_files = dir + .map(|e| e.unwrap()) + .filter(|file| { + let file_name = file.file_name(); + let file_name = file_name.to_str().unwrap(); + file_name.starts_with("appmanifest") + }) + .collect::>(); + + let mut games = Vec::new(); + + for file in manifest_files { + let file_path = file.path(); + let contents = std::fs::read_to_string(file_path); + let lines = contents.unwrap(); + let lines = lines.lines().skip(2).collect::>(); + + let mut game = "".to_string(); + let mut id = "".to_string(); + + for line in lines.iter().take(lines.len() - 1) { + let line = line + .split('\t') + .filter(|s| !s.is_empty()) + .collect::>(); + if line[0].contains("name") { + let app_name = line[1].replace("\"", ""); + + game = app_name.clone(); + } + + if line[0].contains("appid") { + let app_id = line[1].replace("\"", ""); + id = app_id.clone(); + } + } + if !is_blacklisted(&game) { + games.push((game, id)); + } + } + + games +} + +#[derive(Debug, PartialEq)] +enum SteamKind { + Vanilla, + Flatpak, + NotFound, +} + +#[cfg(target_os = "linux")] +fn detect_steam() -> SteamKind { + let has_steam_vanilla = which::which("steam").is_ok(); + let flatpak_list = Command::new("flatpak") + .arg("list") + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let mut flatpak_steam_output = Command::new("grep") + .stdin(flatpak_list.stdout.unwrap()) + .arg("-c") + .arg("Steam") + .output() + .unwrap() + .stdout; + flatpak_steam_output.remove(flatpak_steam_output.len() - 1); + let has_flatpak_steam = String::from_utf8(flatpak_steam_output).unwrap(); + + let has_flatpak_steam = has_flatpak_steam.parse::().unwrap() > 0; + + match (has_steam_vanilla, has_flatpak_steam) { + (true, _) => SteamKind::Vanilla, + (_, true) => SteamKind::Flatpak, + _ => SteamKind::NotFound, + } +} + +#[cfg(target_os = "windows")] +fn detect_steam() -> SteamKind { + let has_steam_vanilla = which::which(r#"C:\Program Files (x86)\Steam\steam.exe"#).is_ok(); + match (has_steam_vanilla) { + true => SteamKind::Vanilla, + _ => SteamKind::NotFound, + } +} + +#[cfg(target_os = "linux")] +fn run(steam_type: SteamKind, id: &str) { + match steam_type { + SteamKind::Flatpak => std::process::Command::new("flatpak") + .args(&[ + "run", + "com.valvesoftware.Steam", + &generate_steam_rungame(id), + ]) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn(), + SteamKind::Vanilla => std::process::Command::new("steam") + .arg(&generate_steam_rungame(id)) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn(), + SteamKind::NotFound => panic!("Couldn't find steam!"), + }; +} + +#[cfg(target_os = "windows")] +fn run(steam_type: SteamKind, id: &str) { + match steam_type { + SteamKind::Vanilla => { + std::process::Command::new(r#"C:\Program Files (x86)\Steam\steam.exe"#) + .arg(&generate_steam_rungame(id)) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + } + _ => panic!("Couldn't find steam!"), + }; +} + +fn main() { + let steam_type = detect_steam(); + + if steam_type == SteamKind::NotFound { + eprintln!("Couldn't find Steam."); + return; + } + + let mut path = { + let mut home = dirs::home_dir().unwrap(); + match steam_type { + SteamKind::Flatpak => home.push(FLATPAK_APPLICATIONS_PATH), + #[cfg(target_os = "linux")] + SteamKind::Vanilla => home.push(VANILLA_APPLICATIONS_PATH_NIX), + #[cfg(target_os = "windows")] + SteamKind::Vanilla => home.push(VANILLA_APPLICATIONS_PATH_WINDOWS), + _ => {} + } + home + }; + + path.push(MANIFEST_DIR); + + let install_dirs = get_other_install_dirs(&path); + + let mut games = get_games_from_manifest_in_path(&path); + + for other_dir in install_dirs { + let mut path = PathBuf::new(); + path.push(other_dir); + path.push(MANIFEST_DIR); + games.extend(get_games_from_manifest_in_path(&path)); + } + + let (game, id) = games.choose(&mut rand::thread_rng()).unwrap(); + + println!("Randomly launching \"{}\"! Have fun!", game); + + run(steam_type, id); +}