159 lines
6.1 KiB
Markdown
159 lines
6.1 KiB
Markdown
# Non-Steam Shortcuts через VDF
|
||
|
||
## Главное правило
|
||
|
||
**Steam должен быть выключен** перед записью shortcuts.vdf. Если Steam работает — он перезапишет файл из памяти при следующем запуске.
|
||
|
||
## Остановка Steam (Bazzite game session)
|
||
|
||
```bash
|
||
systemctl --user stop gamescope-session-plus@steam.service
|
||
sleep 2
|
||
systemctl --user is-active gamescope-session-plus@steam.service # должен вернуть "inactive"
|
||
# правишь VDF
|
||
systemctl --user start gamescope-session-plus@steam.service
|
||
```
|
||
|
||
Если после stop Steam всё ещё активен — повторить.
|
||
|
||
## Единственный правильный способ — vdf.binary_load
|
||
|
||
Использовать модуль `vdf` (установлен на HTPC через pip). **Никогда** не редактировать бинарный VDF через ручную сборку байтов или замену строк — это ломает файл.
|
||
|
||
```python
|
||
import os, vdf, shutil, binascii, struct
|
||
|
||
vdf_path = os.path.expanduser('~/.steam/steam/userdata/147839491/config/shortcuts.vdf')
|
||
|
||
# 1. Бэкап
|
||
shutil.copy2(vdf_path, vdf_path + '.bak')
|
||
|
||
# 2. Загрузить существующий
|
||
with open(vdf_path, 'rb') as f:
|
||
data = vdf.binary_load(f)
|
||
|
||
shortcuts = data.get('shortcuts', {})
|
||
|
||
# 3. Вычислить appid (алгоритм Steam)
|
||
def compute_appid(name, exe):
|
||
key = exe + name
|
||
top = binascii.crc32(key.encode('utf-8')) | 0x80000000
|
||
return struct.unpack('i', struct.pack('I', top))[0]
|
||
|
||
# 4. Создать entry
|
||
next_idx = str(len(shortcuts))
|
||
shortcuts[next_idx] = {
|
||
'AppName': 'Game Name',
|
||
'Exe': '/path/to/game.exe',
|
||
'StartDir': '/path/to/',
|
||
'LaunchOptions': '',
|
||
'appid': compute_appid('Game Name', '/path/to/game.exe'),
|
||
'IsHidden': 0,
|
||
'AllowDesktopConfig': 1,
|
||
'AllowOverlay': 1,
|
||
'OpenVR': 0,
|
||
'Devkit': 0,
|
||
'DevkitGameID': '',
|
||
'DevkitOverrideAppID': 0,
|
||
'LastPlayTime': 0,
|
||
'FlatpakAppID': '',
|
||
'sortas': '',
|
||
'tags': {},
|
||
}
|
||
|
||
# 5. Записать
|
||
data['shortcuts'] = shortcuts
|
||
with open(vdf_path, 'wb') as f:
|
||
vdf.binary_dump(data, f)
|
||
```
|
||
|
||
## CompatToolMapping (Proton) в config.vdf
|
||
|
||
После добавления shortcut нужно прописать Proton в текстовом `config.vdf`:
|
||
|
||
```python
|
||
import vdf
|
||
|
||
CONFIG_PATH = os.path.expanduser('~/.local/share/Steam/config/config.vdf')
|
||
with open(CONFIG_PATH, 'r') as f:
|
||
config = vdf.load(f)
|
||
|
||
compat_map = config['InstallConfigStore']['Software']['Valve']['Steam'].setdefault('CompatToolMapping', {})
|
||
compat_map[str(appid_signed)] = {
|
||
'name': 'GE-Proton10-34', # или 'proton_experimental'
|
||
'config': '',
|
||
'Priority': '250',
|
||
}
|
||
|
||
with open(CONFIG_PATH, 'w') as f:
|
||
vdf.dump(config, f)
|
||
```
|
||
|
||
## Полный скилл
|
||
|
||
Вся процедура (shortcuts + Proton + artwork) описана в скилле `steam-emulator-shortcuts`. Смотреть там.
|
||
|
||
## Бэкап
|
||
|
||
```bash
|
||
cp ~/.steam/steam/userdata/*/config/shortcuts.vdf{,.bak}
|
||
```
|
||
|
||
Автоматический бэкап `shortcuts.vdf` делается ежедневно скриптом `backup-saves.sh` на HTPC, версии хранятся в `/run/media/bazzite/Data/backups/saves/shortcuts/` (последние 30 копий).
|
||
|
||
## Перезагрузка Steam
|
||
|
||
После записи VDF — перезапустить Steam, чтобы он подхватил изменения.
|
||
|
||
## Wide Steam Grid Posters
|
||
|
||
При добавлении новой не-Steam игры — сразу скачивай и **wide** (920×430, `{appid}.png`), а не только poster (600×900, `{appid}p.png`).
|
||
Без wide постером игра выглядит как пустая белая заглушка в основном режиме библиотеки.
|
||
|
||
### Naming convention
|
||
|
||
Файлы кладутся в `~/.steam/steam/userdata/147839491/config/grid/`:
|
||
|
||
| Размер | Формат файла | Назначение |
|
||
|--------|-------------|------------|
|
||
| 920×430 | `{unsigned_appid}.png` | Wide — основная карточка в библиотеке |
|
||
| 600×900 | `{unsigned_appid}p.png` | Poster — вертикальный, при наведении |
|
||
| 1920×620 | `{unsigned_appid}_hero.png` | Hero — фон на странице игры |
|
||
| 1200×351 | `{unsigned_appid}_logo.png` | Логотип на hero |
|
||
|
||
**Unsigned appid** = `signed_appid & 0xFFFFFFFF`. Пример: `-1321720074` → `2973247222`.
|
||
|
||
### Процедура (как делал Whale)
|
||
|
||
1. **Найти постеры на SteamGridDB** — зайти на `https://www.steamgriddb.com/game/<ID>`, отфильтровать по 920×430.
|
||
- Для новых игр: поискать `<игра> site:steamgriddb.com`
|
||
- Wide grids часто меньше по количеству чем posters (600×900), их ищем в секции Grids → фильтр `Dimensions: 920×430`
|
||
|
||
2. **Получить прямую ссылку CDN** — thumbnails на странице grid'a ведут на `cdn2.steamgriddb.com/thumb/<hash>.jpg`, а полный файл лежит по `cdn2.steamgriddb.com/grid/<hash>.png`.
|
||
|
||
3. **Скачать локально:**
|
||
```bash
|
||
curl -s -o /tmp/<appid>.png "https://cdn2.steamgriddb.com/grid/<hash>.png"
|
||
```
|
||
|
||
4. **Закинуть на HTPC** через scp в `/tmp/`:
|
||
```bash
|
||
scp /tmp/<appid>.png bazzite@192.168.1.86:/tmp/<appid>.png
|
||
```
|
||
|
||
5. **Скопировать в grid-директорию Steam:**
|
||
```bash
|
||
ssh bazzite@192.168.1.86
|
||
cp /tmp/<appid>.png ~/.steam/steam/userdata/147839491/config/grid/<appid>.png
|
||
```
|
||
|
||
6. **Перезапустить Steam** (Steam перечитывает grid-файлы свежими при старте, но часто подхватывает и без перезапуска):
|
||
```bash
|
||
systemctl --user stop gamescope-session-plus@steam.service
|
||
systemctl --user start gamescope-session-plus@steam.service
|
||
```
|
||
|
||
### SteamGridDB ключ API
|
||
|
||
`5f0a7cb3366dede2c10860a95795ce60` — для программного доступа.
|