Система SIGNAL

Что умеет SIGNAL

Полная техническая картина: от реального UDP-опроса серверов до встроенного музыкального плеера с 70 млн треков. Здесь — всё что делает SIGNAL под капотом, без маркетинговых формулировок.

Документация для разработчиков

Реальный мониторинг серверов

Наша серверная инфраструктура опрашивает DayZ-серверы напрямую по A2S-протоколу, отдавая лаунчеру моментальный онлайн без задержек Steam API. DDoS-защита и обход клиентских файрволов реализованы на уровне Node.js кластера — лаунчер просто забирает готовый кэш по REST.

REST кэшSource Engine10 мин цикл

Нативный Windows-клиент

Лаунчер написан на .NET 10 + Avalonia UI. Это не Electron и не веб-обёртка — нативный код, нативный GPU-рендер. Современные glass-эффекты, плавные анимации и минимальное потребление RAM: технологично выглядит и не мешает игре.

.NET 10Avalonia UIMVVMAcrylic blur

Музыкальный плеер

Встроен прямо в лаунчер. Стриминг по 70+ млн треков, алгоритм «Моя Волна» подбирает музыку по настроению и времени суток, синхронизация плейлистов с популярных платформ. Живой плазменный фон пульсирует синхронно с ритмом. Работает в фоне во время загрузки игры.

70M трековFFT визуализацияМоя Волна

Авторизация и безопасность

Два независимых JWT-потока: владельцы серверов и администраторы — разные секреты, разные middleware, разные права. Steam OAuth только для отзывов. Пароли — bcrypt cost 12. Один device_id — один отзыв на сервер.

JWTbcrypt-12Steam OAuth

Биллинг через внутренний баланс

Никаких рекуррентных списаний с карты. Владелец пополняет баланс через платёжный провайдер или вручную, подписка списывается с баланса. Webhook-верификация входящих платежей. Ручное зачисление для крипты.

Внутренний балансWebhookUSDT TRC20

Хаб владельца с браузером

Super Premium даёт владельцу отдельный экран в лаунчере: несколько серверов под одним брендом, встроенный браузер с его сайтом/донатом/правилами, Discord webhook при падении сервера, 90-дневная история онлайна.

Встроенный браузерDiscord webhook90д история
Developer Reference

Как всё устроено внутри

Полное описание архитектуры, баз данных, авторизации, API и всего остального. Написано так, чтобы у тебя не осталось вопросов.

1 Архитектура
Из чего всё состоит и как части общаются между собой
Стек и разделение ответственности

Система — это две независимые части: бэкенд и клиент. Они общаются только через REST API. Это не монолит — если упадёт сервер, лаунчер сохранит последний закэшированный конфиг и продолжит показывать серверы (без истории).

Node.js Backend

Express.js, CommonJS, cluster-режим. Три SQLite-базы, файловый конфиг серверов, JWT, Steam OAuth через Passport.js. Отвечает за авторизацию, биллинг, хранение истории, раздачу конфига.

Avalonia Launcher

.NET 10, Avalonia UI, паттерн MVVM. Только Windows. Получает онлайн серверов из REST-кэша бэкенда, рендерит UI нативно через GPU. С сервером общается за конфигом, онлайном, историей и аутентификацией.

SQLite × 3

Три отдельных файла БД для трёх задач: пользователи/аккаунты, история онлайна серверов, отзывы и баны. Разделены намеренно — разные нагрузки, разные паттерны записи.

Схема взаимодействия
┌─────────────────────┐       HTTPS / REST       ┌──────────────────────────────┐
│  Avalonia Launcher  │ ◄────────────────────►   │       Node.js / Express      │
│    (.NET 10, Win)   │    (конфиг, онлайн,      │    Cluster (VPS, port 443)   │
└─────────────────────┘     история, auth)        └──────────────────────────────┘
                                                            
                                                            │ UDP A2S (централизованный опрос)
                                                            
                                          ┌──────────────────────────────────────┐
                                          │          DayZ серверы                │
                                          │         (любые IP:порт)              │
                                          └──────────────────────────────────────┘
                                                            │ read / write
                                                            
                                          ┌──────────────────────────────────────┐
                                          │  users.db / history.db / reviews.db  │
                                          │            + JSON-конфиг             │
                                          └──────────────────────────────────────┘
Cluster mode — зачем и как

Node.js однопоточный, но на VPS несколько ядер. Бэкенд работает в cluster-режиме: master-процесс поднимает по одному воркеру на каждое CPU-ядро, каждый воркер — полноценный Express-сервер. При падении воркера master автоматически поднимает новый — zero downtime при любом краше.

💡
Каждый воркер держит собственную копию конфига в памяти и синхронизируется через диск. При текущих нагрузках это оптимально — hot-read без обращений к диску на каждый запрос, запись — только при изменениях владельца.
Что происходит при старте лаунчера

Последовательность при первом запуске и при каждом последующем:

  • Проверка обновления — лаунчер сравнивает свою версию с launcher_version из конфига. Если есть новее — показывает уведомление, скачивает инсталлятор по updateUrl.
  • Загрузка конфига — GET /api/config возвращает весь список серверов, premium-проекты, настройки. Кэшируется локально на случай офлайна.
  • Получение онлайна — лаунчер запрашивает актуальный кэш онлайна с серверной инфраструктуры по REST. Данные уже готовы — кластер обновляет их в фоне каждые 10 минут, без нагрузки на клиента.
  • Восстановление сессии — если в хранилище есть JWT-токен, лаунчер делает GET /api/me и проверяет его валидность. Если токен протух — просит залогиниться снова.
2 База данных
Три SQLite-файла, каждый со своей задачей и своим паттерном нагрузки
Почему три отдельные базы, а не одна

Это не случайность. У каждой базы свой паттерн нагрузки: пользовательская база читается при каждом логине, история — пишется пачками каждые 10 минут и редко читается, отзывы — случайные записи и частые чтения. Если держать всё в одной базе, WAL-журнал будет общим — пачки INSERT в историю будут блокировать чтение пользователей. Разделение устраняет это.

Пользовательская БД

Аккаунты владельцев серверов. Логин, bcrypt-хэш пароля, email, внутренний баланс в рублях, текущий план, ID проекта, Steam ID. Управляется через отдельный модуль с промисифицированными методами — никаких прямых SQL из основного кода.

История онлайна

Только одна таблица, только INSERT и SELECT по диапазону дат. Каждые 10 минут приходит пачка из N строк (по числу серверов). Индекс по (server_id, created_at) — выборка за 30/90 дней быстрая даже при миллионах записей.

Отзывы и баны

Две таблицы: отзывы и баны устройств/Steam-аккаунтов. Мягкое удаление — отзыв не удаляется физически, просто ставится флаг. Это позволяет восстановить ошибочно удалённый отзыв без бэкапа.

Конфиг администраторов

Хранится не в БД, а в зашифрованном JSON-файле: логины и хэши паролей администраторов, JWT-секрет. При первом запуске генерируется автоматически с рандомным криптостойким секретом через crypto.randomBytes.

💡
Все три SQLite-базы работают с PRAGMA journal_mode = WAL и synchronous = NORMAL. WAL (Write-Ahead Logging) позволяет параллельные чтения без блокировки записи — читатели не ждут, пока завершится пачечный INSERT в историю. На практике это разница между 5ms и 500ms при нагрузке.
Схема таблицы: история онлайна
КолонкаТипОписание
idINTEGER PKАвтоинкремент
server_idTEXTИдентификатор сервера — ip:port или ID из конфига. Индексирован.
playersINTEGERОнлайн в момент снепшота (из A2S-ответа)
max_playersINTEGERМаксимальный слот в момент снепшота
created_atDATETIMEUTC-время снепшота. DEFAULT CURRENT_TIMESTAMP. По этому полю строится график.
Схема таблицы: отзывы
КолонкаТипОписание
idINTEGER PKАвтоинкремент
server_idTEXTК какому серверу относится отзыв
device_idTEXTUUID устройства — генерируется лаунчером при первом запуске, хранится локально
steam_idTEXTSteam ID64 (если пользователь авторизован). NULL для анонимных.
is_anonymousINTEGER1 = только device_id, 0 = от Steam-аккаунта
ratingINTEGER1–5 звёзд
textTEXTТекст отзыва после фильтрации
is_deletedINTEGERМягкое удаление. 1 = скрыт, 0 = виден. Физически не удаляется.
created_atDATETIMEUTC-время создания
3 Авторизация
Два независимых JWT-потока и Steam OAuth — намеренно разделены
JWT flow: владельцы и администраторы

В системе два типа привилегированных пользователей. Ключевой момент: у них разные JWT-секреты и разные middleware для проверки. Это сделано намеренно — токен admin'а физически не пройдёт через middleware владельца и наоборот. Никакой логики "если есть role=admin, то пропустить везде" — нет.

Владельцы серверов

Аккаунты в пользовательской БД. Регистрируются сами через форму. Пароль хэшируется bcrypt с cost 12 (это ~250ms на современном CPU — намеренно медленно, чтобы брутфорс был нерентабелен). Токен живёт 30 дней.

Администраторы

Логины в конфигурационном файле, не в БД. Это значит что добавить/удалить администратора — это правка файла и рестарт, не SQL. Есть роли: admin и super_admin. Super_admin может делать то, что обычный admin — нет (например, удалять пользователей).

🔒
Для владельцев и администраторов используется независимая генерация токенов с разными секретами — токен одного типа физически не пройдёт через middleware другого. Пароли хэшируются с намеренно высокой стоимостью (~250ms на проверку), что делает брутфорс нерентабельным. При неверных данных возвращается одно и то же сообщение — чтобы не раскрывать существование аккаунта.
Steam OAuth — только для отзывов

Steam авторизация сделана через Passport.js + Steam OpenID. Важно: она используется только чтобы привязать отзыв к реальному Steam-аккаунту. Войти в личный кабинет через Steam нельзя — это другая система.

ЛаунчерGET /auth/steamSteam OpenIDcallback /auth/steam/returnсессия с steamId

После коллбэка сессия содержит req.user.id (Steam ID64) и req.user.displayName. При следующем POST /api/reviews этот Steam ID привязывается к отзыву. Steam ID публичен сам по себе — мы не получаем ничего, что пользователь не мог бы узнать самостоятельно.

🔑
Steam OpenID не передаёт пароль — это federated auth. Мы получаем только подтверждение "этот Steam ID принадлежит пользователю, который сейчас вошёл". Ключ Steam API нужен для валидации OpenID-ответа, а не для чтения профиля.
Жизненный цикл токена на клиенте

Лаунчер хранит JWT в защищённом локальном хранилище. При каждом запуске проверяет токен через GET /api/me. Если сервер вернул 401 — токен инвалидирован, показывает экран логина. Refresh-токенов нет — при истечении 30 дней просто снова логин. Это сделано намеренно: меньше сложности, меньше векторов атаки.

⚠️
Администратор может вручную инвалидировать токен через смену JWT-секрета в конфиге (требует рестарта). Это "ядерная кнопка" — выкидывает всех владельцев. Для блокировки конкретного аккаунта достаточно установить флаг is_banned в БД — middleware проверяет его при каждом запросе.
4 A2S-опрос серверов
Актуальный онлайн через централизованную инфраструктуру
Почему вся A2S-логика на сервере, а не в лаунчере

Весь A2S-опрос вынесен на централизованный Node.js кластер — и это не случайность. Во-первых, защита от DDoS: если бы каждый клиент сам слал UDP-пакеты, любой сервер мог бы оказаться под распределённым флудом от тысяч лаунчеров одновременно. Во-вторых, обход клиентских файрволов: многие провайдеры режут исходящий UDP-спам — пользователь просто не получил бы онлайн. Лаунчер избавлен от этих проблем: он запрашивает готовый кэш по HTTPS REST и получает данные всегда, независимо от настроек сети.

Steam Web API для этих задач не подходит — rate limit в несколько тысяч запросов в сутки при 50+ серверах с обновлением каждые минуты физически невозможен. Плюс задержка данных 1–5 минут делает его бесполезным для отображения живого онлайна.

Как работает серверный A2S-опрос

A2S (Source Engine Query Protocol) — UDP-протокол, который поддерживает любой Source-движок, включая DayZ. Кластер отправляет запрос на query-порт сервера (игровой порт + 1, это стандарт Source Engine), получает ответ за <50ms и парсит бинарный ответ по спецификации протокола. Сервер может ответить challenge — это стандартная анти-спуф защита протокола, кластер обрабатывает её автоматически.

Если за 5 секунд ответа нет — сервер считается оффлайн. Весь цикл по всем серверам идёт параллельно через Promise.allSettled, что означает: падение одного сервера не задерживает опрос остальных.

Query port vs Game port: DayZ слушает игровой трафик на порту X, A2S-запросы — на X+1. Порт 2302 → query-порт 2303. Если владелец указал 2302, кластер сам добавляет +1 — не нужно указывать два порта.
Как работает автоустановка модов

Используется гибридная модель. Список активных модов сервера приходит с бэкенда в составе конфига — кластер извлекает его из A2S-ответа (DayZ кладёт Workshop ID модов в поле keywords/gametype, это официальное поведение игры). Локальная валидация кэша и путей идёт строго через нативный steam_api64.dll по интерфейсу SteamUGC — именно он отвечает за проверку установленных Workshop-предметов без лишних запросов к Steam Web API.

💡
Такая схема даёт лучшее из обоих миров: актуальный мод-лист с сервера без UDP на клиенте, и надёжная проверка локального состояния через официальный Steamworks — без самодельного парсинга файловой системы Steam.
5 Конфиг и синхронизация лаунчера
Единственный источник правды для всего списка серверов

Вся конфигурация серверов живёт в одном JSON-файле на сервере. Лаунчер получает его при каждом запуске через GET /api/config. Этот файл содержит: список всех серверов (обычных и premium), список Super Premium проектов с хабами, версию лаунчера, URL обновления и ссылки.

JSONСтруктура конфига
{
  "global_settings": {
    "launcher_version": "2.4.0",     // лаунчер сравнивает со своей, если старше — обновление
    "tracked_mods": []              // глобальный список отслеживаемых Workshop-модов
  },
  "premium_projects": [            // Super Premium: полноценные хабы
    {
      "project_id": "my_project",
      "name": "Название проекта",
      "owner_login": "owner123",   // привязка к аккаунту — для проверки прав
      "hub_url": "https://...",     // URL для встроенного браузера в хабе
      "servers": [
        { "ip": "1.2.3.4", "port": 2302, "name": "Main" }
      ],
      "discord_webhook": "https://discord.com/api/webhooks/..."
    }
  ],
  "servers": {                      // Basic Premium — по одному серверу
    "1.2.3.4:2302": {
      "name": "Название сервера",
      "plan": "basic",
      "verified": true,
      "icon_url": "/uploads/icons/xxx.png",
      "banner_url": "/uploads/banners/xxx.png"
    }
  },
  "updateUrl": "https://...",       // ссылка на инсталлятор новой версии
  "discordUrl": "https://..."
}

Когда владелец редактирует настройки в личном кабинете, middleware проверяет что owner_login в его части конфига совпадает с логином из JWT. Он не может трогать чужие секции — только свой servers[ip:port] или свой premium_projects объект.

🚫
Жёсткое правило архитектуры хабов: организационные «папки» (группирующие объекты) внутри хаба не должны содержать IP-адреса. IP-адреса принадлежат только листовым объектам-серверам. Нарушение этого правила сломает логику парсинга серверов: парсер ожидает IP только у конечных узлов, папка с IP будет воспринята как сервер и вызовет неопределённое поведение при опросе и построении списка.
⚠️
При изменении конфига воркер, который записал файл, сразу инвалидирует свой configCache. Остальные воркеры перечитают файл при следующем запросе к /api/config. Окно несогласованности — время между запросами, обычно секунды.
6 Музыкальный плеер
70 млн треков встроены в лаунчер — как это работает
Архитектура плеера

Плеер — это не веб-вью и не Electron-обёртка над YouTube. Это нативный компонент внутри Avalonia-приложения. Он использует внешний аудио API для поиска и стриминга треков, получая прямые аудио-потоки которые воспроизводит нативным декодером.

Поиск треков

Запрос к внешнему API по названию/исполнителю. API возвращает список треков с метаданными и URL потока. 70+ млн треков — это размер каталога, к которому есть доступ. Сам каталог не хранится локально.

Импорт из платформ

Пользователь подписывает свои треки с внешних платформ прямо в лаунчер — это синхронизация плейлиста, не загрузка файлов. Треки стримятся по запросу, не хранятся локально.

Визуализация плазмы

Во время воспроизведения декодер вычисляет FFT (быстрое преобразование Фурье) аудиосигнала в реальном времени. Амплитуды частотных полос передаются в рендерер плазменного фона — он меняет скорость и интенсивность анимации синхронно с ритмом.

Алгоритм «Моя Волна»

Умная подборка треков на основе текущего трека (BPM, жанр, тональность), времени суток и истории прослушивания. Реализован через API рекомендаций — не машинное обучение на клиенте. Каждая сессия звучит иначе, но всегда в тему.

💡
Плеер работает в фоновом потоке и не блокирует UI-поток. Это значит что даже если запускается игра, загружаются моды или обновляется список серверов — музыка не заикается. Avalonia рендерит в отдельном потоке, аудио идёт в своём.
7 API эндпоинты
Все маршруты, уровни доступа и что они делают
МетодПутьДоступЧто делает
GET/api/configPublicПолный конфиг — список серверов, premium-проекты, версия лаунчера, ссылки. Главный эндпоинт.
POST/api/loginPublicАутентификация владельца. Возвращает JWT-токен на 30 дней.
POST/api/registerPublicРегистрация нового владельца. bcrypt хэширование, запись в БД.
GET/api/meOwner JWTДанные текущего аккаунта: баланс, план, ID проекта, дата окончания подписки.
POST/api/owner/serverOwner JWTОбновить настройки своего сервера в конфиге: имя, иконка, баннер, Discord webhook.
GET/api/history/:idOwner JWTИстория онлайна за N дней. Basic — 30 дней, Super — 90. Проверяет что сервер принадлежит этому аккаунту.
POST/api/balance/topupOwner JWTСоздать счёт на пополнение баланса через платёжный провайдер. Возвращает URL для оплаты.
POST/api/payment/callbackHMAC подписьWebhook от платёжного провайдера. Верифицирует подпись, зачисляет баланс. Идемпотентный — повторный вызов с тем же ID не задвоит деньги.
POST/api/subscribeOwner JWTАктивировать/продлить подписку. Списывает с баланса, обновляет план и срок в конфиге.
GET/api/reviews/:idPublicСписок отзывов для сервера. Пагинация, средняя оценка, фильтр is_deleted=0.
POST/api/reviewsDevice IDОставить отзыв. Один device_id или Steam ID — один отзыв на сервер. Дубли отклоняются.
GET/api/admin/usersAdmin JWTСписок всех owner-аккаунтов с балансами, планами, статусами.
POST/api/admin/balanceAdmin JWTРучное зачисление баланса (для крипто-оплат, подтверждённых вручную).
DELETE/api/admin/review/:idAdmin JWTМягкое удаление отзыва (is_deleted=1). Опционально — бан device_id или Steam ID.
🔐
requireOwnerAuth и requireAdminAuth — физически разные функции с разной логикой проверки. Admin-токен не пройдёт через requireOwnerAuth и наоборот. Это намеренно — нет никакого "суперпользователя" который проходит везде.
8 Кэш и фоновый мониторинг
Что живёт в памяти воркера и как работает цикл истории
In-memory кэши

Каждый воркер держит несколько объектов в памяти — чтобы не лезть в файл или базу на каждый запрос:

JavaScriptКэши в памяти
let configCache           = null;  // конфиг серверов — читается с диска при первом запросе
let adminsConfig          = null;  // конфиг администраторов
let globalServerListCache = [];    // последний A2S-результат по всем серверам
let modUpdateCache        = {};    // { 'ip:port': { mods: [], ts: Date } }
let premiumModsCache      = {};    // то же для premium-серверов

globalServerListCache — это последний известный статус всех серверов (онлайн, слоты). Лаунчер получает его вместе с конфигом в одном ответе. Обновляется фоновым циклом — лаунчер никогда не ждёт свежего A2S от сервера, он всегда получает последний закэшированный результат.

Цикл мониторинга (каждые 10 минут)
  • Берёт полный список серверов из configCache
  • Параллельно опрашивает все серверы по UDP через Promise.allSettled
  • Обновляет globalServerListCache актуальными данными
  • Пишет снепшот в историю пачечным INSERT — одна транзакция на все серверы
  • Если Super Premium сервер был онлайн → стал оффлайн → стреляет в Discord webhook
Promise.allSettled вместо Promise.all — ключевое решение. Если один сервер не отвечает и таймаут 5 секунд — остальные не ждут, цикл продолжается. Весь мониторинг 50 серверов занимает ~5 секунд (время таймаута одного упавшего), а не 50×5 секунд.

История не чистится автоматически — это намеренно. Старые данные (старше 90 дней) просто не показываются на графике, но хранятся. Можно добавить VACUUM по расписанию если база вырастет до неприемлемого размера.

9 Биллинг
Внутренний баланс, подписки и обработка платежей
Модель "внутренний баланс"

Прямых рекуррентных списаний с карты нет. Владелец пополняет внутренний баланс (число в рублях в пользовательской БД), затем сам активирует или продлевает подписку — средства списываются с баланса. Плюсы этой модели:

  • Не нужна согласованность платёжных систем на рекуррентные списания
  • Возврат = просто увеличить баланс, не нужна операция возврата у провайдера
  • Пользователь сам контролирует когда платит — нет неожиданных списаний
  • Одна модель работает для всех способов оплаты (карта, крипта, ручное зачисление)
JavaScriptРасчёт стоимости подписки
const PLAN_PRICES = {
  basic: 2000,  // ₽/мес
  super: 5000,  // ₽/мес
};
const EXTRA_SERVER_PRICE = 500; // ₽/мес за каждый сервер сверх первого

function calcPrice(plan, days, serverCount = 1) {
  const base    = PLAN_PRICES[plan] * days / 30;
  const extra   = Math.max(0, serverCount - 1) * EXTRA_SERVER_PRICE * days / 30;
  return Math.round(base + extra);
}
Обработка входящих платежей

Платёжный провайдер присылает webhook при успешном платеже. Сервер:

  • Проверяет HMAC-подпись запроса — без этого любой мог бы сфейкать пополнение
  • Проверяет что платёж с таким ID ещё не обработан (идемпотентность)
  • Добавляет сумму к балансу владельца в БД
  • Логирует транзакцию с временной меткой и ID платежа

Карта / СБП

Через платёжный агрегатор. Автоматический webhook → верификация подписи → зачисление баланса. Пользователь видит пополнение сразу после подтверждения платежа.

USDT TRC20

Ручной процесс. Пользователь переводит на указанный кошелёк, пишет в поддержку с хэшем транзакции. Администратор проверяет в блокчейне и зачисляет через POST /api/admin/balance.

10 OV-сертификат
Что это такое и почему .exe без него выглядит как вирус
Проблема и решение

Когда пользователь скачивает и запускает .exe-файл, Windows SmartScreen проверяет его. Если файл не подписан или подписан дешёвым сертификатом — показывается красное окно "Неизвестный издатель". Кнопки "Запустить" нет — только "Больше сведений" и "Всё равно запустить". Большинство пользователей закроют и не вернутся.

Code Signing сертификат — это криптографическая подпись, встроенная в .exe. Windows проверяет её и видит: файл не изменялся с момента подписания, и мы знаем кем он подписан.

DV, OV, EV — почему это важно

DV (Domain Validation)

Центр сертификации проверяет только владение доменом. Для HTTPS-сайтов — нормально. Для подписи кода — не подходит: имя организации не проверяется, SmartScreen продолжает показывать предупреждение.

OV (Organization Validation)

CA проверяет реальные данные организации или физлица. В подписи отображается название. SmartScreen убирает красное окно после набора репутации (несколько тысяч чистых установок).

EV (Extended Validation)

Максимальная проверка, физический HSM-токен. SmartScreen доверяет сразу без набора репутации. Дороже и сложнее в получении — нужен при выпуске первой версии.

🛡️
SIGNAL использует OV-сертификат. Пользователь при запуске видит имя издателя вместо "Неизвестный". Defender не блокирует файл. Кроме того, подпись гарантирует целостность — если кто-то подменит .exe после публикации, Windows это обнаружит и покажет предупреждение. Это одновременно UX и защита от подмены дистрибутива.
11 Антиспам и безопасность
Как устроена защита от накрутки, флуда и злоупотреблений
Антинакрутка отзывов

Каждый лаунчер при первом запуске генерирует UUID (device_id) и хранит его локально. Этот ID отправляется с каждым отзывом. Сервер проверяет: если device_id уже оставил отзыв на этот сервер — дубль отклоняется. Одно устройство = один голос.

Если пользователь авторизован через Steam — дополнительно проверяется Steam ID64. Смена device_id не помогает если Steam ID уже голосовал. Оба механизма работают параллельно.

🚫
Администратор может забанить конкретный device_id или Steam ID через admin-панель. Забаненный device/аккаунт при попытке оставить отзыв получает 403 с понятным сообщением. Бан хранится в отдельной таблице — легко снять при ошибке.
Rate limiting и защита от флуда

На эндпоинтах логина и регистрации стоит rate limiter по IP. Это стандартная защита от брутфорса — после N попыток за окно времени IP получает 429. bcrypt с cost 12 добавляет ещё один барьер: даже при пропуске rate limit'а каждая проверка занимает ~250ms.

На публичных эндпоинтах (конфиг, отзывы) rate limiting мягче — защита от парсеров, не от пользователей.

Загрузка файлов (иконки, баннеры)

Загрузка через multer с ограничениями: только изображения по MIME-типу, ограничение размера. Файлы сохраняются в директорию /uploads/ с рандомным именем — имя оригинального файла игнорируется, чтобы не было path traversal. Старый файл удаляется при загрузке нового.

Что лаунчер НЕ делает
  • Не модифицирует и не читает файлы DayZ или Steam
  • Не вмешивается в анти-чит (BattlEye и VAC работают независимо)
  • Не хранит пароль Steam — авторизация через официальный OpenID, пароль до нас не доходит
  • Не собирает данные об установленных программах или системе
  • Не делает запросы в фоне когда лаунчер закрыт
12 Оптимизация
Как лаунчер улучшает производительность DayZ на уровне системы

Все настройки оптимизации работают исключительно через официальные Windows API — никакой модификации игровых файлов, никакого вмешательства в анти-чит. По сути это то, что опытный геймер делал бы вручную через диспетчер задач и командную строку, но автоматически и в один клик.

Режим S1 — что он делает под капотом

S1 — это комплексный режим, который применяет несколько системных оптимизаций одновременно в момент запуска игры:

Таймер разрешения

Вызов timeBeginPeriod(1) через Win32 API снижает разрешение системного таймера с дефолтных 15.6ms до 1ms. Это уменьшает джиттер в сетевых пакетах и делает фреймтайм стабильнее. Применяется на время сессии, сбрасывается при выходе.

CPU affinity и планировщик

Процесс DayZ.exe привязывается к определённым ядрам через SetProcessAffinityMask, оставляя часть ядер под систему. Это снижает конкуренцию за кэш L3 между игрой и фоновыми процессами.

Сетевые параметры

Через реестр и netsh временно применяются настройки TCP: отключается Nagle-алгоритм (TcpNoDelay) для уменьшения задержки, корректируется размер буфера приёма. Всё откатывается при закрытии игры.

Параметры запуска

К командной строке DayZ добавляются launch-параметры: -noPause, -noSplash, -malloc=system и другие — в зависимости от конфига. Это стандартные параметры самой игры, не патч.

S1 применяется только когда лаунчер запускает DayZ. Все системные изменения временные — при закрытии игры или лаунчера всё возвращается к исходному состоянию. Лаунчер отслеживает PID процесса DayZ и по его завершению вызывает откат.
Очистка RAM и высокий приоритет

Очистка RAM

Перед запуском игры лаунчер вызывает EmptyWorkingSet для всех некритичных процессов — браузеров, мессенджеров и прочего. Это не убивает процессы, а переводит их страницы памяти в page file, освобождая физическую RAM для DayZ. DayZ жрёт много — каждый свободный гигабайт на счету.

Высокий приоритет процесса

После запуска DayZ.exe лаунчер меняет приоритет процесса через SetPriorityClass(handle, HIGH_PRIORITY_CLASS). Планировщик Windows отдаёт CPU-время высокоприоритетным процессам в первую очередь. Лаунчер при этом сам переключается на ниже нормального — чтобы не конкурировать с игрой.

Ограничение ресурсов лаунчера

В режиме "игра запущена" лаунчер сам себя ограничивает: снижает приоритет своих потоков, уменьшает частоту обновления UI до минимума, отключает фоновые задачи (опрос серверов, кэш обновлений). Цель — отдать максимум ресурсов DayZ.

Мониторинг в фоне

Пока игра запущена, лаунчер отслеживает PID и состояние процесса. Когда DayZ закрывается — автоматически откатывает все изменения приоритетов, таймеров и сетевых параметров, и возобновляет свою нормальную работу.

VPN-детектор и переключатель BattlEye

VPN-детектор

При запуске лаунчер перебирает сетевые адаптеры через GetAdaptersInfo / WMI и ищет признаки VPN-адаптера: тип интерфейса IF_TYPE_PPP или IF_TYPE_TUNNEL, характерные имена драйверов (TAP, WireGuard, OpenVPN и аналоги). Если VPN обнаружен — показывает предупреждение, потому что многие серверы банят VPN-адреса.

Переключатель BattlEye

BattlEye управляется параметром запуска -bepath=... и наличием BE-директории. Когда BattlEye отключён — лаунчер передаёт -noBattlEye в командной строке DayZ. Это нужно для модифицированных серверов, где BE выключен владельцем. На серверах с включённым BE это не поможет — сервер сам проверяет клиента.

⚠️
Отключение BattlEye само по себе не является читом и не нарушает VAC. Это официальный параметр запуска DayZ. Лаунчер показывает предупреждение при попытке подключиться к BE-серверу с отключённым BattlEye — сервер всё равно не пустит, но лучше предупредить заранее.
13 Визуальная система
Как устроен интерфейс: от живой плазмы до режима для дальтоников
Плазменный фон и FFT-визуализация

Плазма — это процедурно-анимированный фон на базе шейдера. Несколько слоёв синус-волн с разными частотами, фазами и скоростями складываются в органичное плавное движение. Цветовая схема и скорость — настраиваемые параметры.

Схема: плазма + аудио
Аудио потокFFT (быстрое преобразование Фурье)массив амплитуд по частотамBass (20–200 Hz)скорость и масштаб волн плазмы
Mid (200–2000 Hz)интенсивность цвета и яркость
High (2000+ Hz)мелкие детали и частота пульсации

FFT вычисляется в отдельном потоке с частотой ~60 раз в секунду. Результат передаётся в рендерер как массив float-значений. Шейдер плазмы читает эти значения и модифицирует параметры волн в реальном времени — фон буквально "дышит" в такт музыке.

Когда музыка не играет — плазма работает в "idle" режиме с медленной спокойной анимацией. Когда пауза — плавно переходит в idle через интерполяцию, без резкого скачка.

Масштаб UI, прозрачность и blur

Масштаб интерфейса

Avalonia имеет встроенную поддержку DPI-независимого масштабирования. Слайдер масштаба в настройках меняет глобальный LayoutTransform корневого контейнера. Изменение применяется мгновенно без перезапуска — всё перерисовывается в рамках следующего layout-прохода Avalonia.

Прозрачность панелей

Каждая панель лаунчера использует полупрозрачный фон через Opacity в XAML-стилях. Слайдер пишет значение в глобальный ресурс (ResourceDictionary), от которого зависят все панели. Все панели реагируют одновременно без перебора элементов.

Blur-эффект

Реализован через BlurEffect в Avalonia или нативный Acrylic (если поддерживается системой и включён в настройках Windows). Применяется к подложке панелей — размывает то что за ними. На слабых GPU можно отключить без потери функциональности.

Режим для дальтоников

Применяет матрицу цветовой коррекции ко всему рендереру. Три пресета: протанопия (красный), дейтеранопия (зелёный), тританопия (синий). Матрица подобрана так чтобы ключевые UI-элементы (статус серверов, уведомления) оставались различимы при любом типе нарушения.

💡
Все визуальные настройки сохраняются в локальный конфиг-файл лаунчера и применяются при следующем запуске. Это не серверные настройки — хранятся только на устройстве пользователя, сервер о них не знает.
Discord Rich Presence и свободное перемещение окна

Discord Activity (Rich Presence)

Реализован через Discord IPC — именованный пайп \\.\pipe\discord-ipc-0 (и до -9 если занят). Лаунчер подключается к пайпу, авторизуется с Application ID, затем отправляет SET_ACTIVITY команды. Обновление — каждые ~15 секунд или при смене состояния (в меню / смотрит сервер / в игре). Discord сам контролирует частоту отображения.

Свободное перетаскивание окна

Лаунчер — окно без системного titlebar (кастомный). По умолчанию такое окно нельзя перетащить кроме как за узкую область. Решение: переопределить HitTest на фоне и пустых областях UI — они возвращают HitTestResult.Caption, что говорит Windows "это titlebar, можно тащить". Кнопки и интерактивные элементы возвращают Client — их перетаскивание не триггерит move.

Смена языка без перезапуска

Локализация через Avalonia ResourceDictionary с XAML-ресурсами для каждого языка. При смене языка в настройках лаунчер меняет активный MergedDictionary в корневых ресурсах приложения. Avalonia автоматически обновляет все Binding'и, которые ссылаются на локализованные ресурсы — перезапуск не нужен.

Кастомные акцентные цвета

Базовая цветовая схема лаунчера задаётся через глобальные ресурсы Avalonia (аналог CSS-переменных). Смена темы или акцентного цвета — это замена значений в ResourceDictionary во время выполнения. Все элементы UI, которые ссылаются на эти ресурсы, перерисовываются автоматически.