Files
minecraft-launcher/launcherbackend.h
T

298 lines
15 KiB
C++

#pragma once
#include "minecraftversion.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QQmlEngine>
#include <QStringList>
#include <QVariantMap>
struct AuthResult;
class AuthService;
class GameLauncher;
class VersionManifestService;
class VersionInstaller;
class ModLoaderVersionService;
class ModLoaderInstaller;
class BuildSwitcher;
class JavaRuntimeService;
class JavaInstaller;
class LauncherBackend : public QObject
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QStringList profileNames READ profileNames NOTIFY profilesChanged)
Q_PROPERTY(QStringList customBuildNames READ customBuildNames NOTIFY customBuildsChanged)
// Сборка, которую запускает кнопка игры. Хранится по id сборки, а не по
// индексу: удаление соседней записи не должно переназначать активную.
Q_PROPERTY(int activeBuildIndex READ activeBuildIndex WRITE setActiveBuildIndex
NOTIFY activeBuildChanged)
Q_PROPERTY(QString activeBuildName READ activeBuildName NOTIFY activeBuildChanged)
Q_PROPERTY(QStringList installedVersions READ installedVersions NOTIFY installedVersionsChanged)
Q_PROPERTY(bool gameRunning READ gameRunning NOTIFY gameRunningChanged)
// Идёт архивация/распаковка .minecraft при смене сборки. Отдельно от busy,
// потому что на это время блокируется ещё и список сборок.
Q_PROPERTY(bool switching READ switching NOTIFY switchChanged)
Q_PROPERTY(double switchProgress READ switchProgress NOTIFY switchChanged)
Q_PROPERTY(QString switchStage READ switchStage NOTIFY switchChanged)
Q_PROPERTY(QString switchStatus READ switchStatus NOTIFY switchChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
// Объединённый список для выпадающего меню версий: установленные, разделитель,
// затем весь каталог Mojang.
Q_PROPERTY(QVariantList versionCatalog READ versionCatalog NOTIFY versionCatalogChanged)
Q_PROPERTY(bool catalogLoading READ catalogLoading NOTIFY catalogLoadingChanged)
// Сборки Java для окна выбора: скачиваемые из сети плюс те, что уже лежат
// в папке лаунчера.
Q_PROPERTY(QVariantList javaCatalog READ javaCatalog NOTIFY javaCatalogChanged)
Q_PROPERTY(bool javaCatalogLoading READ javaCatalogLoading NOTIFY javaCatalogLoadingChanged)
// Одного сигнала на все свойства загрузки достаточно: установщик уже
// ограничивает частоту, а QML всё равно перечитывает их разом.
Q_PROPERTY(bool downloading READ downloading NOTIFY downloadChanged)
Q_PROPERTY(double downloadProgress READ downloadProgress NOTIFY downloadChanged)
Q_PROPERTY(QString downloadVersion READ downloadVersion NOTIFY downloadChanged)
Q_PROPERTY(QString downloadStatus READ downloadStatus NOTIFY downloadChanged)
Q_PROPERTY(qint64 downloadBytesDone READ downloadBytesDone NOTIFY downloadChanged)
Q_PROPERTY(qint64 downloadBytesTotal READ downloadBytesTotal NOTIFY downloadChanged)
public:
explicit LauncherBackend(QObject *parent = nullptr);
~LauncherBackend() override;
QStringList profileNames() const;
QStringList customBuildNames() const;
int activeBuildIndex() const;
void setActiveBuildIndex(int index);
QString activeBuildName() const;
QStringList installedVersions() const;
bool gameRunning() const;
bool switching() const;
double switchProgress() const;
QString switchStage() const;
QString switchStatus() const;
// Пока идёт загрузка версии, лаунчер тоже занят: кнопка запуска гаснет.
bool busy() const;
QVariantList versionCatalog() const;
bool catalogLoading() const;
QVariantList javaCatalog() const;
bool javaCatalogLoading() const;
bool downloading() const;
double downloadProgress() const;
QString downloadVersion() const;
QString downloadStatus() const;
qint64 downloadBytesDone() const;
qint64 downloadBytesTotal() const;
Q_INVOKABLE void addProfile(const QString &name,
const QString &login,
const QString &password,
const QString &authType = QStringLiteral("offline"));
Q_INVOKABLE void addCustomBuild(const QString &name,
const QString &serverUrl,
const QString &minecraftVersion = QString());
Q_INVOKABLE void updateProfile(int index,
const QString &name,
const QString &login,
const QString &password,
const QString &authType = QStringLiteral("offline"));
// Мержит только присланные ключи: name, serverUrl, minecraftVersion,
// loader, loaderVersion, resolvedVersionId.
Q_INVOKABLE void updateCustomBuild(int index, const QVariantMap &fields);
Q_INVOKABLE QVariantMap profileAt(int index) const;
Q_INVOKABLE QVariantMap customBuildAt(int index) const;
Q_INVOKABLE void removeProfile(int index);
// Что именно потеряется при удалении — для текста предупреждения.
Q_INVOKABLE QVariantMap customBuildRemovalInfo(int index) const;
Q_INVOKABLE void removeCustomBuild(int index);
// Докачать то, чего не хватает выбранной сборке. Отдельной кнопкой, потому
// что карточка сборки сохраняет правки по ходу редактирования.
Q_INVOKABLE void installCustomBuild(int index);
// Главная кнопка. Проверяет выбор профиля и версии, комплектность .minecraft,
// при необходимости авторизуется и стартует игру.
Q_INVOKABLE void launchGame(int profileIndex, int buildIndex);
Q_INVOKABLE void submitTwoFactorCode(const QString &code);
Q_INVOKABLE void cancelPendingLaunch();
Q_INVOKABLE void stopGame();
// Проверка без запуска — для подсказки в интерфейсе.
Q_INVOKABLE QStringList checkInstallation(int buildIndex) const;
// Вызывается при открытии выпадающего списка версий: свежий кэш — без сети.
Q_INVOKABLE void refreshVersionCatalog(bool force = false);
Q_INVOKABLE bool isVersionInstalled(const QString &versionId) const;
Q_INVOKABLE void installVersion(const QString &versionId);
Q_INVOKABLE void cancelDownload();
// Версии модлоадеров для выбранной версии игры. Возвращает список карт
// {version, label, recommended, stable}; несовместимых строк в нём нет —
// отбор заложен в сам источник данных.
Q_INVOKABLE QVariantList loaderVersions(const QString &loaderKey,
const QString &gameVersion) const;
Q_INVOKABLE void refreshLoaderVersions(const QString &loaderKey,
const QString &gameVersion,
bool force = false);
Q_INVOKABLE bool loaderVersionsLoading(const QString &loaderKey,
const QString &gameVersion) const;
// Ставит модлоадер, выбранный в сборке, и записывает получившийся профиль
// в resolvedVersionId.
Q_INVOKABLE void installLoaderForBuild(int index);
Q_INVOKABLE QVariantMap settings() const;
Q_INVOKABLE void updateSettings(const QVariantMap &values);
Q_INVOKABLE QStringList detectedJava() const;
// ── Сборки Java ─────────────────────────────────────────────────────
// Вызывается при открытии окна выбора: свежий кэш — без сети.
Q_INVOKABLE void refreshJavaCatalog(bool force = false);
Q_INVOKABLE void installJavaRuntime(const QString &runtimeId);
Q_INVOKABLE void removeJavaRuntime(const QString &runtimeId);
// Пусто — сборки с таким id в папке лаунчера нет.
Q_INVOKABLE QVariantMap javaRuntimeInfo(const QString &runtimeId) const;
// Минимальная Java для версии игры выбранной сборки; 0 — версия не выбрана.
Q_INVOKABLE int requiredJavaMajor(int buildIndex) const;
Q_INVOKABLE void openMinecraftFolder();
Q_INVOKABLE void openGameFolder();
signals:
void profilesChanged();
void customBuildsChanged();
void activeBuildChanged();
void switchChanged();
void loaderVersionsChanged(const QString &loaderKey, const QString &gameVersion);
void installedVersionsChanged();
void settingsChanged();
void gameRunningChanged();
void busyChanged();
void versionCatalogChanged();
void catalogLoadingChanged();
void javaCatalogChanged();
void javaCatalogLoadingChanged();
void downloadChanged();
// Сборка Java установлена: окно настроек обновляет подпись, не переоткрываясь.
void javaRuntimeInstalled(const QString &runtimeId);
void launchProgress(const QString &message);
void launched(const QString &profileName, const QString &buildName, const QString &serverUrl);
void launchError(const QString &message);
void twoFactorRequired(const QString &profileName);
void gameOutput(const QString &line);
void gameFinished(int exitCode, bool crashed);
private:
struct Profile
{
int id = 0;
QString name, login, password;
QString authType = QStringLiteral("offline"); // offline | elyby
QString clientToken, accessToken, uuid, resolvedName, userType;
};
// Пользовательская сборка: имя, сервер, версия Minecraft и, начиная со
// второго этапа, выбранный модлоадер.
struct CustomBuild
{
int id = 0;
QString name, serverUrl;
QString minecraftVersion; // 1.20.1
QString loader; // "" | forge | fabric | neoforge | quilt
QString loaderVersion; // 47.4.0 | 0.19.3
QString resolvedVersionId; // versions/<id>, который реально запускается
};
void loadData();
void loadSettings();
void ensureDataFiles();
void saveProfiles();
void saveCustomBuilds();
void saveSettings();
// Чтение файлов лаунчера. Отсутствие файла — норма (первый запуск), а вот
// повреждённое содержимое отводится в .bak, чтобы файл создался заново.
QJsonArray readArrayFile(const QString &path);
QJsonObject readObjectFile(const QString &path);
bool moveAsideBroken(const QString &path, const QString &reason);
// Проверяет папку лаунчера и пишет документ. Об ошибке сообщает через launchError.
bool writeJsonFile(const QString &path, const QJsonDocument &document);
QString gameDir() const;
// id профиля в <gameDir>/versions, который запускает эта сборка.
QString launchVersionIdOf(const CustomBuild &build) const;
int indexOfBuildId(int buildId) const;
// Перекладывает <root>/versions.json в customBuilds.json при первом запуске
// после переименования. true — миграция прошла, читать нужно новый файл.
bool migrateLegacyBuildsFile();
void invalidateCatalog();
void rebuildCatalog() const;
void invalidateJavaCatalog();
void rebuildJavaCatalog() const;
// Панель загрузки занята установкой Java, а не версии или модлоадера.
bool javaDownloadShown() const;
// Путь к java выбранной сборки из папки лаунчера. Пусто — сборка не выбрана
// или ещё не скачана; тогда в дело идёт javaPath и автопоиск.
QString managedJavaPath() const;
// Версия выбрана, но её нет в .minecraft — ставим в фоне.
void maybeInstall(const QString &versionId);
void setBusy(bool busy);
void failLaunch(const QString &message);
void beginAuthentication();
void continueLaunch(const AuthResult &auth);
QList<Profile> m_profiles;
QList<CustomBuild> m_builds;
QVariantMap m_settings;
// Проблемы хранилища, замеченные на старте: показываются одним сообщением,
// когда интерфейс уже подключился к сигналам.
QStringList m_storageIssues;
int m_nextProfileId = 1;
int m_nextBuildId = 1;
AuthService *m_auth;
GameLauncher *m_launcher;
VersionManifestService *m_manifest;
VersionInstaller *m_installer;
ModLoaderVersionService *m_loaderMeta;
ModLoaderInstaller *m_loaderInstaller;
BuildSwitcher *m_switcher;
JavaRuntimeService *m_javaMeta;
JavaInstaller *m_javaInstaller;
// Сборка, для которой сейчас ставится модлоадер: по её id находим запись,
// когда установщик отчитается.
int m_loaderInstallBuildId = -1;
// Каталог версий пересобирается только по изменениям: QML читает свойство
// помногу раз за кадр, пока открыт список.
mutable QVariantList m_catalog;
mutable bool m_catalogDirty = true;
mutable QVariantList m_javaCatalog;
mutable bool m_javaCatalogDirty = true;
bool m_busy = false;
// Состояние запуска, начатого до асинхронной авторизации.
int m_pendingProfile = -1;
int m_pendingBuild = -1;
MinecraftVersion m_pendingVersionData;
QString m_pendingInjectorPath;
};