Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for discovering shortcuts #13

Conversation

CosmicHorrorDev
Copy link
Collaborator

Resolves #12

(Would love to get your feedback @mtkennerly)

This adds in some basic support for discovering shortcuts. This current implementation just adds a .shortcuts() method to SteamDir that discovers and returns the full listing of shortcuts found (Note: this returns all user's shortcuts). There is an example that makes this easy to try out

$ cargo run --example shortcuts

The amount of information parsed for each shortcut is pretty slim. I just opted to parse the app id, app name, and executable (this looks like it is either the program name or path to the executable depending on how the user added it). Here is a full listing of some example information converted to JSON using Python's vdf package. This should give an idea of what all information is available. If something else seems important to parse then just lmk (Note: also I think Python's vdf package botches the app id since docs I found say that it should be an unsigned 32-bit LE value. Would be good if someone could double-check that)

import json
import vdf
d = vdf.binary_load(open('shortcuts.vdf', 'rb'))
print(json.dumps(d, indent=4))
{
    "shortcuts": {
        "0": {
            "appid": -1508692987,
            "AppName": "Anki",
            "Exe": "\"anki\"",
            "StartDir": "\"./\"",
            "icon": "",
            "ShortcutPath": "/usr/share/applications/anki.desktop",
            "LaunchOptions": "",
            "IsHidden": 0,
            "AllowDesktopConfig": 1,
            "AllowOverlay": 1,
            "OpenVR": 0,
            "Devkit": 0,
            "DevkitGameID": "",
            "DevkitOverrideAppID": 0,
            "LastPlayTime": 0,
            "FlatpakAppID": "",
            "tags": {}
        },
        "1": {
            "appid": -1802792558,
            "AppName": "LibreOffice Calc",
            "Exe": "\"libreoffice\"",
            "StartDir": "\"./\"",
            "icon": "",
            "ShortcutPath": "/usr/share/applications/libreoffice-calc.desktop",
            "LaunchOptions": "--calc",
            "IsHidden": 0,
            "AllowDesktopConfig": 1,
            "AllowOverlay": 1,
            "OpenVR": 0,
            "Devkit": 0,
            "DevkitGameID": "",
            "DevkitOverrideAppID": 0,
            "LastPlayTime": 0,
            "FlatpakAppID": "",
            "tags": {}
        },
        "2": {
            "appid": -591941795,
            "AppName": "foo.sh",
            "Exe": "\"/usr/local/bin/foo.sh\"",
            "StartDir": "\"/usr/local/bin/\"",
            "icon": "",
            "ShortcutPath": "",
            "LaunchOptions": "",
            "IsHidden": 0,
            "AllowDesktopConfig": 1,
            "AllowOverlay": 1,
            "OpenVR": 0,
            "Devkit": 0,
            "DevkitGameID": "",
            "DevkitOverrideAppID": 0,
            "LastPlayTime": 0,
            "FlatpakAppID": "",
            "tags": {}
        }
    }
}

You can see the information that is parsed out using this library from the new unit test

[
    Shortcut {
        appid: 2786274309,
        app_name: "Anki".into(),
        executable: "\"anki\"".into(),
    },
    Shortcut {
        appid: 2492174738,
        app_name: "LibreOffice Calc".into(),
        executable: "\"libreoffice\"".into(),
    },
    Shortcut {
        appid: 3703025501,
        app_name: "foo.sh".into(),
        executable: "\"/usr/local/bin/foo.sh\"".into(),
    }
]

@mtkennerly
Copy link

mtkennerly commented Oct 24, 2022

Thanks for this :D Works great on my end.

This is my output for the example (Windows 11):

Shortcuts - [
    Shortcut {
        appid: 2943761701,
        app_name: "ludusavi",
        executable: "\"C:\\git\\ludusavi\\target\\debug\\ludusavi.exe\"",
    },
]

I can then get the launch ID like this (plus a dependency on crc = "3.0.0"):

fn main() {
    let mut steamdir = steamlocate::SteamDir::locate().unwrap();
    let shortcuts = steamdir.shortcuts();
    for shortcut in shortcuts {
        let input = format!("{}{}", &shortcut.executable, &shortcut.app_name);
        let algorithm = crc::Crc::<u32>::new(&crc::CRC_32_ISO_HDLC);
        let top = algorithm.checksum(input.as_bytes()) | 0x80000000;
        println!("Top: {:?}", top);
        let id = ((top as u64) << 32) | 0x02000000;
        println!("Launch ID: {:?}", id);
    }
}
Top: 2197487322
Launch ID: 9438136181398175744

...which matches the ID when Steam creates a desktop shortcut for the app on Windows:

image

I'm not sure which of those (219... or 943...) would be used for Proton compatdata folders, but either way, I can get the info I need by using this new feature ❤️

Do you think it would make sense to include a Shortcut method to do the CRC stuff (maybe behind a feature flag if you prefer minimizing dependencies)? I'm fine with doing it on my end, but I bet other people who want to inspect the shortcuts would want those IDs too. Would just need to verify if the 219... variant is used for anything or if just the longer one is.

@mtkennerly
Copy link

If something else seems important to parse then just lmk

I potentially have a use case for StartDir to back up save files that are known to be located in the game's own folder. It may not work in all cases (like if the executable is in an arbitrary subfolder of the game's install), but it would be nice as an option.

@CosmicHorrorDev
Copy link
Collaborator Author

Do you think it would make sense to include a Shortcut method to do the CRC stuff (maybe behind a feature flag if you prefer minimizing dependencies)?

Sure! I think a feature flag would be appropriate. I would also like to know if that shorter value is actually used anywhere. I see the longer value gets used in several places (I think the article in the linked issue referred to it as a legacy steam id).

If the shorter value isn't used then I would prefer not to expose it at the moment at least

I potentially have a use case for StartDir

I should be able to add that too. Shouldn't be a problem

@mtkennerly
Copy link

I would also like to know if that shorter value is actually used anywhere.

I just tried adding a shortcut to a Windows program on Linux and enabling Proton (game -> properties -> compatibility), but I think because I'm using VirtualBox, Proton doesn't want to run and won't make the compatdata subfolder, so I can't verify what ID it would use :/

I can at least confirm that creating a desktop shortcut to a non-Steam game on Linux uses the same kind of ID as on Windows.

@CosmicHorrorDev
Copy link
Collaborator Author

I'll give it a shot on my machine later tonight and let you know!

@CosmicHorrorDev
Copy link
Collaborator Author

It looks like the App ID from shortcuts.vdf is what is used for the compdata folder among some other things. I've also seen the longer of the two IDs used in several other places while I haven't seen the shorter ID used anywhere

So, it looks like all we need is to expose the longer of the two IDs as well as adding in parsing for the StartDir value, and if we need anything else then we should be able to add it without a breaking change (planning on slapping a #[non_exhaustive] on Shortcut

I'll try to get the changes out tomorrow. Always short on time on weekdays

Copy link
Owner

@WilliamVenner WilliamVenner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely stuff

@CosmicHorrorDev CosmicHorrorDev merged commit 5745ea2 into WilliamVenner:master Oct 27, 2022
@CosmicHorrorDev
Copy link
Collaborator Author

@WilliamVenner feel free to cut a new release whenever. I already bumped the version

@WilliamVenner
Copy link
Owner

Done - really sorry about the delay. Busy weekend. We need to get CI setup at some point to publish on tag pushes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for non-Steam games added to Steam client
3 participants