2026-06-09 00:43:06 +03:00
|
|
|
#pragma once
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
#include "minecraftversion.h"
|
|
|
|
|
|
2026-08-23 03:30:31 +03:00
|
|
|
#include <QJsonArray>
|
|
|
|
|
#include <QJsonDocument>
|
|
|
|
|
#include <QJsonObject>
|
2026-06-09 00:43:06 +03:00
|
|
|
#include <QObject>
|
2026-08-22 18:19:13 +03:00
|
|
|
#include <QQmlEngine>
|
2026-06-09 00:43:06 +03:00
|
|
|
#include <QStringList>
|
2026-06-16 12:16:59 +03:00
|
|
|
#include <QVariantMap>
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
|
|
|
struct AuthResult;
|
|
|
|
|
class AuthService;
|
|
|
|
|
class GameLauncher;
|
2026-08-24 21:09:52 +03:00
|
|
|
class VersionManifestService;
|
|
|
|
|
class VersionInstaller;
|
2026-08-27 09:21:51 +03:00
|
|
|
class ModLoaderVersionService;
|
|
|
|
|
class ModLoaderInstaller;
|
|
|
|
|
class BuildSwitcher;
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
class LauncherBackend : public QObject
|
|
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
QML_ELEMENT
|
|
|
|
|
|
|
|
|
|
Q_PROPERTY(QStringList profileNames READ profileNames NOTIFY profilesChanged)
|
2026-08-27 09:21:51 +03:00
|
|
|
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)
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_PROPERTY(QStringList installedVersions READ installedVersions NOTIFY installedVersionsChanged)
|
|
|
|
|
Q_PROPERTY(bool gameRunning READ gameRunning NOTIFY gameRunningChanged)
|
2026-08-27 09:21:51 +03:00
|
|
|
|
|
|
|
|
// Идёт архивация/распаковка .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)
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
|
2026-06-09 00:43:06 +03:00
|
|
|
|
2026-08-24 21:09:52 +03:00
|
|
|
// Объединённый список для выпадающего меню версий: установленные, разделитель,
|
|
|
|
|
// затем весь каталог Mojang.
|
|
|
|
|
Q_PROPERTY(QVariantList versionCatalog READ versionCatalog NOTIFY versionCatalogChanged)
|
|
|
|
|
Q_PROPERTY(bool catalogLoading READ catalogLoading NOTIFY catalogLoadingChanged)
|
|
|
|
|
|
|
|
|
|
// Одного сигнала на все свойства загрузки достаточно: установщик уже
|
|
|
|
|
// ограничивает частоту, а 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)
|
|
|
|
|
|
2026-06-09 00:43:06 +03:00
|
|
|
public:
|
|
|
|
|
explicit LauncherBackend(QObject *parent = nullptr);
|
2026-08-22 18:19:13 +03:00
|
|
|
~LauncherBackend() override;
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
QStringList profileNames() const;
|
2026-08-27 09:21:51 +03:00
|
|
|
QStringList customBuildNames() const;
|
|
|
|
|
int activeBuildIndex() const;
|
|
|
|
|
void setActiveBuildIndex(int index);
|
|
|
|
|
QString activeBuildName() const;
|
2026-08-22 18:19:13 +03:00
|
|
|
QStringList installedVersions() const;
|
|
|
|
|
bool gameRunning() const;
|
2026-08-27 09:21:51 +03:00
|
|
|
bool switching() const;
|
|
|
|
|
double switchProgress() const;
|
|
|
|
|
QString switchStage() const;
|
|
|
|
|
QString switchStatus() const;
|
2026-08-24 21:09:52 +03:00
|
|
|
// Пока идёт загрузка версии, лаунчер тоже занят: кнопка запуска гаснет.
|
|
|
|
|
bool busy() const;
|
|
|
|
|
|
|
|
|
|
QVariantList versionCatalog() const;
|
|
|
|
|
bool catalogLoading() const;
|
|
|
|
|
|
|
|
|
|
bool downloading() const;
|
|
|
|
|
double downloadProgress() const;
|
|
|
|
|
QString downloadVersion() const;
|
|
|
|
|
QString downloadStatus() const;
|
|
|
|
|
qint64 downloadBytesDone() const;
|
|
|
|
|
qint64 downloadBytesTotal() const;
|
2026-06-09 00:43:06 +03:00
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_INVOKABLE void addProfile(const QString &name,
|
|
|
|
|
const QString &login,
|
|
|
|
|
const QString &password,
|
|
|
|
|
const QString &authType = QStringLiteral("offline"));
|
2026-08-27 09:21:51 +03:00
|
|
|
Q_INVOKABLE void addCustomBuild(const QString &name,
|
|
|
|
|
const QString &serverUrl,
|
|
|
|
|
const QString &minecraftVersion = QString());
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_INVOKABLE void updateProfile(int index,
|
|
|
|
|
const QString &name,
|
|
|
|
|
const QString &login,
|
|
|
|
|
const QString &password,
|
|
|
|
|
const QString &authType = QStringLiteral("offline"));
|
2026-08-27 09:21:51 +03:00
|
|
|
// Мержит только присланные ключи: name, serverUrl, minecraftVersion,
|
|
|
|
|
// loader, loaderVersion, resolvedVersionId.
|
|
|
|
|
Q_INVOKABLE void updateCustomBuild(int index, const QVariantMap &fields);
|
2026-06-16 12:16:59 +03:00
|
|
|
Q_INVOKABLE QVariantMap profileAt(int index) const;
|
2026-08-27 09:21:51 +03:00
|
|
|
Q_INVOKABLE QVariantMap customBuildAt(int index) const;
|
2026-06-15 00:22:27 +03:00
|
|
|
Q_INVOKABLE void removeProfile(int index);
|
2026-08-27 16:44:42 +03:00
|
|
|
// Что именно потеряется при удалении — для текста предупреждения.
|
|
|
|
|
Q_INVOKABLE QVariantMap customBuildRemovalInfo(int index) const;
|
2026-08-27 09:21:51 +03:00
|
|
|
Q_INVOKABLE void removeCustomBuild(int index);
|
|
|
|
|
|
|
|
|
|
// Докачать то, чего не хватает выбранной сборке. Отдельной кнопкой, потому
|
|
|
|
|
// что карточка сборки сохраняет правки по ходу редактирования.
|
|
|
|
|
Q_INVOKABLE void installCustomBuild(int index);
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
|
|
|
// Главная кнопка. Проверяет выбор профиля и версии, комплектность .minecraft,
|
|
|
|
|
// при необходимости авторизуется и стартует игру.
|
2026-08-27 09:21:51 +03:00
|
|
|
Q_INVOKABLE void launchGame(int profileIndex, int buildIndex);
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_INVOKABLE void submitTwoFactorCode(const QString &code);
|
|
|
|
|
Q_INVOKABLE void cancelPendingLaunch();
|
|
|
|
|
Q_INVOKABLE void stopGame();
|
|
|
|
|
|
|
|
|
|
// Проверка без запуска — для подсказки в интерфейсе.
|
2026-08-27 09:21:51 +03:00
|
|
|
Q_INVOKABLE QStringList checkInstallation(int buildIndex) const;
|
2026-08-22 18:19:13 +03:00
|
|
|
|
2026-08-24 21:09:52 +03:00
|
|
|
// Вызывается при открытии выпадающего списка версий: свежий кэш — без сети.
|
|
|
|
|
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();
|
|
|
|
|
|
2026-08-27 09:21:51 +03:00
|
|
|
// Версии модлоадеров для выбранной версии игры. Возвращает список карт
|
|
|
|
|
// {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);
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_INVOKABLE QVariantMap settings() const;
|
|
|
|
|
Q_INVOKABLE void updateSettings(const QVariantMap &values);
|
|
|
|
|
Q_INVOKABLE QStringList detectedJava() const;
|
|
|
|
|
|
2026-06-19 21:03:37 +03:00
|
|
|
Q_INVOKABLE void openMinecraftFolder();
|
2026-08-22 18:19:13 +03:00
|
|
|
Q_INVOKABLE void openGameFolder();
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
signals:
|
|
|
|
|
void profilesChanged();
|
2026-08-27 09:21:51 +03:00
|
|
|
void customBuildsChanged();
|
|
|
|
|
void activeBuildChanged();
|
|
|
|
|
void switchChanged();
|
|
|
|
|
void loaderVersionsChanged(const QString &loaderKey, const QString &gameVersion);
|
2026-08-22 18:19:13 +03:00
|
|
|
void installedVersionsChanged();
|
|
|
|
|
void settingsChanged();
|
|
|
|
|
void gameRunningChanged();
|
|
|
|
|
void busyChanged();
|
2026-08-24 21:09:52 +03:00
|
|
|
void versionCatalogChanged();
|
|
|
|
|
void catalogLoadingChanged();
|
|
|
|
|
void downloadChanged();
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
|
|
|
void launchProgress(const QString &message);
|
2026-08-27 09:21:51 +03:00
|
|
|
void launched(const QString &profileName, const QString &buildName, const QString &serverUrl);
|
2026-06-09 00:43:06 +03:00
|
|
|
void launchError(const QString &message);
|
2026-08-22 18:19:13 +03:00
|
|
|
void twoFactorRequired(const QString &profileName);
|
|
|
|
|
void gameOutput(const QString &line);
|
|
|
|
|
void gameFinished(int exitCode, bool crashed);
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
private:
|
2026-08-22 18:19:13 +03:00
|
|
|
struct Profile
|
|
|
|
|
{
|
|
|
|
|
int id = 0;
|
|
|
|
|
QString name, login, password;
|
2026-08-22 23:39:46 +00:00
|
|
|
QString authType = QStringLiteral("offline"); // offline | elyby
|
2026-08-22 18:19:13 +03:00
|
|
|
QString clientToken, accessToken, uuid, resolvedName, userType;
|
|
|
|
|
};
|
2026-08-27 09:21:51 +03:00
|
|
|
// Пользовательская сборка: имя, сервер, версия Minecraft и, начиная со
|
|
|
|
|
// второго этапа, выбранный модлоадер.
|
|
|
|
|
struct CustomBuild
|
2026-08-22 18:19:13 +03:00
|
|
|
{
|
|
|
|
|
int id = 0;
|
2026-08-27 09:21:51 +03:00
|
|
|
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>, который реально запускается
|
2026-08-22 18:19:13 +03:00
|
|
|
};
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
void loadData();
|
2026-08-22 18:19:13 +03:00
|
|
|
void loadSettings();
|
2026-08-23 03:30:31 +03:00
|
|
|
void ensureDataFiles();
|
2026-06-09 00:43:06 +03:00
|
|
|
void saveProfiles();
|
2026-08-27 09:21:51 +03:00
|
|
|
void saveCustomBuilds();
|
2026-08-22 18:19:13 +03:00
|
|
|
void saveSettings();
|
|
|
|
|
|
2026-08-23 03:30:31 +03:00
|
|
|
// Чтение файлов лаунчера. Отсутствие файла — норма (первый запуск), а вот
|
|
|
|
|
// повреждённое содержимое отводится в .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);
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
QString gameDir() const;
|
2026-08-27 09:21:51 +03:00
|
|
|
// id профиля в <gameDir>/versions, который запускает эта сборка.
|
|
|
|
|
QString launchVersionIdOf(const CustomBuild &build) const;
|
|
|
|
|
int indexOfBuildId(int buildId) const;
|
|
|
|
|
|
|
|
|
|
// Перекладывает <root>/versions.json в customBuilds.json при первом запуске
|
|
|
|
|
// после переименования. true — миграция прошла, читать нужно новый файл.
|
|
|
|
|
bool migrateLegacyBuildsFile();
|
2026-08-22 18:19:13 +03:00
|
|
|
|
2026-08-24 21:09:52 +03:00
|
|
|
void invalidateCatalog();
|
|
|
|
|
void rebuildCatalog() const;
|
|
|
|
|
// Версия выбрана, но её нет в .minecraft — ставим в фоне.
|
|
|
|
|
void maybeInstall(const QString &versionId);
|
|
|
|
|
|
2026-08-22 18:19:13 +03:00
|
|
|
void setBusy(bool busy);
|
|
|
|
|
void failLaunch(const QString &message);
|
|
|
|
|
void beginAuthentication();
|
|
|
|
|
void continueLaunch(const AuthResult &auth);
|
2026-06-09 00:43:06 +03:00
|
|
|
|
|
|
|
|
QList<Profile> m_profiles;
|
2026-08-27 09:21:51 +03:00
|
|
|
QList<CustomBuild> m_builds;
|
2026-08-22 18:19:13 +03:00
|
|
|
QVariantMap m_settings;
|
2026-08-23 03:30:31 +03:00
|
|
|
// Проблемы хранилища, замеченные на старте: показываются одним сообщением,
|
|
|
|
|
// когда интерфейс уже подключился к сигналам.
|
|
|
|
|
QStringList m_storageIssues;
|
2026-06-09 00:43:06 +03:00
|
|
|
int m_nextProfileId = 1;
|
2026-08-27 09:21:51 +03:00
|
|
|
int m_nextBuildId = 1;
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
|
|
|
AuthService *m_auth;
|
|
|
|
|
GameLauncher *m_launcher;
|
2026-08-24 21:09:52 +03:00
|
|
|
VersionManifestService *m_manifest;
|
|
|
|
|
VersionInstaller *m_installer;
|
2026-08-27 09:21:51 +03:00
|
|
|
ModLoaderVersionService *m_loaderMeta;
|
|
|
|
|
ModLoaderInstaller *m_loaderInstaller;
|
|
|
|
|
BuildSwitcher *m_switcher;
|
|
|
|
|
|
|
|
|
|
// Сборка, для которой сейчас ставится модлоадер: по её id находим запись,
|
|
|
|
|
// когда установщик отчитается.
|
|
|
|
|
int m_loaderInstallBuildId = -1;
|
2026-08-24 21:09:52 +03:00
|
|
|
|
|
|
|
|
// Каталог версий пересобирается только по изменениям: QML читает свойство
|
|
|
|
|
// помногу раз за кадр, пока открыт список.
|
|
|
|
|
mutable QVariantList m_catalog;
|
|
|
|
|
mutable bool m_catalogDirty = true;
|
2026-08-22 18:19:13 +03:00
|
|
|
|
|
|
|
|
bool m_busy = false;
|
|
|
|
|
|
|
|
|
|
// Состояние запуска, начатого до асинхронной авторизации.
|
|
|
|
|
int m_pendingProfile = -1;
|
2026-08-27 09:21:51 +03:00
|
|
|
int m_pendingBuild = -1;
|
2026-08-22 18:19:13 +03:00
|
|
|
MinecraftVersion m_pendingVersionData;
|
|
|
|
|
QString m_pendingInjectorPath;
|
2026-06-09 00:43:06 +03:00
|
|
|
};
|