| name | steam-linux |
| description | This skill should be used when working with Steam on Linux - managing non-Steam game shortcuts, configuring Proton/Wine compatibility, parsing VDF files, or finding Steam paths and prefixes. MUST BE USED when user mentions Steam launch options, modifying shortcuts.vdf, Proton environment variables (PROTON_*, WINE*), Battle.net/Blizzard games on Linux, or RTX 4000+ performance fixes. ALSO USE when troubleshooting Proton/Wine game issues (black screen, game won't launch, launcher issues, CEF/Chromium rendering problems). |
Steam Linux Management
Use this skill when modifying Steam shortcuts, configuring Proton, or working with Steam's configuration files on Linux.
Steam Paths
STEAM_ROOT = ~/.local/share/Steam
โโโ config/
โ โโโ config.vdf # Text VDF - Proton mappings, settings
โ โโโ libraryfolders.vdf # Text VDF - Steam library locations
โโโ steamapps/
โ โโโ common/ # Installed games and Proton versions
โ โโโ compatdata/ # Proton prefixes (Wine bottles)
โ โโโ libraryfolders.vdf # Library paths
โโโ userdata/{USER_ID}/
โโโ config/
โโโ shortcuts.vdf # Binary VDF - Non-Steam game shortcuts
Finding Steam User ID
from pathlib import Path
STEAM_ROOT = Path.home() / ".local/share/Steam"
userdata = STEAM_ROOT / "userdata"
user_id = next((d.name for d in userdata.iterdir() if d.is_dir() and d.name.isdigit()), None)
Finding Steam Library Folders
Parse libraryfolders.vdf (text VDF):
libs = [STEAM_ROOT]
with open(STEAM_ROOT / "steamapps/libraryfolders.vdf") as f:
for line in f:
if '"path"' in line:
path = line.split('"')[3]
libs.append(Path(path))
Binary VDF Format (shortcuts.vdf)
Non-Steam shortcuts use binary VDF format.
Type Bytes
0x00 - Nested object start
0x01 - String value
0x02 - Int32 value (little-endian)
0x08 - Object end
Structure
0x00 "shortcuts" 0x00
0x00 "0" 0x00 # First shortcut (index)
0x02 "appid" 0x00 [4 bytes LE int]
0x01 "AppName" 0x00 "Game Name" 0x00
0x01 "Exe" 0x00 "\"path/to/exe\"" 0x00
0x01 "StartDir" 0x00 "\"path/to/dir\"" 0x00
0x01 "LaunchOptions" 0x00 "options" 0x00
...
0x08 # End of shortcut
0x08 0x08 # End of shortcuts, end of root
Parsing Example
See references/vdf-parser.py for complete implementation.
Shortcut App ID Generation
Steam generates app IDs for non-Steam games using CRC32:
import zlib
def generate_app_id(exe_path, app_name):
key = f'"{exe_path}"{app_name}'
crc = zlib.crc32(key.encode('utf-8')) & 0xFFFFFFFF
return crc | 0x80000000
Shortcut Fields
Required fields for a shortcut entry:
{
'appid': int,
'AppName': str,
'Exe': str,
'StartDir': str,
'icon': str,
'ShortcutPath': str,
'LaunchOptions': str,
'IsHidden': int,
'AllowDesktopConfig': int,
'AllowOverlay': int,
'OpenVR': int,
'Devkit': int,
'DevkitGameID': str,
'DevkitOverrideAppID': int,
'LastPlayTime': int,
'FlatpakAppID': str,
'tags': {}
}
Text VDF Format (config.vdf)
Steam's main config uses text VDF format - key-value pairs with tabs.
Proton Compatibility Tool Mapping
Located in config.vdf under CompatToolMapping:
"CompatToolMapping"
{
"APP_ID"
{
"name" "proton_experimental"
"config" ""
"priority" "250"
}
}
Adding Proton Mapping
def add_compat_tool_mapping(app_id, tool="proton_experimental"):
config_path = STEAM_ROOT / "config/config.vdf"
with open(config_path, 'r') as f:
content = f.read()
compat_start = content.find('"CompatToolMapping"')
if compat_start != -1:
compat_section = content[compat_start:compat_start+5000]
if f'"{app_id}"' in compat_section:
return
pos = content.find('"CompatToolMapping"')
brace_pos = content.find('{', pos)
TAB = chr(9)
NL = chr(10)
entry = NL + TAB*5 + f'"{app_id}"' + NL
entry += TAB*5 + '{' + NL
entry += TAB*6 + '"name"' + TAB*2 + f'"{tool}"' + NL
entry += TAB*6 + '"config"' + TAB*2 + '""' + NL
entry += TAB*6 + '"priority"' + TAB*2 + '"250"' + NL
entry += TAB*5 + '}'
new_content = content[:brace_pos+1] + entry + content[brace_pos+1:]
with open(config_path, 'w') as f:
f.write(new_content)
Proton/Compatdata
Each game/shortcut has a Wine prefix in compatdata:
~/.local/share/Steam/steamapps/compatdata/{APP_ID}/
โโโ pfx/ # Wine prefix root
โ โโโ drive_c/ # C: drive
โ โโโ Program Files/
โ โโโ Program Files (x86)/
โ โโโ users/steamuser/
โโโ version # Proton version used
โโโ config_info # Configuration metadata
Finding a Game's Prefix
def find_game_prefix(game_name_pattern):
"""Find compatdata prefix containing a specific game/app."""
for lib in get_steam_libraries():
compatdata = lib / "steamapps/compatdata"
if not compatdata.exists():
continue
for prefix in compatdata.iterdir():
for check_path in [
prefix / "pfx/drive_c/Program Files" / game_name_pattern,
prefix / "pfx/drive_c/Program Files (x86)" / game_name_pattern,
]:
if check_path.exists():
return prefix
return None
Sharing Prefixes Between Shortcuts
To make a shortcut use an existing prefix (e.g., for Battle.net games):
LaunchOptions: STEAM_COMPAT_DATA_PATH="/path/to/compatdata/APP_ID" %command% [args]
Proton Versions
Common Proton tool IDs:
proton_experimental - Proton Experimental
proton_10 - Proton 10.0
proton_9 - Proton 9.0
Proton locations:
{LIBRARY}/steamapps/common/Proton - Experimental/proton
{LIBRARY}/steamapps/common/Proton 10.0/proton
Launching Games
Via Steam Protocol
steam steam://rungameid/{APP_ID}
xdg-open steam://rungameid/{APP_ID}
Via Command Line
steam -applaunch {APP_ID}
Note: These require Steam to be running and may not work reliably for non-Steam shortcuts.
Modifying LaunchOptions in shortcuts.vdf
To modify launch options for a non-Steam shortcut programmatically:
import shutil
from pathlib import Path
vdf_path = Path.home() / ".local/share/Steam/userdata/{USER_ID}/config/shortcuts.vdf"
shutil.copy2(vdf_path, str(vdf_path) + '.bak')
with open(vdf_path, 'rb') as f:
data = f.read()
launch_options_marker = b'\x01LaunchOptions\x00'
pos = data.find(launch_options_marker)
if pos != -1:
value_start = pos + len(launch_options_marker)
value_end = data.find(b'\x00', value_start)
new_options = b'PROTON_NVIDIA_LIBS_NO_32BIT=1 %command%'
new_data = data[:value_start] + new_options + data[value_end:]
with open(vdf_path, 'wb') as f:
f.write(new_data)
Common Launch Options
RTX 4000+ performance fix:
PROTON_NVIDIA_LIBS_NO_32BIT=1 %command%
Disable FSYNC (fallback to ESYNC):
PROTON_NO_FSYNC=1 %command%
Force specific Proton prefix:
STEAM_COMPAT_DATA_PATH="/path/to/compatdata/APP_ID" %command%
Troubleshooting
Steam/Proton Won't Launch - "User namespaces" or Mount Errors
Symptoms: Steam fails to start with errors like:
bwrap: Can't bind mount /oldroot/ on /newroot/: Unable to apply mount flags
Steam now requires user namespaces to be enabled
- Proton games silently fail to launch
Cause: Steam's pressure-vessel container (bubblewrap) tries to bind-mount ALL existing mount points. If any mount is stale or unavailable (e.g., network share with x-systemd.automount when the server is offline), the entire container fails.
Diagnosis:
mount | grep -E "autofs|cifs|nfs"
ls -la /mnt/
Fix:
grep -E "automount|cifs|nfs" /etc/fstab
sudo sed -i 's|^//server/share|#//server/share|' /etc/fstab
sudo systemctl daemon-reload
sudo systemctl stop mnt-sharename.automount
Prevention: For network shares that may be unavailable, consider:
- Using
x-systemd.automount with x-systemd.mount-timeout=5 to fail faster
- Mounting on-demand via a script instead of fstab automount
- Using
nofail option (though this doesn't help with automount)
Important Notes
- Close Steam before modifying config files - Steam may overwrite changes
- Restart Steam after changes - New shortcuts won't appear until restart
- App IDs for shortcuts - Must use the CRC32-based generation algorithm
- Quote paths in Exe/StartDir - Always wrap paths in double quotes
- Template escaping - When using in chezmoi templates, avoid
\n, \t, use chr() instead
- Binary VDF backups - Always backup shortcuts.vdf before modifying