Фаза и направление: адрес пакета
Номер пакета сам по себе ничего не значит: один и тот же номер в разных фазах и
в разных направлениях - это разные пакеты. Тип пакета ищется не по номеру, а по
тройке фаза-направление-номер, да ещё и с оглядкой на версию протокола: одна и
та же тройка в разных версиях может вести к разным пакетам или пропадать вовсе.
Именно эту тройку с версией и принимает 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 = 2 | Login |
| - | - | SetProtocol с NextState = 1 | Status |
Login | LoginSuccess | LoginAcknowledged | Configuration |
Configuration | FinishConfiguration | FinishConfiguration | Play |
Play | StartConfiguration | ConfigurationAcknowledged | Configuration |
Первый переход задаётся ещё до всякой фазы: поле NextState в рукопожатии
говорит серверу, куда идти дальше. Остальные три - ответ на конкретный пакет,
после которого приложение переставляет Phase у обработчика.
Последняя строка - не опечатка: сервер возвращает игрока из play обратно в
configuration, когда меняет ресурспак или реестры. Обработчик, который об этом
не знает, продолжит читать пакеты по каталогу play и утонет в Unknown.
Сжатие и шифрование включаются внутри Login и фазу не меняют - про них
«Сжатие и шифрование». Как
этот путь выглядит кодом целиком - в
«Первом боте».
Если фазу не переставить
Если Phase не переставлен вовремя, входящие пакеты продолжают разбираться по
старой фазе - и адрес выходит неверным: то, что сервер шлёт как пакет play,
PacketRegistry ищет среди пакетов configuration. Итог - либо Unknown (номер
не нашёлся в чужом каталоге), либо пакет, разобранный по чужому описанию.
Дальше
- «Из сырого пакета в объект» - как из номера пакета получить его имя и разобранный объект
- «Обработчики» - как подписаться на конкретный пакет
- «Вход на сервер» - весь путь от TCP до игрового мира