new builds menu and modloaders implementation

This commit is contained in:
2026-08-27 09:21:51 +03:00
parent 5bc59f8018
commit 168884127b
23 changed files with 3979 additions and 1081 deletions
+84 -22
View File
@@ -15,6 +15,9 @@ class AuthService;
class GameLauncher;
class VersionManifestService;
class VersionInstaller;
class ModLoaderVersionService;
class ModLoaderInstaller;
class BuildSwitcher;
class LauncherBackend : public QObject
{
@@ -22,9 +25,22 @@ class LauncherBackend : public QObject
QML_ELEMENT
Q_PROPERTY(QStringList profileNames READ profileNames NOTIFY profilesChanged)
Q_PROPERTY(QStringList versionNames READ versionNames NOTIFY versionsChanged)
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)
// Объединённый список для выпадающего меню версий: установленные, разделитель,
@@ -46,9 +62,16 @@ public:
~LauncherBackend() override;
QStringList profileNames() const;
QStringList versionNames() 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;
@@ -66,32 +89,35 @@ public:
const QString &login,
const QString &password,
const QString &authType = QStringLiteral("offline"));
Q_INVOKABLE void addVersion(const QString &name,
const QString &serverUrl,
const QString &versionId = QString());
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"));
Q_INVOKABLE void updateVersion(int index,
const QString &name,
const QString &serverUrl,
const QString &versionId = QString());
// Мержит только присланные ключи: 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 versionAt(int index) const;
Q_INVOKABLE QVariantMap customBuildAt(int index) const;
Q_INVOKABLE void removeProfile(int index);
Q_INVOKABLE void removeVersion(int index);
Q_INVOKABLE void removeCustomBuild(int index);
// Докачать то, чего не хватает выбранной сборке. Отдельной кнопкой, потому
// что карточка сборки сохраняет правки по ходу редактирования.
Q_INVOKABLE void installCustomBuild(int index);
// Главная кнопка. Проверяет выбор профиля и версии, комплектность .minecraft,
// при необходимости авторизуется и стартует игру.
Q_INVOKABLE void launchGame(int profileIndex, int versionIndex);
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 versionIndex) const;
Q_INVOKABLE QStringList checkInstallation(int buildIndex) const;
// Вызывается при открытии выпадающего списка версий: свежий кэш — без сети.
Q_INVOKABLE void refreshVersionCatalog(bool force = false);
@@ -99,6 +125,20 @@ public:
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;
@@ -108,7 +148,10 @@ public:
signals:
void profilesChanged();
void versionsChanged();
void customBuildsChanged();
void activeBuildChanged();
void switchChanged();
void loaderVersionsChanged(const QString &loaderKey, const QString &gameVersion);
void installedVersionsChanged();
void settingsChanged();
void gameRunningChanged();
@@ -118,7 +161,7 @@ signals:
void downloadChanged();
void launchProgress(const QString &message);
void launched(const QString &profileName, const QString &versionName, const QString &serverUrl);
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);
@@ -132,17 +175,23 @@ private:
QString authType = QStringLiteral("offline"); // offline | elyby
QString clientToken, accessToken, uuid, resolvedName, userType;
};
struct Version
// Пользовательская сборка: имя, сервер, версия Minecraft и, начиная со
// второго этапа, выбранный модлоадер.
struct CustomBuild
{
int id = 0;
QString name, serverUrl, versionId;
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 saveVersions();
void saveCustomBuilds();
void saveSettings();
// Чтение файлов лаунчера. Отсутствие файла — норма (первый запуск), а вот
@@ -155,7 +204,13 @@ private:
bool writeJsonFile(const QString &path, const QJsonDocument &document);
QString gameDir() const;
QString versionIdOf(const Version &version) 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;
@@ -168,18 +223,25 @@ private:
void continueLaunch(const AuthResult &auth);
QList<Profile> m_profiles;
QList<Version> m_versions;
QList<CustomBuild> m_builds;
QVariantMap m_settings;
// Проблемы хранилища, замеченные на старте: показываются одним сообщением,
// когда интерфейс уже подключился к сигналам.
QStringList m_storageIssues;
int m_nextProfileId = 1;
int m_nextVersionId = 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;
// Сборка, для которой сейчас ставится модлоадер: по её id находим запись,
// когда установщик отчитается.
int m_loaderInstallBuildId = -1;
// Каталог версий пересобирается только по изменениям: QML читает свойство
// помногу раз за кадр, пока открыт список.
@@ -190,7 +252,7 @@ private:
// Состояние запуска, начатого до асинхронной авторизации.
int m_pendingProfile = -1;
int m_pendingVersion = -1;
int m_pendingBuild = -1;
MinecraftVersion m_pendingVersionData;
QString m_pendingInjectorPath;
};