Это протокол между помощником лаунчера и сервером NodeMP, сетевой протокол v23. Ресурсы его не
видят: они отправляют и получают события. С ним сталкиваются в фильтре ретрансляции нативного
модуля, которому пакеты описываются категорией и подтипом, и при чтении дампа трафика или
отладочного лога сервера. Здесь названы пакеты и их порядок; раскладка по полям — в
include/net/Protocol.h сервера, точная копия которого есть у помощника.
У сессии два канала между помощником и сервером: одно TCP-соединение внутри TLS 1.3 для всего
надёжного и, после привязки, один поток UDP для позиций машин.
- По UDP идут только
State::Pos, State::HeadPose, State::PosBatch и пакеты рукопожатия
Ping, Pong, UdpHello и UdpHelloAck. Датаграммы клиента подписываются ключом сессии,
который один раз приходит по TLS (UdpToken) и никогда не передаётся по UDP. Пока UDP не
привязан, позиции идут по TCP.
- Локальные каналы. Игра говорит с помощником по двум локальным TCP-портам:
4444 — канал
команд, 4445 — ретрансляция. Первый кадр на каждом — Command::Auth с токеном помощника;
с неверным токеном соединение закрывается.
- Сжатие. Сжатое тело помечено битом
FlagCompressed и несёт точный размер после распаковки,
поэтому получатель сверяет его с пределом пакета до распаковки. Клиентский мод сжатых тел не видит:
их распаковывает помощник.
- Лимиты. Кадр — до 4 КБ до
Welcome и до 1 МБ после; UDP-датаграмма — до 10 КБ.
Конфигурация машины — до 768 КБ, чтобы её повтор при входе помещался в кадр. Эталонный манифест
передаётся частями по IntegrityChunkBytes = 32 КиБ.
Каждый пакет назван двумя байтами: категорией и подтипом внутри неё. TCP-кадр — это
[u32 length][category][subtype][flags][body], UDP-датаграмма — то же без длины. Существует восемь
категорий:
| Категория |
Байт |
Что в ней |
Handshake |
0x01 |
Установка соединения, проверка связи, привязка UDP. |
Session |
0x02 |
Приход и уход игроков, сообщения для всей сессии. |
Content |
0x03 |
Список модов, загрузка файлов, клиентские скрипты ресурсов. |
Vehicle |
0x04 |
Машины: спавн, места, авторитет. |
State |
0x05 |
Частое состояние машин. |
Event |
0x06 |
Именованные события: API плагинов и клиентских скриптов. |
Command |
0x07 |
Канал команд между игрой и помощником. |
Module |
0x08 |
Двоичные данные между нативными модулями и клиентами. |
Подтип 0 недопустим во всех категориях, так что обнулённый буфер никогда не означает пакет. Числа
— little-endian, строки — UTF-8 без нулевого байта, и без префикса длины может быть только одно поле
переменной длины — последнее, оно занимает остаток кадра.
В таблицах L — помощник, S — сервер, G — клиентский мод в игре; T — TCP-канал
сервера, U — его UDP-канал, R — порт ретрансляции, CC — порт команд.
| Подтип |
Направление |
Назначение |
Hello |
L→S, T |
Версия протокола и имя, которое попросил игрок. При несовпадении версий — отказ с обеими версиями. |
Welcome |
S→L, T |
Id игрока, токен для восстановления сессии, окно восстановления и биты возможностей сервера. |
Ping |
L→S, T и U |
Проверка связи и времени отклика; необязательная метка возвращается в Pong. |
Pong |
S→L, T и U |
Ответ на Ping. |
UdpToken |
S→L, T |
64-байтный ключ сессии для подписи UDP. |
UdpHello |
L→S, U |
Привязывает UDP-адрес клиента нонсом и HMAC. Верный пакет привязывает с любого адреса — это нужно при раздельном туннелировании VPN. |
MapInfo |
S→L, T |
Карта, которую должна загрузить игра. |
JoinWorld |
L→S, T |
Карта загружена, присылай мир. |
VerifyRequest |
S→L, T |
Насколько строго проверять установку игры ([integrity] verify), а для strict — id эталонного манифеста. Во время сессии его отправляет player:verify. |
VerifyReport |
L→S, T |
Что лаунчер проверил и что нашёл: уровень, итог, число проблем и строка подробностей. |
Identity |
L→S, T |
Билет на вход, всегда сразу после Hello; пустой — билета нет. |
IntegrityManifestRequest |
L→S, T |
В кэше лаунчера нет эталонного манифеста с этим id, и он его просит. |
IntegrityManifestChunk |
S→L, T |
Очередная часть сжатого манифеста. |
IntegrityManifestDone |
S→L, T |
Все части отправлены; хеш, который лаунчер сверяет, прежде чем сохранить файл. |
Resume |
L→S, T |
Идёт после Hello вместо Identity, чтобы вернуть оборванную сессию в пределах окна. |
UdpHelloAck |
S→L, U |
Подтверждает принятый UdpHello на привязанный адрес. |
| Подтип |
Направление |
Назначение |
Kick |
S→G, T и R |
Соединение отклонено или завершено, причина — текстом. |
SelfInfo |
S→G, T и R |
Собственный id и итоговое имя вошедшего. |
PlayerJoined |
S→G, T и R |
Id и имя другого игрока. |
PlayerLeft |
S→G, T и R |
Игрок вышел. |
PlayerList |
S→G, T и R |
Состав: сколько игроков, максимум, все id и имена. |
SessionEnd |
L→G, R |
Помощник сообщает игре, что сессия закончилась, и почему. |
ClientId |
L→G, R |
Помощник сообщает игре её id игрока. |
Policy |
G→S, T и R |
Выбор игрока для его машин: кто может садиться, пользоваться триггерами, тянуть узлы. |
Reconnecting |
L→G, R |
Связь оборвалась, помощник восстанавливает сессию: номер попытки и следующая пауза. |
Resumed |
L→G, R |
Сервер вернул сессию. |
| Подтип |
Направление |
Назначение |
ModsRequest |
L→S, T |
Запросить список модов сервера. |
ModsInfo |
S→L, T |
Список модов в JSON. |
FileRequest |
L→S, T |
Запросить один файл мода по имени. |
FileBegin |
S→L, T |
Сейчас пойдёт файл: размер, затем сами байты. |
FileDeny |
S→L, T |
Файла не будет: неизвестен, защищён или не читается. |
SyncDone |
L→S, T |
У помощника есть все нужные файлы. |
ResourceChunk |
S→G, T и R |
Часть клиентских скриптов одного ресурса, в JSON. |
ResourceDone |
S→G, T и R |
Все ресурсы доставлены. |
| Подтип |
Направление |
Назначение |
SpawnReq |
G→S, T и R |
Игрок просит заспавнить машину или заменить ту, в которой сидит. |
Spawn |
S→G, T и R |
Машина есть: её id, кто заспавнил, повреждения и конфигурация. |
SpawnDeny |
S→one, T и R |
Отказ в спавне с причиной; игрок убирает свою локальную машину. |
Edit |
G↔S, T и R |
Новая конфигурация (детали, настройка). |
Delete |
G↔S, T и R |
Машина удалена. |
Reset |
G↔S, T и R |
Машину сбросили на позицию. |
Coupler |
G↔S, T и R |
Изменилось сцепление или дверь. |
Paint |
G↔S, T и R |
Новая покраска. |
Camera |
G↔S, T и R |
За какой машиной следит камера игрока. |
SeatClaim |
G→S, T и R |
Игрок просит место водителя, пассажира или выйти. |
SeatVerdict |
S→one, T и R |
Ответ на SeatClaim, с причиной, если отказал плагин. |
Driver |
S→G, T и R |
Кто теперь ведёт машину. |
SeatFree |
S→G, T и R |
Место водителя освободилось. |
Authority |
S→G, T и R |
Чья игра теперь считает машину. |
AuthRevoke |
S→one, T и R |
Эта игра больше не считает машину. |
PlayerVehicle |
S→G, T и R |
В какой машине игрок и в какой роли. |
Resync |
S→G, T и R |
Полное сохранённое состояние машины заново. |
ResyncReq |
G→S, T и R |
Клиент просит пересинхронизировать одну машину или все. |
Trigger |
G↔S, T и R |
Нажатие триггера или двери, повторяется всем. |
DamageStat |
G→S, T и R |
Счётчик повреждений от авторитета машины; остаётся на сервере. |
DamageBlob |
G→S, T и R |
Сами данные о повреждениях, изредка; остаются на сервере для входа и пересинхронизации. |
CouplerSet |
S→G, T и R |
Сервер открывает или закрывает сцепку или дверь. |
Tag |
S→G, T и R |
Тег, который плагин поставил машине; пустое значение удаляет его. |
Lock |
S→G, T и R |
Кто может садиться в машину. |
ConfigHash |
G↔S, T и R |
Контрольная сумма сохранённой конфигурации; при расхождении — тихая пересинхронизация. |
TriggerReq |
G→S, T и R |
Попросить авторитета машины выполнить триггер; плагины могут отказать. |
NodeGrab |
G→S, T и R |
Запрос экспериментального захвата узлов; отклоняется, если плагин не разрешил. |
NodeGrabSet |
S→one, T и R |
Разрешённый захват, переданный авторитету машины. |
Grab |
G↔S, T и R |
Поток синхронизированного захвата узлов, пересылается остальным клиентам. |
EditDeny |
S→one, T и R |
Отказ в правке или покраске с причиной от плагина, перед откатом. |
| Подтип |
Направление |
Назначение |
Pos |
G↔S, U с откатом на T |
Положение, поворот, скорости и органы управления машины, в фиксированном двоичном виде. |
Inputs |
G↔S, T |
Оси ввода сверх тех, что в Pos, при изменении. |
Electrics |
G↔S, T |
Фары, поворотники, приборы — изменения. |
Nodes |
G↔S, T |
Положения узлов: деформация. |
BreakGroups |
G↔S, T |
Отвалившиеся детали. |
Controller |
G↔S, T |
Один вызов контроллера. |
Powertrain |
G↔S, T |
Режимы трансмиссии. |
Engine |
G↔S, T |
Двигатель и зажигание. |
HeadPose |
G↔S, U с откатом на T |
Положение камеры или головы игрока; не сохраняется. |
PosBatch |
S→G, U |
Несколько снимков Pos в одной датаграмме. Отправляет только сервер. |
Fire |
G↔S, T |
Огонь на машине от её авторитета, сохраняется как Electrics. |
| Подтип |
Направление |
Назначение |
Event |
G↔S, T и R |
Именованное событие: имя и данные. node.emitServer на клиенте, player:send и node.broadcast на сервере. |
Пакеты Command не покидают компьютер игрока.
| Подтип |
Направление |
Назначение |
Keepalive |
G↔L, CC |
Возвращается как есть. |
VersionReq |
G→L, CC |
Узнать версию протокола помощника. |
Version |
L→G, CC |
Версия протокола. |
Connect |
G→L, CC |
Подключиться к серверу по адресу и порту. |
Quit |
G→L, CC |
Выйти из сессии. |
StatusReq |
G→L, CC |
Узнать состояние сессии. |
Status |
L→G, CC |
Состояние сессии со строкой подробностей. |
PingReq |
G→L, CC |
Узнать пинг до сервера. |
Ping |
L→G, CC |
Пинг в миллисекундах, -1, если неизвестен. |
MapReq |
G→L, CC |
Узнать карту сессии. |
Map |
L→G, CC |
Карта; до сессии — пусто. |
ModLoaded |
G→L, CC |
Игра загрузила файл мода. |
ModList |
L→G, CC |
Файлы модов, нужные сессии. |
ConnectFailed |
L→G, CC |
Вход не удался, с причиной. |
Prompt |
L→G, CC |
Вопрос игроку. |
PromptAnswer |
G→L, CC |
Ответ игрока: да или нет. |
Auth |
G→L, CC и R |
Локальный токен; первый кадр на обоих каналах. |
Server |
L→G, CC |
На каком сервере сессия, если вход начал лаунчер. |
ClockPing |
G→L, CC |
Часы игры; помощник отвечает сразу. |
ClockPong |
L→G, CC |
Метка игры и часы помощника, чтобы их согласовать. |
ArrivalStamp |
L→G, R |
Когда следующая пересланная датаграмма пришла к помощнику. |
ServerName |
L→G, CC |
Имя сервера, как в списке. |
GameCaps |
G→L, CC |
Что умеет принимать этот клиентский мод; отправляется один раз при подключении. |
| Подтип |
Направление |
Назначение |
Data |
G↔S, T и R |
Сырые байты на канале с номером, который выбирает модуль. На сервере — node.modules.on и node.modules.send. |
- Помощник открывает TLS и отправляет
Hello, затем Identity с билетом на вход (или Resume,
чтобы вернуть оборванную сессию).
- Сервер проверяет баны, свободные места и билет, выдаёт игроку id и спрашивает плагины:
playerConnectRequest, затем playerAuthenticated. Отказ — это Kick с причиной.
Welcome и VerifyRequest. Если строгому серверу нужен эталонный манифест, помощник скачивает
его, проверяет игру и отправляет VerifyReport. Проваленная проверка — Kick до любых загрузок.
- Моды:
ModsRequest, ModsInfo, затем FileRequest и FileBegin или FileDeny для каждого
недостающего файла, пока не придёт SyncDone.
UdpToken и MapInfo, сразу за ними — клиентские скрипты ресурсов (ResourceChunk …
ResourceDone). Сервер вызывает playerJoining, игра загружает карту.
- Помощник привязывает UDP через
UdpHello и отправляет JoinWorld.
- Сервер отвечает
SelfInfo вошедшему и PlayerJoined остальным, повторяет все машины с их
состоянием и вызывает playerJoined.
Версия протокола растёт с каждым изменением на проводе и передаётся в Hello и Command::Version.
Версии должны совпадать точно: лаунчер с другой версией получает отказ, в котором названы обе.
Добавления, которые старые участники могут спокойно пропустить, вроде битов возможностей в конце
Welcome, обходятся без новой версии.