Система SIGNAL

Что умеет SIGNAL

Полная техническая картина: от реального UDP-опроса серверов до архитектуры режима оптимизации S1. Здесь — всё что делает 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

Режим оптимизации S1

Профиль системных твиков для DayZ: GPU preference HIGH, отключение парковки ядер CPU, очистка standby-памяти перед запуском, QoS DSCP-46 для трафика игры, MMCSS-приоритеты потоков. Все изменения обратимы и снимаются при выходе из лаунчера.

GPU HIGHISLCQoS

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

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

JWTbcrypt-12Steam OAuth

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

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

Внутренний балансWebhookRobokassa · Альфа

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

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

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

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

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

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

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

Node.js Backend

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

Avalonia Launcher

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

Edge: CDN + WAAP

Перед бэкендом стоит CDN с WAAP-фильтром: терминация TLS, DDoS-защита, Bot Management, кэш статики. Origin доступен только из доверенных подсетей CDN — прямой стук в обход не проходит.

Схема взаимодействия
┌─────────────────────┐       HTTPS / REST       ┌──────────────────────────────┐
│  Avalonia Launcher  │ ◄────────────────────►   │      CDN + WAAP (edge)       │
│    (.NET 10, Win)   │   (TLS termination,      │   DDoS / Bot Management      │
└─────────────────────┘    кэш статики)          └──────────────────────────────┘
                                                            
                                                            │ origin-pull HTTP, whitelist IP
                                                            
                                          ┌──────────────────────────────────────┐
                                          │       nginx → Node.js cluster        │
                                          │   signal-api + signal-music сервисы  │
                                          └──────────────────────────────────────┘
                                                            
                                                            │ UDP A2S (централизованный опрос)
                                                            
                                          ┌──────────────────────────────────────┐
                                          │          DayZ серверы                │
                                          │         (любые IP:порт)              │
                                          └──────────────────────────────────────┘
                                                            │ read / write
                                                            
                                          ┌──────────────────────────────────────┐
                                          │  локальные SQLite (WAL) + JSON-конфиг │
                                          │   аккаунты / история / отзывы / музыка│
                                          └──────────────────────────────────────┘
Cluster mode — зачем и как

Node.js однопоточный, но на VPS несколько ядер. Бэкенд работает в cluster-режиме: master поднимает воркеров, каждый — полноценный Express-сервер. Под общим nginx живут два сервиса: основной API (signal-api) и музыкальный модуль (signal-music) — у каждого свои воркеры, чтобы тяжёлые операции одного не блокировали другой. При падении воркера master автоматически поднимает новый — zero downtime при любом краше.

💡
Каждый воркер держит собственную копию конфига в памяти и синхронизируется через диск + IPC-сообщения от master. При текущих нагрузках это оптимально — 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 — намеренно медленно, чтобы брутфорс был нерентабелен). Токен живёт 7 дней.

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

Логины в конфигурационном файле, не в БД. Это значит что добавить/удалить администратора — это правка файла и рестарт, не 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-токенов нет — по истечении срока — повторный логин. Это сделано намеренно: меньше сложности, меньше векторов атаки.

⚠️
Администратор может вручную инвалидировать токен через смену 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. Окно несогласованности — время между запросами, обычно секунды.
7 API эндпоинты
Все маршруты, уровни доступа и что они делают
МетодПутьДоступЧто делает
GET/api/servers/globalPublicСписок серверов: глобальный кэш + premium-проекты. Главный эндпоинт лаунчера.
POST/api/auth/loginPublicАутентификация. Возвращает JWT-токен на 7 дней + SSO-cookie для admin/owner-страниц.
POST/api/auth/registerPublicРегистрация пользователя. bcrypt cost 12, email-верификация шестизначным кодом.
GET/api/auth/meJWTТекущий аккаунт: роль, баланс, план, ID проекта, срок подписки.
POST/api/auth/verify-emailPublicПодтверждение email кодом из SMTP-письма. После успеха — финализация регистрации.
GET/api/owner/projectOwner JWTПолучить настройки своего проекта: имя, иконка, баннер, серверы, Discord webhook.
POST/api/owner/projectOwner JWTОбновить настройки своего проекта. Проверка ownership через JWT payload.
GET/api/server/statsPublicПодробная статистика сервера: онлайн, графики 24ч/7д/30д, отзывы, средний онлайн.
POST/api/user/payment/createJWTСоздать платёж через платёжный провайдер. Возвращает URL/реквизиты для оплаты.
POST/api/user/subscription/renewOwner JWTПродлить подписку. Списывает с внутреннего баланса, продлевает срок.
GET/api/reviews/:serverIdPublicОтзывы сервера. Пагинация, средняя оценка, фильтр is_deleted=0.
POST/api/reviews/:serverIdDevice IDОставить отзыв. Один device_id или Steam ID — один отзыв на сервер.
GET/api/reviews/bulkPublicМассовая агрегация лайков/дизлайков по списку server_id для сортировки списка.
GET/api/admin/subscriptionsAdmin JWTСписок всех owner-аккаунтов с балансами, планами, статусами.
POST/api/admin/balance/adjustAdmin JWTРучное зачисление/списание баланса администратором (корректировки, компенсации, спорные платежи).
GET/api/admin/launcher-statsAdmin JWTАктивные пользователи лаунчера за последние 5 мин + счётчик скачиваний установщика.
GET/api/admin/server-loadAdmin JWTНагрузка VPS: CPU по потокам, RAM, канал интернета. Замер по требованию.
🔐
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 Биллинг
Внутренний баланс, подписки и обработка платежей
Модель "внутренний баланс"

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

  • Не нужна согласованность платёжных систем на рекуррентные списания
  • Возврат = просто увеличить баланс, не нужна операция возврата у провайдера
  • Пользователь сам контролирует когда платит — нет неожиданных списаний
  • Одна модель работает для всех способов оплаты (Robokassa, Альфа-эквайринг, ручная корректировка)
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 платежа

Robokassa

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

Альфа-Банк

Интернет-эквайринг АО «Альфа-Банк»: приём карт Visa / MasterCard / МИР напрямую от банка. Callback с верификацией подписи → автоматическое зачисление баланса.

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 Антиспам и безопасность
Как устроена защита от накрутки, флуда и злоупотреблений
Защита клиентского кода

Лаунчер собирается под Windows как single-file .exe с обфускацией коммерческим инструментом класса code-protector. Это означает:

  • Имена классов, методов и полей в скомпилированной сборке переименованы в случайные последовательности — декомпилятор показывает кашу, не читабельную бизнес-логику.
  • Строковые константы (URL, идентификаторы, маркеры) зашифрованы и расшифровываются только в рантайме перед использованием.
  • Поток управления (control flow) переплетён — статический анализ путей выполнения сильно затруднён.
  • Антиотладка и проверки целостности — попытка подцепиться отладчиком или подменить байт-код приводит к мгновенному выходу процесса.

Цель защиты — не сделать reverse engineering невозможным (это недостижимо), а сделать его экономически нецелесообразным. Раскрутить хорошо обфусцированный .NET-бинарь до уровня «понять архитектуру и достать ключи» — это часы-дни ручной работы специалиста, а не пара минут в dnSpy.

🔐
Любые секреты, попадающие в публичные репозитории случайно, ротируются автоматически по расписанию через скрипт rotate-secrets + ручной триггер. История ключей не хранится — старые значения становятся невалидными мгновенно после ротации.
Антинакрутка отзывов

Каждый лаунчер при первом запуске генерирует 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 Визуальная система
Как устроен интерфейс: от живой плазмы до режима для дальтоников
Плазменный фон (GPU-шейдер)

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

Рендерится напрямую на GPU через Avalonia Skia backend, цикл анимации синхронизирован с VBlank монитора. Никакой нагрузки на CPU — даже на ноутбуке с интегрированной графикой это менее 1% утилизации GPU.

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

Масштаб 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, которые ссылаются на эти ресурсы, перерисовываются автоматически.