12 KiB
ModLoaderInstaller
Обзор класса
ModLoaderInstaller ставит модлоадер в .minecraft. Под одним фасадом он прячет два совершенно
разных пути установки.
Fabric и Quilt отдают готовое описание версии: лаунчер кладёт его в
versions/<id>/<id>.json и передаёт дальше VersionInstaller, который по
полю inheritsFrom сам поставит ванильную версию и библиотеки лоадера.
Forge и NeoForge так не умеют: их установка — это патч клиентского jar. Поэтому лаунчер
запускает официальный installer.jar найденной Java в headless-режиме и смотрит, какой профиль
появился в versions. Процессу установщика при этом подсовывается эталонный zlib — см.
ZlibReference.
Оба пути начинаются одинаково: пока ванильная версия не скачана целиком, ставить лоадер некуда.
Набор геттеров прогресса намеренно повторяет VersionInstaller: панель загрузки в интерфейсе
читает их одинаково, независимо от того, кто сейчас работает.
Место в проекте и зависимости
Подключает modloader.h. В конструктор принимает
ModLoaderVersionService — за адресом установщика — и
VersionInstaller — за базовой версией и за докачиванием того, что
installer.jar не положил.
Экземпляр создаётся и принадлежит LauncherBackend.
Требования сборки: Qt6::Core (QProcess, QFile) и Qt6::Network (QNetworkAccessManager,
QNetworkReply).
Иерархия и роль
Наследует QObject: мета-объектная система, шесть сигналов и владение по родителю. Объявлен
виртуальный деструктор — класс владеет процессом установщика и открытым файлом журнала.
Публичные методы
ModLoaderInstaller(ModLoaderVersionService *meta, VersionInstaller *versionInstaller, QObject *parent = nullptr)
Создаёт установщик поверх двух сервисов. Ни один из них не переходит во владение установщика — оба обязаны пережить его.
bool isRunning() const
Идёт ли установка прямо сейчас.
QString label() const
Подпись текущей установки для интерфейса — название лоадера с версиями.
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.
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 даёт адрес installer.jar,
VersionInstaller ставит базовую версию до начала работы и докачивает
недостающие библиотеки после неё, ZlibReference готовит окружение процесса.
Внешнее взаимодействие
Сеть, исходящие запросы. Загрузка 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.
Пример использования
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);
При создании этого документа использовался ИИ.