# SeasonalBuildService `seasonalbuildservice.h`, `seasonalbuildservice.cpp` Каталог сезонных сборок, то есть готовых паков, которые команда публикует на своём файловом сервере. Сервис читает `/index.json`, хранит копию в `/cache/seasonal_builds.json` и отвечает из неё 15 минут. Экземпляр держит [LauncherBackend](LauncherBackend.md). Он же сводит строки каталога с установленными сборками и ставит выбранную. ## Адрес сервера По умолчанию `https://launcher-builds.galeonworx.com`. Переопределяется ключом `seasonalBaseUrl` в `settings.json`, например чтобы проверить установку на локальном HTTP-сервере. Бэкенд передаёт адрес через `setBaseUrl()` при старте и перед каждым `refreshSeasonalCatalog()`. Если адрес сменился, накопленные данные сбрасываются, а кэш с другим `baseUrl` не читается: ссылки в нём ведут на старый сервер. Пустой адрес или адрес без хоста — сразу ошибка. ## Формат index.json Это контракт с теми, кто публикует сборки. ```json { "formatVersion": 1, "builds": [ { "id": "season-5", "name": "Сезон 5: Пустоши", "revision": 3, "minecraftVersion": "1.20.1", "loader": "forge", "loaderVersion": "47.4.0", "modCount": 142, "seasonStart": "2026-09-01", "seasonEnd": "", "serverUrl": "play.example.com:25565", "javaMajor": 17, "description": "…", "archive": { "url": "https://launcher-builds.galeonworx.com/season-5/r3.zip", "size": 734003200, "sha256": "…64 hex…" } } ] } ``` | Поле | Обязательно | Правила | |------|-------------|---------| | `formatVersion` | да | только `1`. Другое значение — весь каталог отклоняется с понятным текстом (если версия больше, пользователю предложат обновить лаунчер) | | `id` | да | `[a-z0-9][a-z0-9._-]{0,63}`. По нему лаунчер узнаёт уже установленную сборку, менять его нельзя | | `name` | да | | | `revision` | да | > 0. Увеличивается при каждой публикации. Если у игрока ревизия меньше, ему покажут «доступно обновление» | | `minecraftVersion` | да | без `/`, `\`, `:`, не длиннее 128 символов | | `loader` | нет | пусто (ваниль) или `forge`, `fabric`, `neoforge`, `quilt` | | `loaderVersion` | если есть `loader` | | | `modCount` | нет | для таблицы | | `seasonStart`, `seasonEnd` | нет | ISO-даты. Пустой `seasonEnd` значит, что сезон идёт | | `serverUrl` | нет | адрес игрового сервера для автоподключения | | `javaMajor` | нет | 0 или нет поля — определить по версии игры | | `description` | нет | показывается под таблицей | | `archive.url` | да | `http` или `https`, **та же схема, хост и порт, что у `baseUrl`** | | `archive.size` | да | ≥ 0 | | `archive.sha256` | да | 64 hex-символа | Записи, которые не проходят проверку, молча пропускаются. Если не осталось ни одной, это считается испорченным ответом, и хороший кэш не затирается. Требование «тот же хост» не даёт подложить в каталог ссылку на чужой сервер. Пак распаковывается прямо в `.minecraft`, поэтому качать его откуда попало нельзя. Пак — это zip, содержимое которого раскладывается в корень `.minecraft` (`mods/`, `config/` и т. п.). Файлы в `versions/`, `libraries/`, `assets/`, `runtime/` и `launcher_profiles.json` запрещены, пак с ними не установится. Подробнее — [BuildArchiveWorker](BuildArchiveWorker.md). Порядок в каталоге: сначала идущие сезоны, потом по дате начала (новые сверху), потом по имени. ## Интерфейс - `ensureLoaded(callback, forceRefresh)` — как у [VersionManifestService](VersionManifestService.md), только кэш живёт 15 минут. - `builds()`, `find(id)`, `hasData()`, `isRefreshing()`. - `lastError()` — текст последней ошибки сервера или пустая строка. Окно сезонных сборок показывает его на месте таблицы. - Сигналы `buildsChanged()` (в том числе после неудачного запроса, чтобы обновился `lastError`) и `refreshingChanged()`. --- При создании этого документа использовался ИИ.