Графическая программа для отката установленных игр Steam на старые сборки и защиты
от того, чтобы Steam обновил их обратно. Вся «магия» консоли download_depot и ручного
копирования файлов собрана в одно окно.
Arch / CachyOS: пакет в релизах
(sudo pacman -U steam-downgrader-*-any.pkg.tar.zst) или cd packaging/arch && makepkg -si.
Все зависимости берутся из официальных репозиториев; подробнее в packaging/arch.
Из исходников:
uv tool install -e ~/steam-downgrader # команда steam-downgrader в PATH
steam-downgrader # GUI
steam-downgrader list | versions <appid> | relock | unlock <appid> [--validate] | clear-shaders <appid> [--yes] | service install|remove
- Выберите игру слева. В «Версиях» — все сборки, о которых знает эта машина. При первом выборе игры открывается её страница манифестов на SteamDB и окно импорта: выделите таблицу (даты + ID), Ctrl+C, вставьте — версии запомнятся, и в следующий раз будут в списке сразу. Повторить/дополнить (в т.ч. для DLC-депо) — «Импорт версий со SteamDB». Можно и просто вписать ID манифеста в колонку «Целевой манифест».
- Скачать выбранную версию:
-
Встроенный загрузчик (по умолчанию): вход в Steam через окно программы, по QR-коду (приложение Steam → Steam Guard → QR) или по логину и паролю с кодом Steam Guard. Сохраняется только refresh-токен (
~/.local/share/steam-downgrader/auth.json, права 0600, живёт ~200 дней), пароль нигде не хранится. Загрузка идёт в фоне, в очереди, с настоящим прогрессом (скорость, оставшееся время). Качается только то, что отличается от установленной версии:- файлы, совпадающие с установленными (по SHA-1), не скачиваются и не копируются, откат их не трогает;
- изменённые файлы собираются из чанков: всё, что уже есть на диске (в любом файле), берётся локально, из сети докачиваются только новые чанки;
- после обрыва загрузка продолжается с того же места;
- каждый чанк проверяется по SHA-1.
Под капотом отдельный процесс (
steam_downgrader.native.worker) на форке ValvePython/steam: вход CM по токену, request code манифеста, CDN. Если токен истёк, очередь встаёт на паузу и открывается окно входа. -
Консоль Steam (запасной способ, без входа в аккаунт; переключается в настройках): окно выдаёт команды
download_depot …→ «Копировать» → вставить вsteam://open/console. Прогресс и завершение программа видит сама (по размеру папки иconsole_log.txt).
-
- Откатить и защитить: Steam закрывается, файлы старой сборки переносятся в папку игры, файлы, которых в старой сборке не было, удаляются, и включается защита.
«Защитить текущую версию» просто замораживает игру на установленной сборке.
Удалить загрузку (кнопка или правый клик по версии) удаляет скачанные, ещё не применённые файлы
выбранной версии: папку встроенного загрузчика или content/app_X/depot_Y от download_depot.
Установленная игра не затрагивается; идущая загрузка этой версии отменяется.
Кэш шейдеров. Кэши, собранные другой версией, устаревают и могут приводить к падениям,
поэтому при откате они по умолчанию очищаются (галочка «Очистить кэш шейдеров»).
Есть и отдельная кнопка, и команда steam-downgrader clear-shaders <appid> [--yes].
Удаляется только то, что точно является кэшем шейдеров:
vkd3d-proton.cache / *.dxvk-cache рядом с exe (если их не поставляет сама игра),
steamapps/shadercache/<appid>/ (fossilize, NVIDIA, Mesa, RADV, DXVK) и кэши драйвера в префиксе Proton
(NVIDIA/DXCache, AMD/DxCache, D3DSCache…). Сейвы, конфиги и видео-транскоды Steam
(transcoded_video.foz, fozmediav1) не трогаются. Всё пересоздаётся при запуске.
У Valve нет публичного API со старыми манифестами, поэтому используются только локальные источники:
| Источник | Что даёт |
|---|---|
appcache/appinfo.vdf |
текущие сборки всех веток (public, беты, иногда previous_version) |
logs/content_log.txt |
строки finished update … (BuildID …): сборки, которые ставил этот Steam |
depotcache/*.manifest |
каждый скачанный манифест с датой сборки; из них восстанавливаются старые версии («восстановлено») |
logs/console_log.txt |
завершённые download_depot |
| импорт со SteamDB | вставленная таблица манифестов депо (SteamDB не отдаёт её скриптам, поэтому через буфер обмена) |
~/.local/share/steam-downgrader/state.json |
снимки установленных сборок при каждом запуске программы и вручную введённые ID |
Программа ведёт собственную историю, поэтому со временем версий становится больше.
Steam знает только Build ID, а номер версии есть лишь в заголовках патчноутов:
- патчноуты SteamDB (окно импорта принимает и эту таблицу): Build ID, дата и заголовок — номер версии у такой сборки точный;
- новости Steam (
ISteamNews/GetNewsForApp, публичное API, кэш 12 ч): каждый патчноут привязывается к манифесту, который был самым свежим на момент выхода патча. Сборки делают за дни или недели до релиза, поэтому «ближайший патч по дате сборки» давал бы неверный номер. Такие номера помечены «≈»; «1.17+» означает сборку, вышедшую после патча 1.17 без своего патчноута. Если сборка точно была актуальной в какой-то момент (журнал установки, ветка), более поздние патчноуты ей не приписываются.
Версия внутри exe почти всегда бесполезна: внутренний номер, версия движка или 1.0.0.0.
Steam решает, что игра устарела, сравнивая appmanifest_<appid>.acf (buildid и манифест
каждого депо) с текущей сборкой ветки. После отката программа:
- записывает в
.acfактуальные buildid и манифесты изappinfo.vdf: Steam считает, что обновлять нечего; реальные (старые) манифесты хранятся вstate.json; - ставит
AutoUpdateBehavior = 1(«обновлять только при запуске»); - делает
.acfтолько для чтения; - опция сильная защита: вся папка игры только для чтения. Если Steam всё-таки начнёт обновление, оно упадёт с ошибкой записи и не перезапишет файлы. Игры, которые пишут в свою папку, могут на это ругаться.
При выходе новой сборки подмена отстаёт: relock подтягивает её к новому buildid.
Он запускается при каждом старте программы, а флажок Авто-relock в тулбаре ставит
systemd-юнит (steam-downgrader-relock.path), который запускает его каждый раз,
когда Steam обновляет appinfo.vdf.
- Steam читает
.acfпри старте. Если он узнал о новой сборке, пока был запущен, то до перезапуска может считать игру устаревшей и захотеть обновить её при запуске. После relock перезапустите Steam (программа напишет об этом в лог). - «Проверить целостность файлов» в Steam вернёт актуальную версию. Так же работает и «Снять защиту → вернуть актуальную версию».
- Для применения и защиты Steam должен быть закрыт; программа закроет его сама.
- Скачать можно только то, чем владеет аккаунт.
- Онлайн-игры и игры с античитом могут не пустить на старой версии.
uv sync
uv run python tests/test_e2e.py # apply/lock/relock/unlock на синтетическом Steam
uv run python tests/test_gui.py # то же через кнопки главного окна (offscreen)
uv run python tests/test_downloads.py # очередь встроенного загрузчика на фейковом воркере: ошибки, отмена, вход, пауза, дельта-откат
uv run python tests/test_assemble.py # дельта-сборка: без изменений / чанки с диска / докачка