1 parent
1943996608
commit
f10b654953
34 files changed
+2099
-4576
No files matched your search
+54
-167
@@ -1,190 +1,77 @@
|
||||
# ModLoaderInstaller
|
||||
|
||||
## Обзор класса
|
||||
`modloaderinstaller.h`, `modloaderinstaller.cpp`
|
||||
|
||||
`ModLoaderInstaller` ставит модлоадер в `.minecraft`. Под одним фасадом он прячет два совершенно
|
||||
разных пути установки.
|
||||
Ставит модлоадер в `.minecraft`. Fabric и Quilt ставятся иначе, чем Forge и NeoForge, но снаружи у
|
||||
них одинаковый интерфейс.
|
||||
|
||||
**Fabric и Quilt** отдают готовое описание версии: лаунчер кладёт его в
|
||||
`versions/<id>/<id>.json` и передаёт дальше [VersionInstaller](VersionInstaller.md), который по
|
||||
полю `inheritsFrom` сам поставит ванильную версию и библиотеки лоадера.
|
||||
- **Fabric и Quilt** отдают готовое описание версии. Его достаточно положить в
|
||||
`versions/<id>/<id>.json`, а остальное докачает [VersionInstaller](VersionInstaller.md).
|
||||
- **Forge и NeoForge** при установке патчат клиентский jar, поэтому запускается их официальный
|
||||
`installer.jar` без окна. После него проверяется, какой профиль появился в `versions/`.
|
||||
|
||||
**Forge и NeoForge** так не умеют: их установка — это патч клиентского jar. Поэтому лаунчер
|
||||
запускает официальный `installer.jar` найденной Java в headless-режиме и смотрит, какой профиль
|
||||
появился в `versions`. Процессу установщика при этом подсовывается эталонный zlib — см.
|
||||
[ZlibReference](zlibreference.md).
|
||||
Экземпляр держит [LauncherBackend](LauncherBackend.md). Геттеры прогресса устроены так же, как у
|
||||
`VersionInstaller` (`label`, `stage`, `currentFile`, `bytesDone`, `bytesTotal`, `fraction`), и
|
||||
панель загрузки читает их одинаково.
|
||||
|
||||
Оба пути начинаются одинаково: пока ванильная версия не скачана целиком, ставить лоадер некуда.
|
||||
## install(gameDir, loader, gameVersion, loaderVersion, javaPreference)
|
||||
|
||||
Набор геттеров прогресса намеренно повторяет `VersionInstaller`: панель загрузки в интерфейсе
|
||||
читает их одинаково, независимо от того, кто сейчас работает.
|
||||
Если установка уже идёт или не указана версия, сразу приходит `failed`. Иначе:
|
||||
|
||||
## Место в проекте и зависимости
|
||||
1. Список версий модлоадера через [ModLoaderVersionService](ModLoaderVersionService.md).
|
||||
2. **Базовая версия игры.** Если её нет или не хватает файлов, она ставится через
|
||||
`VersionInstaller`, и ход идёт дальше только после её `finished`. Без целой ванильной версии
|
||||
модлоадер ставить некуда.
|
||||
3. Дальше один из двух путей.
|
||||
|
||||
Подключает [modloader.h](modloader.md). В конструктор принимает
|
||||
[ModLoaderVersionService](ModLoaderVersionService.md) — за адресом установщика — и
|
||||
[VersionInstaller](VersionInstaller.md) — за базовой версией и за докачиванием того, что
|
||||
`installer.jar` не положил.
|
||||
### Fabric и Quilt
|
||||
|
||||
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md).
|
||||
Описание скачивается из `meta.fabricmc.net/v2/versions/loader/<игра>/<лоадер>/profile/json`
|
||||
(у Quilt `meta.quiltmc.org/v3/...`). Проверяется, что в нём есть безопасный `id` и что
|
||||
`inheritsFrom` совпадает с выбранной версией игры. Файл сохраняется, и `VersionInstaller` получает
|
||||
этот `id`.
|
||||
|
||||
Требования сборки: `Qt6::Core` (`QProcess`, `QFile`) и `Qt6::Network` (`QNetworkAccessManager`,
|
||||
`QNetworkReply`).
|
||||
### Forge и NeoForge
|
||||
|
||||
## Иерархия и роль
|
||||
1. Ссылка на установщик берётся из записи `ModLoaderVersionService::find()`. Если записи нет,
|
||||
значит, эта пара версий несовместима. Установщик качается в
|
||||
`<root>/cache/loaders/<ключ>-<версия>-installer.jar`.
|
||||
2. Если в `.minecraft` нет `launcher_profiles.json`, создаётся заглушка. Без неё установщик
|
||||
отказывается работать: он считает, что официальный лаунчер не установлен.
|
||||
3. Java берётся через `JavaLocator::select()` по `javaMajor` базовой версии. `javaPreference` — это
|
||||
та же Java, что выбрана для игры.
|
||||
4. Запоминается список профилей в `versions/`.
|
||||
5. Запуск: `java -jar installer.jar --installClient <gameDir>`. Рабочий каталог — папка загрузок
|
||||
модлоадеров. На Linux в окружение процесса подставляется эталонный zlib (см.
|
||||
[zlibreference.h](zlibreference.md)).
|
||||
6. Вывод установщика построчно уходит в `log(line)`, бэкенд показывает его в журнале. Полностью он
|
||||
пишется в `<root>/cache/loaders/<ключ>-<версия>-install.log`, а последние 12 строк хранятся в
|
||||
памяти для текста ошибки.
|
||||
7. Через 15 минут процесс убивается.
|
||||
|
||||
Наследует `QObject`: мета-объектная система, шесть сигналов и владение по родителю. Объявлен
|
||||
виртуальный деструктор — класс владеет процессом установщика и открытым файлом журнала.
|
||||
После успешного завершения новый профиль ищется как разница списков до и после. Угадывать имя по
|
||||
строке нельзя: у Forge в разные годы оно составлялось по-разному. Если новых папок нет (установщик
|
||||
переписал существующую), ищется профиль, который наследует базовую версию и содержит в имени версию
|
||||
модлоадера. Если новых папок несколько, берётся та, где есть версия модлоадера.
|
||||
|
||||
## Публичные методы
|
||||
Найденный профиль передаётся `VersionInstaller`, который докачивает библиотеки, не положенные
|
||||
установщиком. Когда он отчитается, приходит `finished(loaderKey, gameVersion, loaderVersion,
|
||||
producedVersionId)`. Бэкенд записывает `producedVersionId` в `resolvedVersionId` сборки.
|
||||
|
||||
#### ModLoaderInstaller(ModLoaderVersionService \*meta, VersionInstaller \*versionInstaller, QObject \*parent = nullptr)
|
||||
### Если установщик упал
|
||||
|
||||
Создаёт установщик поверх двух сервисов. Ни один из них не переходит во владение установщика —
|
||||
оба обязаны пережить его.
|
||||
Все папки, которые он успел создать в `versions/`, удаляются. В текст ошибки идут код выхода,
|
||||
хвост журнала и путь к полному журналу.
|
||||
|
||||
#### bool isRunning() const
|
||||
Если подменить zlib не удалось, а в журнале есть `Processor failed` или `invalid outputs`, к
|
||||
сообщению добавляется прямая подсказка про zlib-ng. Почти наверняка дело в нём.
|
||||
|
||||
Идёт ли установка прямо сейчас.
|
||||
## cancel()
|
||||
|
||||
#### QString label() const
|
||||
Обрывает загрузку, убивает процесс установщика и удаляет то, что он создал. Если ход уже передан
|
||||
`VersionInstaller`, отменяется и он, а `canceled` придёт после его отчёта.
|
||||
|
||||
Подпись текущей установки для интерфейса — название лоадера с версиями.
|
||||
|
||||
#### QString stage() const
|
||||
|
||||
Текущий этап словами.
|
||||
|
||||
#### QString currentFile() const
|
||||
|
||||
Файл, который обрабатывается сейчас.
|
||||
|
||||
#### qint64 bytesDone() const
|
||||
|
||||
Сколько байт уже получено.
|
||||
|
||||
#### qint64 bytesTotal() const
|
||||
|
||||
Ожидаемый общий объём; `0` — неизвестен.
|
||||
|
||||
#### double fraction() const
|
||||
|
||||
Доля выполнения от `0` до `1` либо `-1`, пока итог неизвестен. У пути с `installer.jar` доля
|
||||
почти всё время равна `-1`: сколько работы осталось внутри чужого процесса, лаунчер не знает.
|
||||
|
||||
#### void install(const QString &gameDir, ModLoader loader, const QString &gameVersion, const QString &loaderVersion, const QString &javaPreference)
|
||||
|
||||
Ставит модлоадер. Параметр `javaPreference` — путь к java, указанный пользователем в настройках;
|
||||
он проверяется первым, а при пустом или неподходящем значении java ищется сама. Java нужна даже на
|
||||
пути Fabric и Quilt, потому что перед установкой лоадера скачивается базовая версия игры.
|
||||
|
||||
Перед запуском `installer.jar` установщик записывает заглушку `launcher_profiles.json`: без этого
|
||||
файла официальные установщики Forge и NeoForge отказываются работать.
|
||||
|
||||
#### void cancel()
|
||||
|
||||
Отменяет установку. Если `installer.jar` уже успел отработать, за появившиеся в `versions` папки
|
||||
отвечает лаунчер, и при отмене они убираются.
|
||||
|
||||
## Сигналы
|
||||
|
||||
#### started(const QString &label)
|
||||
|
||||
Установка началась. Обработчик показывает панель прогресса.
|
||||
|
||||
#### progressChanged()
|
||||
|
||||
Изменились числа прогресса. Обработчик перечитывает геттеры — тот же набор, что у
|
||||
[VersionInstaller](VersionInstaller.md).
|
||||
|
||||
#### finished(const QString &loaderKey, const QString &gameVersion, const QString &loaderVersion, const QString &producedVersionId)
|
||||
|
||||
Модлоадер установлен. Последний параметр — идентификатор появившегося профиля `versions/<id>`.
|
||||
Он важен именно для Forge: заранее этот идентификатор неизвестен, поэтому новый профиль ищется
|
||||
разницей между списком версий до и после запуска установщика, а не угадыванием строки —
|
||||
идентификаторы Forge отличаются по эпохам.
|
||||
|
||||
Обработчик записывает `producedVersionId` в сборку: именно этот профиль будет запускаться.
|
||||
|
||||
#### failed(const QString &label, const QString &message)
|
||||
|
||||
Установка не удалась. Текст ошибки по возможности объясняет причину: отдельно распознаётся случай,
|
||||
когда в системе стоит zlib-ng, а подменить его было нечем — это почти наверняка причина
|
||||
расхождения sha1 у Forge, и о ней стоит сказать прямым текстом.
|
||||
|
||||
#### canceled(const QString &label)
|
||||
|
||||
Установка отменена пользователем.
|
||||
|
||||
#### log(const QString &line)
|
||||
|
||||
Строка вывода процесса `installer.jar`. Обработчик пишет её в журнал; вывод также сохраняется в
|
||||
файл, чтобы разбираться с неудачной установкой после закрытия лаунчера.
|
||||
|
||||
## Владение и время жизни
|
||||
|
||||
Класс наследует `QObject` и принимает `parent` — родитель его и удалит.
|
||||
|
||||
Указатели на `ModLoaderVersionService` и `VersionInstaller`, переданные в конструктор, **не
|
||||
принадлежат** установщику: оба сервиса создаются раньше и живут дольше.
|
||||
|
||||
`QNetworkAccessManager` создаётся в конструкторе с установщиком в роли родителя; сетевой ответ,
|
||||
процесс установщика и файл журнала создаются по ходу работы и закрываются в деструкторе.
|
||||
|
||||
Установка асинхронна и состоит из вложенных продолжений: `ensureBaseVersion()` принимает функцию,
|
||||
которая будет вызвана после появления базовой версии. Уничтожение установщика посреди этой цепочки
|
||||
обрывает её.
|
||||
|
||||
## Потокобезопасность
|
||||
|
||||
Только поток GUI. Дочерний процесс `installer.jar` работает параллельно, но общение с ним идёт
|
||||
через сигналы `QProcess`, которые приходят в поток GUI.
|
||||
|
||||
## Взаимодействие с другими классами
|
||||
|
||||
`LauncherBackend` вызывает `install()` при установке сборки с модлоадером и переправляет сигналы
|
||||
прогресса в те же свойства, что и у `VersionInstaller`, — панель загрузки не различает, кто
|
||||
работает. По сигналу `finished` бэкенд записывает `producedVersionId` в поле сборки
|
||||
`resolvedVersionId`, которое до установки пусто и означает запуск на чистой ванили.
|
||||
|
||||
[ModLoaderVersionService](ModLoaderVersionService.md) даёт адрес `installer.jar`,
|
||||
[VersionInstaller](VersionInstaller.md) ставит базовую версию до начала работы и докачивает
|
||||
недостающие библиотеки после неё, [ZlibReference](zlibreference.md) готовит окружение процесса.
|
||||
|
||||
## Внешнее взаимодействие
|
||||
|
||||
**Сеть, исходящие запросы.** Загрузка `installer.jar` по HTTPS для Forge и NeoForge. Для Fabric и
|
||||
Quilt сеть используется только через сервис версий и `VersionInstaller`.
|
||||
|
||||
**Дочерний процесс.** `installer.jar` запускается java в headless-режиме через `QProcess`.
|
||||
Окружение процесса готовится `ZlibReference::applyTo()`: на Linux с zlib-ng туда дописывается
|
||||
`LD_PRELOAD` с эталонной библиотекой, иначе установщик Forge падает с сообщением «Processor
|
||||
failed, invalid outputs» — он сверяет sha1 собранных им же jar-файлов с эталоном, посчитанным на
|
||||
обычном zlib.
|
||||
|
||||
Вывод процесса читается построчно, отдаётся сигналом `log` и параллельно пишется в файл. Результат
|
||||
установки определяется не кодом выхода, а появлением новой папки в `versions`.
|
||||
|
||||
Все сигналы приходят в поток GUI.
|
||||
|
||||
## Пример использования
|
||||
|
||||
```cpp
|
||||
auto *loaderInstaller = new ModLoaderInstaller(loaderVersions, versionInstaller, this);
|
||||
|
||||
connect(loaderInstaller, &ModLoaderInstaller::finished, this,
|
||||
[this](const QString &key, const QString &game,
|
||||
const QString &loaderVersion, const QString &producedVersionId) {
|
||||
m_build.resolvedVersionId = producedVersionId;
|
||||
saveBuilds();
|
||||
});
|
||||
connect(loaderInstaller, &ModLoaderInstaller::failed, this,
|
||||
[this](const QString &, const QString &message) { emit launchError(message); });
|
||||
|
||||
loaderInstaller->install(gameDir, ModLoader::Forge,
|
||||
QStringLiteral("1.20.1"), QStringLiteral("47.4.0"),
|
||||
settings.javaPath);
|
||||
```
|
||||
Деструктор убивает процесс установщика, если тот ещё работает.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in new issue
Block a user