Skip to content

Commit

Permalink
Goodbye v1.0 Hello v2.0 (#63)
Browse files Browse the repository at this point in the history
* Bump version

* Fully switch from `steamy-vdf` (#9)

* feat: Switch appmanifest parsing to use `keyvalues-serde`

* feat: Add example to showcase `SteamApp` struct changes

* chore: Remove lingering debug `println!()`

* fix: Switch some fields to optional

* feat: Add `dlcappid` and `LauncherPath` to manifest

* fix: Add more missing fields to `SteamApp`

* Parsing tweaks

* Expose `crate::steamapp` types

* Placate clippy

* Publish v2.0.0-alpha.0

* Experimental switch to iterator based api (#10)

* Hack together experimental API

* Get tests passing

* Dont use the apps listing from `libraryfolders.vdf`

* Fix windows build

* Remove caching of values

* Placate clippy

* Custom error type (#29)

* Add additional Steam search paths for Linux (#31)

* Add additional Steam search paths for Linux

* Fix invalid return type for locate_steam_dir

* Fix formatting-related CI fail

* impl std::error::Error for Error {} (#32)

* Remove steamid-ng feature (keep public API stable) (#33)

* Add Snap support for linux (#30)

* Get things back to compiling and passing (#38)

* Update dependencies (#39)

* `cargo upgrade`

* `cargo upgrade --incompatible`

Everything seems to work fine on my machine. We'll see if CI has
anything to say otherwise. We should really beef up our test suite too,
but now I'm rambling

* Switch tests to run in isolated dummy steam installations (#40)

* Switch tests to run in isolated dummy steam installations

* Placate clippy

* Drastically simplify test helpers (#41)

* Run `cargo test` in CI (#42)

* Run `cargo test` in CI

* `#[ignore]` a couple lingering doctests

* Placate clippy (#43)

* Placate clippy on more platforms (#44)

* Run CI on more platforms/channels (#45)

* Bundle existing CI jobs into one

* Run CI on more platforms

* Use `dtolnay/rust-toolchain` action

* Setup CI caching

* Run CI on stable and beta

* Error cleanup (#46)

* `ParseErrorInner` should **not** be part of the public API

* `LibaryFolders` -> `LibraryFolders`

* Make `Error` non-exhaustive

* Add path to io error

* Add path to parse error

* Remove erroneous print

* Fixup error for missing app installation

* Unfocused polish (#47)

* Refactor `StateFlags`

* Restructure public api

* `cargo fmt`

* Rework `locate()` failures (#49)

* Move unsupported OS failure to runtime

* Move `locate()` behind a feature flag

* Port compat tool (#50)

* add method to query configured compatibility tool

* Update things to better match new structure

---------

Co-authored-by: Jan200101 <[email protected]>

* Expose `Library::from_dir()` and `InstallDir::library_paths()` (#51)

* Expose `Library::from_dir()`

* Add a way to get just library paths

* Another round of refactors (#52)

* Rename `InstallDir` back to `SteamDir`

* `FlagIter` -> `StateFlagIter`

* `tests::test_helpers` -> `tests::helpers`

* Conditionally ignore `shortcuts_extras` test

* Provide more context on installation location failure (#53)

* Prepare another alpha (#54)

* Bump version to v2.0.0-alpha.1

* Make `App` plain data

* Docs overhaul (#55)

* Finally found an approach to doctest dependecies thats decent

* Overhaul landing page

* Make github syntax highlighting happy

* `cargo fmt`

* Overhaul all remaining existing docs

* `cargo fmt`

* Prepare the v2.0 beta release (#56)

* `SteamDir::from_steam_dir` -> `SteamDir::from_dir`

* `shortcuts_extra` is now just part of `steamlocate`

* Make `app_id` name more consistent

* `cargo fmt`

* Update the README

* Bump version to v2.0.0-beta.0

* Remove need to use `tempfile` (#57)

* Remove need to use `tempfile`

* Bump version to v2.0.0-beta.1

* Make `app.last_updated` actually optional (#59)

* Fix wasm32 in general (#60)

* Alias `lastupdated` for `last_updated` (#61)

* Add failing test

* Alias `last_updated` to `lastupdated` as well

* Bump version to v2.0.0-beta.2 (#62)

* Temporarily ignore broken test

---------

Co-authored-by: Ethan Green <[email protected]>
Co-authored-by: Jan <[email protected]>
  • Loading branch information
3 people authored Feb 25, 2024
1 parent 4982655 commit 2a518c9
Show file tree
Hide file tree
Showing 31 changed files with 2,311 additions and 889 deletions.
43 changes: 27 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,41 @@ env:
CARGO_TERM_COLOR: always

jobs:
check:
runs-on: ${{ matrix.os }}
validation:
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
toolchain: [stable, beta]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- name: Clippy
- name: Install ${{ matrix.toolchain }} toolchain
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ matrix.toolchain }}
components: clippy, rustfmt
- name: Setup cache
uses: Swatinem/rust-cache@v2
- name: Commune with clippy
run: cargo clippy --all -- -D warnings

fmt:
needs: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check formatting
run: cargo fmt --all -- --check
- name: Run test suite
run: cargo test
- name: Check docs
env:
RUSTDOCFLAGS: -Dwarnings
run: cargo doc --all --no-deps

docs:
needs: fmt
wasm:
runs-on: ubuntu-latest
env:
RUSTDOCFLAGS: -Dwarnings
steps:
- uses: actions/checkout@v3
- name: Check docs
run: cargo doc --all --no-deps
- name: Install stable toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install wasm-pack
uses: taiki-e/install-action@wasm-pack
- name: Run wasm tests (`--no-default-features`)
run: wasm-pack test --node --no-default-features
- name: Run wasm tests (`--all-features`)
run: wasm-pack test --node --all-features
42 changes: 29 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "steamlocate"
version = "1.2.1"
version = "2.0.0-beta.2"
authors = ["William Venner <[email protected]>"]
edition = "2018"
repository = "https://github.com/WilliamVenner/steamlocate-rs"
Expand All @@ -10,22 +10,38 @@ readme = "README.md"
keywords = ["steam", "vdf", "appmanifest", "directory", "steamapps"]
categories = ["os", "hardware-support", "filesystem", "accessibility"]

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[features]
default = []
shortcuts_extras = ["crc"]
steamid_ng = ["steamid-ng"]
default = ["locate"]
locate = ["locate_backend"]

[dependencies]
steamy-vdf = "0.2"
keyvalues-parser = "0.1"
crc = "3.0"
keyvalues-parser = "0.2"
keyvalues-serde = "0.2"
serde = { version = "1.0.0", features = ["derive"] }
keyvalues-serde = "0.1"

crc = { version = "3.0", optional = true }
# Platform-specific dependencies used for locating the steam dir
[target."cfg(target_os=\"windows\")".dependencies]
locate_backend = { package = "winreg", version = "0.51", optional = true }
[target."cfg(not(target_os=\"windows\"))".dependencies]
locate_backend = { package = "dirs", version = "5", optional = true }

[dev-dependencies]
insta = { version = "1.34.0", features = ["ron"] }
wasm-bindgen-test = "0.3.39"

[[example]]
name = "appmanifest"
required-features = ["locate"]

steamid-ng = { version = "1", optional = true }
[[example]]
name = "overview"
required-features = ["locate"]

[target.'cfg(target_os="windows")'.dependencies]
winreg = "0.11"
[target.'cfg(not(target_os="windows"))'.dependencies]
dirs = "5"
[[example]]
name = "shortcuts"
required-features = ["locate"]
162 changes: 66 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,127 +5,97 @@

# steamlocate

A crate which efficiently locates any Steam application on the filesystem, and/or the Steam installation itself.
A crate which efficiently locates any Steam application on the filesystem,
and/or the Steam installation itself.

This crate is best used when you do not want to depend on the Steamworks API for your program. In some cases the Steamworks API may be more appropriate to use, in which case I recommend the fantastic [steamworks](https://github.com/Thinkofname/steamworks-rs) crate. You don't need to be a Steamworks partner to get installation directory locations from the Steamworks API.
This crate is best used when you do not want to depend on the Steamworks API
for your program. In some cases the Steamworks API may be more appropriate to
use, in which case I recommend the fantastic
[steamworks](https://github.com/Thinkofname/steamworks-rs) crate. You don't
need to be a Steamworks partner to get installation directory locations from
the Steamworks API.

**This crate supports Windows, macOS and Linux.**
# Using steamlocate

## Using steamlocate
Simply add to your [Cargo.toml](https://doc.rust-lang.org/cargo/reference/manifest.html) file:
```toml
[dependencies]
steamlocate = "0.*"
```

To use [steamid-ng](#steamid-ng-support) with steamlocate, add this to your [Cargo.toml](https://doc.rust-lang.org/cargo/reference/manifest.html) file:
```toml
[dependencies]
steamid-ng = "1.*"
Simply add `steamlocate` using
[`cargo`](https://doc.rust-lang.org/cargo/getting-started/installation.html).

[dependencies.steamlocate]
version = "0.*"
features = ["steamid_ng"]
```console
$ cargo add steamlocate
```

## Caching
All functions in this crate cache their results, meaning you can call them as many times as you like and they will always return the same reference.
## Feature flags

If you need to get uncached results, simply instantiate a new [SteamDir](https://docs.rs/steamlocate/*/steamlocate/struct.SteamDir.html).
Default: `locate`

## steamid-ng Support
This crate supports [steamid-ng](https://docs.rs/steamid-ng) and can automatically convert [SteamApp::last_user](struct.SteamApp.html#structfield.last_user) to a [SteamID](https://docs.rs/steamid-ng/*/steamid_ng/struct.SteamID.html) for you.
| Feature flag | Description |
| :---: | :--- |
| `locate` | Enables automatically detecting the Steam installation on supported platforms (currently Windows, MacOS, and Linux). Unsupported platforms will return a runtime error. |

To enable this support, [use the `steamid_ng` Cargo.toml feature](#using-steamlocate).
# Examples

## Examples
## Locate the Steam installation and a specific game

#### Locate the installed Steam directory
```rust
extern crate steamlocate;
use steamlocate::SteamDir;
The `SteamDir` is going to be your entrypoint into _most_ parts of the API.
After you locate it you can access related information.

match SteamDir::locate() {
Some(steamdir) => println!("{:#?}", steamdir),
None => panic!("Couldn't locate Steam on this computer!")
}
```rust,ignore
let steam_dir = steamlocate::SteamDir::locate()?;
println!("Steam installation - {}", steam_dir.path().display());
// ^^ prints something like `Steam installation - C:\Program Files (x86)\Steam`
const GMOD_APP_ID: u32 = 4_000;
let (garrys_mod, _lib) = steam_dir
.find_app(GMOD_APP_ID)?
.expect("Of course we have G Mod");
assert_eq!(garrys_mod.name.as_ref().unwrap(), "Garry's Mod");
println!("{garrys_mod:#?}");
// ^^ prints something like vv
```
```rust
SteamDir (
path: PathBuf: "C:\\Program Files (x86)\\Steam"
)
```rust,ignore
App {
app_id: 4_000,
install_dir: "GarrysMod",
name: Some("Garry's Mod"),
universe: Some(Public),
// much much more data
}
```

#### Locate an installed Steam app by its app ID
This will locate Garry's Mod anywhere on the filesystem.
```rust
extern crate steamlocate;
use steamlocate::SteamDir;
## Get an overview of all libraries and apps on the system

let mut steamdir = SteamDir::locate().unwrap();
match steamdir.app(&4000) {
Some(app) => println!("{:#?}", app),
None => panic!("Couldn't locate Garry's Mod on this computer!")
}
```
```rust
SteamApp (
appid: u32: 4000,
path: PathBuf: "C:\\Program Files (x86)\\steamapps\\common\\GarrysMod",
vdf: <steamy_vdf::Table>,
name: Some(String: "Garry's Mod"),
last_user: Some(u64: 76561198040894045)
)
```
You can iterate over all of Steam's libraries from the steam dir. Then from each library you
can iterate over all of its apps.

#### Locate all Steam apps on this filesystem
```rust
extern crate steamlocate;
use steamlocate::{SteamDir, SteamApp};
use std::collections::HashMap;
```rust,ignore
let steam_dir = steamlocate::SteamDir::locate()?;
let mut steamdir = SteamDir::locate().unwrap();
let apps: &HashMap<u32, Option<SteamApp>> = steamdir.apps();
for library in steam_dir.libraries()? {
let library = library?;
println!("Library - {}", library.path().display());
println!("{:#?}", apps);
```
```rust
{
4000: SteamApp (
appid: u32: 4000,
path: PathBuf: "C:\\Program Files (x86)\\steamapps\\common\\GarrysMod",
vdf: <steamy_vdf::Table>,
name: Some(String: "Garry's Mod"),
last_user: Some(u64: 76561198040894045)
)
...
for app in library.apps() {
let app = app?;
println!(" App {} - {:?}", app.app_id, app.name);
}
}
```

#### Locate all Steam library folders
```rust
extern crate steamlocate;
use steamlocate::{SteamDir, LibraryFolders};
use std::{vec, path::PathBuf};

let mut steamdir: SteamDir = SteamDir::locate().unwrap();
let libraryfolders: &LibraryFolders = steamdir.libraryfolders();
let paths: &Vec<PathBuf> = &libraryfolders.paths;

println!("{:#?}", paths);
```
```rust
{
"C:\\Program Files (x86)\\Steam\\steamapps",
"D:\\Steam\\steamapps",
"E:\\Steam\\steamapps",
"F:\\Steam\\steamapps",
...
}
On my laptop this prints

```text
Library - /home/wintermute/.local/share/Steam
App 1628350 - Steam Linux Runtime 3.0 (sniper)
App 1493710 - Proton Experimental
App 4000 - Garry's Mod
Library - /home/wintermute/temp steam lib
App 391540 - Undertale
App 1714040 - Super Auto Pets
App 2348590 - Proton 8.0
```

## Contribution

Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the MIT license,
shall be dual licensed as above, without any additional terms or conditions.
shall be licensed as above, without any additional terms or conditions.
19 changes: 19 additions & 0 deletions examples/appmanifest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use std::{env, process::exit};

use steamlocate::SteamDir;

fn main() {
let args: Vec<_> = env::args().collect();
if args.len() != 2 || args[1].parse::<u32>().is_err() {
eprintln!("Usage: cargo run --example appmanifest -- <STEAM_APP_ID>");
exit(1);
}
let app_id: u32 = args[1].parse().expect("<STEAM_APP_ID> should be a u32");

let steam_dir = SteamDir::locate().unwrap();
match steam_dir.find_app(app_id) {
Ok(Some((app, _library))) => println!("Found app - {:#?}", app),
Ok(None) => println!("No app found for {}", app_id),
Err(err) => println!("Failed reading app: {err}"),
}
}
26 changes: 26 additions & 0 deletions examples/overview.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use steamlocate::SteamDir;

fn main() {
let steamdir = SteamDir::locate().unwrap();
println!("Steam Dir - {:?}", steamdir.path());

// TODO: use `anyhow` to make error handling here simpler
for maybe_library in steamdir.libraries().unwrap() {
match maybe_library {
Err(err) => eprintln!("Failed reading library: {err}"),
Ok(library) => {
println!(" Library - {:?}", library.path());
for app in library.apps() {
match app {
Ok(app) => println!(
" App {} - {}",
app.app_id,
app.name.as_deref().unwrap_or("<no-name>")
),
Err(err) => println!(" Failed reading app: {err}"),
}
}
}
}
}
}
9 changes: 7 additions & 2 deletions examples/shortcuts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
fn main() {
let mut steamdir = steamlocate::SteamDir::locate().unwrap();
let shortcuts = steamdir.shortcuts();
println!("Shortcuts - {:#?}", shortcuts);
println!("Shortcuts:");
for maybe_shortcut in steamdir.shortcuts().unwrap() {
match maybe_shortcut {
Ok(shortcut) => println!(" - {} {}", shortcut.app_id, shortcut.app_name),
Err(err) => println!("Failed reading potential shortcut: {err}"),
}
}
}
Loading

0 comments on commit 2a518c9

Please sign in to comment.