diff --git a/crates/net/src/netchan.rs b/crates/net/src/netchan.rs index a492fe8..944959d 100644 --- a/crates/net/src/netchan.rs +++ b/crates/net/src/netchan.rs @@ -31,10 +31,14 @@ pub const FRAGMENT_OFFSET_BITS: usize = 18; pub const MAX_FRAGMENTS_PER_PACKET_BITS: usize = 5; /// Биты максимального размера файла. pub const MAX_FILE_SIZE_BITS: usize = 30; -/// Биты номера подканала (в GMod — 4: 16 подканалов). -pub const SUBCHANNEL_BITS: usize = 4; +/// Биты номера подканала: 3 (8 подканалов, как в Source 2013 / HL2). +/// +/// Проверено по эталонному разбору пакета: при 4 битах сервер читает +/// subchannel=1 и «0 фрагментов», молча теряя reliable-данные (из-за этого +/// не обрабатывался net_SignonState и не приходил server info). +pub const SUBCHANNEL_BITS: usize = 3; -/// Ширина индекса подканала: GMod — 4 (16 подканалов), Source 2013 — 3. +/// Ширина индекса подканала. /// Переопределяется переменной окружения для экспериментов. pub fn subchannel_bits() -> usize { std::env::var("SOURCE_RS_SUBCHANNEL_BITS") diff --git a/docs/RECON.md b/docs/RECON.md index 5adec9e..0b9d9a2 100644 --- a/docs/RECON.md +++ b/docs/RECON.md @@ -344,3 +344,53 @@ SendServerInfo() ; cl->vfunc(0xd0) - альтернатива для отладки: поднять локальный srcds на незалоченном порту (сейчас мешает сломанный резолвер в системе — `systemd-resolved` не запущен, см. §13). + +### 13.3. НАЙДЕНА ПРИЧИНА: ширина индекса подканала (3 бита, не 4) + +Гипотеза §13.2 оказалась **неверной**: дело не в Steam-авторизации. Настоящая +причина — в заголовке reliable-фрагментов. + +**Как обнаружено.** Захвачен трафик живого подключения (`SOURCE_RS_LOG=net=trace`). +Сервер подтверждает наши netchan-пакеты (поле ack растёт 0→1→2→…), но в его +ответах поле `InReliableState` **всегда равно 0**. Значит сервер принимает +заголовок, но **не читает наш reliable-поток**. + +Побитовый разбор нашего hello-пакета: + +``` +seq=1 ack=0 flags=0x21 csum=0x58df rel=0x0001 chal=0x63896b24 +payload[191]: 10 bc 01 c5 82 ... +``` + +| SUBCHANNEL_BITS | прочитанный subchannel | биты потоков | итог | +|---|---|---|---| +| **4** (было) | **1** | `[0, 0, 0]` | **0 фрагментов → reliable-данные молча отброшены** | +| **3** (стало) | **0** | `[1, 0, 0]` | корректный single-block фрагмент | + +При 4 битах сервер читал индекс 1 (несуществующий подканал) и ноль фрагментов +→ `net_SignonState` не обрабатывался → `m_bSendServerInfo` не выставлялся → +`SendPendingServerInfo` пропускал клиента → **серверный блок не уходил никогда**. + +**Подтверждено дизассемблированием GMod-сервера** (`engine_srv.so`, +`CNetChan::ProcessPacket`): + +``` +189b6f: and $0x1,%esi ; flags & Reliable (0x01) — наш флаг верен +189b93: lea 0x3(%esi),%edx ; читаем РОВНО 3 бита индекса подканала +``` + +То есть `SUBCHANNEL_BITS = 3` (8 подканалов, как в Source 2013/HL2), +а не 4. Прежнее значение 4 было ошибочным предположением; из-за него +наши надёжные сообщения молча терялись. + +Побочно подтверждено: +- reliable-флаг пакета = `0x01` (`and $0x1`) — совпадает с нашим; +- флаг challenge = `0x20` — совпадает; +- `IN_RELIABLE_STATE_BITS = 16` — совпадает с Source.NET. + +Ограничение `SendPendingServerInfo` (для публичного IP требуется +`m_bFullyAuthenticated`) реально, но не блокировало: сервер готов слать данные +сразу после разбора нашего reliable-потока. Новый прогон подключения упирается +уже в `#GameUI_ServerRejectSteam` — это лимит Steam на частые подключения с +одного аккаунта (`k_EBeginAuthSessionResultDuplicateRequest`), а не ошибка +клиента.