108 lines
7.5 KiB
Markdown
108 lines
7.5 KiB
Markdown
# GameLauncher
|
||
|
||
`gamelauncher.h`, `gamelauncher.cpp`
|
||
|
||
Собирает командную строку, распаковывает нативные библиотеки и запускает JVM с игрой. К этому
|
||
моменту версия уже разобрана ([minecraftversion.h](minecraftversion.md)), файлы скачаны, а игрок
|
||
вошёл в аккаунт. Всё остальное приходит в структуре `LaunchOptions`.
|
||
|
||
Экземпляр один, его держит [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` |
|
||
|
||
## launch(options, version, &error)
|
||
|
||
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 секунд. Если процесс не стартовал, отказ.
|
||
|
||
Если всё прошло, метод возвращает `true` и испускает `gameStarted(commandLine)`. В этой строке
|
||
значение после `--accessToken` заменено на `<скрыт>`, чтобы токен не попал в журнал. Других
|
||
мест, где мог бы оказаться токен (например, в `${auth_session}`), замена не касается.
|
||
|
||
Перед шагами 2–4 испускается `progress(message)`.
|
||
|
||
Вывод игры читается кусками и испускается построчно в `output(line)`. Когда процесс завершится,
|
||
придёт `gameFinished(exitCode, crashed)`, где `crashed` значит `QProcess::CrashExit`.
|
||
|
||
`terminate()` вызывает `kill()`, а не `terminate()`. Игра не успеет ничего сохранить, это то же
|
||
самое, что снять процесс в диспетчере задач.
|
||
|
||
## missingFiles(options, version, limit = 12)
|
||
|
||
Проверяет, что на месте папка игры, клиентский jar, все библиотеки, индекс ресурсов и папка
|
||
`assets/objects`. Возвращает до `limit` путей. Если папки игры нет, в списке будет только она.
|
||
|
||
Размеры и хеши не проверяются, только наличие. Функция статическая, бэкенд вызывает её и для
|
||
подсказки в интерфейсе, и перед запуском.
|
||
|
||
## extractNatives(options, version, nativesDir, &error)
|
||
|
||
Для каждой библиотеки с `native == true` открывает jar через `QZipReader` (отсюда зависимость от
|
||
`Qt6::CorePrivate`) и вынимает файлы `.dll`, `.so`, `.so.N`, `.dylib`, `.jnilib`. Пропускаются
|
||
`META-INF/` и пути из `extract.exclude` описания версии. Все файлы кладутся в одну плоскую папку.
|
||
Если файл такого же размера уже лежит, его не переписывают.
|
||
|
||
## buildArguments(options, version, nativesDir)
|
||
|
||
Порядок аргументов:
|
||
|
||
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).
|
||
|
||
Подстановки: `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`.
|
||
|
||
Аргумент, в котором после подстановки остался `${`, выбрасывается целиком. Так отсекаются
|
||
переменные, о которых лаунчер не знает (например, связанные с демо-режимом).
|
||
|
||
У версий со старыми ресурсами (`legacy`, `pre-1.6`) `game_assets` указывает на
|
||
`assets/virtual/legacy`.
|
||
|
||
Classpath — это все не-нативные библиотеки и в конце клиентский jar.
|
||
|
||
### Forge 1.17+ и `-DignoreList`
|
||
|
||
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, если его там ещё нет.
|
||
|
||
---
|
||
|
||
При создании этого документа использовался ИИ.
|