Commit graph

27 commits

Author SHA1 Message Date
77d70f6b9f net: добавлен недостающий userinfo-конвар cl_playermodel
Наш первый netchan-пакет был на 22 байта короче эталонного — ровно на
cl_playermodel\0male07\0. Без него сервер не знает, какую модель ставить
нашему энтити-игроку.

Набор конваров вынесен в client::userinfo_pairs() и теперь сверяется с
эталоном настоящего GMod (новая фикстура fixtures/classicbox_client_hello.bin,
511 байт из пакета #24) попарно, включая порядок. При том же имени наш пакет
теперь совпадает с эталонным побайтово: тело идентично в 513 из 514 байт,
различаются только checksum/challenge из-за другой сессии и один остаточный
бит добивки.
2026-10-12 14:51:57 +03:00
9cee256cb8 net: разбор SVC_SendTable, SVC_ClassInfo, SVC_CreateStringTable
Раскладка взята из Source.NET (NetMessages.cs), т.к. эталонного pcap с этими
сообщениями нет — в захвате потеряно 2576 серверных пакетов именно на участке,
где сервер шлет таблицы (docs/RECON.md 13.13).

Важно: у SVC_SendTable нет имени таблицы, в отличие от раскладки Source 2013
SDK, на которую я ориентировался раньше.

Попутно найден баг через круговые тесты: префикс ':' у строковой таблицы
нужно смотреть (PeekUBitLong), а не читать — иначе теряется первый символ
имени ('userinfo' читался как 'serinfo') и дальше по потоку едет смещение.

Число классов ограничено MAX_SERVER_CLASSES=4096: значение приходит из сети.
2026-10-12 14:38:04 +03:00
fb48358208 net: «неожиданный» фрагмент сбрасывает передачу, а не весь пакет
Source.NET в ReadSubChannelData при фрагменте вне диапазона или нулевой
длине делает data.Return() и выходит — это штатная ситуация для
опоздавшего/дублирующего фрагмента. Наш код возвращал NetError, из-за чего
терялись все остальные сообщения пакета, а с ними и signon-блок.
2026-10-12 14:35:28 +03:00
459fe6b0bc net: PACKET_FLAG_CHOKED = 1<<4 (раньше был 1<<3 = Split)
Взят прямо из Source.NET (IConnectionlessPacketHandler.cs, enum PacketFlag):
Reliable=1, Compressed=2, Encrypted=4, Split=8, Choked=16, Challenge=32.
Из-за неверного бита в пакетах сервера с выставленным битом 3 читался
лишний байт, reliable_state и challenge уезжали на 8 бит.
Добавлены тесты на константы флагов и на заголовок с Choked.
2026-10-12 14:31:16 +03:00
c6b9b04404 net: разбор SVC_ServerInfo по эталону + фикстур ClassicBox
Раскладка ServerInfo была неверной (max_classes как long вместо 16 бит,
4 байта вместо MD5 карты, ServerOS как строка, нет loading_url/gamemode).
Исправлено по спецификации Source.NET и подтверждено тестами на реальном
блоке ClassicBox: map=gm_bigcity_improved_rp, host=ClassicBox,
gamemode=sandbox, maxcl=48, tick=0.030303, proto=24.
2026-10-12 14:24:22 +03:00
5600b53469 net: убран лишний бит «сжатие» в заголовке reliable-фрагмента
Layout одиночного блока, подтверждённый перебором по pcap настоящего GMod:
4 (подканал) + 2 (потоки) + 1 (single-block) + VarInt32 = 23 бита.
Мы писали 24 бита (лишний бит «сжатие»), из-за чего сервер читал длину
с неверного смещения и отбраковывал фрагмент — reliable-поток не читался,
m_bSendServerInfo не выставлялся.

Также набор USERINFO-конваров приведён к реальному (28 шт. с net_maxroutable).
2026-10-12 14:18:53 +03:00
92b4784c2a net: InReliableState=0 в первом пакете (по эталонному pcap)
Захват настоящего GMod (ClassicBox, 8091 пакетов) показал: реальный клиент
шлёт в первом netchan-пакете rel=0x0000, а мы ставили 1. Это поле означает
«какие подканалы мы хотим получить», в начале оно нулевое.
2026-10-12 14:05:49 +03:00
ec38667d2d docs: локальный сервер-эталон Source.NET поднят, разбор connect/accept 2026-10-12 13:53:27 +03:00
2860f67a36 docs: выжимка референса Source.NET по протоколу сигнона 2026-10-12 12:23:11 +03:00
2716884050 docs: полная верификация протокола и локализация блокера
Подтверждено по дизасму engine_srv.so и живому трафику: 4 бита подканала,
LSB-first, NETMSG_TYPE_BITS=6, 2 потока, reliable=0x01, challenge=0x20,
тело NET_SignonState = байт + u32, формат cookie в connect.

Сервер читает наш reliable-блок (InReliableState -> 1) и обрабатывает
net_SignonState; блокер только m_bFullyAuthenticated в SendPendingServerInfo.
2026-10-12 12:18:28 +03:00
c624d7f6ac docs: опровержение гипотез о подканале и порядке битов
Проверка по InReliableState показала: исходные 4 бита и LSB-first верны
(and $0xF / позиция +4 в ProcessPacket; rol в bf_write::WriteByte).
Правки 12f94ae и 50bb363 были регрессией и откачены.
2026-10-12 12:06:49 +03:00
3964e83e0f Revert "net: индекс подканала — 3 бита (исправлен silently-баг reliable)"
This reverts commit 50bb36304e.
2026-10-12 12:06:32 +03:00
7f2ec26030 Revert "net: битовый буфер переведён на MSB-first, как в движке"
This reverts commit 12f94ae55a.
2026-10-12 12:06:32 +03:00
12f94ae55a net: битовый буфер переведён на MSB-first, как в движке
Настоящая причина отсутствия server info: bf_write писал биты LSB-первым,
а Source читает/пишет MSB-первым (подтверждено дизассемблированием
bf_read::ReadUBitLongNoInline в engine_srv.so).

Из-за этого WriteOneBit(true) попадал в поле соседнего потока фрагментов,
а ID сообщений записывались перевёрнутыми — сервер не распознавал
net_SignonState и не выставлял m_bSendServerInfo.

- write_one_bit/write_ubit_long/read_one_bit/read_ubit_long: MSB-first;
- write_u32/write_short/read_long/read_short: побайтово little-endian
  (как ReadLong в движке);
- reliable_state кодируется через WriteUBitLong(16);
- тесты bit_order_matches_source и reliable_block_layout_is_canonical.
2026-10-12 11:52:28 +03:00
50bb36304e net: индекс подканала — 3 бита (исправлен silently-баг reliable)
Главная причина отсутствия server info: SUBCHANNEL_BITS был 4, из-за чего
сервер читал subchannel=1 и 0 фрагментов, молча отбрасывая reliable-поток
(net_SignonState не обрабатывался → m_bSendServerInfo не выставлялся).

Подтверждено pcap-разбором и дизасмом engine_srv.so (CNetChan::ProcessPacket:
lea 0x3(%esi) — ровно 3 бита; and $0x1 — reliable-флаг 0x01).
2026-10-12 11:44:49 +03:00
9cc4daf6b0 docs: дизассемблер SendPendingServerInfo — сигнон ждёт m_bFullyAuthenticated
Проверка поля клиента (0x5af) в GMod-сервере: для удалённого клиента
сигнон-блок не отправляется, пока флаг (вероятно m_bFullyAuthenticated,
ставится при успешной асинхронной валидации тикета) не выставлен.
Симптомы совпадают: connect принимается, netchan работает, сообщения
доходят, но ServerInfo не приходит. Дальнейшие шаги — в docs/RECON.md §13.2.
2026-10-12 10:53:15 +03:00
7befb1f8ee net: диагностика сигнона (выбор подканалов, повтор hello, проба SignonState)
- Регулируемые экспериментальные ручки: SOURCE_RS_SUBCHANNEL_BITS,
  SOURCE_RS_REL_STATE, SOURCE_RS_HELLO_EVERY_MS, SOURCE_RS_PROBE_SIGNON,
  SOURCE_RS_RETRY_DELAY_MS, SOURCE_RS_NO_TICKET_REFRESH.
- docs/RECON.md §13.1: разбор логики сигнона по утёкшему исходнику
  (SendPendingServerInfo/SetSignOnState/ConnectClient), формат challenge-ответа,
  наблюдения по лимитам Steam (DuplicateRequest ~2-3 мин), что уже проверено.
- Сигнон-блок от сервера пока не приходит (наши пакеты доходят, connect
  принимается) — следующая задача; все зацепки в доке.
2026-10-12 10:46:15 +03:00
8414920df2 net: тикет Steam с идентичностью игрового сервера (фикс #GameUI_ServerRejectSteam)
- Современный Steamworks требует GetAuthSessionTicket с
  SteamNetworkingIdentity = SteamID игрового сервера; generic-тикет сервер
  отвергает синхронно (#GameUI_ServerRejectSteam). Тикет привязывается к
  серверу из ответа challenge ('A': keysize u16, GSID u64, secure u8).
- Кэш тикета по SteamID сервера (как CSteam3Client::GetAuthSessionTicket),
  ретраи connect с задержкой (10 с): повторный BeginAuthSession для того же
  SteamID сервер считает дубликатом и отклоняет.
- Тикет запрашивается в момент connect (GSID уже известен), стартовый тикет
  больше не нужен.
- docs/RECON.md §13: полный формат challenge/connect/reject, порядок состояний
  сигнона, наблюдения по лимитам Steam.
2026-10-12 10:07:06 +03:00
44b0ece2ab render: вода (SURF_WARP), area-скайбокс с правильным порядком глубины, лайтмапы диспов по UV
- Вода: детект Water-шейдера (/), отдельный полупрозрачный
  проход с френелем; поверхность/изнанка по нормали. Белая дыра на месте воды
  (2D-скайбокс «вниз») устранена.
- 3D-скайбокс: выбор граней по area листа sky_camera (движок: area bits), а не по
  AABB. Disp-грани не попадают в leaf_faces — выбор по листу центра грани; теперь
  в скайбоксе есть большая плоскость flatgrass_2 (пропадала — торчала «стена»
  бетонного пола). Защита >30% граней = скайбокс не выделяем (gm_flatgrass).
- Порядок: 2D-скайбокс фоном -> 3D-меш (depth Always, без записи) -> мир ->
  вода. Скайбокс больше не проступает сквозь дальние стены карты.
- Лайтмапы: блок грани (size+1)^2 с индексом v*width+u; для displacement'ов —
  плотная сетка по UV поверхности (CalcDispSurfCoords), а не проекция мира:
  убрана «шахматка» теней на рельефе. Скайбокс-меш тоже сэмплит лайтмапы.
- Тесты: gm_flatgrass-диспы живут в скайбоксе (z≈-16000), пороги обновлены.
- docs/RECON.md §12: форматы листьев/граней, лайтмапы, скайбокс, вода.
2026-10-12 08:28:28 +03:00
a72185e808 net: разбор надёжного потока netchan (подканалы, фрагменты), фиксы GMod-флагов
- NetChan: чтение подканалов (single/fragments, сжатие, varint), сборка потоков,
  разбор сообщений из собранных потоков
- GMod-специфика: флаг Challenge = 0x20, subchannel 4 бита (16 подканалов)
- Steam: клиент живёт всю сессию, ждём AuthSessionTicketResponse, свежий тикет
  при повторных connect (challenge одноразовый)
- client: keepalive-пакеты, диагностика (hex-дампы, connectionless)
- docs/RECON: подробности формата netchan и нерешённый кик сервером
2026-10-12 03:44:21 +03:00
efc4b03bc8 net: M2 v0 — хендшейк с ClassicBox, Steam-тикет, netchan-заголовки
- crates/net: битовые буферы Source (LSB), A2S, netchan (флаги/checksum/challenge),
  разбор net/SVC-сообщений, connect с GMod ChecksumXOR и Steam-тикетом
- steamworks-rs: session ticket для VAC-серверов (appid 4000)
- версия клиента в connect берётся из A2S (фикс #GameUI_ServerRejectNewVersion)
- CLI: net info / net connect
- Проверено: ClassicBox принимает подключение (auth_protocol=3, Steam)
- Далее: подканалы надёжного потока → SVC_ServerInfo → signon
- docs: прогресс M2, заметки (нумерация GMod, ChecksumXOR, версии, локальный DS)
2026-10-12 00:27:28 +03:00
b8b5a4c328 engine/render: M3 v0 — рендер мира BSP со скайбоксом
- crates/engine: сборка мира (браши, displacement'ы, лайтмапы в вершинах,
  материалы через VMT/patch/include, кэш текстур, пропуск прозрачных материалов),
  загрузка скайбокса из skyname
- crates/render: wgpu-рендерер мира (VB/IB, батчи, глубина), скайбокс-куб,
  камера-полёт, capture PNG с учётом BGRA, debug-режимы материалов
- client: команда map (диск/VPK, pakfile карты, WASD+мышь, --pos/--angles/--screenshot)
- тесты: gm_construct/gm_flatgrass собираются, 0 заглушек, 41 324 треугольника
- docs: прогресс M3 и заметки (BGRA, pak-VMT, z=w клиппинг)
2026-10-12 00:06:26 +03:00
d6b213cecc formats: VVD/VTX — меш-данные моделей (вершины, fixup, LOD-дерево)
- VVD (IDSV v4): заголовок 64 байта, mstudiovertex_t 48 байт (веса/кости/позиция/нормаль/UV),
  тангенты, таблица fixup
- VTX (packed): BodyPart→Model→LOD→Mesh→StripGroup→Strip, валидация индексов и смещений;
  смещения вложенных структур относительны своей структуры
- fs inspect: для .mdl показывает VVD/VTX-статистику и сверку checksum
- тесты: oildrum (270 вершин/756 индексов), 51 пара VVD/VTX с совпадающим checksum
- docs: прогресс M1 (остался ANIM, PCF/PHY/SND, GMA/mount.cfg)
2026-10-11 23:15:46 +03:00
7236910e69 formats: MDL (заголовок v44-49, кости, аттачменты, хитбоксы, текстуры, keyvalues)
- базовый studiohdr_t = 408 байт, v48+ studiohdr2 (256 байт) перед костями
- строки читаются относительно структур (sznameindex), всё bounded
- fs inspect: полная сводка по модели (кости/аттачменты/текстуры/include/keyvalues)
- тесты: oildrum (v44, 1 кость), advisor (v48, 25 костей), выборка 53 моделей — 0 ошибок
- docs: прогресс M1 (остались VVD/VTX/ANIM, PCF/PHY/SND, GMA/mount.cfg)
2026-10-11 23:09:25 +03:00
34ea6b8521 formats: KeyValues, BSP (+геометрия, LZMA, pakfile ZIP), VTF (декод DXT), VMT; fs inspect/list
- crates/formats: bounded-парсеры kv/bsp/vtf/vmt/pakfile
- BSP: 64 лумпа, LZMA, ENTITIES, texdata/имена, game lumps, геометрия v20 с validate()
- VTF 7.0-7.5: заголовок, ресурсы 7.3+, мипы, декод в RGBA8 (DXT1/3/5 и др.)
- pakfile: ZIP Store/Deflate с CRC и лимитами
- client: fs list, fs inspect (формат по магии), геометрия карты в inspect
- тесты: реальные карты gm_construct/gm_flatgrass, 200 VTF, 200 VMT, pakfile 599 записей
- docs: прогресс M1 и заметки разведки
2026-10-11 23:03:45 +03:00
dec5b2c48f sprint 0: workspace, tier/fs/math, VPK v1/v2 с CRC, окно SDL3+wgpu на Vulkan
- crates/tier: tracing-логи, конвары с реестром, конфиг TOML, авто-детект ассетов
- crates/fs: bounded-парсер VPK v1/v2 (preload, встроенные данные, внешние архивы),
  VirtualFs с приоритетами и защитой от traversal, CRC-проверка
- crates/math: Vec3/Angle/Aabb
- crates/client (gmod-rs): fs doctor / fs cat, окно SDL3+wgpu 30, FPS в заголовке и логе
- тесты на реальных VPK (~/gmod_server/server): 9 контейнеров, 77 933 записи, 0 ошибок CRC
- CI-скелет, fuzz-таргет vpk_parse, config.example.toml, обновление docs
2026-10-11 22:47:09 +03:00
ec0750a829 docs: план Source RS — этапы, архитектура, проблемы, Lua, сеть, разведка 2026-10-11 22:25:36 +03:00