aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp3
-rw-r--r--launcher/CMakeLists.txt16
-rw-r--r--launcher/LaunchController.cpp4
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp3
-rw-r--r--launcher/minecraft/auth/AccountData.cpp121
-rw-r--r--launcher/minecraft/auth/AccountData.h14
-rw-r--r--launcher/minecraft/auth/AccountList.cpp52
-rw-r--r--launcher/minecraft/auth/AccountList.h2
-rw-r--r--launcher/minecraft/auth/AuthSession.h4
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.cpp40
-rw-r--r--launcher/minecraft/auth/MinecraftAccount.h19
-rw-r--r--launcher/minecraft/auth/Yggdrasil.cpp342
-rw-r--r--launcher/minecraft/auth/Yggdrasil.h92
-rw-r--r--launcher/minecraft/auth/flows/AuthFlow.h1
-rw-r--r--launcher/minecraft/auth/flows/Mojang.cpp22
-rw-r--r--launcher/minecraft/auth/flows/Mojang.h17
-rw-r--r--launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp45
-rw-r--r--launcher/minecraft/auth/steps/MigrationEligibilityStep.h21
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStep.cpp9
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp87
-rw-r--r--launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h21
-rw-r--r--launcher/minecraft/auth/steps/XboxUserStep.cpp2
-rw-r--r--launcher/minecraft/auth/steps/YggdrasilStep.cpp57
-rw-r--r--launcher/minecraft/auth/steps/YggdrasilStep.h28
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp4
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp29
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.h1
-rw-r--r--launcher/modplatform/CheckUpdateTask.h14
-rw-r--r--launcher/modplatform/EnsureMetadataTask.cpp3
-rw-r--r--launcher/modplatform/ModIndex.cpp34
-rw-r--r--launcher/modplatform/ModIndex.h30
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp4
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.cpp25
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp19
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.cpp4
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp16
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h1
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp29
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp5
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.h3
-rw-r--r--launcher/net/NetJob.cpp5
-rw-r--r--launcher/net/NetJob.h4
-rw-r--r--launcher/tasks/ConcurrentTask.h3
-rw-r--r--launcher/ui/MainWindow.cpp2
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp3
-rw-r--r--launcher/ui/dialogs/LoginDialog.cpp115
-rw-r--r--launcher/ui/dialogs/LoginDialog.h56
-rw-r--r--launcher/ui/dialogs/LoginDialog.ui77
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.cpp79
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.h2
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp29
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp4
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h1
-rw-r--r--launcher/ui/pages/global/AccountListPage.cpp20
-rw-r--r--launcher/ui/pages/global/AccountListPage.h1
-rw-r--r--launcher/ui/pages/global/AccountListPage.ui6
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp6
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui37
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp4
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/ShaderPackPage.cpp2
-rw-r--r--launcher/ui/pages/instance/TexturePackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/VersionPage.cpp2
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp7
69 files changed, 374 insertions, 1359 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 66044d9a..11e42f16 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -503,6 +503,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("MenuBarInsteadOfToolBar", false);
+ m_settings->registerSetting("NumberOfConcurrentTasks", 10);
+ m_settings->registerSetting("NumberOfConcurrentDownloads", 6);
+
QString defaultMonospace;
int defaultSize = 11;
#ifdef Q_OS_WIN32
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 18e0acab..364ad301 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -216,13 +216,9 @@ set(MINECRAFT_SOURCES
minecraft/auth/MinecraftAccount.h
minecraft/auth/Parsers.cpp
minecraft/auth/Parsers.h
- minecraft/auth/Yggdrasil.cpp
- minecraft/auth/Yggdrasil.h
minecraft/auth/flows/AuthFlow.cpp
minecraft/auth/flows/AuthFlow.h
- minecraft/auth/flows/Mojang.cpp
- minecraft/auth/flows/Mojang.h
minecraft/auth/flows/MSA.cpp
minecraft/auth/flows/MSA.h
minecraft/auth/flows/Offline.cpp
@@ -236,12 +232,8 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/GetSkinStep.h
minecraft/auth/steps/LauncherLoginStep.cpp
minecraft/auth/steps/LauncherLoginStep.h
- minecraft/auth/steps/MigrationEligibilityStep.cpp
- minecraft/auth/steps/MigrationEligibilityStep.h
minecraft/auth/steps/MinecraftProfileStep.cpp
minecraft/auth/steps/MinecraftProfileStep.h
- minecraft/auth/steps/MinecraftProfileStepMojang.cpp
- minecraft/auth/steps/MinecraftProfileStepMojang.h
minecraft/auth/steps/MSAStep.cpp
minecraft/auth/steps/MSAStep.h
minecraft/auth/steps/XboxAuthorizationStep.cpp
@@ -250,8 +242,6 @@ set(MINECRAFT_SOURCES
minecraft/auth/steps/XboxProfileStep.h
minecraft/auth/steps/XboxUserStep.cpp
minecraft/auth/steps/XboxUserStep.h
- minecraft/auth/steps/YggdrasilStep.cpp
- minecraft/auth/steps/YggdrasilStep.h
minecraft/gameoptions/GameOptions.h
minecraft/gameoptions/GameOptions.cpp
@@ -944,8 +934,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/IconPickerDialog.h
ui/dialogs/ImportResourceDialog.cpp
ui/dialogs/ImportResourceDialog.h
- ui/dialogs/LoginDialog.cpp
- ui/dialogs/LoginDialog.h
ui/dialogs/MSALoginDialog.cpp
ui/dialogs/MSALoginDialog.h
ui/dialogs/OfflineLoginDialog.cpp
@@ -1104,7 +1092,6 @@ qt_wrap_ui(LAUNCHER_UI
ui/dialogs/MSALoginDialog.ui
ui/dialogs/OfflineLoginDialog.ui
ui/dialogs/AboutDialog.ui
- ui/dialogs/LoginDialog.ui
ui/dialogs/EditAccountDialog.ui
ui/dialogs/ReviewMessageBox.ui
ui/dialogs/ScrollMessageBox.ui
@@ -1137,6 +1124,9 @@ include(CompilerWarnings)
# Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
+if(BUILD_TESTING)
+target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_TEST)
+endif()
set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index b21eb281..21a14606 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -88,8 +88,8 @@ void LaunchController::decideAccount()
if (accounts->count() <= 0) {
// Tell the user they need to log in at least one account in order to play.
auto reply = CustomMessageBox::selectable(m_parentWidget, tr("No Accounts"),
- tr("In order to play Minecraft, you must have at least one Microsoft or Mojang "
- "account logged in. Mojang accounts can only be used offline. "
+ tr("In order to play Minecraft, you must have at least one Microsoft "
+ "account which owns Minecraft logged in."
"Would you like to open the account manager to add an account now?"),
QMessageBox::Information, QMessageBox::Yes | QMessageBox::No)
->exec();
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 86ef3b30..f9833b97 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -856,9 +856,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
if (sessionRef.access_token != "0") {
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
}
- if (sessionRef.client_token.size()) {
- addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
- }
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
return filter;
diff --git a/launcher/minecraft/auth/AccountData.cpp b/launcher/minecraft/auth/AccountData.cpp
index 474bf7c6..e1f1e9b1 100644
--- a/launcher/minecraft/auth/AccountData.cpp
+++ b/launcher/minecraft/auth/AccountData.cpp
@@ -278,67 +278,6 @@ bool entitlementFromJSONV3(const QJsonObject& parent, MinecraftEntitlement& out)
} // namespace
-bool AccountData::resumeStateFromV2(QJsonObject data)
-{
- // The JSON object must at least have a username for it to be valid.
- if (!data.value("username").isString()) {
- qCritical() << "Can't load Mojang account info from JSON object. Username field is missing or of the wrong type.";
- return false;
- }
-
- QString userName = data.value("username").toString("");
- QString clientToken = data.value("clientToken").toString("");
- QString accessToken = data.value("accessToken").toString("");
-
- QJsonArray profileArray = data.value("profiles").toArray();
- if (profileArray.size() < 1) {
- qCritical() << "Can't load Mojang account with username \"" << userName << "\". No profiles found.";
- return false;
- }
-
- struct AccountProfile {
- QString id;
- QString name;
- bool legacy;
- };
-
- QList<AccountProfile> profiles;
- int currentProfileIndex = 0;
- int index = -1;
- QString currentProfile = data.value("activeProfile").toString("");
- for (QJsonValue profileVal : profileArray) {
- index++;
- QJsonObject profileObject = profileVal.toObject();
- QString id = profileObject.value("id").toString("");
- QString name = profileObject.value("name").toString("");
- bool legacy_ = profileObject.value("legacy").toBool(false);
- if (id.isEmpty() || name.isEmpty()) {
- qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
- continue;
- }
- if (id == currentProfile) {
- currentProfileIndex = index;
- }
- profiles.append({ id, name, legacy_ });
- }
- auto& profile = profiles[currentProfileIndex];
-
- type = AccountType::Mojang;
- legacy = profile.legacy;
-
- minecraftProfile.id = profile.id;
- minecraftProfile.name = profile.name;
- minecraftProfile.validity = Katabasis::Validity::Assumed;
-
- yggdrasilToken.token = accessToken;
- yggdrasilToken.extra["clientToken"] = clientToken;
- yggdrasilToken.extra["userName"] = userName;
- yggdrasilToken.validity = Katabasis::Validity::Assumed;
-
- validity_ = minecraftProfile.validity;
- return true;
-}
-
bool AccountData::resumeStateFromV3(QJsonObject data)
{
auto typeV = data.value("type");
@@ -349,8 +288,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
auto typeS = typeV.toString();
if (typeS == "MSA") {
type = AccountType::MSA;
- } else if (typeS == "Mojang") {
- type = AccountType::Mojang;
} else if (typeS == "Offline") {
type = AccountType::Offline;
} else {
@@ -358,11 +295,6 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
return false;
}
- if (type == AccountType::Mojang) {
- legacy = data.value("legacy").toBool(false);
- canMigrateToMSA = data.value("canMigrateToMSA").toBool(false);
- }
-
if (type == AccountType::MSA) {
auto clientIDV = data.value("msa-client-id");
if (clientIDV.isString()) {
@@ -395,15 +327,7 @@ bool AccountData::resumeStateFromV3(QJsonObject data)
QJsonObject AccountData::saveState() const
{
QJsonObject output;
- if (type == AccountType::Mojang) {
- output["type"] = "Mojang";
- if (legacy) {
- output["legacy"] = true;
- }
- if (canMigrateToMSA) {
- output["canMigrateToMSA"] = true;
- }
- } else if (type == AccountType::MSA) {
+ if (type == AccountType::MSA) {
output["type"] = "MSA";
output["msa-client-id"] = msaClientID;
tokenToJSONV3(output, msaToken, "msa");
@@ -420,51 +344,11 @@ QJsonObject AccountData::saveState() const
return output;
}
-QString AccountData::userName() const
-{
- if (type == AccountType::MSA) {
- return QString();
- }
- return yggdrasilToken.extra["userName"].toString();
-}
-
QString AccountData::accessToken() const
{
return yggdrasilToken.token;
}
-QString AccountData::clientToken() const
-{
- if (type != AccountType::Mojang) {
- return QString();
- }
- return yggdrasilToken.extra["clientToken"].toString();
-}
-
-void AccountData::setClientToken(QString clientToken)
-{
- if (type != AccountType::Mojang) {
- return;
- }
- yggdrasilToken.extra["clientToken"] = clientToken;
-}
-
-void AccountData::generateClientTokenIfMissing()
-{
- if (yggdrasilToken.extra.contains("clientToken")) {
- return;
- }
- invalidateClientToken();
-}
-
-void AccountData::invalidateClientToken()
-{
- if (type != AccountType::Mojang) {
- return;
- }
- yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{-}]"));
-}
-
QString AccountData::profileId() const
{
return minecraftProfile.id;
@@ -482,9 +366,6 @@ QString AccountData::profileName() const
QString AccountData::accountDisplayString() const
{
switch (type) {
- case AccountType::Mojang: {
- return userName();
- }
case AccountType::Offline: {
return QObject::tr("<Offline>");
}
diff --git a/launcher/minecraft/auth/AccountData.h b/launcher/minecraft/auth/AccountData.h
index 9b626c34..bac77e17 100644
--- a/launcher/minecraft/auth/AccountData.h
+++ b/launcher/minecraft/auth/AccountData.h
@@ -71,27 +71,17 @@ struct MinecraftProfile {
Katabasis::Validity validity = Katabasis::Validity::None;
};
-enum class AccountType { MSA, Mojang, Offline };
+enum class AccountType { MSA, Offline };
enum class AccountState { Unchecked, Offline, Working, Online, Disabled, Errored, Expired, Gone };
struct AccountData {
QJsonObject saveState() const;
- bool resumeStateFromV2(QJsonObject data);
bool resumeStateFromV3(QJsonObject data);
//! userName for Mojang accounts, gamertag for MSA
QString accountDisplayString() const;
- //! Only valid for Mojang accounts. MSA does not preserve this information
- QString userName() const;
-
- //! Only valid for Mojang accounts.
- QString clientToken() const;
- void setClientToken(QString clientToken);
- void invalidateClientToken();
- void generateClientTokenIfMissing();
-
//! Yggdrasil access token, as passed to the game.
QString accessToken() const;
@@ -101,8 +91,6 @@ struct AccountData {
QString lastError() const;
AccountType type = AccountType::MSA;
- bool legacy = false;
- bool canMigrateToMSA = false;
QString msaClientID;
Katabasis::Token msaToken;
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index 84dbd841..b141909a 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -54,7 +54,7 @@
#include <chrono>
-enum AccountListVersion { MojangOnly = 2, MojangMSA = 3 };
+enum AccountListVersion { MojangMSA = 3 };
AccountList::AccountList(QObject* parent) : QAbstractListModel(parent)
{
@@ -320,17 +320,6 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
}
}
- case MigrationColumn: {
- if (account->isMSA() || account->isOffline()) {
- return tr("N/A", "Can Migrate");
- }
- if (account->canMigrate()) {
- return tr("Yes", "Can Migrate");
- } else {
- return tr("No", "Can Migrate");
- }
- }
-
default:
return QVariant();
}
@@ -366,8 +355,6 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o
return tr("Type");
case StatusColumn:
return tr("Status");
- case MigrationColumn:
- return tr("Can Migrate?");
default:
return QVariant();
}
@@ -379,11 +366,9 @@ QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation o
case NameColumn:
return tr("User name of the account.");
case TypeColumn:
- return tr("Type of the account - Mojang or MSA.");
+ return tr("Type of the account (MSA or Offline)");
case StatusColumn:
return tr("Current status of the account.");
- case MigrationColumn:
- return tr("Can this account migrate to a Microsoft account?");
default:
return QVariant();
}
@@ -473,9 +458,6 @@ bool AccountList::loadList()
// Make sure the format version matches.
auto listVersion = root.value("formatVersion").toVariant().toInt();
switch (listVersion) {
- case AccountListVersion::MojangOnly: {
- return loadV2(root);
- } break;
case AccountListVersion::MojangMSA: {
return loadV3(root);
} break;
@@ -489,36 +471,6 @@ bool AccountList::loadList()
}
}
-bool AccountList::loadV2(QJsonObject& root)
-{
- beginResetModel();
- auto defaultUserName = root.value("activeAccount").toString("");
- QJsonArray accounts = root.value("accounts").toArray();
- for (QJsonValue accountVal : accounts) {
- QJsonObject accountObj = accountVal.toObject();
- MinecraftAccountPtr account = MinecraftAccount::loadFromJsonV2(accountObj);
- if (account.get() != nullptr) {
- auto profileId = account->profileId();
- if (!profileId.size()) {
- continue;
- }
- if (findAccountByProfileId(profileId) != -1) {
- continue;
- }
- connect(account.get(), &MinecraftAccount::changed, this, &AccountList::accountChanged);
- connect(account.get(), &MinecraftAccount::activityChanged, this, &AccountList::accountActivityChanged);
- m_accounts.append(account);
- if (defaultUserName.size() && account->mojangUserName() == defaultUserName) {
- m_defaultAccount = account;
- }
- } else {
- qWarning() << "Failed to load an account.";
- }
- }
- endResetModel();
- return true;
-}
-
bool AccountList::loadV3(QJsonObject& root)
{
beginResetModel();
diff --git a/launcher/minecraft/auth/AccountList.h b/launcher/minecraft/auth/AccountList.h
index 6a0b0191..051d8f95 100644
--- a/launcher/minecraft/auth/AccountList.h
+++ b/launcher/minecraft/auth/AccountList.h
@@ -55,7 +55,6 @@ class AccountList : public QAbstractListModel {
// TODO: Add icon column.
ProfileNameColumn = 0,
NameColumn,
- MigrationColumn,
TypeColumn,
StatusColumn,
@@ -97,7 +96,6 @@ class AccountList : public QAbstractListModel {
void setListFilePath(QString path, bool autosave = false);
bool loadList();
- bool loadV2(QJsonObject& root);
bool loadV3(QJsonObject& root);
bool saveList();
diff --git a/launcher/minecraft/auth/AuthSession.h b/launcher/minecraft/auth/AuthSession.h
index 40519476..074b2d6e 100644
--- a/launcher/minecraft/auth/AuthSession.h
+++ b/launcher/minecraft/auth/AuthSession.h
@@ -24,10 +24,6 @@ struct AuthSession {
GoneOrMigrated
} status = Undetermined;
- // client token
- QString client_token;
- // account user name
- QString username;
// combined session ID
QString session;
// volatile auth token
diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp
index 6c2f0805..545d06ae 100644
--- a/launcher/minecraft/auth/MinecraftAccount.cpp
+++ b/launcher/minecraft/auth/MinecraftAccount.cpp
@@ -51,7 +51,6 @@
#include <QPainter>
#include "flows/MSA.h"
-#include "flows/Mojang.h"
#include "flows/Offline.h"
MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
@@ -59,15 +58,6 @@ MinecraftAccount::MinecraftAccount(QObject* parent) : QObject(parent)
data.internalId = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
}
-MinecraftAccountPtr MinecraftAccount::loadFromJsonV2(const QJsonObject& json)
-{
- MinecraftAccountPtr account(new MinecraftAccount());
- if (account->data.resumeStateFromV2(json)) {
- return account;
- }
- return nullptr;
-}
-
MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
{
MinecraftAccountPtr account(new MinecraftAccount());
@@ -77,15 +67,6 @@ MinecraftAccountPtr MinecraftAccount::loadFromJsonV3(const QJsonObject& json)
return nullptr;
}
-MinecraftAccountPtr MinecraftAccount::createFromUsername(const QString& username)
-{
- auto account = makeShared<MinecraftAccount>();
- account->data.type = AccountType::Mojang;
- account->data.yggdrasilToken.extra["userName"] = username;
- account->data.yggdrasilToken.extra["clientToken"] = QUuid::createUuid().toString().remove(QRegularExpression("[{}-]"));
- return account;
-}
-
MinecraftAccountPtr MinecraftAccount::createBlankMSA()
{
MinecraftAccountPtr account(new MinecraftAccount());
@@ -138,18 +119,6 @@ QPixmap MinecraftAccount::getFace() const
return skin.scaled(64, 64, Qt::KeepAspectRatio);
}
-shared_qobject_ptr<AccountTask> MinecraftAccount::login(QString password)
-{
- Q_ASSERT(m_currentTask.get() == nullptr);
-
- m_currentTask.reset(new MojangLogin(&data, password));
- connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
- connect(m_currentTask.get(), &Task::failed, this, &MinecraftAccount::authFailed);
- connect(m_currentTask.get(), &Task::aborted, this, [this] { authFailed(tr("Aborted")); });
- emit activityChanged(true);
- return m_currentTask;
-}
-
shared_qobject_ptr<AccountTask> MinecraftAccount::loginMSA()
{
Q_ASSERT(m_currentTask.get() == nullptr);
@@ -182,10 +151,8 @@ shared_qobject_ptr<AccountTask> MinecraftAccount::refresh()
if (data.type == AccountType::MSA) {
m_currentTask.reset(new MSASilent(&data));
- } else if (data.type == AccountType::Offline) {
- m_currentTask.reset(new OfflineRefresh(&data));
} else {
- m_currentTask.reset(new MojangRefresh(&data));
+ m_currentTask.reset(new OfflineRefresh(&data));
}
connect(m_currentTask.get(), &Task::succeeded, this, &MinecraftAccount::authSucceeded);
@@ -296,13 +263,8 @@ void MinecraftAccount::fillSession(AuthSessionPtr session)
}
}
- // the user name. you have to have an user name
- // FIXME: not with MSA
- session->username = data.userName();
// volatile auth token
session->access_token = data.accessToken();
- // the semi-permanent client token
- session->client_token = data.clientToken();
// profile name
session->player_name = data.profileName();
// profile ID
diff --git a/launcher/minecraft/auth/MinecraftAccount.h b/launcher/minecraft/auth/MinecraftAccount.h
index f04f947f..910f4a28 100644
--- a/launcher/minecraft/auth/MinecraftAccount.h
+++ b/launcher/minecraft/auth/MinecraftAccount.h
@@ -85,13 +85,10 @@ class MinecraftAccount : public QObject, public Usable {
//! Default constructor
explicit MinecraftAccount(QObject* parent = 0);
- static MinecraftAccountPtr createFromUsername(const QString& username);
-
static MinecraftAccountPtr createBlankMSA();
static MinecraftAccountPtr createOffline(const QString& username);
- static MinecraftAccountPtr loadFromJsonV2(const QJsonObject& json);
static MinecraftAccountPtr loadFromJsonV3(const QJsonObject& json);
static QUuid uuidFromUsername(QString username);
@@ -100,12 +97,6 @@ class MinecraftAccount : public QObject, public Usable {
QJsonObject saveToJson() const;
public: /* manipulation */
- /**
- * Attempt to login. Empty password means we use the token.
- * If the attempt fails because we already are performing some task, it returns false.
- */
- shared_qobject_ptr<AccountTask> login(QString password);
-
shared_qobject_ptr<AccountTask> loginMSA();
shared_qobject_ptr<AccountTask> loginOffline();
@@ -119,8 +110,6 @@ class MinecraftAccount : public QObject, public Usable {
QString accountDisplayString() const { return data.accountDisplayString(); }
- QString mojangUserName() const { return data.userName(); }
-
QString accessToken() const { return data.accessToken(); }
QString profileId() const { return data.profileId(); }
@@ -129,8 +118,6 @@ class MinecraftAccount : public QObject, public Usable {
bool isActive() const;
- bool canMigrate() const { return data.canMigrateToMSA; }
-
bool isMSA() const { return data.type == AccountType::MSA; }
bool isOffline() const { return data.type == AccountType::Offline; }
@@ -142,12 +129,6 @@ class MinecraftAccount : public QObject, public Usable {
QString typeString() const
{
switch (data.type) {
- case AccountType::Mojang: {
- if (data.legacy) {
- return "legacy";
- }
- return "mojang";
- } break;
case AccountType::MSA: {
return "msa";
} break;
diff --git a/launcher/minecraft/auth/Yggdrasil.cpp b/launcher/minecraft/auth/Yggdrasil.cpp
deleted file mode 100644
index 97f2a78d..00000000
--- a/launcher/minecraft/auth/Yggdrasil.cpp
+++ /dev/null
@@ -1,342 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Yggdrasil.h"
-#include "AccountData.h"
-
-#include <QByteArray>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QNetworkReply>
-#include <QObject>
-#include <QString>
-
-#include <QDebug>
-
-#include "Application.h"
-
-Yggdrasil::Yggdrasil(AccountData* data, QObject* parent) : AccountTask(data, parent)
-{
- changeState(AccountTaskState::STATE_CREATED);
-}
-
-void Yggdrasil::sendRequest(QUrl endpoint, QByteArray content)
-{
- changeState(AccountTaskState::STATE_WORKING);
-
- QNetworkRequest netRequest(endpoint);
- netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- m_netReply = APPLICATION->network()->post(netRequest, content);
- connect(m_netReply, &QNetworkReply::finished, this, &Yggdrasil::processReply);
- connect(m_netReply, &QNetworkReply::uploadProgress, this, &Yggdrasil::refreshTimers);
- connect(m_netReply, &QNetworkReply::downloadProgress, this, &Yggdrasil::refreshTimers);
- connect(m_netReply, &QNetworkReply::sslErrors, this, &Yggdrasil::sslErrors);
- timeout_keeper.setSingleShot(true);
- timeout_keeper.start(timeout_max);
- counter.setSingleShot(false);
- counter.start(time_step);
- progress(0, timeout_max);
- connect(&timeout_keeper, &QTimer::timeout, this, &Yggdrasil::abortByTimeout);
- connect(&counter, &QTimer::timeout, this, &Yggdrasil::heartbeat);
-}
-
-void Yggdrasil::executeTask() {}
-
-void Yggdrasil::refresh()
-{
- start();
- /*
- * {
- * "clientToken": "client identifier"
- * "accessToken": "current access token to be refreshed"
- * "selectedProfile": // specifying this causes errors
- * {
- * "id": "profile ID"
- * "name": "profile name"
- * }
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
- req.insert("clientToken", m_data->clientToken());
- req.insert("accessToken", m_data->accessToken());
- /*
- {
- auto currentProfile = m_account->currentProfile();
- QJsonObject profile;
- profile.insert("id", currentProfile->id());
- profile.insert("name", currentProfile->name());
- req.insert("selectedProfile", profile);
- }
- */
- req.insert("requestUser", false);
- QJsonDocument doc(req);
-
- QUrl reqUrl("https://authserver.mojang.com/refresh");
- QByteArray requestData = doc.toJson();
-
- sendRequest(reqUrl, requestData);
-}
-
-void Yggdrasil::login(QString password)
-{
- start();
- /*
- * {
- * "agent": { // optional
- * "name": "Minecraft", // So far this is the only encountered value
- * "version": 1 // This number might be increased
- * // by the vanilla client in the future
- * },
- * "username": "mojang account name", // Can be an email address or player name for
- * // unmigrated accounts
- * "password": "mojang account password",
- * "clientToken": "client identifier", // optional
- * "requestUser": true/false // request the user structure
- * }
- */
- QJsonObject req;
-
- {
- QJsonObject agent;
- // C++ makes string literals void* for some stupid reason, so we have to tell it
- // QString... Thanks Obama.
- agent.insert("name", QString("Minecraft"));
- agent.insert("version", 1);
- req.insert("agent", agent);
- }
-
- req.insert("username", m_data->userName());
- req.insert("password", password);
- req.insert("requestUser", false);
-
- // If we already have a client token, give it to the server.
- // Otherwise, let the server give us one.
-
- m_data->generateClientTokenIfMissing();
- req.insert("clientToken", m_data->clientToken());
-
- QJsonDocument doc(req);
-
- QUrl reqUrl("https://authserver.mojang.com/authenticate");
- QNetworkRequest netRequest(reqUrl);
- QByteArray requestData = doc.toJson();
-
- sendRequest(reqUrl, requestData);
-}
-
-void Yggdrasil::refreshTimers(qint64, qint64)
-{
- timeout_keeper.stop();
- timeout_keeper.start(timeout_max);
- progress(count = 0, timeout_max);
-}
-
-void Yggdrasil::heartbeat()
-{
- count += time_step;
- progress(count, timeout_max);
-}
-
-bool Yggdrasil::abort()
-{
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = Yggdrasil::BY_USER;
- m_netReply->abort();
- return true;
-}
-
-void Yggdrasil::abortByTimeout()
-{
- progress(timeout_max, timeout_max);
- // TODO: actually use this in a meaningful way
- m_aborted = Yggdrasil::BY_TIMEOUT;
- m_netReply->abort();
-}
-
-void Yggdrasil::sslErrors(QList<QSslError> errors)
-{
- int i = 1;
- for (auto error : errors) {
- qCritical() << "LOGIN SSL Error #" << i << " : " << error.errorString();
- auto cert = error.certificate();
- qCritical() << "Certificate in question:\n" << cert.toText();
- i++;
- }
-}
-
-void Yggdrasil::processResponse(QJsonObject responseData)
-{
- // Read the response data. We need to get the client token, access token, and the selected
- // profile.
- qDebug() << "Processing authentication response.";
-
- // qDebug() << responseData;
- // If we already have a client token, make sure the one the server gave us matches our
- // existing one.
- QString clientToken = responseData.value("clientToken").toString("");
- if (clientToken.isEmpty()) {
- // Fail if the server gave us an empty client token
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a client token."));
- return;
- }
- if (m_data->clientToken().isEmpty()) {
- m_data->setClientToken(clientToken);
- } else if (clientToken != m_data->clientToken()) {
- changeState(AccountTaskState::STATE_FAILED_HARD,
- tr("Authentication server attempted to change the client token. This isn't supported."));
- return;
- }
-
- // Now, we set the access token.
- qDebug() << "Getting access token.";
- QString accessToken = responseData.value("accessToken").toString("");
- if (accessToken.isEmpty()) {
- // Fail if the server didn't give us an access token.
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send an access token."));
- return;
- }
- // Set the access token.
- m_data->yggdrasilToken.token = accessToken;
- m_data->yggdrasilToken.validity = Katabasis::Validity::Certain;
- m_data->yggdrasilToken.issueInstant = QDateTime::currentDateTimeUtc();
-
- // Get UUID here since we need it for later
- auto profile = responseData.value("selectedProfile");
- if (!profile.isObject()) {
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a selected profile."));
- return;
- }
-
- auto profileObj = profile.toObject();
- for (auto i = profileObj.constBegin(); i != profileObj.constEnd(); ++i) {
- if (i.key() == "name" && i.value().isString()) {
- m_data->minecraftProfile.name = i->toString();
- } else if (i.key() == "id" && i.value().isString()) {
- m_data->minecraftProfile.id = i->toString();
- }
- }
-
- if (m_data->minecraftProfile.id.isEmpty()) {
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("Authentication server didn't send a UUID in selected profile."));
- return;
- }
-
- // We've made it through the minefield of possible errors. Return true to indicate that
- // we've succeeded.
- qDebug() << "Finished reading authentication response.";
- changeState(AccountTaskState::STATE_SUCCEEDED);
-}
-
-void Yggdrasil::processReply()
-{
- changeState(AccountTaskState::STATE_WORKING);
-
- switch (m_netReply->error()) {
- case QNetworkReply::NoError:
- break;
- case QNetworkReply::TimeoutError:
- changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation timed out."));
- return;
- case QNetworkReply::OperationCanceledError:
- changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
- return;
- case QNetworkReply::SslHandshakeFailedError:
- changeState(AccountTaskState::STATE_FAILED_SOFT,
- tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
- "<ul>"
- "<li>You use Windows and need to update your root certificates, please install any outstanding updates.</li>"
- "<li>Some device on your network is interfering with SSL traffic. In that case, "
- "you have bigger worries than Minecraft not starting.</li>"
- "<li>Possibly something else. Check the log file for details</li>"
- "</ul>"));
- return;
- // used for invalid credentials and similar errors. Fall through.
- case QNetworkReply::ContentAccessDenied:
- case QNetworkReply::ContentOperationNotPermittedError:
- break;
- case QNetworkReply::ContentGoneError: {
- changeState(AccountTaskState::STATE_FAILED_GONE,
- tr("The Mojang account no longer exists. It may have been migrated to a Microsoft account."));
- return;
- }
- default:
- changeState(AccountTaskState::STATE_FAILED_SOFT, tr("Authentication operation failed due to a network error: %1 (%2)")
- .arg(m_netReply->errorString())
- .arg(m_netReply->error()));
- return;
- }
-
- // Try to parse the response regardless of the response code.
- // Sometimes the auth server will give more information and an error code.
- QJsonParseError jsonError;
- QByteArray replyData = m_netReply->readAll();
- QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
- // Check the response code.
- int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
- if (responseCode == 200) {
- // If the response code was 200, then there shouldn't be an error. Make sure
- // anyways.
- // Also, sometimes an empty reply indicates success. If there was no data received,
- // pass an empty json object to the processResponse function.
- if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0) {
- processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
- return;
- } else {
- changeState(AccountTaskState::STATE_FAILED_SOFT,
- tr("Failed to parse authentication server response JSON response: %1 at offset %2.")
- .arg(jsonError.errorString())
- .arg(jsonError.offset));
- qCritical() << replyData;
- }
- return;
- }
-
- // If the response code was not 200, then Yggdrasil may have given us information
- // about the error.
- // If we can parse the response, then get information from it. Otherwise just say
- // there was an unknown error.
- if (jsonError.error == QJsonParseError::NoError) {
- // We were able to parse the server's response. Woo!
- // Call processError. If a subclass has overridden it then they'll handle their
- // stuff there.
- qDebug() << "The request failed, but the server gave us an error message. Processing error.";
- processError(doc.object());
- } else {
- // The server didn't say anything regarding the error. Give the user an unknown
- // error.
- qDebug() << "The request failed and the server gave no error message. Unknown error.";
- changeState(
- AccountTaskState::STATE_FAILED_SOFT,
- tr("An unknown error occurred when trying to communicate with the authentication server: %1").arg(m_netReply->errorString()));
- }
-}
-
-void Yggdrasil::processError(QJsonObject responseData)
-{
- QJsonValue errorVal = responseData.value("error");
- QJsonValue errorMessageValue = responseData.value("errorMessage");
- QJsonValue causeVal = responseData.value("cause");
-
- if (errorVal.isString() && errorMessageValue.isString()) {
- m_error = std::shared_ptr<Error>(new Error{ errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("") });
- changeState(AccountTaskState::STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
- } else {
- // Error is not in standard format. Don't set m_error and return unknown error.
- changeState(AccountTaskState::STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
- }
-}
diff --git a/launcher/minecraft/auth/Yggdrasil.h b/launcher/minecraft/auth/Yggdrasil.h
deleted file mode 100644
index 560d7fb8..00000000
--- a/launcher/minecraft/auth/Yggdrasil.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "AccountTask.h"
-
-#include <qsslerror.h>
-#include <QJsonObject>
-#include <QString>
-#include <QTimer>
-
-#include "MinecraftAccount.h"
-
-class QNetworkAccessManager;
-class QNetworkReply;
-
-/**
- * A Yggdrasil task is a task that performs an operation on a given mojang account.
- */
-class Yggdrasil : public AccountTask {
- Q_OBJECT
- public:
- explicit Yggdrasil(AccountData* data, QObject* parent = 0);
- virtual ~Yggdrasil() = default;
-
- void refresh();
- void login(QString password);
-
- struct Error {
- QString m_errorMessageShort;
- QString m_errorMessageVerbose;
- QString m_cause;
- };
- std::shared_ptr<Error> m_error;
-
- enum AbortedBy { BY_NOTHING, BY_USER, BY_TIMEOUT } m_aborted = BY_NOTHING;
-
- protected:
- void executeTask() override;
-
- /**
- * Processes the response received from the server.
- * If an error occurred, this should emit a failed signal.
- * If Yggdrasil gave an error response, it should call setError() first, and then return false.
- * Otherwise, it should return true.
- * Note: If the response from the server was blank, and the HTTP code was 200, this function is called with
- * an empty QJsonObject.
- */
- void processResponse(QJsonObject responseData);
-
- /**
- * Processes an error response received from the server.
- * The default implementation will read data from Yggdrasil's standard error response format and set it as this task's Error.
- * \returns a QString error message that will be passed to emitFailed.
- */
- virtual void processError(QJsonObject responseData);
-
- protected slots:
- void processReply();
- void refreshTimers(qint64, qint64);
- void heartbeat();
- void sslErrors(QList<QSslError>);
- void abortByTimeout();
-
- public slots:
- virtual bool abort() override;
-
- private:
- void sendRequest(QUrl endpoint, QByteArray content);
-
- protected:
- QNetworkReply* m_netReply = nullptr;
- QTimer timeout_keeper;
- QTimer counter;
- int count = 0; // num msec since time reset
-
- const int timeout_max = 30000;
- const int time_step = 50;
-};
diff --git a/launcher/minecraft/auth/flows/AuthFlow.h b/launcher/minecraft/auth/flows/AuthFlow.h
index c2c412ab..e39e926d 100644
--- a/launcher/minecraft/auth/flows/AuthFlow.h
+++ b/launcher/minecraft/auth/flows/AuthFlow.h
@@ -12,7 +12,6 @@
#include "minecraft/auth/AccountData.h"
#include "minecraft/auth/AccountTask.h"
#include "minecraft/auth/AuthStep.h"
-#include "minecraft/auth/Yggdrasil.h"
class AuthFlow : public AccountTask {
Q_OBJECT
diff --git a/launcher/minecraft/auth/flows/Mojang.cpp b/launcher/minecraft/auth/flows/Mojang.cpp
deleted file mode 100644
index 7e2db16f..00000000
--- a/launcher/minecraft/auth/flows/Mojang.cpp
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "Mojang.h"
-
-#include "minecraft/auth/steps/GetSkinStep.h"
-#include "minecraft/auth/steps/MigrationEligibilityStep.h"
-#include "minecraft/auth/steps/MinecraftProfileStepMojang.h"
-#include "minecraft/auth/steps/YggdrasilStep.h"
-
-MojangRefresh::MojangRefresh(AccountData* data, QObject* parent) : AuthFlow(data, parent)
-{
- m_steps.append(makeShared<YggdrasilStep>(m_data, QString()));
- m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
- m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
- m_steps.append(makeShared<GetSkinStep>(m_data));
-}
-
-MojangLogin::MojangLogin(AccountData* data, QString password, QObject* parent) : AuthFlow(data, parent), m_password(password)
-{
- m_steps.append(makeShared<YggdrasilStep>(m_data, m_password));
- m_steps.append(makeShared<MinecraftProfileStepMojang>(m_data));
- m_steps.append(makeShared<MigrationEligibilityStep>(m_data));
- m_steps.append(makeShared<GetSkinStep>(m_data));
-}
diff --git a/launcher/minecraft/auth/flows/Mojang.h b/launcher/minecraft/auth/flows/Mojang.h
deleted file mode 100644
index 779ca7e3..00000000
--- a/launcher/minecraft/auth/flows/Mojang.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-#include "AuthFlow.h"
-
-class MojangRefresh : public AuthFlow {
- Q_OBJECT
- public:
- explicit MojangRefresh(AccountData* data, QObject* parent = 0);
-};
-
-class MojangLogin : public AuthFlow {
- Q_OBJECT
- public:
- explicit MojangLogin(AccountData* data, QString password, QObject* parent = 0);
-
- private:
- QString m_password;
-};
diff --git a/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp b/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp
deleted file mode 100644
index 5ce953df..00000000
--- a/launcher/minecraft/auth/steps/MigrationEligibilityStep.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#include "MigrationEligibilityStep.h"
-
-#include <QNetworkRequest>
-
-#include "minecraft/auth/AuthRequest.h"
-#include "minecraft/auth/Parsers.h"
-
-MigrationEligibilityStep::MigrationEligibilityStep(AccountData* data) : AuthStep(data) {}
-
-MigrationEligibilityStep::~MigrationEligibilityStep() noexcept = default;
-
-QString MigrationEligibilityStep::describe()
-{
- return tr("Checking for migration eligibility.");
-}
-
-void MigrationEligibilityStep::perform()
-{
- auto url = QUrl("https://api.minecraftservices.com/rollout/v1/msamigration");
- QNetworkRequest request = QNetworkRequest(url);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Authorization", QString("Bearer %1").arg(m_data->yggdrasilToken.token).toUtf8());
-
- AuthRequest* requestor = new AuthRequest(this);
- connect(requestor, &AuthRequest::finished, this, &MigrationEligibilityStep::onRequestDone);
- requestor->get(request);
-}
-
-void MigrationEligibilityStep::rehydrate()
-{
- // NOOP, for now. We only save bools and there's nothing to check.
-}
-
-void MigrationEligibilityStep::onRequestDone(QNetworkReply::NetworkError error,
- QByteArray data,
- QList<QNetworkReply::RawHeaderPair> headers)
-{
- auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
- requestor->deleteLater();
-
- if (error == QNetworkReply::NoError) {
- Parsers::parseRolloutResponse(data, m_data->canMigrateToMSA);
- }
- emit finished(AccountTaskState::STATE_WORKING, tr("Got migration flags"));
-}
diff --git a/launcher/minecraft/auth/steps/MigrationEligibilityStep.h b/launcher/minecraft/auth/steps/MigrationEligibilityStep.h
deleted file mode 100644
index 8638975d..00000000
--- a/launcher/minecraft/auth/steps/MigrationEligibilityStep.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-#include <QObject>
-
-#include "QObjectPtr.h"
-#include "minecraft/auth/AuthStep.h"
-
-class MigrationEligibilityStep : public AuthStep {
- Q_OBJECT
-
- public:
- explicit MigrationEligibilityStep(AccountData* data);
- virtual ~MigrationEligibilityStep() noexcept;
-
- void perform() override;
- void rehydrate() override;
-
- QString describe() override;
-
- private slots:
- void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-};
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp
index 7cdce23f..a854342b 100644
--- a/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp
+++ b/launcher/minecraft/auth/steps/MinecraftProfileStep.cpp
@@ -41,10 +41,6 @@ void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByt
qCDebug(authCredentials()) << data;
if (error == QNetworkReply::ContentNotFoundError) {
// NOTE: Succeed even if we do not have a profile. This is a valid account state.
- if (m_data->type == AccountType::Mojang) {
- m_data->minecraftEntitlement.canPlayMinecraft = false;
- m_data->minecraftEntitlement.ownsMinecraft = false;
- }
m_data->minecraftProfile = MinecraftProfile();
emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
return;
@@ -73,10 +69,5 @@ void MinecraftProfileStep::onRequestDone(QNetworkReply::NetworkError error, QByt
return;
}
- if (m_data->type == AccountType::Mojang) {
- auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
- m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
- m_data->minecraftEntitlement.ownsMinecraft = validProfile;
- }
emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
}
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp
deleted file mode 100644
index d035e39a..00000000
--- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "MinecraftProfileStepMojang.h"
-
-#include <QNetworkRequest>
-
-#include "Logging.h"
-#include "minecraft/auth/AuthRequest.h"
-#include "minecraft/auth/Parsers.h"
-#include "net/NetUtils.h"
-
-MinecraftProfileStepMojang::MinecraftProfileStepMojang(AccountData* data) : AuthStep(data) {}
-
-MinecraftProfileStepMojang::~MinecraftProfileStepMojang() noexcept = default;
-
-QString MinecraftProfileStepMojang::describe()
-{
- return tr("Fetching the Minecraft profile.");
-}
-
-void MinecraftProfileStepMojang::perform()
-{
- if (m_data->minecraftProfile.id.isEmpty()) {
- emit finished(AccountTaskState::STATE_FAILED_HARD, tr("A UUID is required to get the profile."));
- return;
- }
-
- // use session server instead of profile due to profile endpoint being locked for locked Mojang accounts
- QUrl url = QUrl("https://sessionserver.mojang.com/session/minecraft/profile/" + m_data->minecraftProfile.id);
- QNetworkRequest req = QNetworkRequest(url);
- AuthRequest* request = new AuthRequest(this);
- connect(request, &AuthRequest::finished, this, &MinecraftProfileStepMojang::onRequestDone);
- request->get(req);
-}
-
-void MinecraftProfileStepMojang::rehydrate()
-{
- // NOOP, for now. We only save bools and there's nothing to check.
-}
-
-void MinecraftProfileStepMojang::onRequestDone(QNetworkReply::NetworkError error,
- QByteArray data,
- QList<QNetworkReply::RawHeaderPair> headers)
-{
- auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
- requestor->deleteLater();
-
- qCDebug(authCredentials()) << data;
- if (error == QNetworkReply::ContentNotFoundError) {
- // NOTE: Succeed even if we do not have a profile. This is a valid account state.
- if (m_data->type == AccountType::Mojang) {
- m_data->minecraftEntitlement.canPlayMinecraft = false;
- m_data->minecraftEntitlement.ownsMinecraft = false;
- }
- m_data->minecraftProfile = MinecraftProfile();
- emit finished(AccountTaskState::STATE_SUCCEEDED, tr("Account has no Minecraft profile."));
- return;
- }
- if (error != QNetworkReply::NoError) {
- qWarning() << "Error getting profile:";
- qWarning() << " HTTP Status: " << requestor->httpStatus_;
- qWarning() << " Internal error no.: " << error;
- qWarning() << " Error string: " << requestor->errorString_;
-
- qWarning() << " Response:";
- qWarning() << QString::fromUtf8(data);
-
- if (Net::isApplicationError(error)) {
- emit finished(AccountTaskState::STATE_FAILED_SOFT,
- tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
- } else {
- emit finished(AccountTaskState::STATE_OFFLINE,
- tr("Minecraft Java profile acquisition failed: %1").arg(requestor->errorString_));
- }
- return;
- }
- if (!Parsers::parseMinecraftProfileMojang(data, m_data->minecraftProfile)) {
- m_data->minecraftProfile = MinecraftProfile();
- emit finished(AccountTaskState::STATE_FAILED_SOFT, tr("Minecraft Java profile response could not be parsed"));
- return;
- }
-
- if (m_data->type == AccountType::Mojang) {
- auto validProfile = m_data->minecraftProfile.validity == Katabasis::Validity::Certain;
- m_data->minecraftEntitlement.canPlayMinecraft = validProfile;
- m_data->minecraftEntitlement.ownsMinecraft = validProfile;
- }
- emit finished(AccountTaskState::STATE_WORKING, tr("Minecraft Java profile acquisition succeeded."));
-}
diff --git a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h b/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h
deleted file mode 100644
index 730ec3f6..00000000
--- a/launcher/minecraft/auth/steps/MinecraftProfileStepMojang.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-#include <QObject>
-
-#include "QObjectPtr.h"
-#include "minecraft/auth/AuthStep.h"
-
-class MinecraftProfileStepMojang : public AuthStep {
- Q_OBJECT
-
- public:
- explicit MinecraftProfileStepMojang(AccountData* data);
- virtual ~MinecraftProfileStepMojang() noexcept;
-
- void perform() override;
- void rehydrate() override;
-
- QString describe() override;
-
- private slots:
- void onRequestDone(QNetworkReply::NetworkError, QByteArray, QList<QNetworkReply::RawHeaderPair>);
-};
diff --git a/launcher/minecraft/auth/steps/XboxUserStep.cpp b/launcher/minecraft/auth/steps/XboxUserStep.cpp
index 61c33a18..856036d2 100644
--- a/launcher/minecraft/auth/steps/XboxUserStep.cpp
+++ b/launcher/minecraft/auth/steps/XboxUserStep.cpp
@@ -38,7 +38,7 @@ void XboxUserStep::perform()
QNetworkRequest request = QNetworkRequest(QUrl("https://user.auth.xboxlive.com/user/authenticate"));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Accept", "application/json");
- // set contract-verison header (prevent err 400 bad-request?)
+ // set contract-version header (prevent err 400 bad-request?)
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/live/rest/additional/httpstandardheaders
request.setRawHeader("x-xbl-contract-version", "1");
diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.cpp b/launcher/minecraft/auth/steps/YggdrasilStep.cpp
deleted file mode 100644
index fdcaa0d6..00000000
--- a/launcher/minecraft/auth/steps/YggdrasilStep.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#include "YggdrasilStep.h"
-
-#include "minecraft/auth/AuthRequest.h"
-#include "minecraft/auth/Parsers.h"
-#include "minecraft/auth/Yggdrasil.h"
-
-YggdrasilStep::YggdrasilStep(AccountData* data, QString password) : AuthStep(data), m_password(password)
-{
- m_yggdrasil = new Yggdrasil(m_data, this);
-
- connect(m_yggdrasil, &Task::failed, this, &YggdrasilStep::onAuthFailed);
- connect(m_yggdrasil, &Task::succeeded, this, &YggdrasilStep::onAuthSucceeded);
- connect(m_yggdrasil, &Task::aborted, this, &YggdrasilStep::onAuthFailed);
-}
-
-YggdrasilStep::~YggdrasilStep() noexcept = default;
-
-QString YggdrasilStep::describe()
-{
- return tr("Logging in with Mojang account.");
-}
-
-void YggdrasilStep::rehydrate()
-{
- // NOOP, for now.
-}
-
-void YggdrasilStep::perform()
-{
- if (m_password.size()) {
- m_yggdrasil->login(m_password);
- } else {
- m_yggdrasil->refresh();
- }
-}
-
-void YggdrasilStep::onAuthSucceeded()
-{
- emit finished(AccountTaskState::STATE_WORKING, tr("Logged in with Mojang"));
-}
-
-void YggdrasilStep::onAuthFailed()
-{
- // TODO: hook these in again, expand to MSA
- // m_error = m_yggdrasil->m_error;
- // m_aborted = m_yggdrasil->m_aborted;
-
- auto state = m_yggdrasil->taskState();
- QString errorMessage = tr("Mojang user authentication failed.");
-
- // NOTE: soft error in the first step means 'offline'
- if (state == AccountTaskState::STATE_FAILED_SOFT) {
- state = AccountTaskState::STATE_OFFLINE;
- errorMessage = tr("Mojang user authentication ended with a network error.");
- }
- emit finished(state, errorMessage);
-}
diff --git a/launcher/minecraft/auth/steps/YggdrasilStep.h b/launcher/minecraft/auth/steps/YggdrasilStep.h
deleted file mode 100644
index ef31f34d..00000000
--- a/launcher/minecraft/auth/steps/YggdrasilStep.h
+++ /dev/null
@@ -1,28 +0,0 @@
-#pragma once
-#include <QObject>
-
-#include "QObjectPtr.h"
-#include "minecraft/auth/AuthStep.h"
-
-class Yggdrasil;
-
-class YggdrasilStep : public AuthStep {
- Q_OBJECT
-
- public:
- explicit YggdrasilStep(AccountData* data, QString password);
- virtual ~YggdrasilStep() noexcept;
-
- void perform() override;
- void rehydrate() override;
-
- QString describe() override;
-
- private slots:
- void onAuthSucceeded();
- void onAuthFailed();
-
- private:
- Yggdrasil* m_yggdrasil = nullptr;
- QString m_password;
-};
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index d3237b34..0503b660 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -33,6 +33,10 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, BaseInstance* instance, QObje
connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged);
connect(&m_helper_thread_task, &ConcurrentTask::finished, this, [this] { m_helper_thread_task.clear(); });
+#ifndef LAUNCHER_TEST
+ // in tests the application macro doesn't work
+ m_helper_thread_task.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
+#endif
}
ResourceFolderModel::~ResourceFolderModel()
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
index ee84b1f5..bd1fe940 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
@@ -251,3 +251,32 @@ void GetModDependenciesTask::removePack(const QVariant addonId)
++it;
#endif
}
+
+QHash<QString, QStringList> GetModDependenciesTask::getRequiredBy()
+{
+ QHash<QString, QStringList> rby;
+ auto fullList = m_selected + m_pack_dependencies;
+ for (auto& mod : fullList) {
+ auto addonId = mod->pack->addonId;
+ auto provider = mod->pack->provider;
+ auto version = mod->version.fileId;
+ auto req = QStringList();
+ for (auto& smod : fullList) {
+ if (provider != smod->pack->provider)
+ continue;
+ auto deps = smod->version.dependencies;
+ if (auto dep = std::find_if(deps.begin(), deps.end(),
+ [addonId, provider, version](const ModPlatform::Dependency& d) {
+ return d.type == ModPlatform::DependencyType::REQUIRED &&
+ (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty()
+ ? version == d.version
+ : d.addonId == addonId);
+ });
+ dep != deps.end()) {
+ req.append(smod->pack->name);
+ }
+ }
+ rby[addonId.toString()] = req;
+ }
+ return rby;
+} \ No newline at end of file
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
index a8b9953d..2580f807 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.h
@@ -62,6 +62,7 @@ class GetModDependenciesTask : public SequentialTask {
QList<std::shared_ptr<PackDependency>> selected);
auto getDependecies() const -> QList<std::shared_ptr<PackDependency>> { return m_pack_dependencies; }
+ QHash<QString, QStringList> getRequiredBy();
protected slots:
Task::Ptr prepareDependencyTask(const ModPlatform::Dependency&, const ModPlatform::ResourceProvider, int);
diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h
index d125a587..8bd83d98 100644
--- a/launcher/modplatform/CheckUpdateTask.h
+++ b/launcher/modplatform/CheckUpdateTask.h
@@ -1,6 +1,7 @@
#pragma once
#include "minecraft/mod/Mod.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/ResourceAPI.h"
#include "tasks/Task.h"
@@ -23,6 +24,7 @@ class CheckUpdateTask : public Task {
QString old_hash;
QString old_version;
QString new_version;
+ std::optional<ModPlatform::IndexedVersionType> new_version_type;
QString changelog;
ModPlatform::ResourceProvider provider;
shared_qobject_ptr<ResourceDownloadTask> download;
@@ -32,14 +34,23 @@ class CheckUpdateTask : public Task {
QString old_h,
QString old_v,
QString new_v,
+ std::optional<ModPlatform::IndexedVersionType> new_v_type,
QString changelog,
ModPlatform::ResourceProvider p,
shared_qobject_ptr<ResourceDownloadTask> t)
- : name(name), old_hash(old_h), old_version(old_v), new_version(new_v), changelog(changelog), provider(p), download(t)
+ : name(name)
+ , old_hash(old_h)
+ , old_version(old_v)
+ , new_version(new_v)
+ , new_version_type(new_v_type)
+ , changelog(changelog)
+ , provider(p)
+ , download(t)
{}
};
auto getUpdatable() -> std::vector<UpdatableMod>&& { return std::move(m_updatable); }
+ auto getDependencies() -> QList<std::shared_ptr<GetModDependenciesTask::PackDependency>>&& { return std::move(m_deps); }
public slots:
bool abort() override = 0;
@@ -57,4 +68,5 @@ class CheckUpdateTask : public Task {
std::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable;
+ QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> m_deps;
};
diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp
index c3eadd06..a9ad2258 100644
--- a/launcher/modplatform/EnsureMetadataTask.cpp
+++ b/launcher/modplatform/EnsureMetadataTask.cpp
@@ -3,6 +3,7 @@
#include <MurmurHash2.h>
#include <QDebug>
+#include "Application.h"
#include "Json.h"
#include "minecraft/mod/Mod.h"
@@ -33,7 +34,7 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Resource
EnsureMetadataTask::EnsureMetadataTask(QList<Mod*>& mods, QDir dir, ModPlatform::ResourceProvider prov)
: Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr)
{
- m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", 10));
+ m_hashing_task.reset(new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
for (auto* mod : mods) {
auto hash_task = createNewHash(mod);
if (!hash_task)
diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp
index e8e4a38d..fc79dff1 100644
--- a/launcher/modplatform/ModIndex.cpp
+++ b/launcher/modplatform/ModIndex.cpp
@@ -24,6 +24,40 @@
namespace ModPlatform {
+static const QMap<QString, IndexedVersionType::VersionType> s_indexed_version_type_names = {
+ { "release", IndexedVersionType::VersionType::Release },
+ { "beta", IndexedVersionType::VersionType::Beta },
+ { "alpha", IndexedVersionType::VersionType::Alpha }
+};
+
+IndexedVersionType::IndexedVersionType(const QString& type) : IndexedVersionType(enumFromString(type)) {}
+
+IndexedVersionType::IndexedVersionType(const IndexedVersionType::VersionType& type)
+{
+ m_type = type;
+}
+
+IndexedVersionType::IndexedVersionType(const IndexedVersionType& other)
+{
+ m_type = other.m_type;
+}
+
+IndexedVersionType& IndexedVersionType::operator=(const IndexedVersionType& other)
+{
+ m_type = other.m_type;
+ return *this;
+}
+
+const QString IndexedVersionType::toString(const IndexedVersionType::VersionType& type)
+{
+ return s_indexed_version_type_names.key(type, "unknown");
+}
+
+IndexedVersionType::VersionType IndexedVersionType::enumFromString(const QString& type)
+{
+ return s_indexed_version_type_names.value(type, IndexedVersionType::VersionType::Unknown);
+}
+
auto ProviderCapabilities::name(ResourceProvider p) -> const char*
{
switch (p) {
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index 7d144176..4d6759d3 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -25,6 +25,7 @@
#include <QVariant>
#include <QVector>
#include <memory>
+#include <optional>
class QIODevice;
@@ -58,6 +59,34 @@ struct DonationData {
QString url;
};
+struct IndexedVersionType {
+ enum class VersionType { Release = 1, Beta, Alpha, Unknown };
+ IndexedVersionType(const QString& type);
+ IndexedVersionType(const IndexedVersionType::VersionType& type);
+ IndexedVersionType(const IndexedVersionType& type);
+ IndexedVersionType() : IndexedVersionType(IndexedVersionType::VersionType::Unknown) {}
+ static const QString toString(const IndexedVersionType::VersionType& type);
+ static IndexedVersionType::VersionType enumFromString(const QString& type);
+ bool isValid() const { return m_type != IndexedVersionType::VersionType::Unknown; }
+ IndexedVersionType& operator=(const IndexedVersionType& other);
+ bool operator==(const IndexedVersionType& other) const { return m_type == other.m_type; }
+ bool operator==(const IndexedVersionType::VersionType& type) const { return m_type == type; }
+ bool operator!=(const IndexedVersionType& other) const { return m_type != other.m_type; }
+ bool operator!=(const IndexedVersionType::VersionType& type) const { return m_type != type; }
+ bool operator<(const IndexedVersionType& other) const { return m_type < other.m_type; }
+ bool operator<(const IndexedVersionType::VersionType& type) const { return m_type < type; }
+ bool operator<=(const IndexedVersionType& other) const { return m_type <= other.m_type; }
+ bool operator<=(const IndexedVersionType::VersionType& type) const { return m_type <= type; }
+ bool operator>(const IndexedVersionType& other) const { return m_type > other.m_type; }
+ bool operator>(const IndexedVersionType::VersionType& type) const { return m_type > type; }
+ bool operator>=(const IndexedVersionType& other) const { return m_type >= other.m_type; }
+ bool operator>=(const IndexedVersionType::VersionType& type) const { return m_type >= type; }
+
+ QString toString() const { return toString(m_type); }
+
+ IndexedVersionType::VersionType m_type;
+};
+
struct Dependency {
QVariant addonId;
DependencyType type;
@@ -69,6 +98,7 @@ struct IndexedVersion {
QVariant fileId;
QString version;
QString version_number = {};
+ IndexedVersionType version_type;
QStringList mcVersion;
QString downloadUrl;
QString date;
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index e99ce3a5..a9697893 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -133,7 +133,9 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
for (auto file : arr) {
auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
- if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders))
+ if (file_tmp.date > ver.date &&
+ (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders) &&
+ file_tmp.version_type <= ver.version_type)
ver = file_tmp;
}
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp
index 476a4667..c014863a 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.cpp
+++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp
@@ -10,6 +10,7 @@
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "net/ApiDownload.h"
@@ -154,18 +155,17 @@ void FlameCheckUpdate::executeTask()
continue;
}
+ // Fake pack with the necessary info to pass to the download task :)
+ auto pack = std::make_shared<ModPlatform::IndexedPack>();
+ pack->name = mod->name();
+ pack->slug = mod->metadata()->slug;
+ pack->addonId = mod->metadata()->project_id;
+ pack->websiteUrl = mod->homeurl();
+ for (auto& author : mod->authors())
+ pack->authors.append({ author });
+ pack->description = mod->description();
+ pack->provider = ModPlatform::ResourceProvider::FLAME;
if (!latest_ver.hash.isEmpty() && (mod->metadata()->hash != latest_ver.hash || mod->status() == ModStatus::NotInstalled)) {
- // Fake pack with the necessary info to pass to the download task :)
- auto pack = std::make_shared<ModPlatform::IndexedPack>();
- pack->name = mod->name();
- pack->slug = mod->metadata()->slug;
- pack->addonId = mod->metadata()->project_id;
- pack->websiteUrl = mod->homeurl();
- for (auto& author : mod->authors())
- pack->authors.append({ author });
- pack->description = mod->description();
- pack->provider = ModPlatform::ResourceProvider::FLAME;
-
auto old_version = mod->version();
if (old_version.isEmpty() && mod->status() != ModStatus::NotInstalled) {
auto current_ver = getFileInfo(latest_ver.addonId.toInt(), mod->metadata()->file_id.toInt());
@@ -173,10 +173,11 @@ void FlameCheckUpdate::executeTask()
}
auto download_task = makeShared<ResourceDownloadTask>(pack, latest_ver, m_mods_folder);
- m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version,
+ m_updatable.emplace_back(pack->name, mod->metadata()->hash, old_version, latest_ver.version, latest_ver.version_type,
api.getModFileChangelog(latest_ver.addonId.toInt(), latest_ver.fileId.toInt()),
ModPlatform::ResourceProvider::FLAME, download_task);
}
+ m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, latest_ver));
}
emitSucceeded();
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index 494dc2a7..2adcd781 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -96,8 +96,9 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
+ bool a_better_release = a.version_type <= b.version_type;
// dates are in RFC 3339 format
- return a.date > b.date;
+ return a.date > b.date && a_better_release;
};
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
pack.versions = unsortedVersions;
@@ -139,6 +140,22 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
file.downloadUrl = Json::ensureString(obj, "downloadUrl");
file.fileName = Json::requireString(obj, "fileName");
+ ModPlatform::IndexedVersionType::VersionType ver_type;
+ switch (Json::requireInteger(obj, "releaseType")) {
+ case 1:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Release;
+ break;
+ case 2:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Beta;
+ break;
+ case 3:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Alpha;
+ break;
+ default:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Unknown;
+ }
+ file.version_type = ModPlatform::IndexedVersionType(ver_type);
+
auto hash_list = Json::ensureArray(obj, "hashes");
for (auto h : hash_list) {
auto hash_entry = Json::ensureObject(h);
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index d86d34bf..b5ab7bc7 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.cpp
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -28,6 +28,7 @@
#include <algorithm>
#include <iterator>
#include <memory>
+#include "Application.h"
#include "Json.h"
#include "MMCZip.h"
#include "minecraft/PackProfile.h"
@@ -102,7 +103,8 @@ void FlamePackExportTask::collectHashes()
setStatus(tr("Finding file hashes..."));
setProgress(1, 5);
auto allMods = mcInstance->loaderModList()->allMods();
- ConcurrentTask::Ptr hashingTask(new ConcurrentTask(this, "MakeHashesTask", 10));
+ ConcurrentTask::Ptr hashingTask(
+ new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
task.reset(hashingTask);
for (const QFileInfo& file : files) {
const QString relative = gameRoot.relativeFilePath(file.absoluteFilePath());
diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp
index 21835a54..71f1e4a2 100644
--- a/launcher/modplatform/flame/FlamePackIndex.cpp
+++ b/launcher/modplatform/flame/FlamePackIndex.cpp
@@ -89,6 +89,22 @@ void Flame::loadIndexedPackVersions(Flame::IndexedPack& pack, QJsonArray& arr)
// pick the latest version supported
file.mcVersion = versionArray[0].toString();
file.version = Json::requireString(version, "displayName");
+
+ ModPlatform::IndexedVersionType::VersionType ver_type;
+ switch (Json::requireInteger(version, "releaseType")) {
+ case 1:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Release;
+ break;
+ case 2:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Beta;
+ break;
+ case 3:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Alpha;
+ break;
+ default:
+ ver_type = ModPlatform::IndexedVersionType::VersionType::Unknown;
+ }
+ file.version_type = ModPlatform::IndexedVersionType(ver_type);
file.downloadUrl = Json::ensureString(version, "downloadUrl");
// only add if we have a download URL (third party distribution is enabled)
diff --git a/launcher/modplatform/flame/FlamePackIndex.h b/launcher/modplatform/flame/FlamePackIndex.h
index b089b722..b2a12a67 100644
--- a/launcher/modplatform/flame/FlamePackIndex.h
+++ b/launcher/modplatform/flame/FlamePackIndex.h
@@ -17,6 +17,7 @@ struct IndexedVersion {
int addonId;
int fileId;
QString version;
+ ModPlatform::IndexedVersionType version_type;
QString mcVersion;
QString downloadUrl;
};
diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
index c65f4fa8..9b7c5385 100644
--- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
+++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
@@ -38,7 +38,7 @@ void ModrinthCheckUpdate::executeTask()
QStringList hashes;
auto best_hash_type = ProviderCaps.hashType(ModPlatform::ResourceProvider::MODRINTH).first();
- ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10);
+ ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt());
for (auto* mod : m_mods) {
if (!mod->enabled()) {
emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!"));
@@ -144,26 +144,27 @@ void ModrinthCheckUpdate::executeTask()
auto mod = *mod_iter;
auto key = project_ver.hash;
+
+ // Fake pack with the necessary info to pass to the download task :)
+ auto pack = std::make_shared<ModPlatform::IndexedPack>();
+ pack->name = mod->name();
+ pack->slug = mod->metadata()->slug;
+ pack->addonId = mod->metadata()->project_id;
+ pack->websiteUrl = mod->homeurl();
+ for (auto& author : mod->authors())
+ pack->authors.append({ author });
+ pack->description = mod->description();
+ pack->provider = ModPlatform::ResourceProvider::MODRINTH;
if ((key != hash && project_ver.is_preferred) || (mod->status() == ModStatus::NotInstalled)) {
if (mod->version() == project_ver.version_number)
continue;
- // Fake pack with the necessary info to pass to the download task :)
- auto pack = std::make_shared<ModPlatform::IndexedPack>();
- pack->name = mod->name();
- pack->slug = mod->metadata()->slug;
- pack->addonId = mod->metadata()->project_id;
- pack->websiteUrl = mod->homeurl();
- for (auto& author : mod->authors())
- pack->authors.append({ author });
- pack->description = mod->description();
- pack->provider = ModPlatform::ResourceProvider::MODRINTH;
-
auto download_task = makeShared<ResourceDownloadTask>(pack, project_ver, m_mods_folder);
- m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.changelog,
- ModPlatform::ResourceProvider::MODRINTH, download_task);
+ m_updatable.emplace_back(pack->name, hash, mod->version(), project_ver.version_number, project_ver.version_type,
+ project_ver.changelog, ModPlatform::ResourceProvider::MODRINTH, download_task);
}
+ m_deps.append(std::make_shared<GetModDependenciesTask::PackDependency>(pack, project_ver));
}
} catch (Json::JsonException& e) {
failed(e.cause() + " : " + e.what());
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index 107b9900..f1e77d46 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -109,8 +109,9 @@ void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArra
unsortedVersions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
+ bool a_better_release = a.version_type <= b.version_type;
// dates are in RFC 3339 format
- return a.date > b.date;
+ return a.date > b.date && a_better_release;
};
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
pack.versions = unsortedVersions;
@@ -149,6 +150,8 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
}
file.version = Json::requireString(obj, "name");
file.version_number = Json::requireString(obj, "version_number");
+ file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type"));
+
file.changelog = Json::requireString(obj, "changelog");
auto dependencies = Json::ensureArray(obj, "dependencies");
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
index 0d07c636..a154317f 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp
@@ -111,8 +111,9 @@ void loadIndexedVersions(Modpack& pack, QJsonDocument& doc)
unsortedVersions.append(file);
}
auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool {
+ bool a_better_release = a.version_type <= b.version_type;
// dates are in RFC 3339 format
- return a.date > b.date;
+ return a.date > b.date && a_better_release;
};
std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
@@ -128,6 +129,7 @@ auto loadIndexedVersion(QJsonObject& obj) -> ModpackVersion
file.name = Json::requireString(obj, "name");
file.version = Json::requireString(obj, "version_number");
+ file.version_type = ModPlatform::IndexedVersionType(Json::requireString(obj, "version_type"));
file.changelog = Json::ensureString(obj, "changelog");
file.id = Json::requireString(obj, "id");
diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h
index effa1a84..8e530677 100644
--- a/launcher/modplatform/modrinth/ModrinthPackManifest.h
+++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h
@@ -45,6 +45,8 @@
#include <QUrl>
#include <QVector>
+#include "modplatform/ModIndex.h"
+
class MinecraftInstance;
namespace Modrinth {
@@ -79,6 +81,7 @@ struct ModpackExtra {
struct ModpackVersion {
QString name;
QString version;
+ ModPlatform::IndexedVersionType version_type;
QString changelog;
QString id;
diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp
index 3869316e..b99c5acb 100644
--- a/launcher/net/NetJob.cpp
+++ b/launcher/net/NetJob.cpp
@@ -36,6 +36,11 @@
*/
#include "NetJob.h"
+#include "Application.h"
+
+NetJob::NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network)
+ : ConcurrentTask(nullptr, job_name, APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt()), m_network(network)
+{}
auto NetJob::addNetAction(NetAction::Ptr action) -> bool
{
diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h
index cc63f449..1c4337ec 100644
--- a/launcher/net/NetJob.h
+++ b/launcher/net/NetJob.h
@@ -52,9 +52,7 @@ class NetJob : public ConcurrentTask {
public:
using Ptr = shared_qobject_ptr<NetJob>;
- explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network)
- : ConcurrentTask(nullptr, job_name), m_network(network)
- {}
+ explicit NetJob(QString job_name, shared_qobject_ptr<QNetworkAccessManager> network);
~NetJob() override = default;
void startNext() override;
diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h
index 8b696bf5..00b1d48d 100644
--- a/launcher/tasks/ConcurrentTask.h
+++ b/launcher/tasks/ConcurrentTask.h
@@ -51,6 +51,9 @@ class ConcurrentTask : public Task {
explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6);
~ConcurrentTask() override;
+ // safe to call before starting the task
+ void setMaxConcurrent(int max_concurrent) { m_total_max_size = max_concurrent; }
+
bool canAbort() const override { return true; }
inline auto isMultiStep() const -> bool override { return totalSize() > 1; }
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 5e55a5ab..64873ebb 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -872,7 +872,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
} else {
CustomMessageBox::selectable(this, tr("Error"),
tr("The launcher cannot download Minecraft or update instances unless you have at least "
- "one account added.\nPlease add your Microsoft or Mojang account."),
+ "one account added.\nPlease add a Microsoft account."),
QMessageBox::Warning)
->show();
}
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index 727c0614..5a1a2f80 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -44,7 +44,8 @@
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
: QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods)
{
- m_hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10));
+ m_hashing_task = shared_qobject_ptr<ConcurrentTask>(
+ new ConcurrentTask(this, "MakeHashesTask", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()));
connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished);
ui->setupUi(this);
diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp
deleted file mode 100644
index 7296a13e..00000000
--- a/launcher/ui/dialogs/LoginDialog.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "LoginDialog.h"
-#include "ui_LoginDialog.h"
-
-#include "minecraft/auth/AccountTask.h"
-
-#include <QtWidgets/QPushButton>
-
-LoginDialog::LoginDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LoginDialog)
-{
- ui->setupUi(this);
- ui->progressBar->setVisible(false);
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
-
- connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
- connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
-}
-
-LoginDialog::~LoginDialog()
-{
- delete ui;
-}
-
-// Stage 1: User interaction
-void LoginDialog::accept()
-{
- setUserInputsEnabled(false);
- ui->progressBar->setVisible(true);
-
- // Setup the login task and start it
- m_account = MinecraftAccount::createFromUsername(ui->userTextBox->text());
- m_loginTask = m_account->login(ui->passTextBox->text());
- connect(m_loginTask.get(), &Task::failed, this, &LoginDialog::onTaskFailed);
- connect(m_loginTask.get(), &Task::succeeded, this, &LoginDialog::onTaskSucceeded);
- connect(m_loginTask.get(), &Task::status, this, &LoginDialog::onTaskStatus);
- connect(m_loginTask.get(), &Task::progress, this, &LoginDialog::onTaskProgress);
- m_loginTask->start();
-}
-
-void LoginDialog::setUserInputsEnabled(bool enable)
-{
- ui->userTextBox->setEnabled(enable);
- ui->passTextBox->setEnabled(enable);
- ui->buttonBox->setEnabled(enable);
-}
-
-// Enable the OK button only when both textboxes contain something.
-void LoginDialog::on_userTextBox_textEdited(const QString& newText)
-{
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->passTextBox->text().isEmpty());
-}
-void LoginDialog::on_passTextBox_textEdited(const QString& newText)
-{
- ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!newText.isEmpty() && !ui->userTextBox->text().isEmpty());
-}
-
-void LoginDialog::onTaskFailed(const QString& reason)
-{
- // Set message
- auto lines = reason.split('\n');
- QString processed;
- for (auto line : lines) {
- if (line.size()) {
- processed += "<font color='red'>" + line + "</font><br />";
- } else {
- processed += "<br />";
- }
- }
- ui->label->setText(processed);
-
- // Re-enable user-interaction
- setUserInputsEnabled(true);
- ui->progressBar->setVisible(false);
-}
-
-void LoginDialog::onTaskSucceeded()
-{
- QDialog::accept();
-}
-
-void LoginDialog::onTaskStatus(const QString& status)
-{
- ui->label->setText(status);
-}
-
-void LoginDialog::onTaskProgress(qint64 current, qint64 total)
-{
- ui->progressBar->setMaximum(total);
- ui->progressBar->setValue(current);
-}
-
-// Public interface
-MinecraftAccountPtr LoginDialog::newAccount(QWidget* parent, QString msg)
-{
- LoginDialog dlg(parent);
- dlg.ui->label->setText(msg);
- if (dlg.exec() == QDialog::Accepted) {
- return dlg.m_account;
- }
- return nullptr;
-}
diff --git a/launcher/ui/dialogs/LoginDialog.h b/launcher/ui/dialogs/LoginDialog.h
deleted file mode 100644
index 601b5fa7..00000000
--- a/launcher/ui/dialogs/LoginDialog.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/* Copyright 2013-2021 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QtCore/QEventLoop>
-#include <QtWidgets/QDialog>
-
-#include "minecraft/auth/MinecraftAccount.h"
-#include "tasks/Task.h"
-
-namespace Ui {
-class LoginDialog;
-}
-
-class LoginDialog : public QDialog {
- Q_OBJECT
-
- public:
- ~LoginDialog();
-
- static MinecraftAccountPtr newAccount(QWidget* parent, QString message);
-
- private:
- explicit LoginDialog(QWidget* parent = 0);
-
- void setUserInputsEnabled(bool enable);
-
- protected slots:
- void accept();
-
- void onTaskFailed(const QString& reason);
- void onTaskSucceeded();
- void onTaskStatus(const QString& status);
- void onTaskProgress(qint64 current, qint64 total);
-
- void on_userTextBox_textEdited(const QString& newText);
- void on_passTextBox_textEdited(const QString& newText);
-
- private:
- Ui::LoginDialog* ui;
- MinecraftAccountPtr m_account;
- Task::Ptr m_loginTask;
-};
diff --git a/launcher/ui/dialogs/LoginDialog.ui b/launcher/ui/dialogs/LoginDialog.ui
deleted file mode 100644
index 8fa4a45d..00000000
--- a/launcher/ui/dialogs/LoginDialog.ui
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>LoginDialog</class>
- <widget class="QDialog" name="LoginDialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>421</width>
- <height>198</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="windowTitle">
- <string>Add Account</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string notr="true">Message label placeholder.</string>
- </property>
- <property name="textFormat">
- <enum>Qt::RichText</enum>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="userTextBox">
- <property name="placeholderText">
- <string>Email</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="passTextBox">
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- <property name="placeholderText">
- <string>Password</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QProgressBar" name="progressBar">
- <property name="value">
- <number>24</number>
- </property>
- <property name="textVisible">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp
index 1f0fa7cd..1a70ea59 100644
--- a/launcher/ui/dialogs/ModUpdateDialog.cpp
+++ b/launcher/ui/dialogs/ModUpdateDialog.cpp
@@ -3,6 +3,9 @@
#include "CustomMessageBox.h"
#include "ProgressDialog.h"
#include "ScrollMessageBox.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui_ReviewMessageBox.h"
#include "Markdown.h"
@@ -41,7 +44,8 @@ ModUpdateDialog::ModUpdateDialog(QWidget* parent,
, m_parent(parent)
, m_mod_model(mods)
, m_candidates(search_for)
- , m_second_try_metadata(new ConcurrentTask())
+ , m_second_try_metadata(
+ new ConcurrentTask(nullptr, "Second Metadata Search", APPLICATION->settings()->get("NumberOfConcurrentTasks").toInt()))
, m_instance(instance)
{
ReviewMessageBox::setGeometry(0, 0, 800, 600);
@@ -124,6 +128,8 @@ void ModUpdateDialog::checkCandidates()
return;
}
+ QList<std::shared_ptr<GetModDependenciesTask::PackDependency>> selectedVers;
+
// Add found updates for Modrinth
if (m_modrinth_check_task) {
auto modrinth_updates = m_modrinth_check_task->getUpdatable();
@@ -133,6 +139,7 @@ void ModUpdateDialog::checkCandidates()
appendMod(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
+ selectedVers.append(m_modrinth_check_task->getDependencies());
}
// Add found updated for Flame
@@ -144,6 +151,7 @@ void ModUpdateDialog::checkCandidates()
appendMod(updatable);
m_tasks.insert(updatable.name, updatable.download);
}
+ selectedVers.append(m_flame_check_task->getDependencies());
}
// Report failed update checking
@@ -178,6 +186,49 @@ void ModUpdateDialog::checkCandidates()
}
}
+ { // dependencies
+ auto depTask = makeShared<GetModDependenciesTask>(this, m_instance, m_mod_model.get(), selectedVers);
+
+ connect(depTask.get(), &Task::failed, this,
+ [&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
+
+ connect(depTask.get(), &Task::succeeded, this, [&]() {
+ QStringList warnings = depTask->warnings();
+ if (warnings.count()) {
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->exec();
+ }
+ });
+
+ ProgressDialog progress_dialog_deps(m_parent);
+ progress_dialog_deps.setSkipButton(true, tr("Abort"));
+ progress_dialog_deps.setWindowTitle(tr("Checking for dependencies..."));
+ auto dret = progress_dialog_deps.execWithTask(depTask.get());
+
+ // If the dialog was skipped / some download error happened
+ if (dret == QDialog::DialogCode::Rejected) {
+ m_aborted = true;
+ QMetaObject::invokeMethod(this, "reject", Qt::QueuedConnection);
+ return;
+ }
+ static FlameAPI api;
+
+ auto getRequiredBy = depTask->getRequiredBy();
+
+ for (auto dep : depTask->getDependecies()) {
+ auto changelog = dep->version.changelog;
+ if (dep->pack->provider == ModPlatform::ResourceProvider::FLAME)
+ changelog = api.getModFileChangelog(dep->version.addonId.toInt(), dep->version.fileId.toInt());
+ auto download_task = makeShared<ResourceDownloadTask>(dep->pack, dep->version, m_mod_model);
+ CheckUpdateTask::UpdatableMod updatable = {
+ dep->pack->name, dep->version.hash, "", dep->version.version, dep->version.version_type,
+ changelog, dep->pack->provider, download_task
+ };
+
+ appendMod(updatable, getRequiredBy.value(dep->version.addonId.toString()));
+ m_tasks.insert(updatable.name, updatable.download);
+ }
+ }
+
// If there's no mod to be updated
if (ui->modTreeWidget->topLevelItemCount() == 0) {
m_no_updates = true;
@@ -236,6 +287,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool
if (skip_rest)
continue;
+ if (candidate->type() == ResourceType::FOLDER) {
+ continue;
+ }
+
if (confirm_rest) {
addToTmp(candidate, provider_rest);
should_try_others.insert(candidate->internal_id(), try_others_rest);
@@ -346,7 +401,7 @@ void ModUpdateDialog::onMetadataFailed(Mod* mod, bool try_others, ModPlatform::R
}
}
-void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info)
+void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info, QStringList requiredBy)
{
auto item_top = new QTreeWidgetItem(ui->modTreeWidget);
item_top->setCheckState(0, Qt::CheckState::Checked);
@@ -362,6 +417,26 @@ void ModUpdateDialog::appendMod(CheckUpdateTask::UpdatableMod const& info)
auto new_version_item = new QTreeWidgetItem(item_top);
new_version_item->setText(0, tr("New version: %1").arg(info.new_version));
+ if (info.new_version_type.has_value()) {
+ auto new_version_type_itme = new QTreeWidgetItem(item_top);
+ new_version_type_itme->setText(0, tr("New Version Type: %1").arg(info.new_version_type.value().toString()));
+ }
+
+ if (!requiredBy.isEmpty()) {
+ auto requiredByItem = new QTreeWidgetItem(item_top);
+ if (requiredBy.length() == 1) {
+ requiredByItem->setText(0, tr("Required by: %1").arg(requiredBy.back()));
+ } else {
+ requiredByItem->setText(0, tr("Required by:"));
+ auto i = 0;
+ for (auto req : requiredBy) {
+ auto reqItem = new QTreeWidgetItem(requiredByItem);
+ reqItem->setText(0, req);
+ reqItem->insertChildren(i++, { reqItem });
+ }
+ }
+ }
+
auto changelog_item = new QTreeWidgetItem(item_top);
changelog_item->setText(0, tr("Changelog of the latest version"));
diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h
index 12dddf5e..b79aa494 100644
--- a/launcher/ui/dialogs/ModUpdateDialog.h
+++ b/launcher/ui/dialogs/ModUpdateDialog.h
@@ -23,7 +23,7 @@ class ModUpdateDialog final : public ReviewMessageBox {
void checkCandidates();
- void appendMod(const CheckUpdateTask::UpdatableMod& info);
+ void appendMod(const CheckUpdateTask::UpdatableMod& info, QStringList requiredBy = {});
const QList<ResourceDownloadTask::Ptr> getTasks();
auto indexDir() const -> QDir { return m_mod_model->indexDir(); }
diff --git a/launcher/ui/dialogs/ResourceDownloadDialog.cpp b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
index bf76b01e..dc7cfff0 100644
--- a/launcher/ui/dialogs/ResourceDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ResourceDownloadDialog.cpp
@@ -127,35 +127,12 @@ void ResourceDownloadDialog::connectButtons()
static ModPlatform::ProviderCapabilities ProviderCaps;
-QStringList getRequiredBy(QList<ResourceDownloadDialog::DownloadTaskPtr> tasks, ResourceDownloadDialog::DownloadTaskPtr pack)
-{
- auto addonId = pack->getPack()->addonId;
- auto provider = pack->getPack()->provider;
- auto version = pack->getVersionID();
- auto req = QStringList();
- for (auto& task : tasks) {
- if (provider != task->getPack()->provider)
- continue;
- auto deps = task->getVersion().dependencies;
- if (auto dep = std::find_if(deps.begin(), deps.end(),
- [addonId, provider, version](const ModPlatform::Dependency& d) {
- return d.type == ModPlatform::DependencyType::REQUIRED &&
- (provider == ModPlatform::ResourceProvider::MODRINTH && d.addonId.toString().isEmpty()
- ? version == d.version
- : d.addonId == addonId);
- });
- dep != deps.end()) {
- req.append(task->getName());
- }
- }
- return req;
-}
-
void ResourceDownloadDialog::confirm()
{
auto confirm_dialog = ReviewMessageBox::create(this, tr("Confirm %1 to download").arg(resourcesString()));
confirm_dialog->retranslateUi(resourcesString());
+ QHash<QString, QStringList> getRequiredBy;
if (auto task = getModDependenciesTask(); task) {
connect(task.get(), &Task::failed, this,
[&](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->exec(); });
@@ -180,6 +157,7 @@ void ResourceDownloadDialog::confirm()
} else {
for (auto dep : task->getDependecies())
addResource(dep->pack, dep->version);
+ getRequiredBy = task->getRequiredBy();
}
}
@@ -189,7 +167,8 @@ void ResourceDownloadDialog::confirm()
});
for (auto& task : selected) {
confirm_dialog->appendResource({ task->getName(), task->getFilename(), task->getCustomPath(),
- ProviderCaps.name(task->getProvider()), getRequiredBy(selected, task) });
+ ProviderCaps.name(task->getProvider()), getRequiredBy.value(task->getPack()->addonId.toString()),
+ task->getVersion().version_type.toString() });
}
if (confirm_dialog->exec()) {
diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp
index 78c2542f..aa668f8c 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.cpp
+++ b/launcher/ui/dialogs/ReviewMessageBox.cpp
@@ -77,6 +77,10 @@ void ReviewMessageBox::appendResource(ResourceInformation&& info)
itemTop->insertChildren(childIndx++, { requiredByItem });
}
+ auto versionTypeItem = new QTreeWidgetItem(itemTop);
+ versionTypeItem->setText(0, tr("Version Type: %1").arg(info.version_type));
+ itemTop->insertChildren(childIndx++, { versionTypeItem });
+
ui->modTreeWidget->addTopLevelItem(itemTop);
}
diff --git a/launcher/ui/dialogs/ReviewMessageBox.h b/launcher/ui/dialogs/ReviewMessageBox.h
index a520cc2a..596f39c8 100644
--- a/launcher/ui/dialogs/ReviewMessageBox.h
+++ b/launcher/ui/dialogs/ReviewMessageBox.h
@@ -18,6 +18,7 @@ class ReviewMessageBox : public QDialog {
QString custom_file_path{};
QString provider;
QStringList required_by;
+ QString version_type;
};
void appendResource(ResourceInformation&& info);
diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp
index c95bfabd..50f3ff2b 100644
--- a/launcher/ui/pages/global/AccountListPage.cpp
+++ b/launcher/ui/pages/global/AccountListPage.cpp
@@ -45,7 +45,6 @@
#include "net/NetJob.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/LoginDialog.h"
#include "ui/dialogs/MSALoginDialog.h"
#include "ui/dialogs/OfflineLoginDialog.h"
#include "ui/dialogs/ProgressDialog.h"
@@ -64,8 +63,7 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
ui->setupUi(this);
ui->listView->setEmptyString(
tr("Welcome!\n"
- "If you're new here, you can select the \"Add Microsoft\" or \"Add Mojang\" buttons to link your Microsoft and/or Mojang "
- "accounts."));
+ "If you're new here, you can select the \"Add Microsoft\" button to link your Microsoft account."));
ui->listView->setEmptyMode(VersionListView::String);
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -74,7 +72,6 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
ui->listView->setModel(m_accounts.get());
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::ProfileNameColumn, QHeaderView::Stretch);
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::NameColumn, QHeaderView::Stretch);
- ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::MigrationColumn, QHeaderView::ResizeToContents);
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::TypeColumn, QHeaderView::ResizeToContents);
ui->listView->header()->setSectionResizeMode(AccountList::VListColumns::StatusColumn, QHeaderView::ResizeToContents);
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
@@ -139,19 +136,6 @@ void AccountListPage::listChanged()
updateButtonStates();
}
-void AccountListPage::on_actionAddMojang_triggered()
-{
- MinecraftAccountPtr account =
- LoginDialog::newAccount(this, tr("Please enter your Mojang account email and password to add your account."));
-
- if (account) {
- m_accounts->addAccount(account);
- if (m_accounts->count() == 1) {
- m_accounts->setDefaultAccount(account);
- }
- }
-}
-
void AccountListPage::on_actionAddMicrosoft_triggered()
{
MinecraftAccountPtr account =
@@ -169,7 +153,7 @@ void AccountListPage::on_actionAddOffline_triggered()
{
if (!m_accounts->anyAccountIsValid()) {
QMessageBox::warning(this, tr("Error"),
- tr("You must add a Microsoft or Mojang account that owns Minecraft before you can add an offline account."
+ tr("You must add a Microsoft account that owns Minecraft before you can add an offline account."
"<br><br>"
"If you have lost your account you can contact Microsoft for support."));
return;
diff --git a/launcher/ui/pages/global/AccountListPage.h b/launcher/ui/pages/global/AccountListPage.h
index add0f4aa..f3b80191 100644
--- a/launcher/ui/pages/global/AccountListPage.h
+++ b/launcher/ui/pages/global/AccountListPage.h
@@ -70,7 +70,6 @@ class AccountListPage : public QMainWindow, public BasePage {
void retranslate() override;
public slots:
- void on_actionAddMojang_triggered();
void on_actionAddMicrosoft_triggered();
void on_actionAddOffline_triggered();
void on_actionRemove_triggered();
diff --git a/launcher/ui/pages/global/AccountListPage.ui b/launcher/ui/pages/global/AccountListPage.ui
index 469955b5..d8cf3ac0 100644
--- a/launcher/ui/pages/global/AccountListPage.ui
+++ b/launcher/ui/pages/global/AccountListPage.ui
@@ -53,7 +53,6 @@
<bool>false</bool>
</attribute>
<addaction name="actionAddMicrosoft"/>
- <addaction name="actionAddMojang"/>
<addaction name="actionAddOffline"/>
<addaction name="actionRefresh"/>
<addaction name="actionRemove"/>
@@ -63,11 +62,6 @@
<addaction name="actionUploadSkin"/>
<addaction name="actionDeleteSkin"/>
</widget>
- <action name="actionAddMojang">
- <property name="text">
- <string>Add &amp;Mojang</string>
- </property>
- </action>
<action name="actionRemove">
<property name="text">
<string>Remo&amp;ve</string>
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 7f22fdb5..6d8c65ec 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -189,6 +189,9 @@ void LauncherPage::applySettings()
s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked());
+ s->set("NumberOfConcurrentTasks", ui->numberOfConcurrentTasksSpinBox->value());
+ s->set("NumberOfConcurrentDownloads", ui->numberOfConcurrentDownloadsSpinBox->value());
+
// Console settings
s->set("ShowConsole", ui->showConsoleCheck->isChecked());
s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked());
@@ -236,6 +239,9 @@ void LauncherPage::loadSettings()
#endif
ui->preferMenuBarCheckBox->setChecked(s->get("MenuBarInsteadOfToolBar").toBool());
+ ui->numberOfConcurrentTasksSpinBox->setValue(s->get("NumberOfConcurrentTasks").toInt());
+ ui->numberOfConcurrentDownloadsSpinBox->setValue(s->get("NumberOfConcurrentDownloads").toInt());
+
// Console settings
ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool());
ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool());
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index bc259a9b..250a8bc8 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -190,6 +190,43 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="miscellaneousGroupBox">
+ <property name="title">
+ <string>Miscellaneous</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="numberOfConcurrentTasksLabel">
+ <property name="text">
+ <string>Number of concurrent tasks</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QSpinBox" name="numberOfConcurrentTasksSpinBox">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="numberOfConcurrentDownloadsLabel">
+ <property name="text">
+ <string>Number of concurrent downloads</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QSpinBox" name="numberOfConcurrentDownloadsSpinBox">
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index b42bbf46..69c34daf 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -175,7 +175,7 @@ void ModFolderPage::installMods()
ResourceDownload::ModDownloadDialog mdownload(this, m_model, m_instance);
if (mdownload.exec()) {
- ConcurrentTask* tasks = new ConcurrentTask(this);
+ auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
@@ -234,7 +234,7 @@ void ModFolderPage::updateMods()
}
if (update_dialog.exec()) {
- ConcurrentTask* tasks = new ConcurrentTask(this);
+ auto tasks = new ConcurrentTask(this, "Download Mods", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/ResourcePackPage.cpp b/launcher/ui/pages/instance/ResourcePackPage.cpp
index 26c14ca4..85be6425 100644
--- a/launcher/ui/pages/instance/ResourcePackPage.cpp
+++ b/launcher/ui/pages/instance/ResourcePackPage.cpp
@@ -72,7 +72,8 @@ void ResourcePackPage::downloadRPs()
ResourceDownload::ResourcePackDownloadDialog mdownload(this, std::static_pointer_cast<ResourcePackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
- auto tasks = new ConcurrentTask(this);
+ auto tasks =
+ new ConcurrentTask(this, "Download Resource Pack", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/ShaderPackPage.cpp b/launcher/ui/pages/instance/ShaderPackPage.cpp
index dc8b0a05..40366a1b 100644
--- a/launcher/ui/pages/instance/ShaderPackPage.cpp
+++ b/launcher/ui/pages/instance/ShaderPackPage.cpp
@@ -65,7 +65,7 @@ void ShaderPackPage::downloadShaders()
ResourceDownload::ShaderPackDownloadDialog mdownload(this, std::static_pointer_cast<ShaderPackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
- auto tasks = new ConcurrentTask(this);
+ auto tasks = new ConcurrentTask(this, "Download Shaders", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/TexturePackPage.cpp b/launcher/ui/pages/instance/TexturePackPage.cpp
index fa478a9a..7c8d7e06 100644
--- a/launcher/ui/pages/instance/TexturePackPage.cpp
+++ b/launcher/ui/pages/instance/TexturePackPage.cpp
@@ -74,7 +74,8 @@ void TexturePackPage::downloadTPs()
ResourceDownload::TexturePackDownloadDialog mdownload(this, std::static_pointer_cast<TexturePackFolderModel>(m_model), m_instance);
if (mdownload.exec()) {
- auto tasks = new ConcurrentTask(this);
+ auto tasks =
+ new ConcurrentTask(this, "Download Texture Packs", APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
connect(tasks, &Task::failed, [this, tasks](QString reason) {
CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
tasks->deleteLater();
diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp
index e22c764c..2918261d 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -411,7 +411,7 @@ void VersionPage::on_actionDownload_All_triggered()
if (!APPLICATION->accounts()->anyAccountIsValid()) {
CustomMessageBox::selectable(this, tr("Error"),
tr("Cannot download Minecraft or update instances unless you have at least "
- "one account added.\nPlease add your Microsoft or Mojang account."),
+ "one account added.\nPlease add a Microsoft account."),
QMessageBox::Warning)
->show();
return;
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 7eb08f6a..d6cc1fdc 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -131,8 +131,10 @@ void ModPage::updateVersionList()
}
// Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out
- if ((valid || m_filter->versions.empty()) && !optedOut(version))
- m_ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ if ((valid || m_filter->versions.empty()) && !optedOut(version)) {
+ auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
+ m_ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(i));
+ }
}
if (m_ui->versionSelectionBox->count() == 0) {
m_ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index cb8f1920..48e66efc 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.cpp
+++ b/launcher/ui/pages/modplatform/ResourceModel.cpp
@@ -31,6 +31,9 @@ QHash<ResourceModel*, bool> ResourceModel::s_running_models;
ResourceModel::ResourceModel(ResourceAPI* api) : QAbstractListModel(), m_api(api)
{
s_running_models.insert(this, true);
+#ifndef LAUNCHER_TEST
+ m_current_info_job.setMaxConcurrent(APPLICATION->settings()->get("NumberOfConcurrentDownloads").toInt());
+#endif
}
ResourceModel::~ResourceModel()
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index fc7d64a4..44a91003 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -266,6 +266,9 @@ void ResourcePage::updateVersionList()
if (optedOut(version))
continue;
+ auto release_type = current_pack->versions[i].version_type.isValid()
+ ? QString(" [%1]").arg(current_pack->versions[i].version_type.toString())
+ : "";
m_ui->versionSelectionBox->addItem(current_pack->versions[i].version, QVariant(i));
}
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 50656f42..584d94ad 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -176,7 +176,8 @@ void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelInde
}
for (auto version : current.versions) {
- ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
+ auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
+ ui->versionSelectionBox->addItem(QString("%1%2").arg(version.version, release_type), QVariant(version.downloadUrl));
}
QVariant current_updated;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index f7fa8fd7..8e2b9a90 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -217,12 +217,13 @@ void ModrinthPage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelI
qDebug() << *response;
qWarning() << "Error while reading modrinth modpack version: " << e.cause();
}
-
for (auto version : current.versions) {
+ auto release_type = version.version_type.isValid() ? QString(" [%1]").arg(version.version_type.toString()) : "";
if (!version.name.contains(version.version))
- ui->versionSelectionBox->addItem(QString("%1 — %2").arg(version.name, version.version), QVariant(version.id));
+ ui->versionSelectionBox->addItem(QString("%1 — %2%3").arg(version.name, version.version, release_type),
+ QVariant(version.id));
else
- ui->versionSelectionBox->addItem(version.name, QVariant(version.id));
+ ui->versionSelectionBox->addItem(QString("%1%2").arg(version.name, release_type), QVariant(version.id));
}
QVariant current_updated;