Files
minecraft-launcher/doc/cpp/ModLoaderInstaller.md
T
2026-09-03 09:16:56 +03:00

12 KiB
Raw Blame History

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);

При создании этого документа использовался ИИ.