2

Similar to How do I find the ID for a game on Steam? but this is about non-steam games (shortcuts).

I'm trying to figure out what to name my custom artwork for non-steam games to get steam to recognize them.

  • I know they go in this folder: steam/userdata/<userid>/config/grid
  • I know there are three: <id>_logo.png, <id>_hero.jpg, <id>p.jpg,

I know I can look in localconfig.vdf to find the <userid> value, but I don't know how to find the <id> value.

By adding custom art through the steam client, I see the correct value is 2853772086. However, I want to do this programmatically. How do I compute this value?

That doesn't match the index 14 nor the appid -1441195210 (signed or unsigned) in the shortcuts.vdf:

 '14': {'AllowDesktopConfig': 1,
        'AllowOverlay': 1,
        'Devkit': 0,
        'DevkitGameID': '',
        'IsHidden': 0,
        'LastPlayTime': 0,
        'LaunchOptions': '',
        'ShortcutPath': '',
        'StartDir': 'c:\\libraries\\itch\\baba',
        'appid': -1441195210,
        'appname': 'Baba Is You',
        'exe': 'c:\\libraries\\itch\\baba\\Baba Is You\\Baba Is You.exe',
        'icon': 'c:\\libraries\\itch\\baba\\Baba Is You\\Baba Is You.exe',
        'openvr': 0,
        'tags': {'0': 'steamsync'}},

The game's appid on steam 736260 so (unsurprisingly) it's not related to that either.

1
  • 1
    After figuring this out, I contributed changes to steamsync to download art from steam for Non-Steam Games (it also add shortcuts from other storefronts). This code computes these id values.
    – idbrii
    Nov 8, 2021 at 19:29

3 Answers 3

6

Steam uses two different ids used to load art for non-steam shortcuts.

You can ignore the Big Picture specific logic and set the icon as your desired Big Picture art but that icon will also be used in Steam desktop client.

Steam Desktop

steamgrid figured out how to get a normal shortcut id. They describe the solution as "crc32(exe + appname) + "02000000", using IEEE standard polynomials" and using exe and appname from shortcuts.vdf.

For some reason, steamgrid refers it as the LegacyID, but this is the format for art for steam's redesigned client. This id seems to be unrelated to the desktop shortcut id (which is also used in screenshots.vdf) and unrelated to images in Big Picture mode (which uses the icon field in shortcuts.vdf).

UWPHook as a C# implementation to find the id and separate code for the paths.

import binascii

def get_steam_shortcut_id(exe, appname):
    """Get id for non-steam shortcut.

    get_steam_shortcut_id(str, str) -> int
    """
    grid = Path(f"{steam_path}/userdata/{steamid}/config/grid")
    unique_id = ''.join([exe, appname])
    id_int = binascii.crc32(str.encode(unique_id)) | 0x80000000
    return id_int

Big Picture

scottrice/Ice steamgrid figured out how to get a Big Picture shortcut id. They explain:

Calculates the filename for a given shortcut. This filename is a 64bit integer, where the first 32bits are a CRC32 based off of the name and target (with the added condition that the first bit is always high), and the last 32bits are 0x02000000.

The implementation looks roughly like this (requires pycrc):

# Copyright (c) 2012-2013, 2013 Scott Rice
# All rights reserved. MIT License
import pycrc.algorithms as crc
def get_bigpicture_shortcut_id(exe, appname):
    algorithm = crc.Crc(width = 32, poly = 0x04C11DB7, reflect_in = True, xor_in = 0xffffffff, reflect_out = True, xor_out = 0xffffffff)
    input_string = ''.join([exe,appname])
    top_32 = algorithm.bit_by_bit(input_string) | 0x80000000
    full_64 = (top_32 << 32) | 0x02000000
    return str(full_64)

See the original for more details/comments.

You can see that this is very similar to the normal shortcut id except it has some extra lower bits.

Bringing it Together

We can put it all together to get the locations for all the art like so:

from pathlib import Path
import binascii

def get_steam_shortcut_id(exe, appname):
    """Get id for non-steam shortcut.

    get_steam_shortcut_id(str, str) -> str
    """
    unique_id = ''.join([exe, appname])
    id_int = binascii.crc32(str.encode(unique_id)) | 0x80000000
    return id_int


def get_grid_art_destinations(steam_path, steamid, exe, appname):
    """Get filepaths for the grid images for the input shortcut.

    get_grid_art_destinations(str, str, str, str) -> dict[str,Path]
    """
    grid = Path(f"{steam_path}/userdata/{steamid}/config/grid")
    shortcut = get_steam_shortcut_id(exe, appname)
    bp_shortcut = (shortcut << 32) | 0x02000000
    return {
        'boxart': grid / f"{shortcut}p.jpg",
        'hero':   grid / f"{shortcut}_hero.jpg",
        'logo':   grid / f"{shortcut}_logo.png",
        '10foot': grid / f"{bp_shortcut}.png",
    }

import pprint
pprint.pprint(get_grid_art_destinations("C:/Program Files (x86)/Steam", '00000000', 'c:\\libraries\\itch\\baba\\Baba Is You\\Baba Is You.exe','Baba Is You'))

Here's a complete python implementation.

0

There are a couple open source solutions that do this; UWPHook and Ice are some examples. Both are using CRC algorithms with a combination of target and app name. These don't seem to be the complete solution though.

For instance if you add a shortcut, remove it and then re-add it by following these steps, you will find that when adding images for the re-added shortcut that the images will have different names/id under ../steam/userdata/<userid>/config/grid for the <id>.

  1. Add a steam shortcut
  2. Provide the new shortcut with custom artwork
  3. Check ../steam/userdata/<userid>/config/grid for the <id>
  4. Remove the shortcut from Steam
  5. Repeat 1-3 and note the different <id>

You can also try adding a shortcut to steam, giving it custom artwork, then add programs with UWPHook and notice that when you restart steam, the shorcut you manually added no longer has any custom artwork even though it's target and app name are the same.

1
  • If you backup your shortcuts.vdf and follow those steps, is the new shortcuts.vdf different from the backed up one? I wonder if something is subtly changing the shortcut.
    – idbrii
    Jul 10, 2021 at 1:12
0

I needed to do the same for a little script that logs the start and end of a game for an internet cafe-like situation. I don't know why, but the code of idbrii gave me the wrong ID. I'm just leaving my code here for future seekers.

import os
import re
from zlib import crc32
import struct

def add_non_steam_games(config_path):
    with open(config_path, "r") as f:
        lines = f.readlines()

    for line in lines:
        if "paths=" in line:
            paths = line.split("=")[1].split(",")
            steam_path = paths[0].strip()  
            break
    if steam_path.endswith("/"):
        steam_path = steam_path[:-1]
    steam_path = os.path.dirname(steam_path)
    userdata_path = os.path.join(steam_path, "userdata")
    print(f"userdatapath: {userdata_path}")
    folders = [f for f in os.listdir(userdata_path) if os.path.isdir(os.path.join(userdata_path, f))]
    for folder in folders:
        print(folder)
        if folder.isdigit() and folder != "0":
            target_folder = folder
            break

    shortcuts_vdf = os.path.join(userdata_path, target_folder, "config", "shortcuts.vdf")
    
    if not os.path.exists(shortcuts_vdf):
        return []

    games_list = []

    with open(shortcuts_vdf, 'rb') as f:
        shortcut_bytes = f.read()

    game_pattern = re.compile(b"\x00\x02appid\x00(.{4})\x01AppName\x00([^\x08]+?)\x00\x01exe\x00([^\x08]+?)\x00", re.DOTALL)
    matches = game_pattern.findall(shortcut_bytes)

    if not matches:
        game_pattern = re.compile(b"\x00\x02appid\x00(.{4})\x01AppName\x00([^\x08]+?)\x00\x01exe\x00([^\x08]+?)\x00", re.DOTALL)
        matches = game_pattern.findall(shortcut_bytes)

    for match in matches:
        game_id_bytes = match[0]
        game_name = match[1].strip().decode()
        game_exe = match[2].strip().decode()
        print(game_name)

        game_id = struct.unpack("<I", game_id_bytes)[0]

        unique_name = game_exe.encode() + game_name.encode()
        legacy_id = crc32(unique_name) | 0x80000000
        print(game_id)
        games_list.append({
            "name": game_name,
            "id": game_id
        })

    return games_list

#config hast following line: [SteamApps]
#paths=/home/xxxx/.steam/steam/steamapps/, /mnt/HDD1/Spiele/steamapps/
#results = add_non_steam_games("path-to config")
#print(results)
1
  • Unfortunately, steam changed the way they compute ids for shortcuts in 2023 and I haven't figured out the new format. BoilR's figured it out, but I haven't looked at how they do it. Maybe they search for the right id like you are (nice solve!) or maybe there's a math solution.
    – idbrii
    Sep 18, 2023 at 23:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .