net: InReliableState=0 в первом пакете (по эталонному pcap)
Захват настоящего GMod (ClassicBox, 8091 пакетов) показал: реальный клиент шлёт в первом netchan-пакете rel=0x0000, а мы ставили 1. Это поле означает «какие подканалы мы хотим получить», в начале оно нулевое.
This commit is contained in:
parent
ec38667d2d
commit
92b4784c2a
2 changed files with 49 additions and 2 deletions
|
|
@ -586,11 +586,15 @@ pub fn build_hello_packet(
|
|||
w.write_u32(ack);
|
||||
w.write_byte(PACKET_FLAG_CHALLENGE | PACKET_FLAG_RELIABLE); // flags (патчится ниже)
|
||||
w.write_short(0); // checksum (патчится ниже)
|
||||
// `InReliableState` — какие подканалы мы ХОТИМ получить от сервера.
|
||||
// Настоящий клиент шлёт здесь 0 (мы ещё ничего не ждём), и только после
|
||||
// приёма данных сервера бит подканала выставляется. Проверено pcap-захватом
|
||||
// настоящего GMod: `seq=1 ack=0 flags=0x21 rel=0x0000`.
|
||||
let rel_state = std::env::var("SOURCE_RS_REL_STATE")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u16>().ok())
|
||||
.unwrap_or(if messages.is_empty() { 0 } else { 1 });
|
||||
w.write_short(rel_state); // in_reliable_state (состояние принятых подканалов)
|
||||
.unwrap_or(0);
|
||||
w.write_short(rel_state);
|
||||
w.write_u32(challenge);
|
||||
|
||||
if !messages.is_empty() && std::env::var_os("SOURCE_RS_UNRELIABLE_MSG").is_none() {
|
||||
|
|
|
|||
|
|
@ -536,3 +536,46 @@ IClientGameServerInternal::* таймаутятся). Установка GMod DS
|
|||
|
||||
Вывод: для отладки клиента полезнее снимать трафик **настоящего** клиента —
|
||||
сервер тогда не нужен вовсе.
|
||||
|
||||
### 13.9. Эталонный захват настоящего GMod (ClassicBox) — 8091 пакетов
|
||||
|
||||
Снят pcap полной сессии настоящего клиента (`/tmp/opencode/real_client.pcap`).
|
||||
Инструменты: `/tmp/opencode/pcap2.py` (разбор pcap, **заголовок 24 байта**, а не 16!)
|
||||
и `/tmp/opencode/ncdec.py` (декод reliable-блока).
|
||||
|
||||
**Рукопожатие полностью совпало с нашим** (побайтно, порядок полей):
|
||||
|
||||
| пакет | что | наш клиент |
|
||||
|---|---|---|
|
||||
| C→S `q` | getchallenge + наш challenge | так же |
|
||||
| C→S `k` | `18000000` протокол, `03000000` authProtocol=3, challengeNr, challenge, checksum, имя, пароль, `2026.04.29`, `f200`=242, SteamID, тикет | так же |
|
||||
| S→C `B` | challenge + `"0000000000\0"` | так же |
|
||||
|
||||
**Найденное расхождение — `InReliableState`:**
|
||||
|
||||
| | первый netchan-пакет |
|
||||
|---|---|
|
||||
| настоящий клиент | `seq=1 ack=0 flags=0x21 csum=0x7568 rel=0x0000` (531 б) |
|
||||
| наш клиент (было) | `seq=1 ack=0 flags=0x21 csum=0x58df rel=0x0001` (208 б) |
|
||||
|
||||
`InReliableState` — это «какие подканалы мы **хотим получить** от сервера», а не
|
||||
«сколько мы отправили». Настоящий клиент в первом пакете шлёт **0**, и бит
|
||||
подканала выставляется только после приёма серверных данных. Мы же ставили 1
|
||||
(костыль `if messages.is_empty() {0} else {1}`). Исправлено на 0.
|
||||
|
||||
Ещё наблюдаемые отличия (наш выбор, не ошибка):
|
||||
|
||||
- настоящий клиент **сжимает** reliable-блок (LZSS, бит compressed=1) и шлёт
|
||||
его несколькими фрагментами — 531 байт против наших 208. Сжатие необязательно
|
||||
(в `ReadSubChannelData` бит сжатия проверяется, но 0 допустим);
|
||||
- наш hello — один фрагмент, этого достаточно для `SetConVar`+`SignonState`.
|
||||
|
||||
**Объём эталона:** 536 reliable-пакетов S→C и 878 C→C за сессию — полный
|
||||
signon (server info, send tables, class info, string tables), снапшоты и
|
||||
обычный игровой трафик. Это готовый фикстур для реализации разбора signon-блока
|
||||
без живого сервера.
|
||||
|
||||
**Ограничение повторной проверки:** когда в игре настоящий клиент того же
|
||||
SteamID, наш вход отбивается `#GameUI_ServerRejectSteam`
|
||||
(`k_EBeginAuthSessionResultDuplicateRequest`). Для проверки правки нужен
|
||||
перерыв в сессии.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue