#pragma once #include "minecraftversion.h" #include #include #include #include #include #include #include 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 QVariantMap versionRemovalInfo(const QString &versionId) const; Q_INVOKABLE void removeVersion(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/, который реально запускается }; 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 профиля в /versions, который запускает эта сборка. QString launchVersionIdOf(const CustomBuild &build) const; int indexOfBuildId(int buildId) const; // Перекладывает /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 m_profiles; QList 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; };