[2026-05-27] doc: non-Steam binary VDF shortcuts guide
This commit is contained in:
@@ -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)
|
||||
@@ -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._
|
||||
|
||||
Reference in New Issue
Block a user