documentation rewrite

Signed-off-by: galeon <dertiellis@gmail.com>
This commit is contained in:
galeon committed 2026-10-11 22:32:13 +03:00
1 parent 1943996608
commit f10b654953
34 files changed
+2099 -4576

No files matched your search

+77 -170
View File
@@ -1,199 +1,106 @@
# GameLauncher
## Обзор класса
`gamelauncher.h`, `gamelauncher.cpp`
`GameLauncher` — то, ради чего существует весь остальной лаунчер: он готовит и запускает JVM с
Minecraft. К моменту его вызова уже известно всё — версия разобрана, файлы скачаны, пользователь
авторизован, — и класс превращает это в командную строку и дочерний процесс.
Собирает командную строку, распаковывает нативные библиотеки и запускает JVM с игрой. К этому
моменту версия уже разобрана ([minecraftversion.h](minecraftversion.md)), файлы скачаны, а игрок
вошёл в аккаунт. Всё остальное приходит в структуре `LaunchOptions`.
Кроме самого запуска класс умеет проверять комплектность `.minecraft` и распаковывать нативные
библиотеки, без которых игра не стартует.
Экземпляр один, его держит [LauncherBackend](LauncherBackend.md). Одновременно может работать
только одна игра.
Один экземпляр — одна игра одновременно.
## LaunchOptions
## Место в проекте и зависимости
| Поле | По умолчанию | Что это |
|------|--------------|---------|
| `gameDir` | | папка игры, она же рабочий каталог процесса |
| `versionId` | | папка в `versions/` |
| `playerName`, `uuid`, `accessToken`, `userType`, `clientToken`, `xuid` | | из [AuthResult](AuthService.md) |
| `javaPath` | пусто | пусто — ищет [JavaLocator](javalocator.md) |
| `minMemoryMb`, `maxMemoryMb` | 512, 4096 | `-Xms`, `-Xmx`; 0 — не передавать |
| `extraJvmArgs` | | аргументы JVM из настроек |
| `windowWidth`, `windowHeight` | 0, 0 | 0 — не передавать размер |
| `fullscreen` | `false` | |
| `serverAddress` | пусто | `host[:port]`, сервер для автоподключения |
| `authlibInjectorPath` | пусто | путь к `authlib-injector.jar`; пусто — без него |
| `authlibInjectorApi` | `ely.by` | сервер авторизации для инжектора |
| `launcherName`, `launcherVersion` | `BiLauncher`, `LAUNCHER_VERSION` | бэкенд передаёт имя и версию из `QCoreApplication` |
Подключает [minecraftversion.h](minecraftversion.md): разобранная версия — половина входных данных
запуска, вторая половина приходит структурой `LaunchOptions` из этого же заголовка.
## launch(options, version, &error)
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md), который заполняет
`LaunchOptions` из настроек, выбранной сборки и результата авторизации.
1. Если игра уже идёт, отказ.
2. `missingFiles()`: если чего-то не хватает, отказ со списком.
3. Распаковка нативных библиотек в `<gameDir>/versions/<id>/natives`.
4. `JavaLocator::select()` с `version.javaMajor` и `options.javaPath`. На Windows вместо
`java.exe` запускается `javaw.exe`, чтобы не было консольного окна.
5. `QProcess` с рабочим каталогом `gameDir` и объединёнными stdout и stderr.
6. Ожидание старта до 15 секунд. Если процесс не стартовал, отказ.
Требования сборки: `Qt6::Core` (`QProcess`, `QStringList`) и `Qt6::CorePrivate` — последний нужен
ради `QZipReader`, которым распаковываются нативные библиотеки LWJGL. Зависимость от приватного
модуля привязывает проект к конкретной версии Qt; в `CMakeLists.txt` это осознанный выбор, и
предупреждение о нём отключено.
Если всё прошло, метод возвращает `true` и испускает `gameStarted(commandLine)`. В этой строке
значение после `--accessToken` заменено на `<скрыт>`, чтобы токен не попал в журнал. Других
мест, где мог бы оказаться токен (например, в `${auth_session}`), замена не касается.
## Иерархия и роль
Перед шагами 2–4 испускается `progress(message)`.
Наследует `QObject`: мета-объектная система, четыре сигнала и владение по родителю. Виртуальных
методов базового класса не переопределяет.
Вывод игры читается кусками и испускается построчно в `output(line)`. Когда процесс завершится,
придёт `gameFinished(exitCode, crashed)`, где `crashed` значит `QProcess::CrashExit`.
## Публичные структуры
`terminate()` вызывает `kill()`, а не `terminate()`. Игра не успеет ничего сохранить, это то же
самое, что снять процесс в диспетчере задач.
### LaunchOptions
## missingFiles(options, version, limit = 12)
Всё, что лаунчер знает к моменту нажатия кнопки запуска.
Проверяет, что на месте папка игры, клиентский jar, все библиотеки, индекс ресурсов и папка
`assets/objects`. Возвращает до `limit` путей. Если папки игры нет, в списке будет только она.
| Поле | Тип | По умолчанию | Описание |
|------|-----|--------------|----------|
| `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` | `BiLauncher` | Имя лаунчера, которое видит игра |
| `launcherVersion` | `QString` | `1.0` | Версия лаунчера |
Размеры и хеши не проверяются, только наличие. Функция статическая, бэкенд вызывает её и для
подсказки в интерфейсе, и перед запуском.
## Публичные методы
## extractNatives(options, version, nativesDir, &error)
#### explicit GameLauncher(QObject \*parent = nullptr)
Для каждой библиотеки с `native == true` открывает jar через `QZipReader` (отсюда зависимость от
`Qt6::CorePrivate`) и вынимает файлы `.dll`, `.so`, `.so.N`, `.dylib`, `.jnilib`. Пропускаются
`META-INF/` и пути из `extract.exclude` описания версии. Все файлы кладутся в одну плоскую папку.
Если файл такого же размера уже лежит, его не переписывают.
Создаёт объект. Процесс игры при этом не запускается. Конструктор помечен `explicit`.
## buildArguments(options, version, nativesDir)
#### bool isRunning() const
Порядок аргументов:
Идёт ли сейчас игра. От этого зависит доступность кнопки запуска в интерфейсе.
1. `-javaagent:<authlib-injector>=ely.by` и `-Dauthlibinjector.side=client`, если инжектор задан;
2. `-Xms`, `-Xmx`, потом `extraJvmArgs`;
3. JVM-аргументы из описания версии;
4. аргумент логирования, если файл конфигурации логов скачан;
5. главный класс;
6. аргументы игры из описания версии;
7. `--width`/`--height`, если размер задан, а описание версии само его не передаёт;
8. `--fullscreen`;
9. сервер: `--quickPlayMultiplayer host:port` для версий, которые это понимают, иначе
`--server host --port port` (порт по умолчанию 25565).
#### static QStringList missingFiles(const LaunchOptions &options, const MinecraftVersion &version, int limit = 12)
Подстановки: `auth_player_name`, `auth_uuid`, `auth_access_token`, `auth_session`
(`token:<токен>:<uuid>`), `auth_xuid` (пусто → `0`), `clientid` (пусто → `0`), `user_type`,
`user_properties` (`{}`), `version_name`, `version_type`, `game_directory`, `assets_root`,
`assets_index_name`, `game_assets`, `natives_directory`, `library_directory`, `classpath`,
`classpath_separator`, `primary_jar`, `launcher_name`, `launcher_version`, `resolution_width`,
`resolution_height`.
Проверяет `.minecraft` на комплектность и возвращает описания недостающих файлов. Пустой список
означает, что всё на месте и сборку можно запускать.
Аргумент, в котором после подстановки остался `${`, выбрасывается целиком. Так отсекаются
переменные, о которых лаунчер не знает (например, связанные с демо-режимом).
Параметр `limit` ограничивает длину списка: перечислять все отсутствующие файлы у неустановленной
версии бессмысленно, важен сам факт и пара примеров.
У версий со старыми ресурсами (`legacy`, `pre-1.6`) `game_assets` указывает на
`assets/virtual/legacy`.
Метод статический, ничего не меняет и вызывается интерфейсом для строки состояния сборки.
Classpath — это все не-нативные библиотеки и в конце клиентский jar.
#### static QStringList buildArguments(const LaunchOptions &options, const MinecraftVersion &version, const QString &nativesDir)
### Forge 1.17+ и `-DignoreList`
Собирает аргументы ровно в том порядке, в котором их ждёт 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);
```
Forge собирает слой модулей из classpath и пропускает файлы, имя которых начинается с записи в
`-DignoreList`. В профиле Forge там записан `${version_name}.jar`. Официальный лаунчер кладёт в
classpath jar самого профиля, а у нас своего jar у профиля нет, и в classpath попадает
родительский, например `1.20.1.jar`. Если его имени нет в списке, он становится вторым модулем с
классами Minecraft, и игра падает с ошибкой вида `Modules minecraft and _1._20._1 export package
...`. Поэтому к `-DignoreList` дописывается имя клиентского jar, если его там ещё нет.
---