1 parent
1943996608
commit
f10b654953
34 files changed
+2099
-4576
No files matched your search
+310
-488
@@ -1,531 +1,353 @@
|
||||
# LauncherBackend
|
||||
|
||||
## Обзор класса
|
||||
`launcherbackend.h`, `launcherbackend.cpp`
|
||||
|
||||
`LauncherBackend` — единственный класс проекта, видимый из QML, и центр всего приложения. Интерфейс
|
||||
лаунчера ничего не знает ни о сети, ни о файлах, ни о процессах: он читает свойства этого класса,
|
||||
вызывает его методы и слушает его сигналы.
|
||||
Через этот класс QML работает со всем остальным кодом. Интерфейс читает его свойства, вызывает
|
||||
методы и слушает сигналы, а к сервисам напрямую не обращается.
|
||||
|
||||
Сам бэкенд почти ничего не делает руками. Он владеет двенадцатью сервисами — авторизация, запуск
|
||||
игры, каталоги версий, модлоадеров, Java и сезонных сборок, три установщика, загрузчик паков и
|
||||
переключатель сборок — и отвечает за то, чтобы они работали в правильном порядке. Кроме того, он
|
||||
хранит состояние лаунчера: профили игрока, пользовательские сборки и настройки запуска, которые
|
||||
читает и пишет в файлы папки лаунчера.
|
||||
Сам бэкенд почти ничего не скачивает и не запускает. Он хранит профили, сборки и настройки,
|
||||
создаёт двенадцать сервисов и следит, чтобы они не мешали друг другу: нельзя ставить Java во время
|
||||
смены сборки, удалять версию, пока она качается, или запускать игру посреди установки модлоадера.
|
||||
Ещё он сводит каталоги из сети с тем, что лежит на диске, чтобы окнам не пришлось этого делать.
|
||||
|
||||
Ещё одна его задача — приводить данные к виду, удобному QML. Каталоги отдаются в интерфейс уже
|
||||
сведёнными с локальным состоянием: строка версии знает, скачана ли она, строка сезонной сборки —
|
||||
установлена ли и не устарела ли. Окно показывает статус, не считая ничего само.
|
||||
Экземпляр один, его объявляет [Main.qml](../qml/Main.md). Во вложенные окна он передаётся через
|
||||
`required property var backend`. Тип зарегистрирован через `QML_ELEMENT` в модуле `BiLauncher`;
|
||||
синглтоном он не сделан.
|
||||
|
||||
## Место в проекте и зависимости
|
||||
## Сервисы
|
||||
|
||||
Единственный экземпляр создаётся декларативно в [Main.qml](../qml/Main.md); в `main.cpp` он не
|
||||
упоминается.
|
||||
Все создаются в конструкторе с бэкендом в роли родителя.
|
||||
|
||||
Владеет двенадцатью сервисами, каждому из которых посвящена своя страница:
|
||||
|
||||
| Поле | Класс | Роль |
|
||||
|------|-------|------|
|
||||
| `m_auth` | [AuthService](AuthService.md) | вход через Ely.by и офлайн |
|
||||
| Поле | Класс | Зачем |
|
||||
|------|-------|-------|
|
||||
| `m_auth` | [AuthService](AuthService.md) | Ely.by, офлайн, загрузка authlib-injector |
|
||||
| `m_msa` | [MsaAuthService](MsaAuthService.md) | вход через Microsoft |
|
||||
| `m_launcher` | [GameLauncher](GameLauncher.md) | запуск JVM с игрой |
|
||||
| `m_launcher` | [GameLauncher](GameLauncher.md) | запуск JVM |
|
||||
| `m_manifest` | [VersionManifestService](VersionManifestService.md) | каталог версий Mojang |
|
||||
| `m_installer` | [VersionInstaller](VersionInstaller.md) | установка версии игры |
|
||||
| `m_loaderMeta` | [ModLoaderVersionService](ModLoaderVersionService.md) | списки версий модлоадеров |
|
||||
| `m_loaderMeta` | [ModLoaderVersionService](ModLoaderVersionService.md) | версии модлоадеров |
|
||||
| `m_loaderInstaller` | [ModLoaderInstaller](ModLoaderInstaller.md) | установка модлоадера |
|
||||
| `m_switcher` | [BuildSwitcher](BuildSwitcher.md) | смена активной сборки |
|
||||
| `m_javaMeta` | [JavaRuntimeService](JavaRuntimeService.md) | каталог сборок Java |
|
||||
| `m_switcher` | [BuildSwitcher](BuildSwitcher.md) | смена активной сборки, раскатка пака |
|
||||
| `m_javaMeta` | [JavaRuntimeService](JavaRuntimeService.md) | каталог Java |
|
||||
| `m_javaInstaller` | [JavaInstaller](JavaInstaller.md) | установка Java |
|
||||
| `m_seasonalMeta` | [SeasonalBuildService](SeasonalBuildService.md) | каталог сезонных сборок |
|
||||
| `m_packDownloader` | [SeasonalPackDownloader](SeasonalPackDownloader.md) | загрузка архива сезонной сборки |
|
||||
|
||||
Пути ко всем файлам состояния берутся из [launcherpaths.h](launcherpaths.md), описания версий — из
|
||||
[minecraftversion.h](minecraftversion.md), словарь модлоадеров — из [modloader.h](modloader.md),
|
||||
поиск системной Java — из [javalocator.md](javalocator.md).
|
||||
`VersionInstaller` получает указатель на сервис манифеста, `ModLoaderInstaller` — на сервис версий
|
||||
модлоадеров и на установщик версий. Владельцем этих объектов остаётся бэкенд.
|
||||
|
||||
Требования сборки: `Qt6::Core`, `Qt6::Gui`, `Qt6::Network`, `Qt6::CorePrivate` (опосредованно) и
|
||||
`Qt6::Quick` — класс зарегистрирован в QML-модуле `Minecraft_launcher`, объявленном в
|
||||
`CMakeLists.txt`.
|
||||
## Что происходит при старте
|
||||
|
||||
## Иерархия и роль
|
||||
Конструктор делает следующее, порядок важен:
|
||||
|
||||
Наследует `QObject`: мета-объектная система, свойства, сигналы и владение по родителю. Объявлен
|
||||
виртуальный деструктор. Виртуальных методов базового класса не переопределяет.
|
||||
1. `LauncherPaths::ensureRootExists()` создаёт папку лаунчера и проверяет, что в неё можно писать.
|
||||
2. `loadSettings()` заполняет настройки значениями по умолчанию и накладывает сверху
|
||||
`settings.json`.
|
||||
3. `migrateLegacyBuildsFile()` переносит старый `versions.json` в `customBuilds.json`. Это должно
|
||||
случиться до следующего шага, иначе поверх старых данных появится пустой файл.
|
||||
4. `loadData()` читает профили и сборки и сортирует их по `id`.
|
||||
5. `ensureDataFiles()` создаёт недостающие файлы.
|
||||
6. Если `BuildSwitcher` нашёл журнал оборванного переключения, бэкенд сразу продолжает его через
|
||||
`resumeInterrupted()`. Пользователю ничего не предлагается. Если начать переключение заново,
|
||||
оно сначала заархивирует полуразобранную `.minecraft` поверх целого архива прежней сборки.
|
||||
7. Подписка на сигналы сервисов.
|
||||
|
||||
## Свойства Q_PROPERTY
|
||||
Все проблемы, найденные по пути (битый JSON, папка без права записи, пропущенные записи сборок),
|
||||
копятся в `m_storageIssues` и уходят одним `launchError` через `QTimer::singleShot(0)`: к этому
|
||||
моменту QML уже подписан на сигнал.
|
||||
|
||||
### Профили и сборки
|
||||
## Файлы на диске
|
||||
|
||||
| Свойство | Тип | READ | WRITE | NOTIFY | Описание |
|
||||
|----------|-----|------|-------|--------|----------|
|
||||
| `profileNames` | `QStringList` | `profileNames` | — | `profilesChanged` | Имена профилей игрока в порядке добавления. Только для чтения; модель выпадающего списка профилей |
|
||||
| `customBuildNames` | `QStringList` | `customBuildNames` | — | `customBuildsChanged` | Имена пользовательских сборок. Только для чтения; модель списка в окне сборок |
|
||||
| `activeBuildIndex` | `int` | `activeBuildIndex` | `setActiveBuildIndex` | `activeBuildChanged` | Сборка, которую запускает кнопка игры. Хранится по идентификатору сборки, а не по индексу: удаление соседней записи не должно переназначать активную. Запись в свойство запускает смену сборки |
|
||||
| `activeBuildName` | `QString` | `activeBuildName` | — | `activeBuildChanged` | Имя активной сборки для подписи на кнопке. Только для чтения |
|
||||
| `installedVersions` | `QStringList` | `installedVersions` | — | `installedVersionsChanged` | Версии, реально установленные в папке игры. Только для чтения |
|
||||
Все пути берутся из [launcherpaths.h](launcherpaths.md).
|
||||
|
||||
### Занятость и смена сборки
|
||||
- `settings.json` — объект с настройками;
|
||||
- `profiles.json` — массив профилей;
|
||||
- `customBuilds.json` — массив сборок.
|
||||
|
||||
| Свойство | Тип | 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 показать нечем, и интерфейс не должен предлагать этот путь |
|
||||
Если файла нет, это первый запуск. Если он не читается как JSON или в нём не тот тип (объект вместо
|
||||
массива и наоборот), файл переименовывается в `<имя>.bak`, а на его месте создаётся пустой.
|
||||
Данные из `.bak` можно потом достать руками.
|
||||
|
||||
### Каталог версий
|
||||
Перед каждой записью снова вызывается `ensureRootExists()`: папку могли удалить, пока лаунчер
|
||||
работал, и без этой проверки сохранения пропадали бы без ошибки. Пишется всё через обычный `QFile`,
|
||||
кроме `builds/<id>/season.json`, для которого используется `QSaveFile`.
|
||||
|
||||
| Свойство | Тип | 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`.
|
||||
Пароли Ely.by и токены лежат в `profiles.json` открытым текстом.
|
||||
|
||||
### Настройки
|
||||
|
||||
#### QVariantMap settings() const
|
||||
|
||||
Настройки запуска одной картой: `gameDir`, `javaPath`, `javaRuntime`, `minMemoryMb`,
|
||||
`maxMemoryMb`, `jvmArgs`, `windowWidth`, `windowHeight`, `fullscreen`, `language` и вычисленный
|
||||
`resolvedGameDir`.
|
||||
|
||||
#### void updateSettings(const QVariantMap &values)
|
||||
|
||||
Записывает настройки и сохраняет их на диск. Испускает `settingsChanged`.
|
||||
|
||||
Смену `language` после записи пробрасывает в [Localization](Localization.md) — порядок
|
||||
«сохранили → переключили» гарантирует, что выбранный язык переживёт падение сразу после
|
||||
переключения. Сам `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](../qml/LoaderRow.md).
|
||||
|
||||
### События установки
|
||||
|
||||
#### 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](VersionInstaller.md) принимает в
|
||||
конструктор сервис манифеста, а [ModLoaderInstaller](ModLoaderInstaller.md) — сервис версий
|
||||
лоадеров и установщик версий; эти указатели не переходят во владение принимающей стороны.
|
||||
|
||||
Кэши каталогов помечены `mutable` и пересобираются лениво из константных геттеров: QML читает
|
||||
свойства помногу раз за кадр, пока открыт список, и пересборка по каждому чтению обошлась бы
|
||||
дорого.
|
||||
|
||||
## Потокобезопасность
|
||||
|
||||
Только поток GUI. Единственная работа в другом потоке — файловые операции над содержимым
|
||||
`.minecraft`, и она полностью инкапсулирована в [BuildSwitcher](BuildSwitcher.md): сам бэкенд
|
||||
общается с ним обычными сигналами и слотами.
|
||||
|
||||
## Доступ из QML
|
||||
|
||||
Класс зарегистрирован макросом `QML_ELEMENT` в модуле `Minecraft_launcher`, объявленном через
|
||||
`qt_add_qml_module` в `CMakeLists.txt`. Имя типа в QML совпадает с именем класса —
|
||||
`LauncherBackend`.
|
||||
|
||||
Из QML доступны все 26 свойств, все 42 метода `Q_INVOKABLE` и все сигналы, перечисленные выше.
|
||||
Синглтоном тип не объявлен: экземпляр создаётся декларативно в [Main.qml](../qml/Main.md) и
|
||||
передаётся во вложенные диалоги через их свойство `backend`. Все диалоги проекта объявляют его как
|
||||
`required property var backend`.
|
||||
|
||||
Объект, созданный из QML, принадлежит движку QML — удалять его из C++ нельзя.
|
||||
|
||||
## Взаимодействие с другими классами
|
||||
|
||||
**Вниз, к сервисам.** Бэкенд подписан на сигналы всех двенадцати сервисов и сводит их к своим
|
||||
свойствам. Четыре разных источника загрузки — установщик версий, установщик модлоадеров,
|
||||
установщик Java и загрузчик паков — отображаются в одну группу свойств `download*`, поэтому панель
|
||||
в интерфейсе не различает, кто работает; какой из источников показывать, бэкенд решает сам.
|
||||
|
||||
**Вверх, к QML.** Интерфейс не обращается ни к одному сервису напрямую. Каталоги отдаются уже
|
||||
сведёнными с локальным состоянием: строка версии знает про `installed`, строка сезонной сборки —
|
||||
про установленную ревизию и доступное обновление.
|
||||
|
||||
**Состояние на диске.** Профили, сборки и настройки читаются при создании и пишутся при каждом
|
||||
изменении. Отсутствие файла — норма (первый запуск), а повреждённое содержимое отводится в файл с
|
||||
расширением `.bak`, чтобы рабочий файл создался заново. Проблемы хранилища, замеченные на старте,
|
||||
накапливаются и показываются одним сообщением, когда интерфейс уже подключился к сигналам.
|
||||
|
||||
Отдельно предусмотрена миграция: файл `versions.json` от прежней схемы именования переносится в
|
||||
`customBuilds.json` при первом запуске после переименования.
|
||||
|
||||
**Восстановление после сбоя.** При старте бэкенд спрашивает у переключателя сборок, не было ли
|
||||
прервано переключение, и предлагает доиграть его.
|
||||
|
||||
## Внешнее взаимодействие
|
||||
|
||||
Собственных сетевых обращений и дочерних процессов у класса нет: всё внешнее взаимодействие
|
||||
делегировано сервисам — сеть у каталогов, установщиков и служб авторизации, процессы у
|
||||
[GameLauncher](GameLauncher.md), [ModLoaderInstaller](ModLoaderInstaller.md) и
|
||||
[JavaInstaller](JavaInstaller.md), файловые операции над `.minecraft` у
|
||||
[BuildSwitcher](BuildSwitcher.md).
|
||||
|
||||
Единственное прямое обращение к системе — открытие папки игры в файловом менеджере методами
|
||||
`openMinecraftFolder()` и `openGameFolder()`.
|
||||
|
||||
## Пример использования
|
||||
|
||||
Класс предназначен для создания из QML, а не из C++:
|
||||
| Ключ | По умолчанию | Что это |
|
||||
|------|--------------|---------|
|
||||
| `gameDir` | пусто | папка игры; пусто — `LauncherPaths::defaultMinecraftDir()` |
|
||||
| `javaPath` | пусто | путь к java, заданный руками |
|
||||
| `javaRuntime` | пусто | id сборки Java из `<root>/java`; важнее `javaPath` |
|
||||
| `minMemoryMb`, `maxMemoryMb` | 512, 4096 | `-Xms`, `-Xmx` |
|
||||
| `jvmArgs` | пусто | дополнительные аргументы JVM одной строкой, делятся по пробелам |
|
||||
| `windowWidth`, `windowHeight` | 0, 0 | размер окна игры; 0 — не передавать |
|
||||
| `fullscreen` | `false` | |
|
||||
| `language` | `system` | язык интерфейса, см. [Localization](Localization.md) |
|
||||
| `activeBuildId` | 0 | `id` активной сборки |
|
||||
| `seasonalBaseUrl` | `https://launcher-builds.galeonworx.com` | сервер сезонных сборок |
|
||||
|
||||
`seasonalBaseUrl` в интерфейсе нет. Он нужен, чтобы проверять установку против локального
|
||||
HTTP-сервера. Хвостовые слэши отрезаются.
|
||||
|
||||
Имя и версия лаунчера для `${launcher_name}` и `${launcher_version}` в настройках не хранятся: они
|
||||
берутся из `QCoreApplication`, то есть из `project(... VERSION)` в CMake. Ключи `launcherName` и
|
||||
`launcherVersion`, оставшиеся в старых `settings.json`, при загрузке выбрасываются и пропадают из
|
||||
файла при следующем сохранении.
|
||||
|
||||
`settings()` возвращает всю карту и добавляет к ней вычисленный `resolvedGameDir`.
|
||||
`updateSettings()` вливает присланные ключи, сохраняет файл и испускает `settingsChanged`. Если
|
||||
сменился язык, после записи вызывается `Localization::setLanguage()`: так выбор сохранится, даже если
|
||||
лаунчер упадёт сразу после переключения. Если сменилась папка игры, перестраивается каталог версий.
|
||||
|
||||
## Профили
|
||||
|
||||
Профиль хранит имя, логин, пароль, тип входа (`offline`, `elyby`, `microsoft`; всё прочее читается
|
||||
как `offline`) и полученные при входе данные: токены, UUID, ник с сервера, `xuid`, срок жизни токена.
|
||||
|
||||
- `addProfile(name, login, password, authType)` — добавить.
|
||||
- `updateProfile(index, ...)` — перезаписать. Если у профиля Ely.by поменялись логин или пароль,
|
||||
сохранённый токен сбрасывается. Если поменялся тип входа, сбрасывается всё, что было получено
|
||||
при входе. Переименование профиля Microsoft сессию не трогает.
|
||||
- `profileAt(index)` — `name`, `login`, `password`, `authType`, `resolvedName` и
|
||||
`hasMicrosoftSession` (есть ли refresh-токен).
|
||||
- `removeProfile(index)` — удалить.
|
||||
|
||||
Все испускают `profilesChanged`. Свойство `profileNames` отдаёт имена в порядке `id`.
|
||||
|
||||
## Сборки
|
||||
|
||||
Сборка — это имя, адрес сервера для автоподключения, версия Minecraft и, если выбран, модлоадер с
|
||||
версией. После установки модлоадера в `resolvedVersionId` записывается id профиля в `versions/`,
|
||||
который на самом деле запускается. У сезонных сборок есть ещё `seasonalId` и `seasonalRevision`.
|
||||
|
||||
Какую папку из `versions/` запускать, решает `launchVersionIdOf()`: сначала `resolvedVersionId`,
|
||||
потом `minecraftVersion`, а если и её нет, то имя сборки. Последний вариант остался от старых
|
||||
записей, у которых версия была записана в имени.
|
||||
|
||||
- `addCustomBuild(name, serverUrl, minecraftVersion)` — создать и сразу начать ставить версию, если
|
||||
она указана и её нет на диске. Если версия пустая (так создаёт сборку кнопка «+ Новая сборка»),
|
||||
ничего не ставится.
|
||||
- `updateCustomBuild(index, fields)` — изменить только переданные ключи. Карточка сборки сохраняет
|
||||
правки по одному полю, поэтому установка здесь не запускается. Если сменилась версия игры, а
|
||||
`loaderVersion` в том же вызове не прислали, `loaderVersion` и `resolvedVersionId` сбрасываются;
|
||||
сам `loader` остаётся.
|
||||
- `customBuildAt(index)` — все поля плюс `launchVersionId`.
|
||||
- `customBuildRemovalInfo(index)` — `name`, `hasArchive`, `active`, `lastOne` для текста
|
||||
предупреждения.
|
||||
- `removeCustomBuild(index)` — удалить запись и архив. Пока лаунчер занят или игра запущена,
|
||||
отказывает. Если удалили активную, активной становится первая из оставшихся, и запускается
|
||||
переключение.
|
||||
- `installCustomBuild(index)` — поставить, чего не хватает. Для ванильной сборки это версия игры,
|
||||
для сборки с модлоадером — модлоадер вместе с версией. Вызывает `installLoaderForBuild()`.
|
||||
- `installLoaderForBuild(index)` — то же самое; по завершении записывает `resolvedVersionId`.
|
||||
- `checkInstallation(index)` — список того, чего не хватает для запуска; пустой список значит, что
|
||||
всё на месте.
|
||||
|
||||
### Активная сборка
|
||||
|
||||
`activeBuildIndex` хранится в настройках как `activeBuildId`, а не как индекс. Если этот `id`
|
||||
пропал, активной считается первая сборка.
|
||||
|
||||
Запись в свойство запускает `BuildSwitcher::switchTo()`: содержимое `.minecraft` уезжает в архив
|
||||
прежней сборки, на его место распаковывается архив новой. Смена отклоняется, если уже идёт другая
|
||||
смена, запущена игра или работает любой установщик. `activeBuildId` меняется только в обработчике
|
||||
`BuildSwitcher::finished`, поэтому после неудачи активной остаётся прежняя сборка.
|
||||
|
||||
Пока идёт смена, выставлено `switching`, а ход виден в `switchProgress` (от 0 до 1, или −1, пока
|
||||
объём неизвестен), `switchStage` и `switchStatus`.
|
||||
|
||||
## Версии игры
|
||||
|
||||
- `versionCatalog` — плоский список строк `{id, type, category, label, installed, search}` в порядке
|
||||
манифеста, новые сверху. `category` — `release`, `snapshot`, `old_beta`, `old_alpha` или `other`.
|
||||
В конец добавлены установленные версии, которых нет в манифесте (профили модлоадеров), по
|
||||
алфавиту, с категорией `other`. Разделы и поиск делает
|
||||
[VersionPickerDialog](../qml/VersionPickerDialog.md).
|
||||
- `catalogLoading` — идёт запрос манифеста.
|
||||
- `refreshVersionCatalog(force)` — сбросить кэш строк и попросить манифест. Свежий кэш манифеста
|
||||
отвечает без сети.
|
||||
- `installedVersions`, `isVersionInstalled(id)` — что лежит в `versions/`.
|
||||
- `installVersion(id)` — поставить; если эта версия уже ставится или стоит в очереди, ничего не
|
||||
делает.
|
||||
- `versionRemovalInfo(id)` — `id`, `installed`, `sizeMb`, `dependents` (профили модлоадеров поверх
|
||||
этой версии), `builds` (имена сборок, которые на неё ссылаются).
|
||||
- `removeVersion(id)` — удалить папку версии. Библиотеки и ресурсы общие, их не трогает. Пока
|
||||
лаунчер занят или запущена игра, отказывает. Сборки, ссылающиеся на версию, не меняются:
|
||||
ссылка на неустановленную версию — нормальное состояние.
|
||||
|
||||
Список строк каталога строится лениво и кэшируется: QML читает свойство много раз за кадр, пока
|
||||
открыт список. То же сделано для каталогов Java и сезонных сборок.
|
||||
|
||||
## Модлоадеры
|
||||
|
||||
- `loaderVersions(loaderKey, gameVersion)` — список `{version, label, recommended, stable}` для этой
|
||||
версии игры, новые сверху. Несовместимых строк в нём не бывает. Пустой список означает, что
|
||||
модлоадер эту версию не поддерживает.
|
||||
- `refreshLoaderVersions(loaderKey, gameVersion, force)` — обновить список. По окончании всегда
|
||||
приходит `loaderVersionsChanged(loaderKey, gameVersion)`, даже если ответил кэш. Обработчик должен
|
||||
сверить пару со своей: сигнал может относиться к другому модлоадеру или к прошлой версии игры.
|
||||
- `loaderVersionsLoading(loaderKey, gameVersion)` — помогает отличить «ещё грузится» от «не
|
||||
поддерживается».
|
||||
|
||||
Ключи: `forge`, `fabric`, `neoforge`, `quilt`.
|
||||
|
||||
Установщику модлоадера передаётся та же Java, что пойдёт в игру: сначала скачанная лаунчером
|
||||
сборка, потом `javaPath`.
|
||||
|
||||
## Java
|
||||
|
||||
- `javaCatalog` — строки `{id, kind, major, version, label, detail, coverage, sizeMb, installed, lts,
|
||||
downloadable, search}`. Сначала то, что уже скачано в `<root>/java`, потом каталог Mojang, Temurin
|
||||
JDK и Temurin JRE.
|
||||
- `javaCatalogLoading`, `refreshJavaCatalog(force)` — как у версий.
|
||||
- `installJavaRuntime(id)` — скачать. Пока работает другой установщик или идёт смена сборки,
|
||||
отказывает. По завершении приходит `javaRuntimeInstalled(id)`.
|
||||
- `removeJavaRuntime(id)` — удалить из папки лаунчера. Если это была выбранная в настройках сборка,
|
||||
`javaRuntime` очищается.
|
||||
- `javaRuntimeInfo(id)` — `id`, `kind`, `major`, `version`, `label`, `javaPath`, `installed`.
|
||||
Если сборка выбрана, но не скачана, `installed` будет `false` и `javaPath` пустым. Для пустого
|
||||
`id` возвращается пустая карта. Метод не пересчитывается сам, поэтому окно настроек перечитывает
|
||||
его по `javaRuntimeInstalled`.
|
||||
- `requiredJavaMajor(buildIndex)` — минимальная Java для версии игры сборки, 0 если версия не
|
||||
выбрана.
|
||||
- `detectedJava()` — найденные в системе java строками вида `Java 17 — /путь`, для справки в
|
||||
настройках.
|
||||
|
||||
## Запуск игры
|
||||
|
||||
`launchGame(profileIndex, buildIndex)` сначала проверяет всё, что можно проверить синхронно:
|
||||
|
||||
- не работает ни один установщик, не идёт другой запуск и игра не запущена;
|
||||
- профиль и сборка выбраны, папка игры существует;
|
||||
- версия сборки есть в `versions/`;
|
||||
- описание версии читается (`VersionLoader::load`), и все нужные файлы на месте
|
||||
(`GameLauncher::missingFiles`).
|
||||
|
||||
После этого выставляется `busy`, а сборка запоминается по `id`, а не по индексу: пока идёт вход,
|
||||
её могут удалить в другом окне.
|
||||
|
||||
Дальше зависит от типа профиля:
|
||||
|
||||
- **офлайн** — ник из логина или имени профиля, UUID считается из ника;
|
||||
- **Ely.by** — сначала докачивается `authlib-injector` в `<root>/runtime`, потом вход. Если Ely.by
|
||||
просит второй фактор, `busy` снимается и приходит `twoFactorRequired(profileName)`. Окно должно
|
||||
вызвать `submitTwoFactorCode(code)` или `cancelPendingLaunch()`, иначе запуск так и останется
|
||||
в ожидании;
|
||||
- **Microsoft** — если refresh-токена нет, запуск прерывается с `microsoftReloginRequired(index)`.
|
||||
Если сохранённый токен Minecraft живёт ещё дольше двух минут, сеть не трогается. Иначе сессия
|
||||
продлевается по refresh-токену. Если продлить не вышло, тоже `microsoftReloginRequired`. Если у
|
||||
аккаунта нет лицензии, просто ошибка.
|
||||
|
||||
Полученные при входе ник, UUID и токены сохраняются в профиль. Java выбирается так: скачанная
|
||||
лаунчером сборка из `javaRuntime`, иначе `javaPath`, иначе её ищет `GameLauncher`. Если
|
||||
`javaRuntime` задан, но такой сборки на диске нет, об этом будет сообщение в `launchProgress`.
|
||||
|
||||
Ход виден по сигналам `launchProgress`, `launched(profileName, buildName, serverUrl)` и
|
||||
`launchError`. `stopGame()` завершает процесс. Пока игра работает, `gameRunning` равно `true`;
|
||||
вывод процесса приходит построчно в `gameOutput`, конец — в `gameFinished(exitCode, crashed)`.
|
||||
|
||||
## Вход через Microsoft из окна профиля
|
||||
|
||||
1. `startMicrosoftLogin(profileIndex)` испускает `microsoftLoginUrlReady(url)`. `-1` значит, что
|
||||
профиля ещё нет и его нужно создать по нику из аккаунта. Без WebEngine сразу приходит
|
||||
`microsoftLoginFailed`.
|
||||
2. [MicrosoftLoginDialog](../qml/MicrosoftLoginDialog.md) открывает страницу и на каждую смену
|
||||
адреса вызывает `inspectMicrosoftRedirect(url)`. Ответ: `{matched, code, error}`. Разбор сделан
|
||||
в C++, чтобы правила совпадали с тем, как `MsaAuthService` строит `redirect_uri`.
|
||||
3. Код уходит в `finishMicrosoftLogin(code)`. Итог приходит в `microsoftLoginSucceeded(playerName)`
|
||||
или `microsoftLoginFailed(message)`.
|
||||
|
||||
`cancelMicrosoftLogin()` забывает начатый вход. Если окно закрыли, пока шёл обмен кода, результат
|
||||
будет выброшен.
|
||||
|
||||
`microsoftAvailable` — константа: собран ли лаунчер с Qt WebEngine.
|
||||
|
||||
## Сезонные сборки
|
||||
|
||||
- `seasonalCatalog` — строки каталога с полями сервера (`id`, `name`, `minecraftVersion`, `loader`,
|
||||
`loaderTitle`, `loaderVersion`, `modCount`, `seasonStart`, `seasonEnd` в виде `дд.мм.гггг`,
|
||||
`sizeBytes`, `revision`, `description`, `serverUrl`) и локальным состоянием (`installed`,
|
||||
`installedRevision`, `updateAvailable`, `status`). Сборка считается установленной, если есть
|
||||
запись с таким `seasonalId`. Обновление доступно, если её ревизия меньше серверной.
|
||||
- `seasonalCatalogLoading`, `seasonalCatalogError`.
|
||||
- `refreshSeasonalCatalog(force)` — перед запросом заново читает `seasonalBaseUrl`.
|
||||
- `installSeasonalBuild(id)` — поставить или обновить.
|
||||
- `cancelSeasonalInstall()` — отменить.
|
||||
- `seasonalInstalling` — идёт установка.
|
||||
|
||||
Установка — цепочка шагов. Каждый шаг либо запускает асинхронную работу и ждёт сигнала, либо
|
||||
решает, что делать нечего, и сразу передаёт ход следующему (`seasonAdvance()` со сквозными `case`).
|
||||
|
||||
1. **Запись.** Если сборки с таким `seasonalId` нет, она создаётся. Если есть, в неё записываются
|
||||
новые версия, модлоадер и адрес сервера. Ревизию пока не трогаем: её запишем, только когда файлы
|
||||
лягут на диск, иначе оборванная установка выглядела бы законченной.
|
||||
2. **Switching.** Сборка становится активной: пак раскатывается поверх текущей `.minecraft`.
|
||||
3. **Java.** Нужная версия берётся из `javaMajor` каталога или из таблицы требований. Если
|
||||
подходящая java уже есть (своя, системная или скачанная раньше), шаг пропускается. Иначе
|
||||
загружается каталог Java и ставится лучшая сборка для этой версии. Java ставится раньше
|
||||
модлоадера, потому что она нужна установщику Forge.
|
||||
4. **Loader.** Для ванильной сборки ставится версия игры, для остальных — модлоадер. Если всё уже
|
||||
установлено и укомплектовано, шаг пропускается.
|
||||
5. **Downloading.** Архив качается в `builds/<id>/.pack-r<ревизия>.zip`. Точка в начале имени нужна,
|
||||
чтобы `BuildSwitcher` не принял файл за архив сборки и не стёр его.
|
||||
6. **Applying.** `BuildSwitcher::applyPack()` удаляет всё, что принесла прошлая ревизия (список из
|
||||
`season.json`), и распаковывает новую. Миры и скриншоты в этот список не попадают, потому что
|
||||
пак их не приносил.
|
||||
|
||||
После раскатки `recordSeasonalPack()` пишет `builds/<id>/season.json` (`seasonalId`, `revision`,
|
||||
`sha256`, `installedAt`, `entries`) и проставляет ревизию в запись сборки. Это делается и тогда,
|
||||
когда раскатка была доиграна после перезапуска лаунчера. Конец цепочки — `seasonalInstallFinished
|
||||
(seasonalId, buildName)`.
|
||||
|
||||
При ошибке на любом шаге скачанный архив удаляется, и приходит `launchError`. На шагах Switching и
|
||||
Applying отменить нельзя: `.minecraft` уже разобрана, операцию нужно довести до конца.
|
||||
|
||||
Строка каталога копируется в `m_season.entry` при старте. Если каталог обновится во время установки,
|
||||
ставиться будет всё равно то, что выбрали.
|
||||
|
||||
## Панель загрузки
|
||||
|
||||
Источников загрузки четыре: установщик версий, установщик модлоадера, установщик Java и загрузчик
|
||||
пака. Наружу они видны через одну группу свойств: `downloading`, `downloadProgress`,
|
||||
`downloadVersion` (что качается), `downloadStatus` (`этап — файл`), `downloadBytesDone`,
|
||||
`downloadBytesTotal`. Все обновляются одним сигналом `downloadChanged`.
|
||||
|
||||
Чьи числа показывать, решается так. Пак качается, только когда остальные уже закончили. Java не
|
||||
качается одновременно с версией или модлоадером. Во время установки модлоадера доля берётся у
|
||||
`VersionInstaller`, если он сейчас качает файлы версии, потому что его оценка точнее.
|
||||
|
||||
`cancelDownload()` останавливает первое, что работает, в порядке: модлоадер (он сам остановит
|
||||
вложенную установку версии), версия, Java, пак.
|
||||
|
||||
`busy` равно `true`, пока идёт вход или запуск, работает любой установщик, идёт смена сборки или
|
||||
качается пак. На это время в интерфейсе гаснет кнопка запуска.
|
||||
|
||||
## Папки
|
||||
|
||||
- `openMinecraftFolder()` открывает `<gameDir>/mods` (создаёт, если её нет).
|
||||
- `openGameFolder()` открывает саму папку игры.
|
||||
|
||||
## Потоки
|
||||
|
||||
Весь класс работает в GUI-потоке. В отдельном потоке выполняются только файловые операции
|
||||
`BuildSwitcher`, и этот поток скрыт внутри него.
|
||||
|
||||
## Пример
|
||||
|
||||
```qml
|
||||
import QtQuick
|
||||
import Minecraft_launcher
|
||||
import BiLauncher
|
||||
|
||||
Window {
|
||||
id: window
|
||||
visible: true
|
||||
|
||||
LauncherBackend {
|
||||
id: backend
|
||||
|
||||
onLaunchError: (message) => console.warn(message)
|
||||
onTwoFactorRequired: (profileName) => twoFactorDialog.open()
|
||||
onMicrosoftLoginUrlReady: (url) => window.openMicrosoftLogin(url)
|
||||
}
|
||||
|
||||
Button {
|
||||
|
||||
Reference in new issue
Block a user