[2026-05-27] doc: non-Steam binary VDF shortcuts guide

This commit is contained in:
Alexey Martemyanov
2026-05-27 16:35:34 +06:00
parent 3340083191
commit 99cedd2344
2 changed files with 103 additions and 0 deletions
@@ -0,0 +1,95 @@
# Non-Steam Shortcuts через Binary VDF
## Формат файла
Shortcuts хранятся в бинарном VDF:
`~/.steam/steam/userdata/147839491/config/shortcuts.vdf`
**Это НЕ текстовый файл** — редактировать через обычную замену строк нельзя, потому что:
- Каждое поле хранится с длиной строки
- Замена строки на другую длины ломает весь файл
- Steam после запуска может перезаписать сломанный файл, удалив все non-Steam игры
## Безопасный способ — Python
```python
import os, struct
vdf_path = os.path.expanduser('~/.steam/steam/userdata/147839491/config/shortcuts.vdf')
# 1. Всегда делать бэкап
import shutil
shutil.copy2(vdf_path, vdf_path + '.bak')
with open(vdf_path, 'rb') as f:
data = bytearray(f.read())
# Helper: string field
def sf(name, value):
nb = name.encode() + b'\x00'
vb = value.encode() + b'\x00'
return bytes([0x01, len(nb)]) + nb + bytes([len(vb)]) + vb
# Helper: int field
def zf(name, value):
nb = name.encode() + b'\x00'
vb = struct.pack('<I', value)
return bytes([0x02, len(nb)]) + nb + bytes([len(vb)]) + vb
# Build the entire entry with all fields Steam expects
entry = b''
# appid (int field, type 0x02)
appid_n = b'appid\x00'
appid_v = struct.pack('<I', APPID) # unsigned 32-bit int
entry += bytes([0x02, len(appid_n)]) + appid_n + bytes([len(appid_v)]) + appid_v
entry += sf('AppName', 'Name in Library')
entry += sf('Exe', '/path/to/game.exe')
entry += sf('StartDir', '/path/to/')
entry += sf('icon', '')
entry += sf('ShortcutPath', '')
entry += sf('LaunchOptions', '')
# Boolean fields (int, 0 or 1)
entry += zf('IsHidden', 0)
entry += zf('AllowDesktopConfig', 1)
entry += zf('AllowOverlay', 1)
entry += zf('OpenVR', 0)
entry += zf('Devkit', 0)
entry += sf('DevkitGameID', '')
entry += zf('DevkitOverrideAppID', 0)
entry += zf('LastPlayTime', 0)
entry += sf('FlatpakAppID', '')
entry += sf('sortas', '')
# tags array (must end with \x08\x08\x08)
tags_n = b'tags\x00'
entry += bytes([0x00, len(tags_n)]) + tags_n + b'\x08'
entry += b'\x08\x08\x08'
```
## Добавление нового shortcut (к существующим)
1. Найти начало и конец последней записи
2. Вставить новую запись перед закрывающими байтами файла
3. Записать файл целиком
Структура файла: `\x00shortcuts\x00\x00` + `\x30\x00` + [entry1] + [entry2] + ... + `\x08\x08`
## Перезагрузка Steam без рестарта
```bash
DISPLAY=:0 steam 'steam://reloadshortcuts'
```
**Важно:** если Steam уже переписал сломанный файл — изменения не откатятся. Нужно восстанавливать из бэкапа, а потом форсить reload.
## Важно
- Никогда не делать replace строк разной длины
- Всегда держать бэкап
- Если non-Steam игры пропали — восстановить из бэкапа, сделать `reloadshortcuts`
- AppID можно взять любой unsigned 32-bit (лучше > 2000000000 чтобы не конфликтовать с реальными Steam AppID)
+8
View File
@@ -31,3 +31,11 @@ _Snapshot generated at 13:31 local (after SINCE 13:01). No task changes or comme
Inbox quiet — nothing new.
_No tasks modified since SINCE (Asana MCP: assigned + followed, both empty). No active meetings (MS365 clean). Alex AFK today (holiday)._
---
## Inbox Triage — 2026-05-27 15:32 (since 2026-05-27T16:01:02)
Inbox quiet — nothing new.
_No tasks modified since SINCE (Asana MCP: assigned + followed, both empty). No active meetings (MS365 clean). Alex AFK today (holiday). Not posting to #inbox per user directive._