Coder Social home page Coder Social logo

williamvenner / steamlocate-rs Goto Github PK

View Code? Open in Web Editor NEW
36.0 7.0 15.0 95 KB

🎮 Rust Crate for locating Steam game installation directories (and Steam itself!)

License: MIT License

Rust 100.00%
rust steam steamapps appmanifest vdf vdf-format acf acf-format games game

steamlocate-rs's Introduction

crates.io docs.rs license Workflow Status

steamlocate

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 crate. You don't need to be a Steamworks partner to get installation directory locations from the Steamworks API.

Using steamlocate

Simply add steamlocate using cargo.

$ cargo add steamlocate

Feature flags

Default: locate

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.

Examples

Locate the Steam installation and a specific game

The SteamDir is going to be your entrypoint into most parts of the API. After you locate it you can access related information.

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
App {
    app_id: 4_000,
    install_dir: "GarrysMod",
    name: Some("Garry's Mod"),
    universe: Some(Public),
    // much much more data
}

Get an overview of all libraries and apps on the system

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.

let steam_dir = steamlocate::SteamDir::locate()?;

for library in steam_dir.libraries()? {
    let library = library?;
    println!("Library - {}", library.path().display());

    for app in library.apps() {
        let app = app?;
        println!("    App {} - {:?}", app.app_id, app.name);
    }
}

On my laptop this prints

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 licensed as above, without any additional terms or conditions.

steamlocate-rs's People

Contributors

coffeeislife87 avatar cosmichorrordev avatar williamvenner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

steamlocate-rs's Issues

`AppId` (possibly others) in shortcuts.vdf ought to be case-insensitive

Really excited to see #13 land, thanks for the work @CosmicHorrorDev.

I've been working on porting some code I had to work with it, but I am running into an issue where I simply get an empty list of shortcuts on my dev machine.

In my case, on macOS, I have a shortcuts.vdf which looks like this:

00000000  00 73 68 6f 72 74 63 75  74 73 00 00 30 00 02 61  |.shortcuts..0..a|
00000010  70 70 69 64 00 40 e5 b3  ae 01 61 70 70 6e 61 6d  |ppid.@峮.appnam|
00000020  65 00 53 65 63 6f 6e 64  20 4c 69 66 65 00 01 45  |e.Second Life..E|
00000030  78 65 00 22 2f 41 70 70  6c 69 63 61 74 69 6f 6e  |xe."/Application|
00000040  73 2f 53 65 63 6f 6e 64  20 4c 69 66 65 20 56 69  |s/Second Life Vi|
00000050  65 77 65 72 2e 61 70 70  22 00 01 53 74 61 72 74  |ewer.app"..Start|
00000060  44 69 72 00 22 2f 41 70  70 6c 69 63 61 74 69 6f  |Dir."/Applicatio|
00000070  6e 73 2f 22 00 01 69 63  6f 6e 00 00 01 53 68 6f  |ns/"..icon...Sho|
00000080  72 74 63 75 74 50 61 74  68 00 00 01 4c 61 75 6e  |rtcutPath...Laun|
00000090  63 68 4f 70 74 69 6f 6e  73 00 00 02 49 73 48 69  |chOptions...IsHi|
000000a0  64 64 65 6e 00 00 00 00  00 02 41 6c 6c 6f 77 44  |dden......AllowD|
000000b0  65 73 6b 74 6f 70 43 6f  6e 66 69 67 00 01 00 00  |esktopConfig....|
000000c0  00 02 41 6c 6c 6f 77 4f  76 65 72 6c 61 79 00 01  |..AllowOverlay..|
000000d0  00 00 00 02 4f 70 65 6e  56 52 00 00 00 00 00 02  |....OpenVR......|
000000e0  44 65 76 6b 69 74 00 00  00 00 00 01 44 65 76 6b  |Devkit......Devk|
000000f0  69 74 47 61 6d 65 49 44  00 00 02 44 65 76 6b 69  |itGameID...Devki|
00000100  74 4f 76 65 72 72 69 64  65 41 70 70 49 44 00 00  |tOverrideAppID..|
00000110  00 00 00 02 4c 61 73 74  50 6c 61 79 54 69 6d 65  |....LastPlayTime|
00000120  00 cc d8 5e 63 01 46 6c  61 74 70 61 6b 41 70 70  |.��^c.FlatpakApp|
00000130  49 44 00 00 00 74 61 67  73 00 08 08 08 08        |ID...tags.....|

Notably, the key appname is all-lowercase for this shortcut.

The library I used for this purpose previously, steam_shortcuts_util, treats all keys as case-insensitive, and doesn't exhibit this problem. I believe this would be the more-correct approach.

`libraryfolders.vdf` layout changed

It looks like some Steam update changed the layout used in libraryfolders.vdf. Here is an anonymized version from my windows install:

"libraryfolders"
{
	"contentstatsid"		"-9876543210987654321"
	"0"
	{
		"path"		"C:\\Program Files (x86)\\Steam"
		"label"		""
		"contentid"		"-9876543210987654321"
		"totalsize"		"0"
		"update_clean_bytes_tally"		"98765432109"
		"time_last_update_corruption"		"0"
		"apps"
		{
			"111"		"12345678901"
			"210987"		"9998887776"
		}
	}
	"1"
	{
		"path"		"D:\\SteamLibrary"
		"label"		""
		"contentid"		"7654321098765432109"
		"totalsize"		"123456789012"
		"update_clean_bytes_tally"		"11223344556"
		"time_last_update_corruption"		"0"
		"apps"
		{
			"10"		"101112131"
		}
	}
}

This matches the same layout present on my linux install

Notably there is a parse error before any information can be extracted because steamy-vdf has an issue parsing empty strings and the new layout contains the pair "label" "". Changing the labels' values manually to contain some text gets past the parse error and instead fails on the different layout

v2.0 roadmap, API overhaul, and more!

Pinging @WilliamVenner since I would love to hear your thoughts on this since it's your library after all 😄, and @super-continent since you mentioned potentially being interested in helping with some of the changes

Pending Fixes

  • #6
    • steamy-vdf parsing issues strike again! This time with app manifests. The obvious way forward is to switch the app manifest parsing to also use keyvalues-parser. The issue is that the SteamApp type directly exposes a steamy_vdf::Table, so this will have to be a breaking change.

API Overhaul

Switching SteamDir::apps from a caching API to an iterator based API

The idea here is to switch SteamDir::apps from returning a reference to an internal hashmap to returning an iterator that tries parsing each manifest and returning a result for each one. This API is inspired by similar situations like std::fs::read_dir.Something like

impl SteamDir {
    pub fn apps(&self) -> AppIter {
        ...
    }
}

impl Iterator for AppIter {
    // Each entry is the app id followed by a result for reading and parsing the manifest
    type Item = (u32, steamlocate::Result<SteamApp>);

    fn next(&mut self) -> Option<Self::Item> {
        ...
    }
}

The reasoning for this switch is that I don't think we gain much by caching, and this switch makes things more flexible along with being more straightforward

Exposing an error type

The current API just converts all errors that are encountered into None which makes it hard to track down the source of issues. The fix here is to enumerate possible error types in an enum and expose it for fallible operations

Lock down some public internals

Things like LibraryFolders' paths shouldn't be manipulated by user's of the library, so it should be private with a public getter. The same goes for SteamDir's path

Avoid exposing parsing types in SteamApp

It would be good to look over a large set of app manifest files to see how reasonable it would be to expose all the information within the struct instead of having to expose a type from the parser publicly. The reasoning is twofold

  1. keyvalues-parser is not stable and it's very possible that I will be making breaking changes in the future (I'm pending a breaking release atm). Breaking changes for exposed types would be a breaking change for this library to upgrade to too which isn't ideal
  2. Usually messing with the low-level parsed types is gnarly, so it would be much more ergonomic for users to not have to touch it

Internal

Run test in CI

Currently the test suite has a lot of requirements for it to actually be run. It would be good to store some sample libraryfolders and appmanifests, and do some path manipulation to run tests against those instead of requiring steam to be installed with some specific games

Give cargo clippy a go

There's some lints that should be easy enough to cleanup :)


If this all sounds good then the task breakdown would be something like

  • Swap appmanifest parsing from using steamy-vdf to keyvalues-parser (or keyvalues-serde)
  • Switch SteamDir::apps to return an iterator for SteamApps
  • Expose a custom error type to indicate possible failure cases
  • Lock down public fields in LibraryFolders and SteamDir
  • Expose all values for app manifests directly in SteamApp instead of exposing the parsed obj
  • Revamp tests so that we can run it from within CI
  • Fix cargo clippy lints
  • Update documentation
  • Refactor App to be just be the plain data it holds. Move the installation folder resolution to a method on the library
  • Setup a changelog / migration guide
  • Test more feature combinations in CI No longer needed now that locate is the only feature

[question] can't use shortcut appid in cli game launch

i have a test game that when i create a desktop shortcut use
steam steam://rungameid/14819369052371156992
which indicate that the appid is 14819369052371156992 but using Shortcut struct i get 4206273994 but this one will not let me run the game. did i misunderstood how steam handle non steam game appid ?

App path resolution fails with lowercase VDF fields

While replacing my existing game path discovery logic with steamlocate v2.0.0-beta.1, I ran into this error while testing on my Fedora machine with a Flatpak Steam install:

Failed parsing VDF file. File kind: App, Error: missing field `LastUpdated` at /home/schuyler/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/appmanifest_2519830.acf

Examining the file, some of the VDF fields (including lastupdated) are all lowercase rather than CamelCase. Here are the full contents of that file:

"AppState"
{
	"appid"		"2519830"
	"Universe"		"1"
	"name"		"Resonite"
	"StateFlags"		"4"
	"installdir"		"Resonite"
	"lastupdated"		"1702688752"
	"SizeOnDisk"		"1102323116"
	"StagingSize"		"0"
	"buildid"		"12967476"
	"LastOwner"		"76561198022773299"
	"UpdateResult"		"0"
	"BytesToDownload"		"2332576"
	"BytesDownloaded"		"2332576"
	"BytesToStage"		"54625540"
	"BytesStaged"		"54625540"
	"TargetBuildID"		"12967476"
	"AutoUpdateBehavior"		"0"
	"AllowOtherDownloadsWhileRunning"		"0"
	"ScheduledAutoUpdate"		"0"
	"InstalledDepots"
	{
		"2519832"
		{
			"manifest"		"1396658363472368690"
			"size"		"514493538"
		}
		"2519831"
		{
			"manifest"		"5082223756179978205"
			"size"		"587829578"
		}
	}
	"SharedDepots"
	{
		"228984"		"228980"
		"228985"		"228980"
		"228988"		"228980"
		"228989"		"228980"
	}
	"UserConfig"
	{
		"language"		"english"
	}
	"MountedConfig"
	{
		"language"		"english"
	}
}

`SteamDir::apps()` not returning complete list of apps

I tried using this crate to try and find a specific game (Guilty Gear XX Accent Core +R) with the ID 348550 and could not find it in the list returned by apps() or app(&384550), I looked in the steam library folder and the appmanifest_384550.acf is there and appears valid. I skimmed the code in this repository and couldn't seem to find any obvious bugs. Is there a possible solution?

Unable to parse my libraryfolders.vdf made by Steam on Windows

So I'm not sure what's going on here but for some reason my specific libraryfolders.vdf, I've been trying to figure it out and it's something to do with the very last value: "E:\Steam" being recognized as a } and the parser therefore erroring out. I've been unable to figure it out for hours so any help would be much appreciated.

Anonymized libraryfolders

I've also raised this issue on the steamy-vdf crate github but it seems to be dead unfortunately :/

Fails to locate game on secondary drive

My application uses steamlocate-rs to find the install directory of Titanfall2 (1237970).

A user of my application on Fedora 38 noticed however that it was unable to locate their install of Titanfall2.
On their setup they installed Steam via the native Fedora package.

As such their library folders are located in

~/.local/share/Steam/config/libraryfolders.vdf
~/.local/share/Steam/steamapps/libraryfolders.vdf

which (partially anonymised) looks like:

"libraryfolders"
{
    "0"
    {
        "path"        "/home/XXXXXXXXX/.local/share/Steam"
        "label"        ""
        "contentid"        "XXXXXXXXXXXXXXXXXXXX"
        "totalsize"        "0"
        "update_clean_bytes_tally"        "6504277467"
        "time_last_update_corruption"        "0"
        "apps"
        {
            "XXXXXXX"        "XXXXXXXXXX"
        }
    }
    "1"
    {
        "path"        "/mnt/0678788f-1ffe-4a12-8608-8ef3b8467106/SteamLibrary"
        "label"        ""
        "contentid"        "4113662200414089328"
        "totalsize"        "737229742080"
        "update_clean_bytes_tally"        "10119163182"
        "time_last_update_corruption"        "0"
        "apps"
        {
            "XXXXXXXXX"        "XXXXXXXXX"
            "1237970"        "68365272890"
            "XXXXXXXXX"        "XXXXXXXXX"
            "XXXXXXXXX"        "XXXXXXXXX"
        }
    }
}

Unfortunately I have not yet been able to figure out where the game detection fails (reading libraryfolders.vdf, accessing secondary drive, ...)

For detection I'm using the newest release of steamlocate, i.e. 1.2.1

[Unexpected behaviour] Steam locate try to list game from flatpak steam when it as been uninstalled instead of native steam

During the development of my app I wanted to add steam flatpak support but steamlocate suddenly stopped working after that test .
It appears that Steamlocate only check if the steam folder is present and not if the steam flatpak is installed which is a big problems in the case of migration from steam flatpak to native steam since it appears that the steam flatpak folder is not deleted upon uninstall.

Support for non-Steam games added to Steam client

Hi! Do you think it would make sense to support listing games added to Steam via the "add a non-Steam game" option? I'm using steamlocate in Ludusavi, and I got a feature request to look up Proton save data for such games. It requires knowing the configured target path and app name, then calculating the stand-in app ID used for the Proton compatdata subfolder (sample code). The info is stored in <steam>/userdata/<user>/config/shortcuts.vdf.

I was imagining something like this:

let mut steam = steamlocate::SteamDir::locate()?;
let id = steam.shortcut("Game Name")?.id();
let shortcuts = steam.shortcuts()?;

Consider adding me as a crate owner

Hi @WilliamVenner!

It seems like you've been pretty busy lately. I've got quite a bit of free time on my hands lately, so I'd like to get v2 for this crate prepared with at least one alpha and beta along the way. If you wouldn't mind adding me as a crate owner I could handle working out all that and then ping you for the final review before publishing v2

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.