Files
2026-09-03 09:16:56 +03:00

12 KiB
Raw Permalink Blame History

VersionInstaller

Обзор класса

Установить версию Minecraft — значит положить в .minecraft пять групп файлов: описание версии, клиентский jar, библиотеки, индекс ресурсов и сами ресурсы. Последних — десятки тысяч мелких файлов.

VersionInstaller делает это фоном, не блокируя интерфейс: складывает всё нужное в очередь загрузок, качает несколько файлов параллельно, пишет их потоком на диск и по ходу сообщает прогресс. Одна версия ставится за раз, остальные ждут в очереди.

Класс также разворачивает цепочку наследования: если у версии есть inheritsFrom, родительская версия ставится перед ней.

Место в проекте и зависимости

Подключает minecraftversion.h — по разобранной версии он и понимает, что качать. В конструктор принимает VersionManifestService: оттуда берётся адрес описания версии.

Экземпляр создаётся и принадлежит LauncherBackend. На него же опирается ModLoaderInstaller — профиль модлоадера ставится поверх установленной версии игры.

Требования сборки: Qt6::Core (QCryptographicHash, QSaveFile, QQueue, QTimer) и Qt6::Network (QNetworkAccessManager, QNetworkReply).

Иерархия и роль

Наследует QObject: мета-объектная система, пять сигналов и владение по родителю. Объявлен виртуальный деструктор — класс владеет незавершёнными загрузками и обязан их закрыть.

Публичные структуры

DownloadTask

Один файл, который нужно положить в .minecraft.

Поле Тип По умолчанию Описание
kind DownloadTask::Kind ClientJar Что это за файл
url QUrl Откуда качать
path QString Абсолютный путь назначения
sha1 QString Контрольная сумма; пустая означает «не проверять»
size qint64 0 Ожидаемый размер; 0 — неизвестен
label QString Что показать в панели прогресса
attempts int 0 Сколько попыток уже сделано

Перечисление DownloadTask::Kind

Значение Описание
ClientJar Клиентский jar версии
Library Библиотека из libraries/
AssetIndex Индекс ресурсов
AssetObject Один файл ресурсов
LoggingConfig Конфигурация журналирования log4j

ActiveDownload

Файл в процессе скачивания: задача, сетевой ответ, открытый QSaveFile, накапливаемая контрольная сумма и число принятых байт. Файлы пишутся потоком — держать десятки мегабайт в памяти незачем, а нескольких параллельных загрузок хватило бы на сотни.

Публичные методы

explicit VersionInstaller(VersionManifestService *manifest, QObject *parent = nullptr)

Создаёт установщик поверх сервиса манифеста. Сервис не переходит во владение установщика и обязан пережить его. Конструктор помечен explicit.

bool isRunning() const

Идёт ли установка прямо сейчас.

QString versionId() const

Идентификатор версии, которая ставится в данный момент.

QString stage() const

Текущий этап установки словами — это же значение показывается в заголовке панели прогресса.

QString currentFile() const

Подпись файла, который качается сейчас.

qint64 bytesDone() const

Сколько байт уже получено, с учётом идущих загрузок.

qint64 bytesTotal() const

Ожидаемый общий объём. Растёт по ходу установки: полный размер ресурсов становится известен только после разбора их индекса.

double fraction() const

Доля выполнения от 0 до 1 либо -1, пока итоговый объём неизвестен. Значение -1 панель прогресса показывает многоточием вместо процентов.

void install(const QString &gameDir, const QString &versionId)

Ставит версию в указанную папку игры. Если установка уже идёт, версия становится в очередь.

Порядок работы: разрешение записи манифеста, загрузка описания версии, подготовка списка задач, скачивание, разворачивание индекса ресурсов и — для версий до 1.6 — раскладка ресурсов в плоскую папку, которую те версии умеют читать.

bool isQueued(const QString &versionId) const

Стоит ли версия в очереди на установку. Позволяет не ставить одну и ту же версию дважды.

void cancel()

Отменяет текущую установку и очищает очередь. Незавершённые файлы не остаются на диске: они пишутся через QSaveFile и фиксируются только целиком.

Сигналы

started(const QString &versionId)

Установка версии началась. Обработчик показывает панель прогресса и выставляет признак занятости.

progressChanged()

Изменились числа прогресса. Испускается не чаще десяти раз в секунду: при тысячах мелких файлов сигнал на каждый принятый блок обошёлся бы дороже самой загрузки.

Обработчик перечитывает stage(), currentFile(), bytesDone(), bytesTotal() и fraction().

finished(const QString &versionId)

Версия установлена успешно. Обработчик убирает панель прогресса, обновляет список установленных версий и пересчитывает комплектность сборок.

failed(const QString &versionId, const QString &message)

Установка не удалась; в параметре — текст ошибки для пользователя. Часть файлов при этом может остаться на диске: повторная установка докачает недостающее.

canceled(const QString &versionId)

Установка отменена пользователем. В отличие от failed, ошибку показывать не нужно.

Владение и время жизни

Класс наследует QObject и принимает parent — родитель его и удалит.

Указатель на VersionManifestService, переданный в конструктор, не принадлежит установщику: сервис создаётся раньше и живёт дольше. QNetworkAccessManager и оба таймера создаются в конструкторе с установщиком в роли родителя.

Активные загрузки хранятся как std::shared_ptr<ActiveDownload>, а файл внутри каждой — как std::unique_ptr<QSaveFile>: незавершённая запись отменяется вместе с уничтожением объекта, и испорченный файл не попадает на место назначения.

Потокобезопасность

Только поток GUI, как и остальной сетевой код лаунчера. Параллелизм здесь — не потоки, а несколько одновременных сетевых запросов в одном цикле событий. Раскладка ресурсов для старых версий выполняется порциями по таймеру, чтобы не занимать поток надолго.

Взаимодействие с другими классами

LauncherBackend вызывает install() при установке сборки и переправляет все пять сигналов в свойства, которые читает QML: панель прогресса главного окна показывает stage(), fraction() и байты, а finished обновляет список установленных версий.

ModLoaderInstaller держит ссылку на установщик: профиль модлоадера требует, чтобы базовая версия игры была уже на месте.

Разбор описания версии идёт через VersionLoader::load() из minecraftversion.h.

Внешнее взаимодействие

Сеть, исходящие запросы. Класс качает файлы с серверов Mojang через QNetworkAccessManager. Направление одностороннее, протокол — HTTPS; описание версии и индекс ресурсов приходят как JSON, остальное — двоичными файлами.

Каждый файл пишется потоком через QSaveFile с одновременным подсчётом sha1; несовпадение контрольной суммы считается неудачей загрузки. Неудачная задача повторяется — счётчик попыток хранится в самой задаче, — и только исчерпав попытки, приводит к сигналу failed.

Существующие файлы сверяются только по размеру: перехеширование сотен мегабайт при каждом добавлении версии дороже, чем риск битого файла.

Все сигналы приходят в поток GUI.

Пример использования

auto *installer = new VersionInstaller(manifestService, this);

connect(installer, &VersionInstaller::progressChanged, this, [this, installer] {
    emit downloadProgress(installer->fraction(), installer->currentFile());
});
connect(installer, &VersionInstaller::finished, this, &Backend::onVersionInstalled);
connect(installer, &VersionInstaller::failed, this, [this](const QString &id, const QString &message) {
    emit launchError(tr("Не удалось установить %1: %2").arg(id, message));
});

if (!installer->isQueued(versionId))
    installer->install(gameDir, versionId);

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