Строгая проверка
[General] VerifyGame = "strict" — четвёртый уровень проверки установленной игры, появившийся с
сервером 1.1.0, лаунчером 1.1.0 и протоколом v18. Три нижних уровня сравнивают установку BeamNG
игрока со списком файлов самой игры, integrity.json — списком, который игрок может
отредактировать вместе с файлами, которые в нём названы. strict сравнивает с эталонным
манифестом: описанием чистой установки, которое вы генерируете один раз на версию игры и
кладёте на сервер. Лаунчер забирает его, кэширует и сверяет с ним всю установку и
пользовательскую папку игры перед каждым подключением и снова во время сессии.
Эта страница говорит, что проверка покрывает и что нет, как сгенерировать и разместить манифест и что видят игроки и авторы плагинов. Сам ключ описан в Конфигурации.
Что проверяет strict
Заголовок раздела «Что проверяет strict»Проверка выполняется в лаунчере игрока, по эталонному манифесту, который назвал сервер. Игрок
проходит, когда сходится всё перечисленное ниже; число несовпадений — это problems в отказе.
- Каждый файл, который перечисляет эталон — его длина.
lua/иui/(код и интерфейс игры), всё подBin64/иBinLinux/и файлы*.exeв корне игры дополнительно хешируются SHA-256 и сверяются с дайджестами эталона, так что правка с сохранённой длиной там ловится. Все остальные свободные файлы (art/,campaigns/,gameplay/, …) сравниваются только по размеру. Находки:missing,size,hash. - Каждый архив (
*.zip) — его оглавление. Машины и уровни лежат внутри многогигабайтных zip-архивов, которые нельзя хешировать перед подключением, но каждый zip заканчивается списком своих записей с несжатыми размерами и CRC-32, и эталон хранит такой список для каждого архива чистой установки. Запись за записью:missing,extra,duplicate,size,crc; zip, чьё оглавление вообще не читается, —unreadable. Находка называет запись как/content/vehicles/pickup.zip!vehicles/pickup/pickup.jbeam. - Вся папка игры — любой файл, которого нет в эталоне, —
unlisted, где бы он ни лежал, если он не подходит под один из изменчивых шаблонов манифеста (integrity.json,*.log,temp/,startup.ini,Bin64/*.pdb,shadercache/,desktop.ini,Thumbs.db)..dllподBin64/и.soподBinLinux/шаблон не извиняет никогда. Если сам лаунчер установлен внутри папки игры, его собственные файлы (исполняемый файл,Launcher.cfg,logs/,cache/) исключаются, и отчёт говорит об этом (excluded=). - Пользовательская папка игры (
current/) — папка, в которой игра монтирует свободные файлы поверх упакованного контента, так чтоvehicles/pickup/pickup.jbeamтам подменяет упакованный. Разрешено только то, что игрок законно там хранит: деревьяsettings/,screenshots/,replays/,cache/,temp/,saves/,mods/(включаяmods/multiplayer/самого клиентского мода),art/nodemp/,trackEditor/,flowgraphEditor/,roadArchitect/;*.logгде угодно; сохранённые конфигурации машин с превью,vehicles/<model>/<name>.pc,.png,.jpg, ровно на этой глубине; заглушкиmain.materials.json, которые движок пишет сам на любой глубине подvehicles/иart/;desktop.iniиThumbs.db. Любой другой файл подvehicles/,levels/,lua/,ui/,art/,scripts/,gameplay/или под папкой верхнего уровня, которая есть и в папке игры (campaigns/, …), — этоoverlay, названный какuserfolder:vehicles/pickup/pickup.jbeam. Файлы в других папках верхнего уровня (multiplayer/, …) и свободные файлы прямо вcurrent/ничего не перекрывают и не учитываются. Папка в одном из этих деревьев, которую лаунчер не смог прочитать, — тоже находка (unreadable), а несуществующая пользовательская папка проваливает проверку с указанием пути.
Судится та пользовательская папка, которую использует игра: --user-path, если лаунчеру его
передали, иначе startup.ini рядом с игрой ([filesystem] UserPath; относительное значение —
относительно папки с ini), иначе BeamNG.Drive.ini в %LOCALAPPDATA%\BeamNG (userFolder),
иначе %LOCALAPPDATA%\BeamNG\BeamNG.drive — всегда с добавленным current. Лаунчер пишет её в лог
при каждом подключении: user folder judged by the strict check: ….
Отчёт, который отправляет лаунчер, называет манифест, с которым сверял (manifest=<id>;), что
исключил как своё (excluded=1:NodeMP/cache/;), сколько папок не смог прочитать (skipped=1;) и до
трёх примеров вида path (reason). Сервер сравнивает id с манифестом, который отдал сам: отчёт
по другому манифесту — устаревший кэш, файл другого сервера — считается несовпадением независимо
от его результата.
Измерено на BeamNG 0.39.4.0 с установкой, уже лежащей в файловом кэше: вся проверка занимает около секунды — 14 193 файла в папке игры, 173 оглавления архивов, 7 203 захешированных файла (около 1,9 ГБ) и обход пользовательской папки. Первая проверка после перезагрузки читает всё это с диска и занимает десятки секунд.
Что strict не проверяет
Заголовок раздела «Что strict не проверяет»Проверка — это проверка честного клиента, а не граница безопасности. Где она останавливается:
- Содержимое архивов. Архив описан только оглавлением — имя записи, несжатый размер, CRC-32 — а CRC-32 подделывается: запись можно отредактировать и четырьмя байтами вернуть её CRC. Хешировать содержимое каждого архива значило бы читать около 50 ГБ при каждом подключении, а такую проверку никто не запускал бы. Поэтому strict держит установку, изменённую так, как установки меняются обычно — моды, перекрытия в пользовательской папке, посторонние или отредактированные свободные файлы, перепакованный zip, — но не игрока, который намеренно правит запись игрового архива и подгоняет её CRC. Это принятый остаточный риск; вторым слоем остаются серверные правила вашего ресурса.
- Сам лаунчер. Вся проверка выполняется на машине игрока, в лаунчере, который игрок мог бы пропатчить. Сервер судит отчёты; наблюдать установку он не может.
- Свободные файлы вне кода и бинарников сравниваются только по размеру (их SHA-256 есть в манифесте, но хешировать ещё 2 ГБ перед каждым подключением не по средствам).
- Перепроверки во время сессии — по событиям наблюдателя папок и по расписанию — не хешируют
бинарники: библиотека, уже загруженная работающей игрой, не может изменить её поведение до
следующего запуска, а следующий запуск снова проходит полную проверку на входе. Запрос от
ресурса (
player:verify("strict")) хеширует всё. - Эталон настолько хорош, насколько хороша установка, с которой он снят. Манифест, сгенерированный с изменённой установки, выдаёт это изменение за чистое. Генерируйте на чистой установке.
- Список разрешённого в пользовательской папке — это список: файл, который движок пишет
куда-то, чего в нём нет, будет отмечен как
overlay. Диагностика на странице Устранение неполадок — способ найти такой случай.
Генерация манифеста
Заголовок раздела «Генерация манифеста»Генератор встроен в бинарник сервера. Запускайте его на Windows-машине с чистой установкой
BeamNG.drive той версии, на которой играют ваши игроки (сначала проверьте файлы в Steam и
убедитесь, что в папку игры ничего не установлено), Windows-сборкой Node-Server из релизного
архива. Игра должна быть установлена на той машине, где работает генератор, — серверу на Linux
или в Docker читать нечего, поэтому хост на Linux генерирует файл на ПК с Windows (игра ваших
игроков в любом случае программа для Windows) и копирует единственный файл на сервер; манифест
переносим и не зависит от того, где сделан:
.\Node-Server.exe --gen-integrity "C:\Program Files (x86)\Steam\steamapps\common\BeamNG.drive"Аргумент — корневая папка игры, та, в которой лежит integrity.json. Инструмент читает из этого
файла только версию игры и номер сборки; его список он не копирует, потому что собственный список
игры пропускает часть поставляемых файлов, а любое пропущенное сделало бы чистую установку
непроходящей strict. Вместо этого он обходит всю установку, хеширует каждый свободный файл
SHA-256, читает оглавление каждого архива, отбрасывает изменчивые пути и пишет один сжатый zstd
JSON-файл:
reading the game install at C:\Program Files (x86)\Steam\steamapps\common\BeamNG.drive (every file is hashed; a few GB take about a minute) ...integrity manifest written game 0.39.4.0 (build 20972) root files 14193 (whole install, 2656.7 MB hashed) archives 173 (59594 entries) size 6.4 MB json, 1.2 MB compressed hash e326499d35bdef6f1d937a6648113b34225d37c5d0e07b4fc22cdee5db1273a6 file integrity\0.39.4.0.manifestPut the file in the server's [General] IntegrityDir (default "integrity") and set VerifyGame = "strict".Числа — для 0.39.4.0; прогон занял около 12 секунд на настольном компьютере с NVMe-диском.
Строка hash — это id манифеста: SHA-256 байтов файла, 64 шестнадцатеричные цифры в нижнем
регистре — тот дайджест, который для этого файла печатает Get-FileHash -Algorithm SHA256, в
нижнем регистре. Всё,
что идёт по сети, называет манифест этим id, и лаунчер кэширует его под этим именем. Два прогона
по одной и той же установке дают побайтно одинаковые файлы с одним id, потому что метка
generated внутри берётся из времени изменения integrity.json, а не из часов.
Опции: --out <file> пишет не в integrity/<game-version>.manifest под рабочим каталогом, а в
другое место; --game-version <x.y.z.w> переопределяет версию, прочитанную из integrity.json.
Обе принимают и написание --flag=value. Инструмент отказывается что-либо писать, когда в папке
нет читаемого integrity.json (no readable integrity.json in … (is this the game's root folder?)), когда файл не читается, когда архив повреждён или когда вывод нельзя записать:
integrity manifest NOT written: …, код выхода 1.
Файл имеет формат 2: ключ format внутри равен 2, каждый id и дайджест — SHA-256. Ранний
черновик использовал XXH64 (формат 1); такой файл сервер отклоняет при запуске с
integrity manifest integrity/0.39.4.0.manifest skipped: format 1 is not supported (this server reads format 2, SHA-256 ids and digests); regenerate with --gen-integrity.
Размещение манифеста
Заголовок раздела «Размещение манифеста»Скопируйте файл в папку integrity/ рабочего каталога сервера — папки, из которой вы запускаете
Node-Server, /opt/nodemp/integrity/ или C:\NodeMP\integrity\ в раскладке
быстрого старта, — или в папку, которую называет
[General] IntegrityDir. Под Docker рабочий каталог — /data, так что файл идёт в
/data/integrity/ на томе данных хоста. Затем поставьте VerifyGame = "strict"
(NODE_VERIFY_GAME=strict) и перезапустите: и настройка, и манифесты читаются при запуске.
Держите в папке ровно один .manifest. Рукопожатие не несёт версию игры игрока, так что
сервер не может выбирать между манифестами; с двумя файлами каждое подключение отклоняется с
This server has integrity manifests for 2 game versions (0.39.4.0, 0.39.3.0) and cannot tell which one you run. Ask the host to keep exactly one manifest in the server's integrity folder.,
а без файлов — с
This server requires a strict check of your BeamNG install but has no integrity manifest to check it against. Ask the host to run `Node-Server --gen-integrity <gamedir>` and put the file in the server's integrity folder.
Оба случая — ещё и ошибки в логе запуска. Здоровый запуск пишет по строке на манифест,
integrity manifest for game 0.39.4.0 (build 20972): 173 archives, 59594 entries, 14193 root files, 1.2 MB (38 chunks), hash e326499d…strict game verification ON: reference manifest for game 0.39.4.0 (e326499d…)Сервер на другом уровне всё равно загружает папку (1 integrity manifest loaded from "integrity" (available to Player:verify("strict"))): ресурс тогда может провести строгий аудит по запросу,
никому не отказывая на входе.
После обновления игры
Заголовок раздела «После обновления игры»Манифест описывает одну версию игры. Когда BeamNG обновляется, обновлённая установка игрока
расходится со старым эталоном в бинарниках, коде и архивах и отклоняется с
Game files do not match this server's reference (N problems). …. Сгенерируйте новый манифест с
обновлённой чистой установки, замените старый файл (в папке — один файл), перезапустите. Лаунчеры
замечают новый id, забирают новый манифест при следующем подключении и кэшируют его. Игрок,
который игру ещё не обновил, отклоняется затем так же, с примерами вида
/Bin64/BeamNG.drive.x64.exe (hash); обновление игры в Steam это исправляет.
Что видят игроки
Заголовок раздела «Что видят игроки»На строгом сервере у подключения в лаунчере два лишних шага: Downloading the server's integrity manifest — один раз на манифест, около 1,2 МБ кусками по 32 КиБ, затем кэш в
%LOCALAPPDATA%\com.nodemp.launcher\helper\cache\integrity\<id>.manifest — и
Checking game files. Чистая установка затем подключается как на любом другом сервере; её лог
говорит game files verified: 14193 files checked in 0.9s (strict), 7203 hashed. Несовпадение
сервер отклоняет с
Game files do not match this server's reference (3 problems). userfolder:vehicles/pickup/pickup.jbeam (overlay), …;
лаунчер 1.1.0 показывает это своими словами, как
Could not join · Your game files do not match this server's reference (3 problems) · vehicles/pickup/pickup.jbeam,
с объяснением первой проблемы в панели под карточкой сервера. Если проверка провалилась позже по
ходу сессии, сервер завершает её с
Game files changed while you were playing and no longer match this server's reference (1 problem). …,
что показывается как
Session ended · Your game files changed while you were playing and no longer match this server's reference (1 problem)
в игре.
Во время сессии лаунчер проверяет снова: в Windows — когда меняются файлы под content/,
scripts/, lua/, ui/ установки или под vehicles/, levels/, lua/, ui/, art/
пользовательской папки (не чаще раза в минуту), и по расписанию каждые StrictRecheckMin минут
из его Launcher.cfg (по умолчанию 10; см.
Настройки). Перепроверка, которая
ничего не нашла, не отправляется; та, которая нашла изменение, отправляется, и сервер завершает
сессию текстом выше. Тексты отказов и диагностика, которую может запустить отклонённый игрок, — на
странице Устранение неполадок и в
Кодах ошибок.
Авторам плагинов
Заголовок раздела «Авторам плагинов»К strict относятся три добавления в серверном API (ABI 1.12):
player:verify(level)просит лаунчер игрока выполнить проверку снова, сейчас, на уровне"size","scripts","full"или"strict". Возвращаетtrue, когда запрос ушёл, илиnilи"unknown player","unsupported"(уровень недоступен —"strict"на сервере без ровно одного загруженного манифеста) или"pending"(один запрос на игрока за 60 с; проверка, которую выполняет каждое подключение, не считается, так что вызов изplayerJoinedуходит сразу). Строгий запрос от ресурса хеширует бинарники, в отличие от собственных перепроверок лаунчера.playerVerifyReportedсрабатывает на каждый отчёт — на входе и на каждый в сессии — после вердикта сервера, с{ level, outcome, problems, detail, manifest }:outcome— что решил сервер ("clean","differs","failed"),manifest— id, который назвал лаунчер (""ниже strict). Отчётdiffersилиfailedуже кикнул игрока, еслиVerifyGameнеoff; на сервере сoffсобытие — единственное следствие, и так строится аудит без отказов.player:setStrict(on)помечает сессию строгой на стороне сервера, что меняет одно правило ретрансляции: кадрVehicle::Cameraот этого игрока, нацеленный на машину, которой он не управляет, в которой не сидит и которая не его пешеходный аватар, не ретранслируется, так что строгий клиент не может следить камерой за чужими машинами. Отклонённый отчёт всё равно вызываетplayerCameraChanged, аplayer:camera()тогда несётdenied = true. Клиенту это ничего не сообщает: клиентские правила включаются таблицейstrictсобытияsession:config, описанной на странице Клиентские скрипты.
У нативных модулей те же три — player_verify, player_set_strict и уведомление
playerVerifyReported — в
C ABI.
- Конфигурация —
VerifyGame,IntegrityDir, текст--help. - Устранение неполадок — что делать отклонённому игроку.
- Сетевой протокол — кадры v18, которые несут манифест и отчёт.
- Клиентские скрипты — клиентская половина строгой сессии.
