201 lines
13 KiB
Markdown
201 lines
13 KiB
Markdown
# GameLauncher
|
||
|
||
## Обзор класса
|
||
|
||
`GameLauncher` — то, ради чего существует весь остальной лаунчер: он готовит и запускает JVM с
|
||
Minecraft. К моменту его вызова уже известно всё — версия разобрана, файлы скачаны, пользователь
|
||
авторизован, — и класс превращает это в командную строку и дочерний процесс.
|
||
|
||
Кроме самого запуска класс умеет проверять комплектность `.minecraft` и распаковывать нативные
|
||
библиотеки, без которых игра не стартует.
|
||
|
||
Один экземпляр — одна игра одновременно.
|
||
|
||
## Место в проекте и зависимости
|
||
|
||
Подключает [minecraftversion.h](minecraftversion.md): разобранная версия — половина входных данных
|
||
запуска, вторая половина приходит структурой `LaunchOptions` из этого же заголовка.
|
||
|
||
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md), который заполняет
|
||
`LaunchOptions` из настроек, выбранной сборки и результата авторизации.
|
||
|
||
Требования сборки: `Qt6::Core` (`QProcess`, `QStringList`) и `Qt6::CorePrivate` — последний нужен
|
||
ради `QZipReader`, которым распаковываются нативные библиотеки LWJGL. Зависимость от приватного
|
||
модуля привязывает проект к конкретной версии Qt; в `CMakeLists.txt` это осознанный выбор, и
|
||
предупреждение о нём отключено.
|
||
|
||
## Иерархия и роль
|
||
|
||
Наследует `QObject`: мета-объектная система, четыре сигнала и владение по родителю. Виртуальных
|
||
методов базового класса не переопределяет.
|
||
|
||
## Публичные структуры
|
||
|
||
### LaunchOptions
|
||
|
||
Всё, что лаунчер знает к моменту нажатия кнопки запуска.
|
||
|
||
| Поле | Тип | По умолчанию | Описание |
|
||
|------|-----|--------------|----------|
|
||
| `gameDir` | `QString` | — | Папка `.minecraft` |
|
||
| `versionId` | `QString` | — | Папка в `versions`, которую запускаем |
|
||
| `playerName` | `QString` | — | Ник игрока из результата авторизации |
|
||
| `uuid` | `QString` | — | UUID игрока |
|
||
| `accessToken` | `QString` | — | Токен доступа |
|
||
| `userType` | `QString` | — | Тип учётной записи: `legacy`, `msa` или `ELYBY` |
|
||
| `clientToken` | `QString` | — | Токен клиента |
|
||
| `xuid` | `QString` | — | Идентификатор Xbox; пустое значение заменяется на `0`, как в офлайне |
|
||
| `javaPath` | `QString` | — | Путь к java; пустое значение означает «искать самим» |
|
||
| `minMemoryMb` | `int` | `512` | Значение `-Xms` |
|
||
| `maxMemoryMb` | `int` | `4096` | Значение `-Xmx` |
|
||
| `extraJvmArgs` | `QStringList` | — | Дополнительные аргументы JVM из настроек |
|
||
| `windowWidth` | `int` | `0` | Ширина окна игры; `0` — не передавать `--width` и `--height` |
|
||
| `windowHeight` | `int` | `0` | Высота окна игры |
|
||
| `fullscreen` | `bool` | `false` | Запускать в полноэкранном режиме |
|
||
| `serverAddress` | `QString` | — | `host[:port]` для автоматического захода на сервер |
|
||
| `authlibInjectorPath` | `QString` | — | Путь к `authlib-injector`; пустое значение — не подключать |
|
||
| `authlibInjectorApi` | `QString` | `ely.by` | Сервер авторизации, на который перенаправляется игра |
|
||
| `launcherName` | `QString` | `KishkaLauncher` | Имя лаунчера, которое видит игра |
|
||
| `launcherVersion` | `QString` | `1.0` | Версия лаунчера |
|
||
|
||
## Публичные методы
|
||
|
||
#### explicit GameLauncher(QObject \*parent = nullptr)
|
||
|
||
Создаёт объект. Процесс игры при этом не запускается. Конструктор помечен `explicit`.
|
||
|
||
#### bool isRunning() const
|
||
|
||
Идёт ли сейчас игра. От этого зависит доступность кнопки запуска в интерфейсе.
|
||
|
||
#### static QStringList missingFiles(const LaunchOptions &options, const MinecraftVersion &version, int limit = 12)
|
||
|
||
Проверяет `.minecraft` на комплектность и возвращает описания недостающих файлов. Пустой список
|
||
означает, что всё на месте и сборку можно запускать.
|
||
|
||
Параметр `limit` ограничивает длину списка: перечислять все отсутствующие файлы у неустановленной
|
||
версии бессмысленно, важен сам факт и пара примеров.
|
||
|
||
Метод статический, ничего не меняет и вызывается интерфейсом для строки состояния сборки.
|
||
|
||
#### static QStringList buildArguments(const LaunchOptions &options, const MinecraftVersion &version, const QString &nativesDir)
|
||
|
||
Собирает аргументы ровно в том порядке, в котором их ждёт JVM: сначала аргументы JVM, затем главный
|
||
класс, затем аргументы игры. Подстановки вида `${...}` из версии заменяются значениями из
|
||
параметров запуска.
|
||
|
||
Метод статический и не имеет побочных эффектов, поэтому годится и для показа собранной командной
|
||
строки без запуска.
|
||
|
||
#### static bool extractNatives(const LaunchOptions &options, const MinecraftVersion &version, const QString &nativesDir, QString \*error)
|
||
|
||
Распаковывает файлы `.dll`, `.so` и `.dylib` из нативных библиотек в `<версия>/natives`. Учитывает
|
||
поле `extractExclude` каждой библиотеки — перечисленные там префиксы не распаковываются.
|
||
|
||
Возвращает `false` и заполняет `error` при неудаче. Без этого шага игра не стартует: LWJGL ищет
|
||
нативные библиотеки именно в этой папке.
|
||
|
||
#### bool launch(const LaunchOptions &options, const MinecraftVersion &version, QString \*error)
|
||
|
||
Полный цикл запуска: проверка комплектности, распаковка нативных библиотек, поиск java, старт
|
||
процесса.
|
||
|
||
Возвращает `false` и заполняет `error`, если что-то из перечисленного не удалось; `true` означает,
|
||
что процесс запущен — дальнейшая судьба игры приходит сигналами.
|
||
|
||
Путь к java берётся из `options.javaPath`, а при пустом значении ищется через
|
||
[JavaLocator](javalocator.md) с учётом требования версии.
|
||
|
||
#### void terminate()
|
||
|
||
Завершает процесс игры. Если игра не запущена, ничего не делает.
|
||
|
||
## Сигналы
|
||
|
||
#### progress(const QString &message)
|
||
|
||
Описание текущего шага подготовки: проверка файлов, распаковка нативных библиотек, поиск java.
|
||
Обработчик показывает сообщение пользователю — подготовка занимает заметное время.
|
||
|
||
#### output(const QString &line)
|
||
|
||
Одна строка вывода процесса игры. Обработчик пишет её в журнал; в лаунчере это вывод в консоль.
|
||
|
||
#### gameStarted(const QString &commandLine)
|
||
|
||
Процесс запущен; в параметре — собранная командная строка целиком. Удобно для диагностики: по ней
|
||
видно, с какими аргументами и какой java стартовала игра.
|
||
|
||
#### gameFinished(int exitCode, bool crashed)
|
||
|
||
Игра завершилась. `exitCode` — код выхода процесса, `crashed` отличает аварийное завершение от
|
||
обычного.
|
||
|
||
Обработчик снимает признак «игра идёт», возвращает доступность кнопки запуска и сообщает
|
||
пользователю итог: ненулевой код или выставленный `crashed` показываются как ошибка.
|
||
|
||
## Владение и время жизни
|
||
|
||
Класс наследует `QObject` и принимает `parent` — родитель его и удалит.
|
||
|
||
Процесс игры хранится в поле `m_process` и создаётся при запуске. Один экземпляр рассчитан ровно
|
||
на одну игру одновременно: повторный вызов `launch()` при работающем процессе не предусмотрен, и
|
||
вызывающий код обязан проверять `isRunning()`.
|
||
|
||
Дочерний процесс переживает уничтожение объекта не сам по себе — завершать игру перед выходом
|
||
должен вызывающий код через `terminate()`.
|
||
|
||
## Потокобезопасность
|
||
|
||
Только поток GUI. `QProcess` привязан к потоку, в котором создан, и все сигналы приходят туда же.
|
||
Статические методы (`missingFiles()`, `buildArguments()`, `extractNatives()`) состояния не имеют,
|
||
но выполняют файловый ввод-вывод и на большой версии могут заметно задержать вызывающий поток.
|
||
|
||
## Взаимодействие с другими классами
|
||
|
||
`LauncherBackend` собирает `LaunchOptions` из трёх источников: настроек лаунчера, описания
|
||
выбранной сборки и `AuthResult` от [AuthService](AuthService.md) или
|
||
[MsaAuthService](MsaAuthService.md). Разобранную версию он получает через
|
||
`VersionLoader::load()` из [minecraftversion.h](minecraftversion.md).
|
||
|
||
Все четыре сигнала бэкенд переправляет в QML: `progress` и `gameFinished` попадают в плашку
|
||
сообщений главного окна, `output` — в консоль, а `gameStarted` меняет признак `gameRunning`.
|
||
|
||
Статический `missingFiles()` вызывается отдельно от запуска — из метода проверки комплектности
|
||
сборки, результат которого показывает [BuildsDialog](../qml/BuildsDialog.md).
|
||
|
||
## Внешнее взаимодействие
|
||
|
||
**Дочерний процесс.** Класс запускает java через `QProcess`. Аргументы собираются
|
||
`buildArguments()`; для профилей Ely.by в них добавляется `-javaagent` с путём к
|
||
`authlib-injector`. Стандартный вывод процесса читается построчно и отдаётся сигналом `output`,
|
||
завершение — сигналом `gameFinished`. Направление обмена одностороннее: лаунчер запускает процесс
|
||
и читает его вывод, ничего не передавая обратно после старта.
|
||
|
||
Все сигналы процесса приходят в поток GUI.
|
||
|
||
## Пример использования
|
||
|
||
```cpp
|
||
auto *launcher = new GameLauncher(this);
|
||
connect(launcher, &GameLauncher::progress, this, &Backend::showStatus);
|
||
connect(launcher, &GameLauncher::gameFinished, this, &Backend::onGameFinished);
|
||
|
||
LaunchOptions options;
|
||
options.gameDir = settings.resolvedGameDir;
|
||
options.versionId = build.resolvedVersionId;
|
||
options.playerName = auth.playerName;
|
||
options.uuid = auth.uuid;
|
||
options.accessToken = auth.accessToken;
|
||
options.userType = auth.userType;
|
||
options.maxMemoryMb = settings.maxMemoryMb;
|
||
|
||
QString error;
|
||
if (!launcher->launch(options, version, &error))
|
||
emit launchError(error);
|
||
```
|
||
|
||
---
|
||
|
||
При создании этого документа использовался ИИ.
|