Перейти к основному содержимому

Фаза и направление: адрес пакета

Номер пакета сам по себе ничего не значит: один и тот же номер в разных фазах и в разных направлениях - это разные пакеты. Тип пакета ищется не по номеру, а по тройке фаза-направление-номер, да ещё и с оглядкой на версию протокола: одна и та же тройка в разных версиях может вести к разным пакетам или пропадать вовсе. Именно эту тройку с версией и принимает PacketRegistry.TryResolve - разбор номера без фазы и направления смысла не имеет.

Фазы и направления

Фазы заданы перечислением PacketPhase в McProtoNet.Protocol, в порядке жизни соединения:

  • Handshaking - первый пакет сессии, выбор следующей фазы;
  • Status - опрос сервера (пинг, список игроков в MOTD);
  • Login - авторизация, шифрование, сжатие;
  • Configuration - обмен данными о клиенте и серверными реестрами;
  • Play - игра.

Направление задаёт PacketDirection: Clientbound - пакет идёт от сервера к клиенту, Serverbound - от клиента к серверу. Одна и та же фаза несёт два независимых набора пакетов, по одному на направление. Порядок фаз и обмена пакетами при входе на сервер описан в протоколе на странице Protocol FAQ.

Каталог и ordinal

Пара (фаза, направление) - это каталог: PacketRegistry.Catalog(phase, dir) отдаёт его целиком, списком PacketDescriptor. Внутри каталога у пакета есть плотный номер Ordinal - PacketIdentity несёт его вместе с именем и ключом:

public readonly record struct PacketIdentity(
string Key,
string Name,
PacketPhase Phase,
PacketDirection Direction,
ushort Ordinal);

Ordinal плотный только внутри своего каталога, и это не то же самое, что номер пакета протокола (поле Id у IncomingPacket): номер приходит с сервера и может меняться от версии к версии, а Ordinal стабилен между сборками и служит индексом в таблицах разбора.

Чтобы получить Ordinal по номеру пакета, нужна вся тройка и версия протокола - так PacketRegistry.TryResolve в MinimalBot находит имя незнакомого пакета:

var packetName = PacketRegistry.TryResolve(
raw.Id, pv, Phase, Direction, out var desc)
? desc.Identity.Name
: $"0x{raw.Id:X2}";

Кто хранит фазу

Библиотека фазу не выводит сама - её знает только код приложения, который ведёт диалог с сервером. У ClientboundHandler и ServerboundHandler есть свойство Phase, и приложение переставляет его в ответ на пакеты, которые сигналят о переходе. Пока Phase не переставлен, PacketRegistry продолжает искать тип по старой фазе - и, значит, по старому каталогу.

Один из переходов MinimalBot - после успешного логина:

protected override async ValueTask OnLoginSuccess(
LoginCb.LoginSuccessPacket packet)
{
await client.SendAsync(new LoginSb.LoginAcknowledgedPacket(), pv);
Phase = PacketPhase.Configuration;
...
}

Все переходы

Переключение фаз - не забота библиотеки: она возит пакеты, а последовательность ведёт код приложения. Но знать эту последовательность нужно, поэтому вот она целиком, для клиента.

Из фазыСигнал от сервераЧто шлёт приложениеНовая фаза
--SetProtocol с NextState = 2Login
--SetProtocol с NextState = 1Status
LoginLoginSuccessLoginAcknowledgedConfiguration
ConfigurationFinishConfigurationFinishConfigurationPlay
PlayStartConfigurationConfigurationAcknowledgedConfiguration

Первый переход задаётся ещё до всякой фазы: поле NextState в рукопожатии говорит серверу, куда идти дальше. Остальные три - ответ на конкретный пакет, после которого приложение переставляет Phase у обработчика.

Последняя строка - не опечатка: сервер возвращает игрока из play обратно в configuration, когда меняет ресурспак или реестры. Обработчик, который об этом не знает, продолжит читать пакеты по каталогу play и утонет в Unknown.

Сжатие и шифрование включаются внутри Login и фазу не меняют - про них «Сжатие и шифрование». Как этот путь выглядит кодом целиком - в «Первом боте».

Если фазу не переставить

Если Phase не переставлен вовремя, входящие пакеты продолжают разбираться по старой фазе - и адрес выходит неверным: то, что сервер шлёт как пакет play, PacketRegistry ищет среди пакетов configuration. Итог - либо Unknown (номер не нашёлся в чужом каталоге), либо пакет, разобранный по чужому описанию.

Дальше