# ModLoaderInstaller ## Обзор класса `ModLoaderInstaller` ставит модлоадер в `.minecraft`. Под одним фасадом он прячет два совершенно разных пути установки. **Fabric и Quilt** отдают готовое описание версии: лаунчер кладёт его в `versions//.json` и передаёт дальше [VersionInstaller](VersionInstaller.md), который по полю `inheritsFrom` сам поставит ванильную версию и библиотеки лоадера. **Forge и NeoForge** так не умеют: их установка — это патч клиентского jar. Поэтому лаунчер запускает официальный `installer.jar` найденной Java в headless-режиме и смотрит, какой профиль появился в `versions`. Процессу установщика при этом подсовывается эталонный zlib — см. [ZlibReference](zlibreference.md). Оба пути начинаются одинаково: пока ванильная версия не скачана целиком, ставить лоадер некуда. Набор геттеров прогресса намеренно повторяет `VersionInstaller`: панель загрузки в интерфейсе читает их одинаково, независимо от того, кто сейчас работает. ## Место в проекте и зависимости Подключает [modloader.h](modloader.md). В конструктор принимает [ModLoaderVersionService](ModLoaderVersionService.md) — за адресом установщика — и [VersionInstaller](VersionInstaller.md) — за базовой версией и за докачиванием того, что `installer.jar` не положил. Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md). Требования сборки: `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](VersionInstaller.md). #### finished(const QString &loaderKey, const QString &gameVersion, const QString &loaderVersion, const QString &producedVersionId) Модлоадер установлен. Последний параметр — идентификатор появившегося профиля `versions/`. Он важен именно для 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); ``` --- При создании этого документа использовался ИИ.