Система 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: приоритет производительных ядер CPU, освобождение резервной памяти перед запуском, приоритизация сетевого трафика игры. Всё через штатные Windows API, обратимо и снимается при выходе из лаунчера.

CPU boostRAM prepQoS

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

Два независимых 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/launcher-config возвращает весь список серверов, premium-проекты и настройки. Кэшируется локально на случай офлайна.
  • Получение онлайна — лаунчер запрашивает актуальный кэш онлайна через GET /api/servers/global. Данные уже готовы — кластер обновляет их в фоне каждые 10 минут, без нагрузки на клиента.
  • Восстановление сессии — если в хранилище есть JWT-токен, лаунчер делает GET /api/auth/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/auth/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-запросы — на отдельном query-порту. Чаще всего это X+1 (2302 → 2303), но не все хостинги придерживаются этого правила. Поэтому кластер не полагается на одно значение, а перебирает набор кандидатных query-портов и берёт тот, что реально ответил — владельцу достаточно указать игровой порт.
Как работает автоустановка модов

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

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

Вся конфигурация серверов живёт в одном JSON-файле на сервере. Лаунчер получает его при каждом запуске через GET /api/launcher-config (ответ отдаётся с Cache-Control: no-store — правки в админке/ЛК видны в лаунчере сразу). Этот файл содержит: список всех серверов (обычных и 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/launcher-config. Окно несогласованности — время между запросами, обычно секунды.
6 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 и наоборот. Это намеренно — нет никакого "суперпользователя" который проходит везде.
7 Кэш и фоновый мониторинг
Что живёт в памяти воркера и как работает цикл истории
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 по расписанию если база вырастет до неприемлемого размера.

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

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

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

9 Доставка обновлений и целостность
Как лаунчер обновляется и как проверяется, что дистрибутив не подменён
Двухкомпонентная схема: апдейтер + лаунчер

Пользователь запускает не сам лаунчер, а отдельную лёгкую программу-апдейтер — это единственная и неизменная точка входа. Апдейтер проверяет наличие новой версии, при необходимости докачивает её и затем запускает лаунчер. Разделение даёт два эффекта: обновление происходит до старта основного приложения (не нужно перезапускаться «на лету»), и стабильная точка входа со временем накапливает доверие в механизмах репутации ОС.

Стабильная точка входа

Апдейтер не меняется от релиза к релизу. Его бинарник стабилен, поэтому репутация у Windows накапливается на нём и не сбрасывается с каждой новой сборкой лаунчера.

SHA-256 в манифесте

Манифест версии (/launcher/version) отдаёт версию, URL, размер и SHA-256 сборки. Хеш считается сервером по самому файлу — целостность скачанного дистрибутива проверяема, любая подмена «по дороге» ловится по несовпадению хеша.

Защита кода в рантайме

Сам лаунчер защищён обфускацией с проверкой целостности (см. раздел 10): модификация сборки обнаруживается, и подменённый код не выполняется. Это слой поверх проверки хеша на скачивании.

🛡️
Целостность держится на двух независимых слоях: SHA-256 манифеста подтверждает, что скачан именно опубликованный файл, а проверки целостности внутри защищённой сборки ловят попытку модификации уже установленного лаунчера. Точка входа (апдейтер) при этом стабильна и предсказуема для систем репутации.
10 Антиспам и безопасность
Как устроена защита от накрутки, флуда и злоупотреблений
Защита клиентского кода

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

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

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

🔐
Чувствительные поля локального конфига лаунчера шифруются через Windows DPAPI — ключ привязан к учётной записи пользователя и не хранится в коде. Серверные секреты живут в окружении вне репозитория. Клиентский ключ доступа к API — не криптографический секрет, а маркер запроса: реальный барьер стоит на edge-уровне (WAAP), который валидирует запрос ещё до origin.
Антинакрутка отзывов

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

Привязка к Steam-аккаунту (Steam ID64) добавляет второй, более сильный слой проверки поверх device_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, пароль до нас не доходит
  • Не собирает данные об установленных программах или системе
  • Не делает запросы в фоне когда лаунчер закрыт
11 Оптимизация
Как лаунчер улучшает производительность DayZ на уровне системы

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

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

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

Приоритет вычислений

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

Сетевой профиль игры

Трафику игры назначается приоритетная политика качества обслуживания (QoS), а сетевой профиль нормализуется — это снижает джиттер на загруженном канале. Применяется адресно к игре, а не ко всей системе.

Подготовка памяти

Перед стартом игры освобождается резервная (standby) память — один проход, не фоновый цикл. DayZ требователен к RAM, и свободный объём на старте уменьшает подгрузки во время сессии.

Полная обратимость

Лаунчер отслеживает PID процесса DayZ. Как только игра закрывается — все системные изменения снимаются и система возвращается в исходное состояние. Ничего не остаётся «висеть» после выхода.

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

Пока DayZ запущен, лаунчер уходит на второй план и отдаёт ресурсы игре:

Самоограничение

Лаунчер снижает собственный приоритет, урезает частоту обновления UI до минимума и отключает фоновые задачи (опрос серверов, проверку обновлений) — чтобы не конкурировать с игрой за CPU и сеть.

Мониторинг сессии

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

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

Рендерится на GPU через Avalonia Skia backend, анимация идёт в составе кадрового цикла Avalonia. Нагрузка на CPU минимальна; для слабых машин и работы от батареи плазму можно полностью отключить (см. ниже).

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

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

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

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

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

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

Blur-эффект

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

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

Переключатель в настройках заменяет акцентный цвет интерфейса на оттенок из дальтоник-безопасной палитры (синий ~#56B4E9). Ключевые элементы — статусы серверов, уведомления, активные кнопки — становятся различимы при нарушениях цветовосприятия, без перекраски всего интерфейса.

💡
Все визуальные настройки сохраняются в локальный конфиг-файл лаунчера и применяются при следующем запуске. Это не серверные настройки — хранятся только на устройстве пользователя, сервер о них не знает.
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, которые ссылаются на эти ресурсы, перерисовываются автоматически.