Files
minecraft-launcher/doc/cpp/LauncherBackend.md
T
2026-09-03 09:16:56 +03:00

36 KiB
Raw Blame History

LauncherBackend

Обзор класса

LauncherBackend — единственный класс проекта, видимый из QML, и центр всего приложения. Интерфейс лаунчера ничего не знает ни о сети, ни о файлах, ни о процессах: он читает свойства этого класса, вызывает его методы и слушает его сигналы.

Сам бэкенд почти ничего не делает руками. Он владеет двенадцатью сервисами — авторизация, запуск игры, каталоги версий, модлоадеров, Java и сезонных сборок, три установщика, загрузчик паков и переключатель сборок — и отвечает за то, чтобы они работали в правильном порядке. Кроме того, он хранит состояние лаунчера: профили игрока, пользовательские сборки и настройки запуска, которые читает и пишет в файлы папки лаунчера.

Ещё одна его задача — приводить данные к виду, удобному QML. Каталоги отдаются в интерфейс уже сведёнными с локальным состоянием: строка версии знает, скачана ли она, строка сезонной сборки — установлена ли и не устарела ли. Окно показывает статус, не считая ничего само.

Место в проекте и зависимости

Единственный экземпляр создаётся декларативно в Main.qml; в main.cpp он не упоминается.

Владеет двенадцатью сервисами, каждому из которых посвящена своя страница:

Поле Класс Роль
m_auth AuthService вход через Ely.by и офлайн
m_msa MsaAuthService вход через Microsoft
m_launcher GameLauncher запуск JVM с игрой
m_manifest VersionManifestService каталог версий Mojang
m_installer VersionInstaller установка версии игры
m_loaderMeta ModLoaderVersionService списки версий модлоадеров
m_loaderInstaller ModLoaderInstaller установка модлоадера
m_switcher BuildSwitcher смена активной сборки
m_javaMeta JavaRuntimeService каталог сборок Java
m_javaInstaller JavaInstaller установка Java
m_seasonalMeta SeasonalBuildService каталог сезонных сборок
m_packDownloader SeasonalPackDownloader загрузка архива сезонной сборки

Пути ко всем файлам состояния берутся из launcherpaths.h, описания версий — из minecraftversion.h, словарь модлоадеров — из modloader.h, поиск системной Java — из javalocator.md.

Требования сборки: Qt6::Core, Qt6::Gui, Qt6::Network, Qt6::CorePrivate (опосредованно) и Qt6::Quick — класс зарегистрирован в QML-модуле Minecraft_launcher, объявленном в CMakeLists.txt.

Иерархия и роль

Наследует QObject: мета-объектная система, свойства, сигналы и владение по родителю. Объявлен виртуальный деструктор. Виртуальных методов базового класса не переопределяет.

Свойства Q_PROPERTY

Профили и сборки

Свойство Тип READ WRITE NOTIFY Описание
profileNames QStringList profileNames profilesChanged Имена профилей игрока в порядке добавления. Только для чтения; модель выпадающего списка профилей
customBuildNames QStringList customBuildNames customBuildsChanged Имена пользовательских сборок. Только для чтения; модель списка в окне сборок
activeBuildIndex int activeBuildIndex setActiveBuildIndex activeBuildChanged Сборка, которую запускает кнопка игры. Хранится по идентификатору сборки, а не по индексу: удаление соседней записи не должно переназначать активную. Запись в свойство запускает смену сборки
activeBuildName QString activeBuildName activeBuildChanged Имя активной сборки для подписи на кнопке. Только для чтения
installedVersions QStringList installedVersions installedVersionsChanged Версии, реально установленные в папке игры. Только для чтения

Занятость и смена сборки

Свойство Тип READ WRITE NOTIFY Описание
gameRunning bool gameRunning gameRunningChanged Игра запущена. Только для чтения; выключает кнопку запуска
switching bool switching switchChanged Идёт архивация или распаковка .minecraft при смене сборки. Отдельно от busy, потому что на это время блокируется ещё и список сборок. Только для чтения
switchProgress double switchProgress switchChanged Доля выполнения смены сборки от 0 до 1; -1 — итог неизвестен. Только для чтения
switchStage QString switchStage switchChanged Этап смены сборки словами. Только для чтения
switchStatus QString switchStatus switchChanged Строка состояния смены сборки. Только для чтения
busy bool busy busyChanged Лаунчер занят: пока идёт загрузка версии, кнопка запуска гаснет. Только для чтения
microsoftAvailable bool microsoftAvailable Собран ли лаунчер с Qt WebEngine. Константное свойство: без WebEngine окно входа Microsoft показать нечем, и интерфейс не должен предлагать этот путь

Каталог версий

Свойство Тип READ WRITE NOTIFY Описание
versionCatalog QVariantList versionCatalog versionCatalogChanged Объединённый список для выбора версии: установленные, разделитель, затем весь каталог Mojang. Строка содержит поля id, label, category, installed и search. Только для чтения
catalogLoading bool catalogLoading catalogLoadingChanged Идёт загрузка манифеста версий. Только для чтения

Каталог Java

Свойство Тип READ WRITE NOTIFY Описание
javaCatalog QVariantList javaCatalog javaCatalogChanged Сборки Java для окна выбора: скачиваемые из сети плюс те, что уже лежат в папке лаунчера. Строка содержит поля id, label, kind, major, installed, downloadable, lts, sizeMb, detail, coverage и search. Только для чтения
javaCatalogLoading bool javaCatalogLoading javaCatalogLoadingChanged Идёт загрузка каталога Java. Только для чтения

Сезонные сборки

Свойство Тип READ WRITE NOTIFY Описание
seasonalCatalog QVariantList seasonalCatalog seasonalCatalogChanged Готовые сборки с сервера. Строки уже сведены с локальными записями: окно показывает статус, не считая ничего само. Только для чтения
seasonalCatalogLoading bool seasonalCatalogLoading seasonalCatalogLoadingChanged Идёт загрузка каталога сезонных сборок. Только для чтения
seasonalCatalogError QString seasonalCatalogError seasonalCatalogChanged Текст ошибки обращения к серверу сборок; пусто — всё в порядке. Только для чтения
seasonalInstalling bool seasonalInstalling seasonalInstallingChanged Идёт установка сезонной сборки: окно не даёт начать вторую. Только для чтения

Загрузка

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

Свойство Тип READ WRITE NOTIFY Описание
downloading bool downloading downloadChanged Идёт какая-либо загрузка. Только для чтения
downloadProgress double downloadProgress downloadChanged Доля выполнения от 0 до 1; -1 — итог неизвестен. Только для чтения
downloadVersion QString downloadVersion downloadChanged Что именно качается — версия, модлоадер, сборка Java или пак. Только для чтения
downloadStatus QString downloadStatus downloadChanged Строка состояния загрузки. Только для чтения
downloadBytesDone qint64 downloadBytesDone downloadChanged Принято байт. Только для чтения
downloadBytesTotal qint64 downloadBytesTotal downloadChanged Ожидаемый объём в байтах; 0 — неизвестен. Только для чтения

Панель загрузки одна на все четыре источника: свойства отдают числа того установщика или загрузчика, который работает сейчас.

Методы Q_INVOKABLE

Все перечисленные ниже методы вызываются из QML.

Профили

void addProfile(const QString &name, const QString &login, const QString &password, const QString &authType = "offline")

Добавляет профиль игрока. Параметр authType принимает значения offline, elyby и microsoft. Для офлайн-профиля пароль не нужен, для профиля Microsoft не нужны ни логин, ни пароль. Испускает profilesChanged.

void updateProfile(int index, const QString &name, const QString &login, const QString &password, const QString &authType = "offline")

Перезаписывает профиль по индексу теми же полями. Испускает profilesChanged.

QVariantMap profileAt(int index) const

Данные профиля для диалога редактирования: имя, логин, пароль, тип, а также признак наличия действующей сессии Microsoft и ник, полученный при официальной авторизации.

void removeProfile(int index)

Удаляет профиль. Испускает profilesChanged.

Вход через Microsoft

void startMicrosoftLogin(int profileIndex)

Начинает вход в аккаунт Microsoft: испускает microsoftLoginUrlReady с адресом страницы входа.

Значение -1 в profileIndex означает, что вход ещё не привязан к профилю: профиль создастся по нику, который вернут Minecraft Services.

void finishMicrosoftLogin(const QString &code)

Завершает вход по коду авторизации, перехваченному окном браузера. Итог приходит сигналом microsoftLoginSucceeded или microsoftLoginFailed.

void cancelMicrosoftLogin()

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

QVariantMap inspectMicrosoftRedirect(const QString &url) const

Разбирает адрес, на который встроенное окно возвращается после входа. Возвращает карту с полями matched (является ли адрес адресом возврата), code и error.

Разбор живёт в C++, чтобы правила совпадения не разъезжались с теми, по которым сервис сам строит redirect_uri.

Сборки

void addCustomBuild(const QString &name, const QString &serverUrl, const QString &minecraftVersion = QString())

Создаёт пользовательскую сборку. Испускает customBuildsChanged.

void updateCustomBuild(int index, const QVariantMap &fields)

Мержит в сборку только присланные ключи: name, serverUrl, minecraftVersion, loader, loaderVersion, resolvedVersionId. Остальные поля остаются как были — это и позволяет карточке сборки сохранять правки по одному полю за раз.

QVariantMap customBuildAt(int index) const

Данные сборки для карточки редактирования.

QVariantMap customBuildRemovalInfo(int index) const

Что именно потеряется при удалении сборки — для текста предупреждения. Возвращает имя сборки и три признака: есть ли у неё архив, активна ли она сейчас и последняя ли она.

void removeCustomBuild(int index)

Удаляет сборку вместе с её архивом. Испускает customBuildsChanged.

void installCustomBuild(int index)

Докачивает то, чего не хватает выбранной сборке: версию игры и, если он выбран, модлоадер. Вынесено отдельной кнопкой, потому что карточка сборки сохраняет правки по ходу редактирования и установка не должна начинаться сама при каждой правке.

QStringList checkInstallation(int buildIndex) const

Проверка комплектности без запуска — для подсказки в интерфейсе. Пустой список означает, что сборку можно запускать; иначе возвращаются описания недостающих файлов.

Запуск игры

void launchGame(int profileIndex, int buildIndex)

Главная кнопка. Проверяет выбор профиля и версии, комплектность .minecraft, при необходимости авторизуется и стартует игру.

Авторизация асинхронна, поэтому метод возвращается сразу; дальнейший ход виден по сигналам launchProgress, launched, launchError и twoFactorRequired.

void submitTwoFactorCode(const QString &code)

Продолжает прерванный запуск, отдавая одноразовый код двухфакторной аутентификации. Вызывается после сигнала twoFactorRequired.

void cancelPendingLaunch()

Отменяет запуск, остановленный на ожидании кода двухфакторной аутентификации.

void stopGame()

Завершает процесс игры.

Каталог версий

void refreshVersionCatalog(bool force = false)

Обновляет каталог версий. Вызывается при открытии окна выбора версии: свежий кэш отвечает без сети. Параметр force обходит проверку свежести.

bool isVersionInstalled(const QString &versionId) const

Установлена ли версия в папке игры.

void installVersion(const QString &versionId)

Ставит версию игры в фоне. Ход виден по свойствам загрузки.

QVariantMap versionRemovalInfo(const QString &versionId) const

Что потеряется при удалении версии — для текста предупреждения. Возвращает признак установки, занимаемый объём в мегабайтах, список зависящих профилей модлоадеров и список сборок, которые эту версию используют.

void removeVersion(const QString &versionId)

Удаляет файлы версии из versions/. Библиотеки и ресурсы остаются: они общие для всех версий. Испускает installedVersionsChanged.

void cancelDownload()

Отменяет текущую загрузку.

Модлоадеры

QVariantList loaderVersions(const QString &loaderKey, const QString &gameVersion) const

Версии модлоадера для выбранной версии игры. Возвращает список карт с полями version, label, recommended и stable.

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

Параметр loaderKey принимает значения forge, fabric, neoforge и quilt.

void refreshLoaderVersions(const QString &loaderKey, const QString &gameVersion, bool force = false)

Запрашивает обновление списка версий лоадера. Результат приходит сигналом loaderVersionsChanged.

bool loaderVersionsLoading(const QString &loaderKey, const QString &gameVersion) const

Идёт ли сейчас запрос по этой паре. Позволяет интерфейсу отличить «ещё грузим» от «не поддерживается».

void installLoaderForBuild(int index)

Ставит модлоадер, выбранный в сборке, и записывает получившийся профиль в resolvedVersionId.

Настройки

QVariantMap settings() const

Настройки запуска одной картой: gameDir, javaPath, javaRuntime, minMemoryMb, maxMemoryMb, jvmArgs, windowWidth, windowHeight, fullscreen, language и вычисленный resolvedGameDir.

void updateSettings(const QVariantMap &values)

Записывает настройки и сохраняет их на диск. Испускает settingsChanged.

Смену language после записи пробрасывает в Localization — порядок «сохранили → переключили» гарантирует, что выбранный язык переживёт падение сразу после переключения. Сам Localization в settings.json не пишет: файл ведёт только бэкенд.

QStringList detectedJava() const

Пути ко всем java, найденным в системе. Показывается справочной строкой в диалоге настроек.

Сборки Java

void refreshJavaCatalog(bool force = false)

Обновляет каталог сборок Java. Вызывается при открытии окна выбора: свежий кэш отвечает без сети.

void installJavaRuntime(const QString &runtimeId)

Скачивает и распаковывает сборку Java. По завершении испускается javaRuntimeInstalled.

void removeJavaRuntime(const QString &runtimeId)

Удаляет скачанную сборку Java из папки лаунчера.

QVariantMap javaRuntimeInfo(const QString &runtimeId) const

Описание установленной сборки: подпись, версия, путь к java и признак установки. Пустая карта означает, что сборки с таким идентификатором в папке лаунчера нет.

Метод не является привязкой и сам не пересчитывается, когда сборка докачается, — диалог настроек обновляет его по сигналу javaRuntimeInstalled.

int requiredJavaMajor(int buildIndex) const

Минимальная мажорная версия Java для версии игры выбранной сборки; 0 — версия не выбрана. Передаётся в окно выбора Java, чтобы пометить слишком старые сборки.

Сезонные сборки

void refreshSeasonalCatalog(bool force = false)

Обновляет каталог сезонных сборок.

void installSeasonalBuild(const QString &seasonalId)

Ставит или обновляет сборку целиком одной цепочкой: запись сборки, версия игры, модлоадер, Java и файлы.

Порядок шагов жёсткий: сначала сборка делается активной, затем ставится Java — она нужна установщику Forge, — затем модлоадер, затем качается пак и только в конце его файлы раскатываются поверх .minecraft. Раскатывать файлы имеет смысл только когда всё остальное на месте.

Строка каталога копируется на момент старта: обновление списка посреди установки не должно менять то, что ставится. По завершении испускается seasonalInstallFinished.

void cancelSeasonalInstall()

Отменяет установку сезонной сборки.

Папки

void openMinecraftFolder()

Открывает папку модов Minecraft в файловом менеджере системы.

void openGameFolder()

Открывает корневую папку игры в файловом менеджере системы.

Сигналы

Сигналы уведомления свойств

Эти сигналы объявлены как NOTIFY соответствующих свойств; обработчик перечитывает свойство. profilesChanged, customBuildsChanged, activeBuildChanged, switchChanged, installedVersionsChanged, settingsChanged, gameRunningChanged, busyChanged, versionCatalogChanged, catalogLoadingChanged, javaCatalogChanged, javaCatalogLoadingChanged, seasonalCatalogChanged, seasonalCatalogLoadingChanged, seasonalInstallingChanged и downloadChanged.

loaderVersionsChanged(const QString &loaderKey, const QString &gameVersion)

Список версий модлоадера изменился. Параметры сужают событие до конкретной пары, поэтому обработчик обязан сверить их со своим текущим состоянием: обновление может относиться к другой строке лоадера или к прошлой версии игры. Именно так поступает LoaderRow.

События установки

seasonalInstallFinished(const QString &seasonalId, const QString &buildName)

Сезонная сборка установлена и активна — можно запускать игру. Обработчик показывает сообщение пользователю.

javaRuntimeInstalled(const QString &runtimeId)

Сборка Java установлена. Диалог настроек по этому сигналу обновляет подпись выбранной сборки, не переоткрываясь.

Запуск игры

launchProgress(const QString &message)

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

launched(const QString &profileName, const QString &buildName, const QString &serverUrl)

Игра запущена. Обработчик сообщает пользователю, какой профиль и какая сборка стартовали.

launchError(const QString &message)

Запуск не удался либо произошла ошибка, о которой нужно сказать пользователю. Через этот же сигнал сообщается о проблемах записи файлов лаунчера.

twoFactorRequired(const QString &profileName)

Ely.by отклонил пароль с пометкой two factor. Обработчик открывает диалог ввода кода и передаёт введённое значение в submitTwoFactorCode(); отказ должен вызвать cancelPendingLaunch(), иначе запуск останется висеть в ожидании.

gameOutput(const QString &line)

Строка вывода процесса игры. Обработчик пишет её в журнал.

gameFinished(int exitCode, bool crashed)

Игра завершилась. Обработчик сообщает итог: ненулевой код или выставленный crashed показываются как ошибка.

Вход через Microsoft

microsoftLoginUrlReady(const QString &url)

Окну входа Microsoft: открыться на этом адресе. Обработчик создаёт окно (в сборке с Qt WebEngine) и открывает его.

microsoftLoginSucceeded(const QString &playerName)

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

microsoftLoginFailed(const QString &message)

Вход не удался.

microsoftReloginRequired(int profileIndex)

Сессия профиля протухла настолько, что нужен повторный вход руками. Обработчик обычно сразу вызывает startMicrosoftLogin() для этого профиля.

Владение и время жизни

Класс наследует QObject и принимает parent. Экземпляр создаётся декларативно в QML, поэтому временем его жизни управляет движок QML: объект живёт столько же, сколько главное окно.

Все двенадцать сервисов создаются в конструкторе с бэкендом в роли родителя и уничтожаются вместе с ним. Порядок создания важен для двоих: VersionInstaller принимает в конструктор сервис манифеста, а ModLoaderInstaller — сервис версий лоадеров и установщик версий; эти указатели не переходят во владение принимающей стороны.

Кэши каталогов помечены mutable и пересобираются лениво из константных геттеров: QML читает свойства помногу раз за кадр, пока открыт список, и пересборка по каждому чтению обошлась бы дорого.

Потокобезопасность

Только поток GUI. Единственная работа в другом потоке — файловые операции над содержимым .minecraft, и она полностью инкапсулирована в BuildSwitcher: сам бэкенд общается с ним обычными сигналами и слотами.

Доступ из QML

Класс зарегистрирован макросом QML_ELEMENT в модуле Minecraft_launcher, объявленном через qt_add_qml_module в CMakeLists.txt. Имя типа в QML совпадает с именем класса — LauncherBackend.

Из QML доступны все 26 свойств, все 42 метода Q_INVOKABLE и все сигналы, перечисленные выше. Синглтоном тип не объявлен: экземпляр создаётся декларативно в Main.qml и передаётся во вложенные диалоги через их свойство backend. Все диалоги проекта объявляют его как required property var backend.

Объект, созданный из QML, принадлежит движку QML — удалять его из C++ нельзя.

Взаимодействие с другими классами

Вниз, к сервисам. Бэкенд подписан на сигналы всех двенадцати сервисов и сводит их к своим свойствам. Четыре разных источника загрузки — установщик версий, установщик модлоадеров, установщик Java и загрузчик паков — отображаются в одну группу свойств download*, поэтому панель в интерфейсе не различает, кто работает; какой из источников показывать, бэкенд решает сам.

Вверх, к QML. Интерфейс не обращается ни к одному сервису напрямую. Каталоги отдаются уже сведёнными с локальным состоянием: строка версии знает про installed, строка сезонной сборки — про установленную ревизию и доступное обновление.

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

Отдельно предусмотрена миграция: файл versions.json от прежней схемы именования переносится в customBuilds.json при первом запуске после переименования.

Восстановление после сбоя. При старте бэкенд спрашивает у переключателя сборок, не было ли прервано переключение, и предлагает доиграть его.

Внешнее взаимодействие

Собственных сетевых обращений и дочерних процессов у класса нет: всё внешнее взаимодействие делегировано сервисам — сеть у каталогов, установщиков и служб авторизации, процессы у GameLauncher, ModLoaderInstaller и JavaInstaller, файловые операции над .minecraft у BuildSwitcher.

Единственное прямое обращение к системе — открытие папки игры в файловом менеджере методами openMinecraftFolder() и openGameFolder().

Пример использования

Класс предназначен для создания из QML, а не из C++:

import QtQuick
import Minecraft_launcher

Window {
    id: window
    visible: true

    LauncherBackend {
        id: backend

        onLaunchError: (message) => console.warn(message)
        onTwoFactorRequired: (profileName) => twoFactorDialog.open()
        onMicrosoftLoginUrlReady: (url) => window.openMicrosoftLogin(url)
    }

    Button {
        text: backend.activeBuildName
        enabled: !backend.busy && !backend.gameRunning
        onClicked: backend.launchGame(profileBox.currentIndex, backend.activeBuildIndex)
    }
}

При создании этого документа использовался ИИ.