3 Commits

Author SHA1 Message Date
sasheg dev d2a0a7a93b Вход без пароля, права админа и F3+B
Аутентификация переделана по схеме пользователя: лаунчер — единственный
источник токена, сервер проверяет его сам и лаунчера не спрашивает.

- rsx-auth/GameToken: токен это base64url(payload) + "." + base64url(HMAC-SHA256),
  payload {uuid, nick, aud, exp, jti, typ}, typ = session или reload.
- Сессия живёт час, reload-токен полтора часа и только внутри игры. Пакет
  SessionReload продлевает сессию и выдаёт новый reload-токен.
- Сервер проверяет подпись, аудиенс, срок и совпадение uuid. HTTP-запрос к
  лаунчеру из сервера убран.
- Клиент: окно пароля убрано совсем, ник берётся из токена, клиент сам
  продлевает сессию за 5 минут до конца.
- Для локальной разработки сервер с --dev-token пишет свой токен в
  world/dev-token.txt, клиент берёт токен оттуда.
- Права админа: свой список world/admins.txt, перечитывается раз в 5 секунд,
  поэтому право снимается на лету. Пакет AdminState.
- Ванильные блоки мира ломает только админ, режим игры прав не даёт.
- F3+B только для админов: без прав показываем отказ, с правом включаются и
  ванильные хитбоксы, и наш отладочный рендер. Права сняли посреди просмотра —
  гаснут сразу.
- run-game.ps1 умеет -Token.

Проверено на живом сервере: own session ok, player in, rights of Player given.
Тесты: протокол 34, сервер 88, форк собирается чисто.
2026-10-22 08:51:27 +03:00
sasheg dev 620f136361 Серверная база со своим логином
Теперь можно зарегистрировать аккаунт, поднять сервер и зайти в него.

Пароль не ходит по сети и не хранится на сервере в обратимом виде. Клиент
называет аккаунт, сервер отдаёт его соль и одноразовый вызов для этого
соединения, клиент тянет из пароля ключ через PBKDF2-HMAC-SHA256 и
подписывает им вызов вместе с именем. Пароль пересекает сокет ноль раз, а
запись обрезать нельзя: сервер не знает исходного пароля.

Отсюда важное следствие, которое сразу отозвало одну ошибку в протоколе:
соль обязана прийти раньше, чем клиент что-то доказывает, поэтому аккаунт
называется в Hello, а не в Auth. Иначе клиент получает соль не того
аккаунта и не может вывести ключ.

Учётная запись хранится как PBKDF2 соль, счётчик раундов и хеш. Раунды
лежат рядом с записью, а не в исходниках: число, существующее только в
коде, однажды становится причиной того, что аккаунт невозможно проверить.

Имена уникальны независимо от регистра: Player и player — это один
аккаунт. Иначе игрок получает двух персонажей и не понимает, какой его.

Троттлинг двумя ведрами, по аккаунту и по адресу: одно не мешает
подбирать одно имя, другое не мешает перебирать много имён с разных
адресов. Ответ на несуществующий аккаунт выполняет ту же работу, что и на
неверный пароль, и занимает то же время.

Токены живут только в памяти. Записанный на диск токен пережил бы процесс,
который его выдал, а украденный каталог сервера стал бы набором живых
сессий.

Сессии различаются номером, а не адресом. Ключ по адресу вытеснял всех, кто
зашёл с одного IP, то есть всех игроков за одним NAT.

Тик-цикл: 20 раз в секунду, с ограничением догоняющих тиков. Мир, который
тикает по кадрам, тикает в зависимости от видеокарты — ровно то, от чего
мы уходим, перенося симуляцию на свой сервер.

Регистрация — команда оператора, а не функция у клиента. Открытая
саморегистрация превращает хранилище аккаунтов в спам-цель и в очередь
поддержки.

Проверено 12 сценариев, и главный из них — не юнит-тест, а настоящий
сокет: регистрация, вход, доказательство пароля, токен, его погашение и
появление игроком. Отдельно проверено, что неверный пароль отвергается, а
старое доказательство не принимается против нового вызова. Тик-цикл
замеряется числом тиков за секунду, а не наличием потока.

Всё одним JDK, без Gradle и без сети, как и весь проект.
2026-10-19 01:45:56 +03:00
sasheg dev bdf0ae866a Форк клиента собирается из исходников, модзагрузчик встроен в игру
Проект начинается с двух вещей: клиент, который мы действительно собрали, и
загрузчик, который является его частью, а не чем-то рядом.

Форк
- prepare-game.ps1 пересобирает клиент целиком: ванильный jar, SRG и MCP,
  SpecialSource, FernFlower, патчи, автофиксеры, javac. Идемпотентен.
- Свой код игры лежит в src-overlay, потому что src пересоздаётся при каждой
  сборке и всё написанное вручную там погибает.
- 116 патчей в patches/decompiler-fixes.txt. Каждый сверялся с оригинальным
  байткодом через javap: компиляция не ловит ошибки декомпиляции.
- struct_diff.py сравнивает исходный байткод с нашей сборкой по классам,
  интерфейсам, суперклассам, методам и полям, и отдельно проверяет, что
  пропавшие классы действительно никому не нужны.

Загрузчик
- Собирается и тестируется без игры: build.ps1 не использует classpath
  вообще, поэтому SelfTest из 128 проверок проходит без запущенного форка.
- Клиентский API не содержит ни одного игрового типа, реализацию даёт игра.
- Политика: мод меняет то, что клиент показывает, но никогда то, что истинно
  в мире. Мир, предметы, игроки и сеть принадлежат серверу.
- Capability приведены в соответствие с этой политикой: осталось шесть, и
  за каждым стоит реальная проверка. Прежние WORLD, PLAYER, NETWORK, TEXT,
  INPUT и COMMAND выдавались, но ничего не проверяли.
- Команды модов имеют область CLIENT, SERVER или BOTH. Серверные не
  выполняются на клиенте никогда.
- Свой jar загрузчика лежит в classpath игры и сборки форка.

Инструменты
- add_patch.py, чтобы каждое исправление переживало передекомпиляцию
- find_self_comparisons.py, find_remove_index_bugs.py, find_callers.py
- stage_run.py и fetch_missing.py для запуска клиента
2026-10-18 21:31:27 +03:00