aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp17
-rw-r--r--launcher/Application.h1
-rw-r--r--launcher/BaseInstance.cpp3
-rw-r--r--launcher/BaseInstance.h6
-rw-r--r--launcher/CMakeLists.txt16
-rw-r--r--launcher/FileIgnoreProxy.cpp5
-rw-r--r--launcher/LaunchController.cpp12
-rw-r--r--launcher/MMCTime.cpp7
-rw-r--r--launcher/MMCTime.h2
-rw-r--r--launcher/NullInstance.h2
-rw-r--r--launcher/Version.h20
-rw-r--r--launcher/launch/LogModel.h2
-rw-r--r--launcher/minecraft/ComponentUpdateTask.cpp4
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp83
-rw-r--r--launcher/minecraft/MinecraftInstance.h4
-rw-r--r--launcher/minecraft/PackProfile.cpp35
-rw-r--r--launcher/minecraft/PackProfile.h11
-rw-r--r--launcher/minecraft/auth/AccountData.cpp121
-rw-r--r--launcher/minecraft/auth/AccountData.h14
-rw-r--r--launcher/minecraft/auth/AccountList.cpp57
-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/DataPack.cpp2
-rw-r--r--launcher/minecraft/mod/DataPack.h2
-rw-r--r--launcher/minecraft/mod/Mod.cpp23
-rw-r--r--launcher/minecraft/mod/Mod.h2
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp68
-rw-r--r--launcher/minecraft/mod/ModFolderModel.h3
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp4
-rw-r--r--launcher/minecraft/mod/ResourcePack.cpp7
-rw-r--r--launcher/minecraft/mod/ResourcePack.h2
-rw-r--r--launcher/minecraft/mod/ShaderPack.cpp7
-rw-r--r--launcher/minecraft/mod/ShaderPack.h1
-rw-r--r--launcher/minecraft/mod/ShaderPackFolderModel.h13
-rw-r--r--launcher/minecraft/mod/TexturePack.cpp5
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp38
-rw-r--r--launcher/minecraft/mod/tasks/GetModDependenciesTask.h3
-rw-r--r--launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp2
-rw-r--r--launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp2
-rw-r--r--launcher/modplatform/CheckUpdateTask.h18
-rw-r--r--launcher/modplatform/EnsureMetadataTask.cpp3
-rw-r--r--launcher/modplatform/ModIndex.cpp55
-rw-r--r--launcher/modplatform/ModIndex.h44
-rw-r--r--launcher/modplatform/ResourceAPI.h32
-rw-r--r--launcher/modplatform/flame/FileResolvingTask.cpp7
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp11
-rw-r--r--launcher/modplatform/flame/FlameAPI.h92
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.cpp27
-rw-r--r--launcher/modplatform/flame/FlameCheckUpdate.h2
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp8
-rw-r--r--launcher/modplatform/flame/FlameModIndex.cpp45
-rw-r--r--launcher/modplatform/flame/FlameModIndex.h2
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.cpp11
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.h2
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.cpp16
-rw-r--r--launcher/modplatform/flame/FlamePackIndex.h1
-rw-r--r--launcher/modplatform/helpers/NetworkResourceAPI.cpp5
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.cpp12
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.h2
-rw-r--r--launcher/modplatform/import_ftb/PackInstallTask.cpp14
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h18
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp36
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.cpp27
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.cpp38
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackIndex.h7
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackManifest.h3
-rw-r--r--launcher/net/HttpMetaCache.cpp19
-rw-r--r--launcher/net/NetJob.cpp5
-rw-r--r--launcher/net/NetJob.h4
-rw-r--r--launcher/resources/multimc/multimc.qrc1
-rw-r--r--launcher/resources/multimc/scalable/instances/neoforged.svg3
-rw-r--r--launcher/tasks/ConcurrentTask.h3
-rw-r--r--launcher/ui/InstanceWindow.cpp117
-rw-r--r--launcher/ui/InstanceWindow.h13
-rw-r--r--launcher/ui/MainWindow.cpp151
-rw-r--r--launcher/ui/MainWindow.h13
-rw-r--r--launcher/ui/MainWindow.ui16
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp3
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.cpp81
-rw-r--r--launcher/ui/dialogs/ExportPackDialog.ui86
-rw-r--r--launcher/ui/dialogs/InstallLoaderDialog.cpp4
-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.cpp85
-rw-r--r--launcher/ui/dialogs/ModUpdateDialog.h2
-rw-r--r--launcher/ui/dialogs/ResourceDownloadDialog.cpp33
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.cpp4
-rw-r--r--launcher/ui/dialogs/ReviewMessageBox.h1
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.ui6
-rw-r--r--launcher/ui/pages/global/AccountListPage.cpp21
-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/JavaPage.cpp4
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp6
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui37
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp8
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui26
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.cpp1
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui11
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp17
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui25
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.cpp42
-rw-r--r--launcher/ui/pages/instance/ModFolderPage.h1
-rw-r--r--launcher/ui/pages/instance/ResourcePackPage.cpp3
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp24
-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.cpp9
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/CustomPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp9
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h2
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.cpp59
-rw-r--r--launcher/ui/pages/modplatform/ResourceModel.h1
-rw-r--r--launcher/ui/pages/modplatform/ResourcePage.cpp6
-rw-r--r--launcher/ui/pages/modplatform/ShaderPackPage.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp3
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp56
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlPage.ui89
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp51
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.h6
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.h8
-rw-r--r--launcher/ui/pages/modplatform/flame/FlamePage.ui4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp25
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourceModels.h17
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp49
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameResourcePages.h30
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp29
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h3
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui52
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.cpp105
-rw-r--r--launcher/ui/pages/modplatform/import_ftb/ListModel.h22
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp90
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.h2
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.cpp13
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.h5
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/Page.ui49
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp58
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h6
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp27
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.h7
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui10
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp10
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp16
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h2
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp72
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.h3
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.h8
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicPage.ui4
-rw-r--r--launcher/ui/themes/CatPack.cpp10
-rw-r--r--launcher/ui/themes/CatPack.h9
-rw-r--r--launcher/ui/widgets/InfoFrame.cpp4
-rw-r--r--launcher/ui/widgets/JavaSettingsWidget.cpp27
-rw-r--r--launcher/ui/widgets/ProjectItem.cpp2
180 files changed, 2034 insertions, 2212 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 78de8747..11e42f16 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -9,7 +9,6 @@
* Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -504,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
@@ -580,9 +582,6 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
- // Mod loader settings
- m_settings->registerSetting("DisableQuiltBeacon", false);
-
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("CustomOpenALPath", "");
@@ -598,6 +597,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true);
+ m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
@@ -968,7 +968,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
- launch(inst, true, false, nullptr, serverToJoin, accountToUse);
+ launch(inst, true, false, serverToJoin, accountToUse);
return;
}
}
@@ -1067,7 +1067,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
- launch(instance, true, false, nullptr, serverObject, accountObject);
+ launch(instance, true, false, serverObject, accountObject);
} else {
qWarning() << "Received invalid message" << message;
}
@@ -1108,7 +1108,6 @@ bool Application::openJsonEditor(const QString& filename)
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
- BaseProfilerFactory* profiler,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
{
@@ -1116,7 +1115,7 @@ bool Application::launch(InstancePtr instance,
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) {
auto& extras = m_instanceExtras[instance->id()];
- auto& window = extras.window;
+ auto window = extras.window;
if (window) {
if (!window->saveAll()) {
return false;
@@ -1127,7 +1126,7 @@ bool Application::launch(InstancePtr instance,
controller->setInstance(instance);
controller->setOnline(online);
controller->setDemo(demo);
- controller->setProfiler(profiler);
+ controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin);
controller->setAccountToUse(accountToUse);
if (window) {
diff --git a/launcher/Application.h b/launcher/Application.h
index 29774b1c..b227bb81 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -195,7 +195,6 @@ class Application : public QApplication {
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
- BaseProfilerFactory* profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp
index 70c6da6b..72503639 100644
--- a/launcher/BaseInstance.cpp
+++ b/launcher/BaseInstance.cpp
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -100,6 +101,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
+
+ m_settings->registerSetting("Profiler", "");
}
QString BaseInstance::getPreLaunchCommand()
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 38dc7c4a..47fff595 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -38,6 +39,7 @@
#include <cassert>
#include <QDateTime>
+#include <QMenu>
#include <QObject>
#include <QProcess>
#include <QSet>
@@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
+ virtual void populateLaunchMenu(QMenu* menu) = 0;
+
bool reloadSettings();
/**
@@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void runningStatusChanged(bool running);
+ void profilerChanged();
+
void statusChanged(Status from, Status to);
protected slots:
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/FileIgnoreProxy.cpp b/launcher/FileIgnoreProxy.cpp
index 4c8c64c7..df06c3c7 100644
--- a/launcher/FileIgnoreProxy.cpp
+++ b/launcher/FileIgnoreProxy.cpp
@@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
- auto fileName = fileInfo.fileName();
- auto path = relPath(fileInfo.absoluteFilePath());
- return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
- m_ignoreFilePaths.covers(path);
+ return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const
diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp
index 380489e0..21a14606 100644
--- a/launcher/LaunchController.cpp
+++ b/launcher/LaunchController.cpp
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -87,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();
@@ -361,22 +362,21 @@ void LaunchController::readyForLaunch()
QString error;
if (!m_profiler->check(&error)) {
m_launcher->abort();
- QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed!");
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
return;
}
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) {
- QMessageBox msg;
+ QMessageBox msg(m_parentWidget);
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1")
.arg(message));
msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information);
- msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
- msg.setModal(true);
+ msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
msg.exec();
m_launcher->proceed();
});
diff --git a/launcher/MMCTime.cpp b/launcher/MMCTime.cpp
index 3972dbd5..1765fd84 100644
--- a/launcher/MMCTime.cpp
+++ b/launcher/MMCTime.cpp
@@ -16,19 +16,20 @@
*/
#include <MMCTime.h>
+#include <qobject.h>
#include <QDateTime>
#include <QObject>
#include <QTextStream>
-QString Time::prettifyDuration(int64_t duration)
+QString Time::prettifyDuration(int64_t duration, bool noDays)
{
int seconds = (int)(duration % 60);
duration /= 60;
int minutes = (int)(duration % 60);
duration /= 60;
- int hours = (int)(duration % 24);
- int days = (int)(duration / 24);
+ int hours = (int)(noDays ? duration : (duration % 24));
+ int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) {
return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
}
diff --git a/launcher/MMCTime.h b/launcher/MMCTime.h
index b7d34b5d..ea6d37e7 100644
--- a/launcher/MMCTime.h
+++ b/launcher/MMCTime.h
@@ -20,7 +20,7 @@
namespace Time {
-QString prettifyDuration(int64_t duration);
+QString prettifyDuration(int64_t duration, bool noDays = false);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.
diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h
index ff658c43..c79600e7 100644
--- a/launcher/NullInstance.h
+++ b/launcher/NullInstance.h
@@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -62,6 +63,7 @@ class NullInstance : public BaseInstance {
bool canExport() const override { return false; }
bool canEdit() const override { return false; }
bool canLaunch() const override { return false; }
+ void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{
QStringList out;
diff --git a/launcher/Version.h b/launcher/Version.h
index 92bff9ba..9c043b01 100644
--- a/launcher/Version.h
+++ b/launcher/Version.h
@@ -103,14 +103,8 @@ class Version {
QString m_fullString;
- [[nodiscard]] inline bool isAppendix() const
- {
- return m_stringPart.startsWith('+');
- }
- [[nodiscard]] inline bool isPreRelease() const
- {
- return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
- }
+ [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
+ [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
inline bool operator==(const Section& other) const
{
@@ -156,14 +150,8 @@ class Version {
return m_fullString < other.m_fullString;
}
- inline bool operator!=(const Section& other) const
- {
- return !(*this == other);
- }
- inline bool operator>(const Section& other) const
- {
- return !(*this < other || *this == other);
- }
+ inline bool operator!=(const Section& other) const { return !(*this == other); }
+ inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
};
private:
diff --git a/launcher/launch/LogModel.h b/launcher/launch/LogModel.h
index ba2e4054..18e51d7e 100644
--- a/launcher/launch/LogModel.h
+++ b/launcher/launch/LogModel.h
@@ -30,7 +30,7 @@ class LogModel : public QAbstractListModel {
enum Roles { LevelRole = Qt::UserRole };
- private /* types */:
+ private /* types */:
struct entry {
MessageLevel::Enum level;
QString line;
diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp
index 0b85b81a..bb838043 100644
--- a/launcher/minecraft/ComponentUpdateTask.cpp
+++ b/launcher/minecraft/ComponentUpdateTask.cpp
@@ -2,14 +2,14 @@
#include "Component.h"
#include "ComponentUpdateTask_p.h"
-#include "OneSixVersionFormat.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "Version.h"
#include "cassert"
#include "meta/Index.h"
#include "meta/Version.h"
-#include "meta/VersionList.h"
+#include "minecraft/OneSixVersionFormat.h"
+#include "minecraft/ProfileUtils.h"
#include "net/Mode.h"
#include "Application.h"
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 62d22019..f9833b97 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -3,8 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
- * Copyright (c) 2023 seth <getchoo at tuta dot io>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -88,6 +87,10 @@
#include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h"
+#include "tools/BaseProfiler.h"
+
+#include <QActionGroup>
+
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
@@ -181,10 +184,6 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
- // Mod loader specific options
- auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
- m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
-
m_settings->set("InstanceType", "OneSix");
}
@@ -196,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
+ m_settings->registerSetting("ExportName", "");
+ m_settings->registerSetting("ExportVersion", "1.0.0");
+ m_settings->registerSetting("ExportSummary", "");
+ m_settings->registerSetting("ExportAuthor", "");
+ m_settings->registerSetting("ExportOptionalFiles", true);
+
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);
@@ -231,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const
return profile->getTraits();
}
+// FIXME: move UI code out of MinecraftInstance
+void MinecraftInstance::populateLaunchMenu(QMenu* menu)
+{
+ QAction* normalLaunch = menu->addAction(tr("&Launch"));
+ normalLaunch->setShortcut(QKeySequence::Open);
+ QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline"));
+ normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
+ QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo"));
+ normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
+
+ normalLaunchDemo->setEnabled(supportsDemo());
+
+ connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); });
+ connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); });
+ connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); });
+
+ QString profilersTitle = tr("Profilers");
+ menu->addSeparator()->setText(profilersTitle);
+
+ auto profilers = new QActionGroup(menu);
+ profilers->setExclusive(true);
+ connect(profilers, &QActionGroup::triggered, [this](QAction* action) {
+ settings()->set("Profiler", action->data());
+ emit profilerChanged();
+ });
+
+ QAction* noProfilerAction = menu->addAction(tr("&No Profiler"));
+ noProfilerAction->setData("");
+ noProfilerAction->setCheckable(true);
+ noProfilerAction->setChecked(true);
+ profilers->addAction(noProfilerAction);
+
+ for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) {
+ QAction* profilerAction = menu->addAction(profiler.value()->name());
+ profilers->addAction(profilerAction);
+ profilerAction->setData(profiler.key());
+ profilerAction->setCheckable(true);
+ profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key());
+
+ QString error;
+ profilerAction->setEnabled(profiler.value()->check(&error));
+ }
+}
+
QString MinecraftInstance::gameRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
@@ -262,7 +311,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
bool MinecraftInstance::supportsDemo() const
{
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
- // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
+ // Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
return instance_ver >= Version("1.3.1");
}
@@ -387,12 +436,6 @@ QStringList MinecraftInstance::extraArguments()
}
{
- const auto loaders = version->getModLoaders();
- if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
- list.append("-Dloader.disable_beacon=true");
- }
-
- {
QString openALPath;
QString glfwPath;
@@ -813,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;
@@ -897,13 +937,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
- description.append(tr(", last played on %1 for %2")
- .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
- .arg(Time::prettifyDuration(lastTimePlayed())));
+ description.append(
+ tr(", last played on %1 for %2")
+ .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
+ .arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
if (totalTimePlayed() > 0) {
- description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
+ description.append(
+ tr(", total played for %1")
+ .arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
if (hasCrashed()) {
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index c331cc6f..dabd44ba 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -70,6 +70,8 @@ class MinecraftInstance : public BaseInstance {
bool canExport() const override { return true; }
+ void populateLaunchMenu(QMenu* menu) override;
+
////// Directories and files //////
QString jarModsDir() const;
QString resourcePacksDir() const;
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index cf8270cd..9e42c5dd 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -58,14 +58,14 @@
#include "ComponentUpdateTask.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
+#include "minecraft/mod/Mod.h"
+#include "modplatform/ModIndex.h"
-#include "Application.h"
-#include "modplatform/ResourceAPI.h"
-
-static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge },
- { "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
- { "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
- { "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
+static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
+ { "net.minecraftforge", ModPlatform::Forge },
+ { "net.fabricmc.fabric-loader", ModPlatform::Fabric },
+ { "org.quiltmc.quilt-loader", ModPlatform::Quilt },
+ { "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{
@@ -989,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
}
}
-std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
+std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
{
- ResourceAPI::ModLoaderTypes result;
+ ModPlatform::ModLoaderTypes result;
bool has_any_loader = false;
- QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
+ QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) {
i.next();
@@ -1008,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
return {};
return result;
}
+
+std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
+{
+ auto loadersOpt = getModLoaders();
+ if (!loadersOpt.has_value())
+ return loadersOpt;
+ auto loaders = loadersOpt.value();
+ // TODO: remove this or add version condition once Quilt drops official Fabric support
+ if (loaders & ModPlatform::Quilt)
+ loaders |= ModPlatform::Fabric;
+ // TODO: remove this or add version condition once NeoForge drops official Forge support
+ if (loaders & ModPlatform::NeoForge)
+ loaders |= ModPlatform::Forge;
+ return loaders;
+}
diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h
index ce44fa58..e72b6ebf 100644
--- a/launcher/minecraft/PackProfile.h
+++ b/launcher/minecraft/PackProfile.h
@@ -44,14 +44,11 @@
#include <QList>
#include <QString>
#include <memory>
+#include <optional>
-#include "BaseVersion.h"
#include "Component.h"
#include "LaunchProfile.h"
-#include "Library.h"
-#include "MojangDownloadInfo.h"
-#include "ProfileUtils.h"
-#include "modplatform/ResourceAPI.h"
+#include "modplatform/ModIndex.h"
#include "net/Mode.h"
class MinecraftInstance;
@@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
// todo(merged): is this the best approach
void appendComponent(ComponentPtr component);
- std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
+ std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
+ // this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
+ std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
private:
void scheduleSave();
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 c534ea3b..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();
}
@@ -415,7 +400,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
{
- if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
+ if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) {
return false;
}
@@ -423,7 +408,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
if (value == Qt::Checked) {
MinecraftAccountPtr account = at(idx.row());
setDefaultAccount(account);
- }
+ } else if (m_defaultAccount == at(idx.row()))
+ setDefaultAccount(nullptr);
}
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));
@@ -472,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;
@@ -488,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/DataPack.cpp b/launcher/minecraft/mod/DataPack.cpp
index 7bf5a311..fc2d3f68 100644
--- a/launcher/minecraft/mod/DataPack.cpp
+++ b/launcher/minecraft/mod/DataPack.cpp
@@ -28,7 +28,7 @@
#include "Version.h"
// Values taken from:
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
+// https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },
diff --git a/launcher/minecraft/mod/DataPack.h b/launcher/minecraft/mod/DataPack.h
index fc2703c7..b3787b23 100644
--- a/launcher/minecraft/mod/DataPack.h
+++ b/launcher/minecraft/mod/DataPack.h
@@ -63,7 +63,7 @@ class DataPack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
- * See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+ * See https://minecraft.wiki/w/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;
diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp
index ae3dea8d..31094637 100644
--- a/launcher/minecraft/mod/Mod.cpp
+++ b/launcher/minecraft/mod/Mod.cpp
@@ -132,17 +132,23 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
- if (metadata()) {
- Metadata::remove(index_dir, metadata()->slug);
- } else {
- auto n = name();
- Metadata::remove(index_dir, n);
- }
+ destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
+void Mod::destroyMetadata(QDir& index_dir)
+{
+ if (metadata()) {
+ Metadata::remove(index_dir, metadata()->slug);
+ } else {
+ auto n = name();
+ Metadata::remove(index_dir, n);
+ }
+ m_local_details.metadata = nullptr;
+}
+
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
@@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h
index 6dafecfc..e97ee9d3 100644
--- a/launcher/minecraft/mod/Mod.h
+++ b/launcher/minecraft/mod/Mod.h
@@ -93,6 +93,8 @@ class Mod : public Resource {
// Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
+ // Delete the metadata only
+ void destroyMetadata(QDir& index_dir);
void finishResolvingWithDetails(ModDetails&& details);
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 280e70d7..a5f1489d 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -51,8 +51,13 @@
#include "Application.h"
+#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
+#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "modplatform/ModIndex.h"
+#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
@@ -228,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
return true;
}
+bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
+{
+ if (indexes.isEmpty())
+ return true;
+
+ for (auto i : indexes) {
+ if (i.column() != 0) {
+ continue;
+ }
+ auto m = at(i.row());
+ auto index_dir = indexDir();
+ m->destroyMetadata(index_dir);
+ }
+
+ update();
+
+ return true;
+}
+
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
@@ -309,3 +333,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
+
+static const FlameAPI flameAPI;
+bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
+{
+ if (vers.addonId.isValid()) {
+ ModPlatform::IndexedPack pack{
+ vers.addonId,
+ ModPlatform::ResourceProvider::FLAME,
+ };
+
+ QEventLoop loop;
+
+ auto response = std::make_shared<QByteArray>();
+ auto job = flameAPI.getProject(vers.addonId.toString(), response);
+
+ QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
+ QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
+ QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
+ QJsonParseError parse_error{};
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if (parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
+ << " reason: " << parse_error.errorString();
+ qDebug() << *response;
+ return;
+ }
+ try {
+ auto obj = Json::requireObject(Json::requireObject(doc), "data");
+ FlameMod::loadIndexedPack(pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading mod info: " << e.cause();
+ }
+ LocalModUpdateTask update_metadata(indexDir(), pack, vers);
+ QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
+ update_metadata.start();
+ });
+
+ job->start();
+
+ loop.exec();
+ }
+ return ResourceFolderModel::installResource(file_path);
+}
diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h
index 06fd7814..61d840f9 100644
--- a/launcher/minecraft/mod/ModFolderModel.h
+++ b/launcher/minecraft/mod/ModFolderModel.h
@@ -48,6 +48,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
+#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
@@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
+ bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
+ bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid();
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/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp
index dab0f6d6..07453440 100644
--- a/launcher/minecraft/mod/ResourcePack.cpp
+++ b/launcher/minecraft/mod/ResourcePack.cpp
@@ -11,7 +11,7 @@
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
// Values taken from:
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
@@ -50,7 +50,8 @@ void ResourcePack::setImage(QImage new_image) const
PixmapCache::instance().remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -68,7 +69,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h
index da354bc1..c06f3793 100644
--- a/launcher/minecraft/mod/ResourcePack.h
+++ b/launcher/minecraft/mod/ResourcePack.h
@@ -51,7 +51,7 @@ class ResourcePack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
- * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+ * See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
*/
int m_pack_format = 0;
diff --git a/launcher/minecraft/mod/ShaderPack.cpp b/launcher/minecraft/mod/ShaderPack.cpp
index 6a9641de..2c094f26 100644
--- a/launcher/minecraft/mod/ShaderPack.cpp
+++ b/launcher/minecraft/mod/ShaderPack.cpp
@@ -22,7 +22,7 @@
#include "ShaderPack.h"
-#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
+#include <QRegularExpression>
void ShaderPack::setPackFormat(ShaderPackFormat new_format)
{
@@ -35,3 +35,8 @@ bool ShaderPack::valid() const
{
return m_pack_format != ShaderPackFormat::INVALID;
}
+
+bool ShaderPack::applyFilter(QRegularExpression filter) const
+{
+ return valid() && Resource::applyFilter(filter);
+}
diff --git a/launcher/minecraft/mod/ShaderPack.h b/launcher/minecraft/mod/ShaderPack.h
index ec0f9404..d07c124b 100644
--- a/launcher/minecraft/mod/ShaderPack.h
+++ b/launcher/minecraft/mod/ShaderPack.h
@@ -54,6 +54,7 @@ class ShaderPack : public Resource {
void setPackFormat(ShaderPackFormat new_format);
bool valid() const override;
+ [[nodiscard]] bool applyFilter(QRegularExpression filter) const override;
protected:
mutable QMutex m_data_lock;
diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h
index 44ed37a4..186d0213 100644
--- a/launcher/minecraft/mod/ShaderPackFolderModel.h
+++ b/launcher/minecraft/mod/ShaderPackFolderModel.h
@@ -1,6 +1,9 @@
#pragma once
#include "ResourceFolderModel.h"
+#include "minecraft/mod/ShaderPack.h"
+#include "minecraft/mod/tasks/BasicFolderLoadTask.h"
+#include "minecraft/mod/tasks/LocalShaderPackParseTask.h"
class ShaderPackFolderModel : public ResourceFolderModel {
Q_OBJECT
@@ -9,4 +12,14 @@ class ShaderPackFolderModel : public ResourceFolderModel {
explicit ShaderPackFolderModel(const QString& dir, BaseInstance* instance) : ResourceFolderModel(QDir(dir), instance) {}
virtual QString id() const override { return "shaderpacks"; }
+
+ [[nodiscard]] Task* createUpdateTask() override
+ {
+ return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return makeShared<ShaderPack>(entry); });
+ }
+
+ [[nodiscard]] Task* createParseTask(Resource& resource) override
+ {
+ return new LocalShaderPackParseTask(m_next_resolution_ticket, static_cast<ShaderPack&>(resource));
+ }
};
diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp
index 7d8c6713..04cc3631 100644
--- a/launcher/minecraft/mod/TexturePack.cpp
+++ b/launcher/minecraft/mod/TexturePack.cpp
@@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
- auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
+ auto pixmap =
+ QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@@ -56,7 +57,7 @@ QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
- return cached_image.scaled(size, mode);
+ return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get
diff --git a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
index 0a0f57bf..bd1fe940 100644
--- a/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
+++ b/launcher/minecraft/mod/tasks/GetModDependenciesTask.cpp
@@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
}
-static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
+static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
{
- return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
+ return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value();
}
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
@@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName)
{
- if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
+ if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
@@ -191,7 +191,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
}
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) {
- if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
+ if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
@@ -201,6 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
}
+ removePack(dep.addonId);
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
@@ -250,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 50eba6af..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);
@@ -80,5 +81,5 @@ class GetModDependenciesTask : public SequentialTask {
Provider m_modrinth_provider;
Version m_version;
- ResourceAPI::ModLoaderTypes m_loaderType;
+ ModPlatform::ModLoaderTypes m_loaderType;
};
diff --git a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
index 5bb44877..82f6b9df 100644
--- a/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalDataPackParseTask.cpp
@@ -133,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
return true;
}
-// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
+// https://minecraft.wiki/w/Data_pack#pack.mcmeta
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
{
try {
diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
index 73cbf891..7b9f4f59 100644
--- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp
@@ -178,7 +178,7 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
return true;
}
-// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
+// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
try {
diff --git a/launcher/modplatform/CheckUpdateTask.h b/launcher/modplatform/CheckUpdateTask.h
index 6d968ea4..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"
@@ -14,7 +15,7 @@ class CheckUpdateTask : public Task {
public:
CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions,
- std::optional<ResourceAPI::ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
@@ -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;
@@ -53,8 +64,9 @@ class CheckUpdateTask : public Task {
protected:
QList<Mod*>& m_mods;
std::list<Version>& m_game_versions;
- std::optional<ResourceAPI::ModLoaderTypes> m_loaders;
+ std::optional<ModPlatform::ModLoaderTypes> m_loaders;
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 350a9f10..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) {
@@ -83,4 +117,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
projectID.toString();
}
+auto getModLoaderString(ModLoaderType type) -> const QString
+{
+ switch (type) {
+ case NeoForge:
+ return "neoforge";
+ case Forge:
+ return "forge";
+ case Cauldron:
+ return "cauldron";
+ case LiteLoader:
+ return "liteloader";
+ case Fabric:
+ return "fabric";
+ case Quilt:
+ return "quilt";
+ default:
+ break;
+ }
+ return "";
+}
+
} // namespace ModPlatform
diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h
index cad21703..4d6759d3 100644
--- a/launcher/modplatform/ModIndex.h
+++ b/launcher/modplatform/ModIndex.h
@@ -25,11 +25,15 @@
#include <QVariant>
#include <QVector>
#include <memory>
+#include <optional>
class QIODevice;
namespace ModPlatform {
+enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
+Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
+
enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@@ -55,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;
@@ -66,11 +98,12 @@ struct IndexedVersion {
QVariant fileId;
QString version;
QString version_number = {};
+ IndexedVersionType version_type;
QStringList mcVersion;
QString downloadUrl;
QString date;
QString fileName;
- QStringList loaders = {};
+ ModLoaderTypes loaders = {};
QString hash_type;
QString hash;
bool is_preferred = true;
@@ -128,7 +161,6 @@ struct IndexedPack {
return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
}
};
-QString getMetaURL(ResourceProvider provider, QVariant projectID);
struct OverrideDep {
QString quilt;
@@ -148,6 +180,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
QString getMetaURL(ResourceProvider provider, QVariant projectID);
+auto getModLoaderString(ModLoaderType type) -> const QString;
+
+constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept
+{
+ auto x = static_cast<int>(l);
+ return x && !(x & (x - 1));
+}
+
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)
diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h
index a92217a0..3b195938 100644
--- a/launcher/modplatform/ResourceAPI.h
+++ b/launcher/modplatform/ResourceAPI.h
@@ -54,9 +54,6 @@ class ResourceAPI {
public:
virtual ~ResourceAPI() = default;
- enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
- Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
-
struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request.
@@ -74,7 +71,7 @@ class ResourceAPI {
std::optional<QString> search;
std::optional<SortingMethod> sorting;
- std::optional<ModLoaderTypes> loaders;
+ std::optional<ModPlatform::ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions;
};
struct SearchCallbacks {
@@ -87,7 +84,7 @@ class ResourceAPI {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions;
- std::optional<ModLoaderTypes> loaders;
+ std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other)
@@ -108,13 +105,15 @@ class ResourceAPI {
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
struct ProjectInfoCallbacks {
- std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
+ std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed;
+ std::function<void(QString const& reason)> on_fail;
+ std::function<void()> on_abort;
};
struct DependencySearchArgs {
ModPlatform::Dependency dependency;
Version mcVersion;
- ModLoaderTypes loader;
+ ModPlatform::ModLoaderTypes loader;
};
struct DependencySearchCallbacks {
@@ -161,25 +160,6 @@ class ResourceAPI {
return nullptr;
}
- static auto getModLoaderString(ModLoaderType type) -> const QString
- {
- switch (type) {
- case Forge:
- return "forge";
- case Cauldron:
- return "cauldron";
- case LiteLoader:
- return "liteloader";
- case Fabric:
- return "fabric";
- case Quilt:
- return "quilt";
- default:
- break;
- }
- return "";
- }
-
protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; }
diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp
index 860d7340..5865bee9 100644
--- a/launcher/modplatform/flame/FileResolvingTask.cpp
+++ b/launcher/modplatform/flame/FileResolvingTask.cpp
@@ -1,6 +1,7 @@
#include "FileResolvingTask.h"
#include "Json.h"
+#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.h"
#include "net/Upload.h"
@@ -102,7 +103,7 @@ void Flame::FileResolvingTask::netJobFinished()
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>();
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
- QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
+ QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output);
@@ -153,7 +154,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
// If there's more than one mod loader for this version, we can't know for sure
// which file is relative to each loader, so it's best to not use any one and
// let the user download it manually.
- if (file.loaders.size() <= 1) {
+ if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
out->url = file.downloadUrl;
qDebug() << "Found alternative on modrinth " << out->fileName;
} else {
@@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName;
- QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
+ QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
auto json = QJsonDocument::fromJson(*output);
auto base =
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index 74d7db97..e99ce3a5 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -6,7 +6,6 @@
#include "FlameModIndex.h"
#include "Application.h"
-#include "BuildConfig.h"
#include "Json.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.h"
@@ -131,19 +130,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto obj = Json::requireObject(doc);
auto arr = Json::requireArray(obj, "data");
- QJsonObject latest_file_obj;
- ModPlatform::IndexedVersion ver_tmp;
-
for (auto file : arr) {
auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
- if (file_tmp.date > ver_tmp.date) {
- ver_tmp = file_tmp;
- latest_file_obj = file_obj;
- }
+ if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders))
+ ver = file_tmp;
}
- ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
} catch (Json::JsonException& e) {
qCritical() << "Failed to parse response from a version request.";
qCritical() << e.what();
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 281c0a09..e22d8f0d 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -24,7 +24,10 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
- static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); }
+ static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
+ {
+ return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt);
+ }
private:
static int getClassId(ModPlatform::ResourceType type)
@@ -35,22 +38,47 @@ class FlameAPI : public NetworkResourceAPI {
return 6;
case ModPlatform::ResourceType::RESOURCE_PACK:
return 12;
+ case ModPlatform::ResourceType::SHADER_PACK:
+ return 6552;
}
}
- static int getMappedModLoader(ModLoaderTypes loaders)
+ static int getMappedModLoader(ModPlatform::ModLoaderType loaders)
{
// https://docs.curseforge.com/?http#tocS_ModLoaderType
- if (loaders & Forge)
- return 1;
- if (loaders & Fabric)
- return 4;
- // TODO: remove this once Quilt drops official Fabric support
- if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently*
- return 4; // Quilt would probably be 5
+ switch (loaders) {
+ case ModPlatform::Forge:
+ return 1;
+ case ModPlatform::Cauldron:
+ return 2;
+ case ModPlatform::LiteLoader:
+ return 3;
+ case ModPlatform::Fabric:
+ return 4;
+ case ModPlatform::Quilt:
+ return 5;
+ case ModPlatform::NeoForge:
+ return 6;
+ }
return 0;
}
+ static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
+ {
+ QStringList l;
+ for (auto loader : { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt }) {
+ if (types & loader) {
+ l << QString::number(getMappedModLoader(loader));
+ }
+ }
+ return l;
+ }
+
+ static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
+ {
+ return "[" + getModLoaderStrings(types).join(',') + "]";
+ }
+
private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{
@@ -67,7 +95,7 @@ class FlameAPI : public NetworkResourceAPI {
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc");
if (args.loaders.has_value())
- get_arguments.append(QString("modLoaderType=%1").arg(getMappedModLoader(args.loaders.value())));
+ get_arguments.append(QString("modLoaderTypes=%1").arg(getModLoaderFilters(args.loaders.value())));
get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
@@ -81,47 +109,27 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{
auto addonId = args.pack.addonId.toString();
- QString url{ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&").arg(addonId) };
+ QString url = QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000").arg(addonId);
- QStringList get_parameters;
if (args.mcVersions.has_value())
- get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString()));
-
- if (args.loaders.has_value()) {
- int mappedModLoader = getMappedModLoader(args.loaders.value());
-
- if (args.loaders.value() & Quilt) {
- auto overide = ModPlatform::getOverrideDeps();
- auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
- return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
- });
- if (over != overide.cend()) {
- mappedModLoader = 5;
- }
- }
+ url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
- get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
+ if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
+ int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
+ url += QString("&modLoaderType=%1").arg(mappedModLoader);
}
-
- return url + get_parameters.join('&');
+ return url;
};
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
- auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString();
- if (args.loader & Quilt) {
- auto overide = ModPlatform::getOverrideDeps();
- auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) {
- return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt;
- });
- if (over != overide.cend()) {
- mappedModLoader = 5;
- }
+ auto url =
+ QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
+ if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
+ int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
+ url += QString("&modLoaderType=%1").arg(mappedModLoader);
}
- return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3")
- .arg(addonId)
- .arg(args.mcVersion.toString())
- .arg(mappedModLoader);
+ return url;
};
};
diff --git a/launcher/modplatform/flame/FlameCheckUpdate.cpp b/launcher/modplatform/flame/FlameCheckUpdate.cpp
index e10fedc3..c014863a 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.cpp
+++ b/launcher/modplatform/flame/FlameCheckUpdate.cpp
@@ -5,13 +5,12 @@
#include <MurmurHash2.h>
#include <memory>
-#include "FileSystem.h"
#include "Json.h"
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
-#include "minecraft/mod/ResourceFolderModel.h"
+#include "minecraft/mod/tasks/GetModDependenciesTask.h"
#include "net/ApiDownload.h"
@@ -156,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());
@@ -175,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/FlameCheckUpdate.h b/launcher/modplatform/flame/FlameCheckUpdate.h
index e3465d7e..05c619a7 100644
--- a/launcher/modplatform/flame/FlameCheckUpdate.h
+++ b/launcher/modplatform/flame/FlameCheckUpdate.h
@@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
public:
FlameCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
- std::optional<ResourceAPI::ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index 9fe8d486..45b4e212 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -284,7 +284,7 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
// filter by minecraft version, if the loader depends on a certain version.
// not all mod loaders depend on a given Minecraft version, so we won't do this
// filtering for those loaders.
- if (loaderType == "forge") {
+ if (loaderType == "forge" || loaderType == "neoforge") {
auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
});
@@ -350,7 +350,11 @@ bool FlameCreationTask::createInstance()
for (auto& loader : m_pack.minecraft.modLoaders) {
auto id = loader.id;
- if (id.startsWith("forge-")) {
+ if (id.startsWith("neoforge-")) {
+ id.remove("neoforge-");
+ loaderType = "neoforge";
+ loaderUid = "net.neoforged";
+ } else if (id.startsWith("forge-")) {
id.remove("forge-");
loaderType = "forge";
loaderUid = "net.minecraftforge";
diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp
index 19803cf6..345883c1 100644
--- a/launcher/modplatform/flame/FlameModIndex.cpp
+++ b/launcher/modplatform/flame/FlameModIndex.cpp
@@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
if (!file.addonId.isValid())
file.addonId = pack.addonId;
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
@@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
if (str.contains('.'))
file.mcVersion.append(str);
+ auto loader = str.toLower();
+ if (loader == "neoforge")
+ file.loaders |= ModPlatform::NeoForge;
+ if (loader == "forge")
+ file.loaders |= ModPlatform::Forge;
+ if (loader == "cauldron")
+ file.loaders |= ModPlatform::Cauldron;
+ if (loader == "liteloader")
+ file.loaders |= ModPlatform::LiteLoader;
+ if (loader == "fabric")
+ file.loaders |= ModPlatform::Fabric;
+ if (loader == "quilt")
+ file.loaders |= ModPlatform::Quilt;
}
file.addonId = Json::requireInteger(obj, "modId");
@@ -124,6 +139,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);
@@ -173,8 +204,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
return file;
}
-ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
+ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
{
+ auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@@ -183,7 +217,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
if (!file.addonId.isValid())
file.addonId = m.addonId;
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file);
}
@@ -192,5 +227,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
- return versions.front();
+ if (versions.size() != 0)
+ return versions.front();
+ return {};
}
diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h
index aa0d6f81..1bcaa44b 100644
--- a/launcher/modplatform/flame/FlameModIndex.h
+++ b/launcher/modplatform/flame/FlameModIndex.h
@@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
-auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
+auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace FlameMod \ No newline at end of file
diff --git a/launcher/modplatform/flame/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index f5f3af37..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"
@@ -43,12 +44,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
FlamePackExportTask::FlamePackExportTask(const QString& name,
const QString& version,
const QString& author,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter)
: name(name)
, version(version)
, author(author)
+ , optionalFiles(optionalFiles)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
@@ -100,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());
@@ -381,6 +385,7 @@ QByteArray FlamePackExportTask::generateIndex()
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
const ComponentPtr forge = profile->getComponent("net.minecraftforge");
+ const ComponentPtr neoforge = profile->getComponent("net.neoforged");
// convert all available components to mrpack dependencies
if (minecraft != nullptr)
@@ -392,6 +397,8 @@ QByteArray FlamePackExportTask::generateIndex()
id = "fabric-" + fabric->getVersion();
else if (forge != nullptr)
id = "forge-" + forge->getVersion();
+ else if (neoforge != nullptr)
+ id = "neoforge-" + neoforge->getVersion();
version["modLoaders"] = QJsonArray();
if (!id.isEmpty()) {
QJsonObject loader;
@@ -407,7 +414,7 @@ QByteArray FlamePackExportTask::generateIndex()
QJsonObject file;
file["projectID"] = mod.addonId;
file["fileID"] = mod.version;
- file["required"] = mod.enabled;
+ file["required"] = mod.enabled || !optionalFiles;
files << file;
}
obj["files"] = files;
diff --git a/launcher/modplatform/flame/FlamePackExportTask.h b/launcher/modplatform/flame/FlamePackExportTask.h
index d3dc6281..78b46e91 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.h
+++ b/launcher/modplatform/flame/FlamePackExportTask.h
@@ -30,6 +30,7 @@ class FlamePackExportTask : public Task {
FlamePackExportTask(const QString& name,
const QString& version,
const QString& author,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter);
@@ -44,6 +45,7 @@ class FlamePackExportTask : public Task {
// inputs
const QString name, version, author;
+ const bool optionalFiles;
const InstancePtr instance;
MinecraftInstance* mcInstance;
const QDir gameRoot;
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/helpers/NetworkResourceAPI.cpp b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
index 46b96662..dccccdc2 100644
--- a/launcher/modplatform/helpers/NetworkResourceAPI.cpp
+++ b/launcher/modplatform/helpers/NetworkResourceAPI.cpp
@@ -72,7 +72,8 @@ Task::Ptr NetworkResourceAPI::getProjectInfo(ProjectInfoArgs&& args, ProjectInfo
callbacks.on_succeed(doc, args.pack);
});
-
+ QObject::connect(job.get(), &NetJob::failed, [callbacks](QString reason) { callbacks.on_fail(reason); });
+ QObject::connect(job.get(), &NetJob::aborted, [callbacks] { callbacks.on_abort(); });
return job;
}
@@ -131,7 +132,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
- netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
+ netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{};
diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp
index 4a1bbef9..ecf97345 100644
--- a/launcher/modplatform/import_ftb/PackHelpers.cpp
+++ b/launcher/modplatform/import_ftb/PackHelpers.cpp
@@ -59,16 +59,20 @@ Modpack parseDirectory(QString path)
auto obj = Json::requireObject(target, "target");
auto name = Json::requireString(obj, "name", "name");
auto version = Json::requireString(obj, "version", "version");
- if (name == "forge") {
- modpack.loaderType = ResourceAPI::Forge;
+ if (name == "neoforge") {
+ modpack.loaderType = ModPlatform::NeoForge;
+ modpack.version = version;
+ break;
+ } else if (name == "forge") {
+ modpack.loaderType = ModPlatform::Forge;
modpack.version = version;
break;
} else if (name == "fabric") {
- modpack.loaderType = ResourceAPI::Fabric;
+ modpack.loaderType = ModPlatform::Fabric;
modpack.version = version;
break;
} else if (name == "quilt") {
- modpack.loaderType = ResourceAPI::Quilt;
+ modpack.loaderType = ModPlatform::Quilt;
modpack.version = version;
break;
}
diff --git a/launcher/modplatform/import_ftb/PackHelpers.h b/launcher/modplatform/import_ftb/PackHelpers.h
index 8ea4f3fa..5400252b 100644
--- a/launcher/modplatform/import_ftb/PackHelpers.h
+++ b/launcher/modplatform/import_ftb/PackHelpers.h
@@ -39,7 +39,7 @@ struct Modpack {
// not needed for instance creation
QVariant jvmArgs;
- std::optional<ResourceAPI::ModLoaderType> loaderType;
+ std::optional<ModPlatform::ModLoaderType> loaderType;
QString loaderVersion;
QIcon icon;
diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp
index b5e424d1..9a3b2595 100644
--- a/launcher/modplatform/import_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp
@@ -68,21 +68,25 @@ void PackInstallTask::copySettings()
auto modloader = m_pack.loaderType;
if (modloader.has_value())
switch (modloader.value()) {
- case ResourceAPI::Forge: {
+ case ModPlatform::NeoForge: {
+ components->setComponentVersion("net.neoforged", m_pack.version, true);
+ break;
+ }
+ case ModPlatform::Forge: {
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break;
}
- case ResourceAPI::Fabric: {
+ case ModPlatform::Fabric: {
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
break;
}
- case ResourceAPI::Quilt: {
+ case ModPlatform::Quilt: {
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
break;
}
- case ResourceAPI::Cauldron:
+ case ModPlatform::Cauldron:
break;
- case ResourceAPI::LiteLoader:
+ case ModPlatform::LiteLoader:
break;
}
components->saveNow();
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.cpp b/launcher/modplatform/modrinth/ModrinthAPI.cpp
index 466c5b10..f453f5cb 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.cpp
+++ b/launcher/modplatform/modrinth/ModrinthAPI.cpp
@@ -41,7 +41,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@@ -71,7 +71,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response)
{
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 58af14cc..d0f0811b 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI {
auto latestVersion(QString hash,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes,
QString hash_format,
std::optional<std::list<Version>> mcVersions,
- std::optional<ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
@@ -35,20 +35,19 @@ class ModrinthAPI : public NetworkResourceAPI {
inline auto getAuthorURL(const QString& name) const -> QString { return "https://modrinth.com/user/" + name; };
- static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
+ static auto getModLoaderStrings(const ModPlatform::ModLoaderTypes types) -> const QStringList
{
QStringList l;
- for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) {
+ for (auto loader :
+ { ModPlatform::NeoForge, ModPlatform::Forge, ModPlatform::Fabric, ModPlatform::Quilt, ModPlatform::LiteLoader }) {
if (types & loader) {
l << getModLoaderString(loader);
}
}
- if ((types & Quilt) && (~types & Fabric)) // Add Fabric if Quilt is in use, if Fabric isn't already there
- l << getModLoaderString(Fabric);
return l;
}
- static auto getModLoaderFilters(ModLoaderTypes types) -> const QString
+ static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
{
QStringList l;
for (auto loader : getModLoaderStrings(types)) {
@@ -141,7 +140,10 @@ class ModrinthAPI : public NetworkResourceAPI {
return s.isEmpty() ? QString() : s;
}
- static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt | LiteLoader); }
+ static inline auto validateModLoaders(ModPlatform::ModLoaderTypes loaders) -> bool
+ {
+ return loaders & (ModPlatform::NeoForge | ModPlatform::Forge | ModPlatform::Fabric | ModPlatform::Quilt | ModPlatform::LiteLoader);
+ }
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{
diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
index a7c22832..9b7c5385 100644
--- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
+++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
@@ -11,7 +11,6 @@
#include "tasks/ConcurrentTask.h"
#include "minecraft/mod/ModFolderModel.h"
-#include "minecraft/mod/ResourceFolderModel.h"
static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps;
@@ -39,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!"));
@@ -111,11 +110,11 @@ void ModrinthCheckUpdate::executeTask()
// so we may want to filter it
QString loader_filter;
if (m_loaders.has_value()) {
- static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric,
- ResourceAPI::ModLoaderType::Quilt };
+ static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
+ ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
- loader_filter = api.getModLoaderString(flag);
+ loader_filter = ModPlatform::getModLoaderString(flag);
break;
}
}
@@ -145,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/ModrinthCheckUpdate.h b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h
index 4583dd6c..f2f2c7e9 100644
--- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.h
+++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.h
@@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
public:
ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions,
- std::optional<ResourceAPI::ModLoaderTypes> loaders,
+ std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{}
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
index cdbbd42d..9ff6b374 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp
@@ -211,6 +211,8 @@ bool ModrinthCreationTask::createInstance()
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", m_forge_version);
+ if (!m_neoForge_version.isEmpty())
+ components->setComponentVersion("net.neoforged", m_neoForge_version);
if (m_instIcon != "default") {
instance.setIconKey(m_instIcon);
@@ -398,6 +400,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
m_quilt_version = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") {
m_forge_version = Json::requireString(*it, "Forge version");
+ } else if (name == "neoforge") {
+ m_neoForge_version = Json::requireString(*it, "NeoForge version");
} else {
throw JSONValidationError("Unknown dependency type: " + name);
}
diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
index 07e417be..1bd5b7de 100644
--- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
+++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h
@@ -39,7 +39,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
private:
QWidget* m_parent = nullptr;
- QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version;
+ QString m_minecraft_version, m_fabric_version, m_quilt_version, m_forge_version, m_neoForge_version;
QString m_managed_id, m_managed_version_id, m_managed_name;
std::vector<Modrinth::File> m_files;
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
index 64c06d1b..a9ddb0c9 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
@@ -33,12 +33,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
const QString& version,
const QString& summary,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter)
: name(name)
, version(version)
, summary(summary)
+ , optionalFiles(optionalFiles)
, instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot())
@@ -245,6 +247,7 @@ QByteArray ModrinthPackExportTask::generateIndex()
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
const ComponentPtr forge = profile->getComponent("net.minecraftforge");
+ const ComponentPtr neoForge = profile->getComponent("net.neoforged");
// convert all available components to mrpack dependencies
QJsonObject dependencies;
@@ -256,6 +259,8 @@ QByteArray ModrinthPackExportTask::generateIndex()
dependencies["fabric-loader"] = fabric->m_version;
if (forge != nullptr)
dependencies["forge"] = forge->m_version;
+ if (neoForge != nullptr)
+ dependencies["neoforge"] = neoForge->m_version;
out["dependencies"] = dependencies;
}
@@ -267,16 +272,18 @@ QByteArray ModrinthPackExportTask::generateIndex()
QString path = iterator.key();
const ResolvedFile& value = iterator.value();
- // detect disabled mod
- const QFileInfo pathInfo(path);
- if (pathInfo.suffix() == "disabled") {
- // rename it
- path = pathInfo.dir().filePath(pathInfo.completeBaseName());
- // ...and make it optional
- QJsonObject env;
- env["client"] = "optional";
- env["server"] = "optional";
- fileOut["env"] = env;
+ if (optionalFiles) {
+ // detect disabled mod
+ const QFileInfo pathInfo(path);
+ if (pathInfo.suffix() == "disabled") {
+ // rename it
+ path = pathInfo.dir().filePath(pathInfo.completeBaseName());
+ // ...and make it optional
+ QJsonObject env;
+ env["client"] = "optional";
+ env["server"] = "optional";
+ fileOut["env"] = env;
+ }
}
fileOut["path"] = path;
diff --git a/launcher/modplatform/modrinth/ModrinthPackExportTask.h b/launcher/modplatform/modrinth/ModrinthPackExportTask.h
index 1f9e0eb7..83540dfa 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.h
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.h
@@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task {
ModrinthPackExportTask(const QString& name,
const QString& version,
const QString& summary,
+ bool optionalFiles,
InstancePtr instance,
const QString& output,
MMCZip::FilterFunction filter);
@@ -50,6 +51,7 @@ class ModrinthPackExportTask : public Task {
// inputs
const QString name, version, summary;
+ const bool optionalFiles;
const InstancePtr instance;
MinecraftInstance* mcInstance;
const QDir gameRoot;
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
index 85e66a91..5c8aed1a 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp
@@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraDataLoaded = true;
}
-void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
- QJsonArray& arr,
- [[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
- const BaseInstance* inst)
+void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
{
QVector<ModPlatform::IndexedVersion> unsortedVersions;
- QString mcVersion = (static_cast<const MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft");
+ auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@@ -134,10 +134,23 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
}
auto loaders = Json::requireArray(obj, "loaders");
for (auto loader : loaders) {
- file.loaders.append(loader.toString());
+ if (loader == "neoforge")
+ file.loaders |= ModPlatform::NeoForge;
+ if (loader == "forge")
+ file.loaders |= ModPlatform::Forge;
+ if (loader == "cauldron")
+ file.loaders |= ModPlatform::Cauldron;
+ if (loader == "liteloader")
+ file.loaders |= ModPlatform::LiteLoader;
+ if (loader == "fabric")
+ file.loaders |= ModPlatform::Fabric;
+ if (loader == "quilt")
+ file.loaders |= ModPlatform::Quilt;
}
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");
@@ -218,15 +231,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {};
}
-auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
+auto Modrinth::loadDependencyVersions([[maybe_unused]] const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
+ -> ModPlatform::IndexedVersion
{
- QVector<ModPlatform::IndexedVersion> versions;
+ auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
+ QString mcVersion = profile->getComponentVersion("net.minecraft");
+ auto loaders = profile->getSupportedModLoaders();
+ QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj);
- if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
+ if (file.fileId.isValid() &&
+ (!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file);
}
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h
index 58a0f227..93f91eec 100644
--- a/launcher/modplatform/modrinth/ModrinthPackIndex.h
+++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h
@@ -26,11 +26,8 @@ namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
-void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
- QJsonArray& arr,
- const shared_qobject_ptr<QNetworkAccessManager>& network,
- const BaseInstance* inst);
+void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion;
-auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
+auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace Modrinth
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/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index 7809d40f..f37bc0bf 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -218,9 +218,24 @@ void HttpMetaCache::Load()
if (!index.open(QIODevice::ReadOnly))
return;
- QJsonDocument json = QJsonDocument::fromJson(index.readAll());
+ QJsonParseError parseError;
+ QJsonDocument json = QJsonDocument::fromJson(index.readAll(), &parseError);
+
+ // Fail if the JSON is invalid.
+ if (parseError.error != QJsonParseError::NoError) {
+ qCritical() << QString("Failed to parse HttpMetaCache file: %1 at offset %2")
+ .arg(parseError.errorString(), QString::number(parseError.offset))
+ .toUtf8();
+ return;
+ }
+
+ // Make sure the root is an object.
+ if (!json.isObject()) {
+ qCritical() << "HttpMetaCache root should be an object.";
+ return;
+ }
- auto root = Json::requireObject(json, "HttpMetaCache root");
+ auto root = json.object();
// check file version first
auto version_val = Json::ensureString(root, "version");
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/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc
index 8f079bb3..80981559 100644
--- a/launcher/resources/multimc/multimc.qrc
+++ b/launcher/resources/multimc/multimc.qrc
@@ -350,6 +350,7 @@
<file>scalable/instances/quiltmc.svg</file> <!-- CC0 QuiltMC -->
<file>scalable/instances/fabricmc.svg</file> <!-- CC0 unascribed, https://github.com/FabricMC/community/blob/main/media/unascribed/README.md -->
+ <file>scalable/instances/neoforged.svg</file>
<file>128x128/instances/forge.png</file> <!-- LGPL3 Forge Development LLC -->
<file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader -->
</qresource>
diff --git a/launcher/resources/multimc/scalable/instances/neoforged.svg b/launcher/resources/multimc/scalable/instances/neoforged.svg
new file mode 100644
index 00000000..706d53a0
--- /dev/null
+++ b/launcher/resources/multimc/scalable/instances/neoforged.svg
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="272" height="272" version="1.1" viewBox="0 0 272 272" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"><g transform="matrix(1.25,0,0,1.25,-544,-34)" stroke-width="0"><path d="m512 224v16h64v-16h32v-16h16v-32h16v-16h-16l-8-8v-32l8-8v-32l-8-8-16 16h-16l-24-24h-32l-24 24h-16l-16-16-8 8v32l8 8v32l-8 8h-16v16h16v32h16v16z" fill="#a44e37"/><path d="m480 176v16l8 8h112l8-8v-16h16v-16l-8-8 16-16-32-32h-16l-8-8h-16l-8 8v16l-16 16v-32l-8-8h-16l-8 8h-16l-32 32 16 16-8 8v16z" fill="#d7742f"/><path d="m528 144h16v-16h16v-32l-8-8h-16l-8 8z" fill="#e68c37"/><path d="m576 96h32v16h-32z" fill="#bf6134"/><path d="m528 80h32v16h-32z" fill="#bf6134"/><path d="m480 96h32v16h-32z" fill="#bb5f33"/><g fill="#bf6134"><path d="m528 160h32v16h-32z"/><path d="m480 192h32v16h-32z"/><path d="m576 192h32v16h-32z"/></g><path d="m512 192v32h16l8-8h16l8 8h16v-32h-16l-8 8h-16l-8-8z" fill="#f9f4f4"/><path d="m528 208v16h32v-16z" fill="#e7d9d3"/><path d="m528 208v-16h32v16z" fill="#13151a"/><path d="m480 128h16l8 8v24l-8 8h-8l-8-8z" fill="#f9f4f4"/><path d="m480 160h16l8 8-8 8h-16z" fill="#e7d9d3"/><path d="m512 128v16l-8 8-8-8v-16z" fill="#e7d9d3"/><path d="m512 144v16l-8 8-8-8v-16z" fill="#262a33"/><path d="m512 160v16h-16v-16z" fill="#13151a"/><g transform="matrix(-1,0,0,1,1088,0)"><path d="m480 128h16l8 8v24l-8 8h-8l-8-8z" fill="#f9f4f4"/><path d="m480 160h16l8 8-8 8h-16z" fill="#e7d9d3"/><path d="m512 128v16l-8 8-8-8v-16z" fill="#e7d9d3"/><path d="m512 144v16l-8 8-8-8v-16z" fill="#262a33"/><path d="m512 160v16h-16v-16z" fill="#13151a"/></g><path d="m608 96v-16h16v-16h-16v-16h-16v-16h-32v48h16l16 16z" fill="#66534d"/><path d="m608 64v16h-16l-16-16v-16h16v16z" fill="#8d7168"/><path d="m584 88-8-8v-16h16v16z" fill="#e7d9d3"/><path d="m576 80v16h16v-16z" fill="#c7a3b9"/><g transform="matrix(-1,0,0,1,1088,0)"><path d="m608 96v-16h16v-16h-16v-16h-16v-16h-32v48h16l16 16z" fill="#66534d"/><path d="m608 64v16h-16l-16-16v-16h16v16z" fill="#8d7168"/><path d="m584 88-8-8v-16h16v16z" fill="#e7d9d3"/><path d="m576 80v16h16v-16z" fill="#c7a3b9"/></g><g fill="#bb5f33"><path d="m480 112h-16v16h16z"/><path d="m464 128h-16v16h16z"/><path d="m480 144h-16v16h16z"/><path d="m624 144h-16v16h16z"/><path d="m640 128h-16v16h16z"/><path d="m624 112h-16v16h16z"/></g></g><metadata><rdf:RDF><cc:Work rdf:about=""><dc:creator><cc:Agent><dc:title>Sefa Eyeoglu &lt;contact@scrumplex.net&gt;</dc:title></cc:Agent></dc:creator></cc:Work></rdf:RDF></metadata></svg>
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/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp
index 7025cb79..bf83a56c 100644
--- a/launcher/ui/InstanceWindow.cpp
+++ b/launcher/ui/InstanceWindow.cpp
@@ -44,8 +44,6 @@
#include <QPushButton>
#include <QScrollBar>
-#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/PageContainer.h"
#include "InstancePageProvider.h"
@@ -76,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin
// Add custom buttons to the page container layout.
{
- auto horizontalLayout = new QHBoxLayout();
+ auto horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setContentsMargins(6, -1, 6, -1);
- auto btnHelp = new QPushButton();
+ auto btnHelp = new QPushButton(this);
btnHelp->setText(tr("Help"));
horizontalLayout->addWidget(btnHelp);
- connect(btnHelp, SIGNAL(clicked(bool)), m_container, SLOT(help()));
+ connect(btnHelp, &QPushButton::clicked, m_container, &PageContainer::help);
auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addSpacerItem(spacer);
- m_killButton = new QPushButton();
- horizontalLayout->addWidget(m_killButton);
- connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked()));
-
- m_launchOfflineButton = new QPushButton();
- horizontalLayout->addWidget(m_launchOfflineButton);
- m_launchOfflineButton->setText(tr("Launch Offline"));
+ m_launchButton = new QToolButton(this);
+ m_launchButton->setText(tr("&Launch"));
+ m_launchButton->setToolTip(tr("Launch the instance"));
+ m_launchButton->setPopupMode(QToolButton::MenuButtonPopup);
+ m_launchButton->setMinimumWidth(80); // HACK!!
+ horizontalLayout->addWidget(m_launchButton);
+ connect(m_launchButton, &QPushButton::clicked, this, [this] { APPLICATION->launch(m_instance); });
- m_launchDemoButton = new QPushButton();
- horizontalLayout->addWidget(m_launchDemoButton);
- m_launchDemoButton->setText(tr("Launch Demo"));
+ m_killButton = new QPushButton(this);
+ m_killButton->setText(tr("&Kill"));
+ m_killButton->setToolTip(tr("Kill the running instance"));
+ m_killButton->setShortcut(QKeySequence(tr("Ctrl+K")));
+ horizontalLayout->addWidget(m_killButton);
+ connect(m_killButton, &QPushButton::clicked, this, [this] { APPLICATION->kill(m_instance); });
- updateLaunchButtons();
- connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked()));
- connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked()));
+ updateButtons();
- m_closeButton = new QPushButton();
+ m_closeButton = new QPushButton(this);
m_closeButton->setText(tr("Close"));
horizontalLayout->addWidget(m_closeButton);
- connect(m_closeButton, SIGNAL(clicked(bool)), SLOT(on_closeButton_clicked()));
+ connect(m_closeButton, &QPushButton::clicked, this, &QMainWindow::close);
m_container->addButtons(horizontalLayout);
+
+ connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons);
+ connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons);
}
// restore window state
@@ -149,47 +151,18 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance
}
}
-void InstanceWindow::updateLaunchButtons()
-{
- if (m_instance->isRunning()) {
- m_launchOfflineButton->setEnabled(false);
- m_launchDemoButton->setEnabled(false);
- m_killButton->setText(tr("Kill"));
- m_killButton->setObjectName("killButton");
- m_killButton->setToolTip(tr("Kill the running instance"));
- } else if (!m_instance->canLaunch()) {
- m_launchOfflineButton->setEnabled(false);
- m_launchDemoButton->setEnabled(false);
- m_killButton->setText(tr("Launch"));
- m_killButton->setObjectName("launchButton");
- m_killButton->setToolTip(tr("Launch the instance"));
- m_killButton->setEnabled(false);
- } else {
- m_launchOfflineButton->setEnabled(true);
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get());
- if (instance) {
- m_launchDemoButton->setEnabled(instance->supportsDemo());
- }
-
- m_killButton->setText(tr("Launch"));
- m_killButton->setObjectName("launchButton");
- m_killButton->setToolTip(tr("Launch the instance"));
- }
- // NOTE: this is a hack to force the button to recalculate its style
- m_killButton->setStyleSheet("/* */");
- m_killButton->setStyleSheet(QString());
-}
-
-void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
+void InstanceWindow::updateButtons()
{
- APPLICATION->launch(m_instance, false, false, nullptr);
-}
+ m_launchButton->setEnabled(m_instance->canLaunch());
+ m_killButton->setEnabled(m_instance->isRunning());
-void InstanceWindow::on_btnLaunchMinecraftDemo_clicked()
-{
- APPLICATION->launch(m_instance, false, true, nullptr);
+ QMenu* launchMenu = m_launchButton->menu();
+ if (launchMenu)
+ launchMenu->clear();
+ else
+ launchMenu = new QMenu(this);
+ m_instance->populateLaunchMenu(launchMenu);
+ m_launchButton->setMenu(launchMenu);
}
void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
@@ -199,18 +172,13 @@ void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> pr
void InstanceWindow::runningStateChanged(bool running)
{
- updateLaunchButtons();
+ updateButtons();
m_container->refreshContainer();
if (running) {
selectPage("log");
}
}
-void InstanceWindow::on_closeButton_clicked()
-{
- close();
-}
-
void InstanceWindow::closeEvent(QCloseEvent* event)
{
bool proceed = true;
@@ -233,15 +201,6 @@ bool InstanceWindow::saveAll()
return m_container->saveAll();
}
-void InstanceWindow::on_btnKillMinecraft_clicked()
-{
- if (m_instance->isRunning()) {
- APPLICATION->kill(m_instance);
- } else {
- APPLICATION->launch(m_instance, true, false, nullptr);
- }
-}
-
QString InstanceWindow::instanceId()
{
return m_instance->id();
@@ -252,17 +211,15 @@ bool InstanceWindow::selectPage(QString pageId)
return m_container->selectPage(pageId);
}
-BasePage* InstanceWindow::selectedPage() const
-{
- return m_container->selectedPage();
-}
-
void InstanceWindow::refreshContainer()
{
m_container->refreshContainer();
}
-InstanceWindow::~InstanceWindow() {}
+BasePage* InstanceWindow::selectedPage() const
+{
+ return m_container->selectedPage();
+}
bool InstanceWindow::requestClose()
{
diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h
index 70f206f2..e5bc24d4 100644
--- a/launcher/ui/InstanceWindow.h
+++ b/launcher/ui/InstanceWindow.h
@@ -38,6 +38,7 @@
#include <QMainWindow>
#include <QSystemTrayIcon>
+#include <QToolButton>
#include "LaunchController.h"
#include "launch/LaunchTask.h"
@@ -53,7 +54,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
public:
explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0);
- virtual ~InstanceWindow();
+ virtual ~InstanceWindow() = default;
bool selectPage(QString pageId) override;
BasePage* selectedPage() const override;
@@ -71,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void isClosing();
private slots:
- void on_closeButton_clicked();
- void on_btnKillMinecraft_clicked();
- void on_btnLaunchMinecraftOffline_clicked();
- void on_btnLaunchMinecraftDemo_clicked();
-
void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
void runningStateChanged(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
@@ -84,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void closeEvent(QCloseEvent*) override;
private:
- void updateLaunchButtons();
+ void updateButtons();
private:
shared_qobject_ptr<LaunchTask> m_proc;
@@ -92,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
bool m_doNotSave = false;
PageContainer* m_container = nullptr;
QPushButton* m_closeButton = nullptr;
+ QToolButton* m_launchButton = nullptr;
QPushButton* m_killButton = nullptr;
- QPushButton* m_launchOfflineButton = nullptr;
- QPushButton* m_launchDemoButton = nullptr;
};
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 067108f2..1202c331 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -43,7 +43,6 @@
#include "FileSystem.h"
#include "MainWindow.h"
-#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h"
#include <QDir>
@@ -90,17 +89,14 @@
#include <news/NewsChecker.h>
#include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h>
-#include "InstancePageProvider.h"
#include "InstanceWindow.h"
-#include "JavaCommon.h"
-#include "LaunchController.h"
#include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ExportPackDialog.h"
+#include "ui/dialogs/ExportToModListDialog.h"
#include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -113,17 +109,22 @@
#include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h"
+#include "minecraft/PackProfile.h"
+#include "minecraft/VersionFile.h"
#include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h"
+#include "minecraft/mod/ResourcePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h"
+#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
+#include "modplatform/flame/FlameModIndex.h"
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
-#include "InstanceImportTask.h"
#include "Json.h"
@@ -362,7 +363,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
// Shouldn't have to use lambdas here like this, but if I don't, the compiler throws a fit.
// Template hell sucks...
connect(APPLICATION->accounts().get(), &AccountList::defaultAccountChanged, [this] { defaultAccountChanged(); });
- connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { repopulateAccountsMenu(); });
+ connect(APPLICATION->accounts().get(), &AccountList::listChanged, [this] { defaultAccountChanged(); });
// Show initial account
defaultAccountChanged();
@@ -553,71 +554,15 @@ void MainWindow::updateMainToolBar()
ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool());
}
-void MainWindow::updateToolsMenu()
+void MainWindow::updateLaunchButton()
{
- bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning();
-
- ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning);
- ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning);
- ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning);
-
QMenu* launchMenu = ui->actionLaunchInstance->menu();
- if (launchMenu) {
+ if (launchMenu)
launchMenu->clear();
- } else {
+ else
launchMenu = new QMenu(this);
- }
- QAction* normalLaunch = launchMenu->addAction(tr("Launch"));
- normalLaunch->setShortcut(QKeySequence::Open);
- QAction* normalLaunchOffline = launchMenu->addAction(tr("Launch Offline"));
- normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
- QAction* normalLaunchDemo = launchMenu->addAction(tr("Launch Demo"));
- normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
- if (m_selectedInstance) {
- normalLaunch->setEnabled(m_selectedInstance->canLaunch());
- normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch());
- normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch());
-
- connect(normalLaunch, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, true, false); });
- connect(normalLaunchOffline, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, false); });
- connect(normalLaunchDemo, &QAction::triggered, [this]() { APPLICATION->launch(m_selectedInstance, false, true); });
- } else {
- normalLaunch->setDisabled(true);
- normalLaunchOffline->setDisabled(true);
- normalLaunchDemo->setDisabled(true);
- }
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
- if (instance) {
- normalLaunchDemo->setEnabled(instance->supportsDemo());
- }
-
- QString profilersTitle = tr("Profilers");
- launchMenu->addSeparator()->setText(profilersTitle);
- for (auto profiler : APPLICATION->profilers().values()) {
- QAction* profilerAction = launchMenu->addAction(profiler->name());
- QAction* profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name()));
- QString error;
- if (!profiler->check(&error)) {
- profilerAction->setDisabled(true);
- profilerOfflineAction->setDisabled(true);
- QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\".");
- profilerAction->setToolTip(profilerToolTip);
- profilerOfflineAction->setToolTip(profilerToolTip);
- } else if (m_selectedInstance) {
- profilerAction->setEnabled(m_selectedInstance->canLaunch());
- profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch());
-
- connect(profilerAction, &QAction::triggered,
- [this, profiler]() { APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); });
- connect(profilerOfflineAction, &QAction::triggered,
- [this, profiler]() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); });
- } else {
- profilerAction->setDisabled(true);
- profilerOfflineAction->setDisabled(true);
- }
- }
+ if (m_selectedInstance)
+ m_selectedInstance->populateLaunchMenu(launchMenu);
ui->actionLaunchInstance->setMenu(launchMenu);
}
@@ -927,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 Mojang or Minecraft account."),
+ "one account added.\nPlease add a Microsoft account."),
QMessageBox::Warning)
->show();
}
@@ -980,6 +925,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty())
url.setScheme("file");
+ ModPlatform::IndexedVersion version;
QMap<QString, QString> extra_info;
QUrl local_url;
if (!url.isLocalFile()) { // download the remote resource and identify
@@ -989,6 +935,11 @@ void MainWindow::processURLs(QList<QUrl> urls)
// format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
QUrlQuery query(url);
+ if (query.allQueryItemValues("addonId").isEmpty() || query.allQueryItemValues("fileId").isEmpty()) {
+ qDebug() << "Invalid curseforge link:" << url;
+ continue;
+ }
+
auto addonId = query.allQueryItemValues("addonId")[0];
auto fileId = query.allQueryItemValues("fileId")[0];
@@ -1000,20 +951,19 @@ void MainWindow::processURLs(QList<QUrl> urls)
auto api = FlameAPI();
auto job = api.getFile(addonId, fileId, array);
- QString resource_name;
-
connect(job.get(), &Task::failed, this,
[this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
- connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &resource_name] {
+ connect(job.get(), &Task::succeeded, this, [this, array, addonId, fileId, &dl_url, &version] {
qDebug() << "Returned CFURL Json:\n" << array->toStdString().c_str();
auto doc = Json::requireDocument(*array);
auto data = Json::ensureObject(Json::ensureObject(doc.object()), "data");
// No way to find out if it's a mod or a modpack before here
// And also we need to check if it ends with .zip, instead of any better way
- auto fileName = Json::ensureString(data, "fileName");
+ version = FlameMod::loadIndexedPackVersion(data);
+ auto fileName = version.fileName;
// Have to use ensureString then use QUrl to get proper url encoding
- dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ dl_url = QUrl(version.downloadUrl);
if (!dl_url.isValid()) {
CustomMessageBox::selectable(
this, tr("Error"),
@@ -1024,7 +974,6 @@ void MainWindow::processURLs(QList<QUrl> urls)
}
QFileInfo dl_file(dl_url.fileName());
- resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
});
{ // drop stack
@@ -1099,7 +1048,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break;
case PackedResourceType::Mod:
- minecraftInst->loaderModList()->installMod(localFileName);
+ minecraftInst->loaderModList()->installMod(localFileName, version);
break;
case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName);
@@ -1278,7 +1227,7 @@ void MainWindow::globalSettingsClosed()
proxymodel->invalidate();
proxymodel->sort(0);
updateMainToolBar();
- updateToolsMenu();
+ updateLaunchButton();
updateThemeMenu();
updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed
@@ -1408,10 +1357,11 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
- return;
+ } else {
+ APPLICATION->instances()->deleteInstance(id);
}
-
- APPLICATION->instances()->deleteInstance(id);
+ APPLICATION->settings()->set("SelectedInstance", QString());
+ selectionBad();
}
void MainWindow::on_actionExportInstanceZip_triggered()
@@ -1513,20 +1463,6 @@ void MainWindow::activateInstance(InstancePtr instance)
APPLICATION->launch(instance);
}
-void MainWindow::on_actionLaunchInstanceOffline_triggered()
-{
- if (m_selectedInstance) {
- APPLICATION->launch(m_selectedInstance, false);
- }
-}
-
-void MainWindow::on_actionLaunchInstanceDemo_triggered()
-{
- if (m_selectedInstance) {
- APPLICATION->launch(m_selectedInstance, false, true);
- }
-}
-
void MainWindow::on_actionKillInstance_triggered()
{
if (m_selectedInstance && m_selectedInstance->isRunning()) {
@@ -1700,6 +1636,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
}
if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ disconnect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
}
QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
@@ -1707,14 +1644,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
ui->instanceToolBar->setEnabled(true);
setInstanceActionsEnabled(true);
ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch());
- ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
- ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch());
-
- // Disable demo-mode if not available.
- auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get());
- if (instance) {
- ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo());
- }
ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
@@ -1723,18 +1652,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
updateStatusCenter();
updateInstanceToolIcon(m_selectedInstance->iconKey());
- updateToolsMenu();
+ updateLaunchButton();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
+ connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
} else {
- ui->instanceToolBar->setEnabled(false);
- setInstanceActionsEnabled(false);
- ui->actionLaunchInstance->setEnabled(false);
- ui->actionLaunchInstanceOffline->setEnabled(false);
- ui->actionLaunchInstanceDemo->setEnabled(false);
- ui->actionKillInstance->setEnabled(false);
APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad();
return;
@@ -1759,11 +1683,12 @@ void MainWindow::selectionBad()
{
// start by reseting everything...
m_selectedInstance = nullptr;
+ m_statusLeft->setText(tr("No instance selected"));
statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false);
- updateToolsMenu();
+ updateLaunchButton();
renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("grass");
@@ -1810,7 +1735,9 @@ void MainWindow::updateStatusCenter()
int timePlayed = APPLICATION->instances()->getTotalPlayTime();
if (timePlayed > 0) {
- m_statusCenter->setText(tr("Total playtime: %1").arg(Time::prettifyDuration(timePlayed)));
+ m_statusCenter->setText(
+ tr("Total playtime: %1")
+ .arg(Time::prettifyDuration(timePlayed, APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
// "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
@@ -1826,7 +1753,7 @@ void MainWindow::setInstanceActionsEnabled(bool enabled)
ui->actionCreateInstanceShortcut->setEnabled(enabled);
}
-void MainWindow::refreshCurrentInstance([[maybe_unused]] bool running)
+void MainWindow::refreshCurrentInstance()
{
auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current);
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index 9fd72d25..0b614452 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -144,10 +144,6 @@ class MainWindow : public QMainWindow {
void on_actionLaunchInstance_triggered();
- void on_actionLaunchInstanceOffline_triggered();
-
- void on_actionLaunchInstanceDemo_triggered();
-
void on_actionKillInstance_triggered();
void on_actionDeleteInstance_triggered();
@@ -155,10 +151,7 @@ class MainWindow : public QMainWindow {
void deleteGroup();
void undoTrashInstance();
- inline void on_actionExportInstance_triggered()
- {
- on_actionExportInstanceZip_triggered();
- }
+ inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered();
@@ -181,7 +174,7 @@ class MainWindow : public QMainWindow {
void updateMainToolBar();
- void updateToolsMenu();
+ void updateLaunchButton();
void updateThemeMenu();
@@ -215,7 +208,7 @@ class MainWindow : public QMainWindow {
void keyReleaseEvent(QKeyEvent* event) override;
#endif
- void refreshCurrentInstance(bool running);
+ void refreshCurrentInstance();
private:
void retranslateUi();
diff --git a/launcher/ui/MainWindow.ui b/launcher/ui/MainWindow.ui
index 6ef32099..91b2c270 100644
--- a/launcher/ui/MainWindow.ui
+++ b/launcher/ui/MainWindow.ui
@@ -440,22 +440,6 @@
<string>Ctrl+D</string>
</property>
</action>
- <action name="actionLaunchInstanceOffline">
- <property name="text">
- <string>Launch &amp;Offline</string>
- </property>
- <property name="toolTip">
- <string>Launch the selected instance in offline mode.</string>
- </property>
- </action>
- <action name="actionLaunchInstanceDemo">
- <property name="text">
- <string>Launch &amp;Demo</string>
- </property>
- <property name="toolTip">
- <string>Launch the selected instance in demo mode.</string>
- </property>
- </action>
<action name="actionExportInstance">
<property name="icon">
<iconset theme="export">
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/ExportPackDialog.cpp b/launcher/ui/dialogs/ExportPackDialog.cpp
index ad8db5ff..5af24b1b 100644
--- a/launcher/ui/dialogs/ExportPackDialog.cpp
+++ b/launcher/ui/dialogs/ExportPackDialog.cpp
@@ -37,15 +37,21 @@
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(provider)
{
+ Q_ASSERT(m_provider == ModPlatform::ResourceProvider::MODRINTH || m_provider == ModPlatform::ResourceProvider::FLAME);
+
ui->setupUi(this);
- ui->name->setText(instance->name());
+ ui->name->setPlaceholderText(instance->name());
+ ui->name->setText(instance->settings()->get("ExportName").toString());
+ ui->version->setText(instance->settings()->get("ExportVersion").toString());
+ ui->optionalFiles->setChecked(instance->settings()->get("ExportOptionalFiles").toBool());
+
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
- ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]);
- setWindowTitle("Export Modrinth Pack");
+ setWindowTitle(tr("Export Modrinth Pack"));
+ ui->summary->setText(instance->settings()->get("ExportSummary").toString());
} else {
- setWindowTitle("Export CurseForge Pack");
- ui->version->setText("");
- ui->summaryLabel->setText("Author");
+ setWindowTitle(tr("Export CurseForge Pack"));
+ ui->summaryLabel->setText(tr("&Author"));
+ ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
}
// ensure a valid pack is generated
@@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
if (mcInstance) {
- mcInstance->loaderModList()->update();
const QDir index = mcInstance->loaderModList()->indexDir();
if (index.exists())
- proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath()));
+ proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath()));
}
- ui->treeView->setModel(proxy);
- ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
- ui->treeView->sortByColumn(0, Qt::AscendingOrder);
+ ui->files->setModel(proxy);
+ ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
+ ui->files->sortByColumn(0, Qt::AscendingOrder);
model->setFilter(filter);
model->setRootPath(instance->gameRoot());
- QHeaderView* headerView = ui->treeView->header();
+ QHeaderView* headerView = ui->files->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(0, QHeaderView::Stretch);
}
@@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog()
void ExportPackDialog::done(int result)
{
+ auto settings = instance->settings();
+ settings->set("ExportName", ui->name->text());
+ settings->set("ExportVersion", ui->version->text());
+ settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
+ settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
+
if (result == Accepted) {
- const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text());
+ const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
+ const QString filename = FS::RemoveInvalidFilenameChars(name);
+
QString output;
- if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
- output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
- FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
- nullptr);
- else
- output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()),
- FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
-
- if (output.isEmpty())
- return;
+ if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+ output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".mrpack"),
+ "Modrinth pack (*.mrpack *.zip)", nullptr);
+ if (output.isEmpty())
+ return;
+ if (!(output.endsWith(".zip") || output.endsWith(".mrpack")))
+ output.append(".mrpack");
+ } else {
+ output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(name), FS::PathCombine(QDir::homePath(), filename + ".zip"),
+ "CurseForge pack (*.zip)", nullptr);
+ if (output.isEmpty())
+ return;
+ if (!output.endsWith(".zip"))
+ output.append(".zip");
+ }
+
Task* task;
- if (m_provider == ModPlatform::ResourceProvider::MODRINTH)
- task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
- std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
- else
- task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output,
+ if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
+ task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
+ output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
+ } else {
+ task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
+ }
connect(task, &Task::failed,
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
@@ -140,7 +160,6 @@ void ExportPackDialog::done(int result)
void ExportPackDialog::validate()
{
- const bool invalid =
- ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty());
- ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
+ ui->buttonBox->button(QDialogButtonBox::Ok)
+ ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty());
}
diff --git a/launcher/ui/dialogs/ExportPackDialog.ui b/launcher/ui/dialogs/ExportPackDialog.ui
index 3976e28f..09dea72a 100644
--- a/launcher/ui/dialogs/ExportPackDialog.ui
+++ b/launcher/ui/dialogs/ExportPackDialog.ui
@@ -7,12 +7,9 @@
<x>0</x>
<y>0</y>
<width>650</width>
- <height>413</height>
+ <height>510</height>
</rect>
</property>
- <property name="windowTitle">
- <string>Export Pack</string>
- </property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
@@ -20,13 +17,16 @@
<item>
<widget class="QGroupBox" name="information">
<property name="title">
- <string>Information</string>
+ <string>&amp;Description</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="summaryLabel">
<property name="text">
- <string>Summary</string>
+ <string>&amp;Summary</string>
+ </property>
+ <property name="buddy">
+ <cstring>summary</cstring>
</property>
</widget>
</item>
@@ -36,14 +36,20 @@
<item row="0" column="0">
<widget class="QLabel" name="nameLabel">
<property name="text">
- <string>Name</string>
+ <string>&amp;Name</string>
+ </property>
+ <property name="buddy">
+ <cstring>name</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="versionLabel">
<property name="text">
- <string>Version</string>
+ <string>&amp;Version</string>
+ </property>
+ <property name="buddy">
+ <cstring>version</cstring>
</property>
</widget>
</item>
@@ -57,31 +63,52 @@
</property>
</widget>
</item>
-
</layout>
</widget>
</item>
<item>
- <widget class="QLabel" name="filesLabel">
- <property name="text">
- <string>Files</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTreeView" name="treeView">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
+ <widget class="QGroupBox" name="options">
+ <property name="title">
+ <string>&amp;Options</string>
</property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="filesLabel">
+ <property name="text">
+ <string>&amp;Files</string>
+ </property>
+ <property name="buddy">
+ <cstring>files</cstring>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTreeView" name="files">
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="optionalFiles">
+ <property name="text">
+ <string>&amp;Mark disabled files as optional</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -97,7 +124,8 @@
<tabstop>name</tabstop>
<tabstop>version</tabstop>
<tabstop>summary</tabstop>
- <tabstop>treeView</tabstop>
+ <tabstop>files</tabstop>
+ <tabstop>optionalFiles</tabstop>
</tabstops>
<resources/>
<connections>
diff --git a/launcher/ui/dialogs/InstallLoaderDialog.cpp b/launcher/ui/dialogs/InstallLoaderDialog.cpp
index 840a328f..541119d1 100644
--- a/launcher/ui/dialogs/InstallLoaderDialog.cpp
+++ b/launcher/ui/dialogs/InstallLoaderDialog.cpp
@@ -129,7 +129,9 @@ InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr<PackProfile> profile, c
QList<BasePage*> InstallLoaderDialog::getPages()
{
- return { // Forge
+ return { // NeoForge
+ new InstallLoaderPage("net.neoforged", "neoforged", tr("NeoForge"), {}, profile),
+ // Forge
new InstallLoaderPage("net.minecraftforge", "forge", tr("Forge"), {}, profile),
// Fabric
new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile),
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 0af1ec59..1a70ea59 100644
--- a/launcher/ui/dialogs/ModUpdateDialog.cpp
+++ b/launcher/ui/dialogs/ModUpdateDialog.cpp
@@ -3,10 +3,11 @@
#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 "FileSystem.h"
-#include "Json.h"
#include "Markdown.h"
#include "tasks/ConcurrentTask.h"
@@ -30,9 +31,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() };
}
-static std::optional<ResourceAPI::ModLoaderTypes> mcLoaders(BaseInstance* inst)
+static std::optional<ModPlatform::ModLoaderTypes> mcLoaders(BaseInstance* inst)
{
- return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders() };
+ return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders() };
}
ModUpdateDialog::ModUpdateDialog(QWidget* parent,
@@ -43,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);
@@ -126,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();
@@ -135,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
@@ -146,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
@@ -180,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;
@@ -238,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);
@@ -348,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);
@@ -364,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 4b82c0c5..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()) {
@@ -279,7 +258,7 @@ QList<BasePage*> ModDownloadDialog::getPages()
{
QList<BasePage*> pages;
- auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getModLoaders().value();
+ auto loaders = static_cast<MinecraftInstance*>(m_instance)->getPackProfile()->getSupportedModLoaders().value();
if (ModrinthAPI::validateModLoaders(loaders))
pages.append(ModrinthModPage::create(this, *m_instance));
@@ -370,6 +349,8 @@ QList<BasePage*> ShaderPackDownloadDialog::getPages()
{
QList<BasePage*> pages;
pages.append(ModrinthShaderPackPage::create(this, *m_instance));
+ if (APPLICATION->capabilities() & Application::SupportsFlame)
+ pages.append(FlameShaderPackPage::create(this, *m_instance));
return pages;
}
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/dialogs/SkinUploadDialog.ui b/launcher/ui/dialogs/SkinUploadDialog.ui
index 2c81a7fe..c6df92df 100644
--- a/launcher/ui/dialogs/SkinUploadDialog.ui
+++ b/launcher/ui/dialogs/SkinUploadDialog.ui
@@ -35,12 +35,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <property name="maximumSize">
- <size>
- <width>28</width>
- <height>16777215</height>
- </size>
- </property>
<property name="text">
<string>Browse</string>
</property>
diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp
index 5c6fb092..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,7 +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 click the \"Add\" button to add your Mojang or Minecraft account."));
+ "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);
@@ -73,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);
@@ -85,6 +83,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
connect(selectionModel, &QItemSelectionModel::selectionChanged,
[this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); });
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
+ connect(ui->listView, &VersionListView::activated, this,
+ [this](const QModelIndex& index) { m_accounts->setDefaultAccount(m_accounts->at(index.row())); });
connect(m_accounts.get(), &AccountList::listChanged, this, &AccountListPage::listChanged);
connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged);
@@ -136,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 =
@@ -166,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/JavaPage.cpp b/launcher/ui/pages/global/JavaPage.cpp
index 45d8f018..ac50319e 100644
--- a/launcher/ui/pages/global/JavaPage.cpp
+++ b/launcher/ui/pages/global/JavaPage.cpp
@@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
+ unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (maxMem < minMem) {
+ iconName = "status-yellow";
+ ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
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/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index 1c774721..553cefd8 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -2,7 +2,6 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -115,13 +114,11 @@ void MinecraftPage::applySettings()
s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
s->set("RecordGameTime", ui->recordGameTime->isChecked());
+ s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());
// Miscellaneous
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
-
- // Mod loader settings
- s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
}
void MinecraftPage::loadSettings()
@@ -169,11 +166,10 @@ void MinecraftPage::loadSettings()
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
+ ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
-
- ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());
}
void MinecraftPage::retranslate()
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 98e90e4e..b5cfa659 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -138,6 +138,13 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QCheckBox" name="showGameTimeWithoutDays">
+ <property name="text">
+ <string>Show time spent playing in hours</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
@@ -191,25 +198,6 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
- <widget class="QGroupBox" name="modLoaderSettingsGroupBox">
- <property name="title">
- <string>Mod loader settings</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_13">
- <item>
- <widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
- <property name="text">
- <string>Disable Quilt Loader Beacon</string>
- </property>
- <property name="toolTip">
- <string>Disable Quilt loader's beacon for counting monthly active users</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title">
<string>Native library workarounds</string>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
index 719db879..1a8fafa9 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp
@@ -152,6 +152,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::itemActivated(const QModelIndex&)
{
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
+ m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
}
void ExternalResourcesPage::filterTextChanged(const QString& newContents)
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index 3c836691..ba703f77 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -168,6 +168,17 @@
<string>Go to mods home page</string>
</property>
</action>
+ <action name="actionRemoveItemMetadata">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove metadata</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove mod's metadata</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index 108f2c27..7aa6bd32 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -3,7 +3,6 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -254,14 +253,6 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("InstanceAccountId");
}
- bool overrideModLoaderSettings = ui->modLoaderSettingsGroupBox->isChecked();
- m_settings->set("OverrideModLoaderSettings", overrideModLoaderSettings);
- if (overrideModLoaderSettings) {
- m_settings->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
- } else {
- m_settings->reset("DisableQuiltBeacon");
- }
-
// FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext();
}
@@ -365,10 +356,6 @@ void InstanceSettingsPage::loadSettings()
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu();
-
- // Mod loader specific settings
- ui->modLoaderSettingsGroupBox->setChecked(m_settings->get("OverrideModLoaderSettings").toBool());
- ui->disableQuiltBeaconCheckBox->setChecked(m_settings->get("DisableQuiltBeacon").toBool());
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
@@ -491,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
{
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value();
+ unsigned int minMem = ui->minMemSpinBox->value();
QString iconName;
@@ -500,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (maxMem < minMem) {
+ iconName = "status-yellow";
+ ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
ui->labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index fcc2a5d0..81cf7093 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -584,31 +584,6 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
- <widget class="QGroupBox" name="modLoaderSettingsGroupBox">
- <property name="checkable">
- <bool>true</bool>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- <property name="title">
- <string>Mod loader settings</string>
- </property>
- <layout class="QVBoxLayout" name="VerticalLayout_16">
- <item>
- <widget class="QCheckBox" name="disableQuiltBeaconCheckBox">
- <property name="text">
- <string>Disable Quilt Loader Beacon</string>
- </property>
- <property name="toolTip">
- <string>Disable Quilt loader's beacon for counting monthly active users</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="gameTimeGroupBox">
<property name="enabled">
<bool>true</bool>
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp
index 0f5e29cb..625d3793 100644
--- a/launcher/ui/pages/instance/ModFolderPage.cpp
+++ b/launcher/ui/pages/instance/ModFolderPage.cpp
@@ -92,6 +92,10 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
ui->actionsToolbar->addAction(ui->actionVisitItemPage);
connect(ui->actionVisitItemPage, &QAction::triggered, this, &ModFolderPage::visitModPages);
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
+ ui->actionsToolbar->insertActionAfter(ui->actionRemoveItem, ui->actionRemoveItemMetadata);
+ connect(ui->actionRemoveItemMetadata, &QAction::triggered, this, &ModFolderPage::deleteModMetadata);
+
auto check_allow_update = [this] { return ui->treeView->selectionModel()->hasSelection() || !m_model->empty(); };
connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] {
@@ -104,11 +108,16 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr<ModFolderModel>
if (selected <= 1) {
ui->actionVisitItemPage->setText(tr("Visit mod's page"));
ui->actionVisitItemPage->setToolTip(tr("Go to mod's home page"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mod's metadata"));
} else {
ui->actionVisitItemPage->setText(tr("Visit mods' pages"));
ui->actionVisitItemPage->setToolTip(tr("Go to the pages of the selected mods"));
+
+ ui->actionRemoveItemMetadata->setToolTip(tr("Remove mods' metadata"));
}
ui->actionVisitItemPage->setEnabled(selected != 0);
+ ui->actionRemoveItemMetadata->setEnabled(selected != 0);
});
connect(mods.get(), &ModFolderModel::rowsInserted, this,
@@ -166,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();
@@ -197,6 +206,14 @@ void ModFolderPage::installMods()
void ModFolderPage::updateMods()
{
+ if (m_instance->typeName() != "Minecraft")
+ return; // this is a null instance or a legacy instance
+
+ auto profile = static_cast<MinecraftInstance*>(m_instance)->getPackProfile();
+ if (!profile->getModLoaders().has_value()) {
+ QMessageBox::critical(this, tr("Error"), tr("Please install a mod loader first!"));
+ return;
+ }
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
auto mods_list = m_model->selectedMods(selection);
@@ -225,7 +242,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();
@@ -297,3 +314,24 @@ void ModFolderPage::visitModPages()
DesktopServices::openUrl(url);
}
}
+
+void ModFolderPage::deleteModMetadata()
+{
+ auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()).indexes();
+ auto selectionCount = m_model->selectedMods(selection).length();
+ if (selectionCount == 0)
+ return;
+ if (selectionCount > 1) {
+ auto response = CustomMessageBox::selectable(this, tr("Confirm Removal"),
+ tr("You are about to remove the metadata for %1 mods.\n"
+ "Are you sure?")
+ .arg(selectionCount),
+ QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
+ ->exec();
+
+ if (response != QMessageBox::Yes)
+ return;
+ }
+
+ m_model->deleteModsMetadata(selection);
+}
diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h
index a23dcae1..0c654d0d 100644
--- a/launcher/ui/pages/instance/ModFolderPage.h
+++ b/launcher/ui/pages/instance/ModFolderPage.h
@@ -61,6 +61,7 @@ class ModFolderPage : public ExternalResourcesPage {
private slots:
void removeItems(const QItemSelection& selection) override;
+ void deleteModMetadata();
void installMods();
void updateMods();
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/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index da49f4a7..2142e6c9 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -3,7 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
- * Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
+ * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -354,14 +354,8 @@ class ServersModel : public QAbstractListModel {
}
}
- virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override
- {
- return parent.isValid() ? 0 : m_servers.size();
- }
- int columnCount(const QModelIndex& parent) const override
- {
- return parent.isValid() ? 0 : COLUMN_COUNT;
- }
+ virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_servers.size(); }
+ int columnCount(const QModelIndex& parent) const override { return parent.isValid() ? 0 : COLUMN_COUNT; }
Server* at(int index)
{
@@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
qDebug() << "Changed:" << path;
load();
}
- void fileChanged(const QString& path)
- {
- qDebug() << "Changed:" << path;
- }
+ void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
private slots:
void save_internal()
@@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.stop();
}
- bool saveIsScheduled() const
- {
- return m_dirty;
- }
+ bool saveIsScheduled() const { return m_dirty; }
void updateFSObserver()
{
@@ -743,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()
void ServersPage::on_actionJoin_triggered()
{
const auto& address = m_model->at(currentServer)->m_address;
- APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
+ APPLICATION->launch(m_inst, true, false, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
}
#include "ServersPage.moc"
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 ef029d1d..2918261d 100644
--- a/launcher/ui/pages/instance/VersionPage.cpp
+++ b/launcher/ui/pages/instance/VersionPage.cpp
@@ -166,14 +166,17 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
- connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
+ connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
-
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
updateVersionControls();
preselect(0);
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
+ connect(ui->packageView, &QAbstractItemView::activated, this, [this](const QModelIndex& index) {
+ auto component = m_profile->getComponent(index.row());
+ component->setEnabled(!component->isEnabled());
+ });
connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
}
@@ -408,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 Mojang or Minecraft account."),
+ "one account added.\nPlease add a Microsoft account."),
QMessageBox::Warning)
->show();
return;
diff --git a/launcher/ui/pages/modplatform/CustomPage.cpp b/launcher/ui/pages/modplatform/CustomPage.cpp
index 4ac21b01..068fb3a3 100644
--- a/launcher/ui/pages/modplatform/CustomPage.cpp
+++ b/launcher/ui/pages/modplatform/CustomPage.cpp
@@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged()
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
ui->loaderVersionList->setEmptyMode(VersionListView::String);
return;
+ } else if (ui->neoForgeFilter->isChecked()) {
+ ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ m_selectedLoader = "net.neoforged";
} else if (ui->forgeFilter->isChecked()) {
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
m_selectedLoader = "net.minecraftforge";
diff --git a/launcher/ui/pages/modplatform/CustomPage.ui b/launcher/ui/pages/modplatform/CustomPage.ui
index 0d89b595..23351ccd 100644
--- a/launcher/ui/pages/modplatform/CustomPage.ui
+++ b/launcher/ui/pages/modplatform/CustomPage.ui
@@ -195,6 +195,16 @@
</widget>
</item>
<item>
+ <widget class="QRadioButton" name="neoForgeFilter">
+ <property name="text">
+ <string>NeoForge</string>
+ </property>
+ <attribute name="buttonGroup">
+ <string notr="true">loaderBtnGroup</string>
+ </attribute>
+ </widget>
+ </item>
+ <item>
<widget class="QRadioButton" name="forgeFilter">
<property name="text">
<string>Forge</string>
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index b7537890..c628f74a 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
auto sort = getCurrentSortingMethodByIndex();
- return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getModLoaders(), versions };
+ return { ModPlatform::ResourceType::MOD, m_next_search_offset, m_search_term, sort, profile->getSupportedModLoaders(), versions };
}
ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
@@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
if (!m_filter->versions.empty())
versions = m_filter->versions;
- return { pack, versions, profile->getModLoaders() };
+ return { pack, versions, profile->getSupportedModLoaders() };
}
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index 60a43128..d6cc1fdc 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -124,16 +124,17 @@ void ModPage::updateVersionList()
auto version = current_pack->versions[i];
bool valid = false;
for (auto& mcVer : m_filter->versions) {
- // NOTE: Flame doesn't care about loader, so passing it changes nothing.
- if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
+ if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
valid = true;
break;
}
}
// 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/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index 5510c191..5a43e49a 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const -> bool = 0;
+ std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const -> bool = 0;
[[nodiscard]] bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
diff --git a/launcher/ui/pages/modplatform/ResourceModel.cpp b/launcher/ui/pages/modplatform/ResourceModel.cpp
index 0a7edb7b..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()
@@ -132,6 +135,32 @@ void ResourceModel::search()
if (hasActiveSearchJob())
return;
+ if (m_search_term.startsWith("#")) {
+ auto projectId = m_search_term.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestFailed(reason, -1);
+ };
+ callbacks.on_abort = [this] {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestAborted();
+ };
+
+ callbacks.on_succeed = [this](auto& doc, auto& pack) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ searchRequestForOneSucceeded(doc);
+ };
+ if (auto job = m_api->getProjectInfo({ projectId }, std::move(callbacks)); job)
+ runSearchJob(job);
+ return;
+ }
+ }
auto args{ createSearchArguments() };
auto callbacks{ createSearchCallbacks() };
@@ -189,11 +218,18 @@ void ResourceModel::loadEntry(QModelIndex& entry)
// Use default if no callbacks are set
if (!callbacks.on_succeed)
- callbacks.on_succeed = [this, entry](auto& doc, auto pack) {
+ callbacks.on_succeed = [this, entry](auto& doc, auto& newpack) {
if (!s_running_models.constFind(this).value())
return;
+ auto pack = newpack;
infoRequestSucceeded(doc, pack, entry);
};
+ if (!callbacks.on_fail)
+ callbacks.on_fail = [this](QString reason) {
+ if (!s_running_models.constFind(this).value())
+ return;
+ QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load project info:%1").arg(reason));
+ };
if (auto job = m_api->getProjectInfo(std::move(args), std::move(callbacks)); job)
runInfoJob(job);
@@ -372,6 +408,27 @@ void ResourceModel::searchRequestSucceeded(QJsonDocument& doc)
endInsertRows();
}
+void ResourceModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ ModPlatform::IndexedPack::Ptr pack = std::make_shared<ModPlatform::IndexedPack>();
+
+ try {
+ auto obj = Json::requireObject(doc);
+ if (obj.contains("data"))
+ obj = Json::requireObject(obj, "data");
+ loadIndexedPack(*pack, obj);
+ } catch (const JSONValidationError& e) {
+ qDebug() << doc;
+ qWarning() << "Error while reading " << debugName() << " resource info: " << e.cause();
+ }
+
+ m_search_state = SearchState::Finished;
+
+ beginInsertRows(QModelIndex(), m_packs.size(), m_packs.size() + 1);
+ m_packs.append(pack);
+ endInsertRows();
+}
+
void ResourceModel::searchRequestFailed([[maybe_unused]] QString reason, int network_error_code)
{
switch (network_error_code) {
diff --git a/launcher/ui/pages/modplatform/ResourceModel.h b/launcher/ui/pages/modplatform/ResourceModel.h
index cc813d6e..ecf4f8f7 100644
--- a/launcher/ui/pages/modplatform/ResourceModel.h
+++ b/launcher/ui/pages/modplatform/ResourceModel.h
@@ -149,6 +149,7 @@ class ResourceModel : public QAbstractListModel {
private:
/* Default search request callbacks */
void searchRequestSucceeded(QJsonDocument&);
+ void searchRequestForOneSucceeded(QJsonDocument&);
void searchRequestFailed(QString reason, int network_error_code);
void searchRequestAborted();
diff --git a/launcher/ui/pages/modplatform/ResourcePage.cpp b/launcher/ui/pages/modplatform/ResourcePage.cpp
index c087e2be..44a91003 100644
--- a/launcher/ui/pages/modplatform/ResourcePage.cpp
+++ b/launcher/ui/pages/modplatform/ResourcePage.cpp
@@ -44,9 +44,6 @@
#include <QKeyEvent>
#include "Markdown.h"
-#include "ResourceDownloadTask.h"
-
-#include "minecraft/MinecraftInstance.h"
#include "ui/dialogs/ResourceDownloadDialog.h"
#include "ui/pages/modplatform/ResourceModel.h"
@@ -269,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/ShaderPackPage.cpp b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
index fbf94e84..586dffc5 100644
--- a/launcher/ui/pages/modplatform/ShaderPackPage.cpp
+++ b/launcher/ui/pages/modplatform/ShaderPackPage.cpp
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "ShaderPackPage.h"
+#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h"
#include "ShaderPackModel.h"
@@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
const std::shared_ptr<ResourceFolderModel> base_model)
{
QString custom_target_folder;
- if (version.loaders.contains(QStringLiteral("canvas")))
+ if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks");
m_model->addPack(pack, version, base_model, false, custom_target_folder);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
index 9cd5eed5..dee3784e 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -67,9 +67,10 @@ bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParen
if (searchTerm.isEmpty()) {
return true;
}
-
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ if (searchTerm.startsWith("#"))
+ return QString::number(pack.id) == searchTerm.mid(1);
return pack.name.contains(searchTerm, Qt::CaseInsensitive);
}
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index 39f4f346..b6fb7153 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -21,6 +21,7 @@
#include <Json.h>
#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
namespace Atl {
@@ -46,27 +47,50 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
ATLauncher::IndexedPack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::ToolTipRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.safeName)) {
- return (m_logoMap.value(pack.safeName));
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
}
- auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.safeName)) {
+ return (m_logoMap.value(pack.safeName));
+ }
+ auto icon = APPLICATION->getThemedIcon("atlauncher-placeholder");
- auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
- ((ListModel*)this)->requestLogo(pack.safeName, url);
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
+ ((ListModel*)this)->requestLogo(pack.safeName, url);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::request()
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
index 5e3b9ecf..c7e80027 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp
@@ -35,11 +35,11 @@
*/
#include "AtlPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_AtlPage.h"
#include "BuildConfig.h"
-#include "AtlOptionalModDialog.h"
#include "AtlUserInteractionSupportImpl.h"
#include "modplatform/atlauncher/ATLPackInstallTask.h"
#include "ui/dialogs/NewInstanceDialog.h"
@@ -71,6 +71,8 @@ AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent),
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
AtlPage::~AtlPage()
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
index 746aa6d1..8b674733 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.ui
@@ -11,21 +11,28 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_3">
- <item row="1" column="0">
- <widget class="QTreeView" name="packView">
- <property name="alternatingRowColors">
- <bool>true</bool>
+ <item row="3" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
</property>
- <property name="iconSize">
- <size>
- <width>96</width>
- <height>48</height>
- </size>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1">
<widget class="QTextBrowser" name="packDescription">
<property name="openExternalLinks">
@@ -36,39 +43,22 @@
</property>
</widget>
</item>
- <item row="0" column="0" colspan="2">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
- </property>
- <property name="wordWrap">
+ <item row="1" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="alternatingRowColors">
<bool>true</bool>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
- <item row="0" column="2">
- <widget class="QComboBox" name="versionSelectionBox"/>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Version selected:</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ <property name="iconSize">
+ <size>
+ <width>96</width>
+ <height>48</height>
+ </size>
</property>
</widget>
</item>
- <item row="0" column="0">
- <widget class="QComboBox" name="sortByBox"/>
- </item>
</layout>
</item>
- <item row="0" column="0">
+ <item row="1" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
<string>Search and filter...</string>
@@ -78,6 +68,31 @@
</property>
</widget>
</item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
+ </property>
+ <property name="text">
+ <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignCenter</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<tabstops>
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index ff21d010..8875a945 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -1,6 +1,8 @@
#include "FlameModel.h"
#include <Json.h>
#include "Application.h"
+#include "modplatform/ResourceAPI.h"
+#include "modplatform/flame/FlameAPI.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@@ -161,6 +163,21 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const FlameAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ }
auto netJob = makeShared<NetJob>("Flame::Search", APPLICATION->network());
auto searchUrl = QString(
"https://api.curseforge.com/v1/mods/search?"
@@ -189,23 +206,24 @@ void ListModel::searchWithTerm(const QString& term, int sort)
}
currentSearchTerm = term;
currentSort = sort;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
void Flame::ListModel::searchRequestFinished()
{
- jobPtr.reset();
+ if (hasActiveSearchJob())
+ return;
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
@@ -246,6 +264,25 @@ void Flame::ListModel::searchRequestFinished()
endInsertRows();
}
+void Flame::ListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = Json::ensureObject(doc.object(), "data");
+
+ Flame::IndexedPack pack;
+ try {
+ Flame::loadIndexedPack(pack, packObj);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading pack from CurseForge: " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void Flame::ListModel::searchRequestFailed(QString reason)
{
jobPtr.reset();
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.h b/launcher/ui/pages/modplatform/flame/FlameModel.h
index b3bc96b8..fd8496df 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.h
@@ -40,6 +40,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void performPaginatedSearch();
@@ -48,6 +51,7 @@ class ListModel : public QAbstractListModel {
void searchRequestFinished();
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
private:
void requestLogo(QString file, QString url);
@@ -63,7 +67,7 @@ class ListModel : public QAbstractListModel {
int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.cpp b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
index 183e16f9..584d94ad 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.cpp
@@ -50,7 +50,8 @@
static FlameAPI api;
-FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
+FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
@@ -61,6 +62,17 @@ FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(paren
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &FlamePage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
// index is used to set the sorting with the curseforge api
ui->sortByBox->addItem(tr("Sort by Featured"));
ui->sortByBox->addItem(tr("Sort by Popularity"));
@@ -90,6 +102,11 @@ bool FlamePage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -114,6 +131,7 @@ void FlamePage::openedImpl()
void FlamePage::triggerSearch()
{
listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(listModel->activeSearchJob().get());
}
void FlamePage::onSelectionChanged(QModelIndex curr, [[maybe_unused]] QModelIndex prev)
@@ -158,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/flame/FlamePage.h b/launcher/ui/pages/modplatform/flame/FlamePage.h
index ff5c7975..d35858fb 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.h
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.h
@@ -39,8 +39,9 @@
#include <Application.h>
#include <modplatform/flame/FlamePackIndex.h>
-#include "tasks/Task.h"
+#include <QTimer>
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class FlamePage;
@@ -86,4 +87,9 @@ class FlamePage : public QWidget, public BasePage {
Flame::IndexedPack current;
int m_selected_version_index = -1;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/flame/FlamePage.ui b/launcher/ui/pages/modplatform/flame/FlamePage.ui
index 71d19513..f9e1fe67 100644
--- a/launcher/ui/pages/modplatform/flame/FlamePage.ui
+++ b/launcher/ui/pages/modplatform/flame/FlamePage.ui
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item row="2" column="0">
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item row="3" column="0">
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
index 2b020c48..7d18e72a 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.cpp
@@ -31,7 +31,7 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
- return FlameMod::loadDependencyVersions(m, arr);
+ return FlameMod::loadDependencyVersions(m, arr, &m_base_instance);
}
auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -121,4 +121,27 @@ auto FlameTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonAr
return Json::ensureArray(obj.object(), "data");
}
+FlameShaderPackModel::FlameShaderPackModel(const BaseInstance& base) : ShaderPackResourceModel(base, new FlameAPI) {}
+
+void FlameShaderPackModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadIndexedPack(m, obj);
+}
+
+// We already deal with the URLs when initializing the pack, due to the API response's structure
+void FlameShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj)
+{
+ FlameMod::loadBody(m, obj);
+}
+
+void FlameShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
+{
+ FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+}
+
+auto FlameShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
+{
+ return Json::ensureArray(obj.object(), "data");
+}
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
index 6cfd6a6f..76dbd7b3 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourceModels.h
@@ -68,4 +68,21 @@ class FlameTexturePackModel : public TexturePackResourceModel {
auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
};
+class FlameShaderPackModel : public ShaderPackResourceModel {
+ Q_OBJECT
+
+ public:
+ FlameShaderPackModel(const BaseInstance&);
+ ~FlameShaderPackModel() override = default;
+
+ private:
+ [[nodiscard]] QString debugName() const override { return Flame::debugName() + " (Model)"; }
+ [[nodiscard]] QString metaEntryBase() const override { return Flame::metaEntryBase(); }
+
+ void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override;
+ void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override;
+ auto documentToArray(QJsonDocument& obj) const -> QJsonArray override;
+};
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
index dc17e705..23373ec9 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.cpp
@@ -68,10 +68,10 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance& instance) :
auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+ std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
- Q_UNUSED(loaders);
- return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty();
+ return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty() &&
+ (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
@@ -173,6 +173,45 @@ void FlameTexturePackPage::openUrl(const QUrl& url)
TexturePackResourcePage::openUrl(url);
}
+FlameShaderPackPage::FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ : ShaderPackResourcePage(dialog, instance)
+{
+ m_model = new FlameShaderPackModel(instance);
+ m_ui->packView->setModel(m_model);
+
+ addSortings();
+
+ // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
+ // so it's best not to connect them in the parent's constructor...
+ connect(m_ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(m_ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameShaderPackPage::onSelectionChanged);
+ connect(m_ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlameShaderPackPage::onVersionSelectionChanged);
+ connect(m_ui->resourceSelectionButton, &QPushButton::clicked, this, &FlameShaderPackPage::onResourceSelected);
+
+ m_ui->packDescription->setMetaEntry(metaEntryBase());
+}
+
+bool FlameShaderPackPage::optedOut(ModPlatform::IndexedVersion& ver) const
+{
+ return isOptedOut(ver);
+}
+
+void FlameShaderPackPage::openUrl(const QUrl& url)
+{
+ if (url.scheme().isEmpty()) {
+ QString query = url.query(QUrl::FullyDecoded);
+
+ if (query.startsWith("remoteUrl=")) {
+ // attempt to resolve url from warning page
+ query.remove(0, 10);
+ ShaderPackResourcePage::openUrl({ QUrl::fromPercentEncoding(query.toUtf8()) }); // double decoding is necessary
+ return;
+ }
+ }
+
+ ShaderPackResourcePage::openUrl(url);
+}
+
// I don't know why, but doing this on the parent class makes it so that
// other mod providers start loading before being selected, at least with
// my Qt, so we need to implement this in every derived class...
@@ -188,5 +227,9 @@ auto FlameTexturePackPage::shouldDisplay() const -> bool
{
return true;
}
+auto FlameShaderPackPage::shouldDisplay() const -> bool
+{
+ return true;
+}
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
index c6ebc1ea..f2f5ceca 100644
--- a/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
+++ b/launcher/ui/pages/modplatform/flame/FlameResourcePages.h
@@ -44,6 +44,7 @@
#include "ui/pages/modplatform/ModPage.h"
#include "ui/pages/modplatform/ResourcePackPage.h"
+#include "ui/pages/modplatform/ShaderPackPage.h"
#include "ui/pages/modplatform/TexturePackPage.h"
namespace ResourceDownload {
@@ -95,7 +96,7 @@ class FlameModPage : public ModPage {
bool validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const override;
+ std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const override;
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
void openUrl(const QUrl& url) override;
@@ -155,4 +156,31 @@ class FlameTexturePackPage : public TexturePackResourcePage {
void openUrl(const QUrl& url) override;
};
+class FlameShaderPackPage : public ShaderPackResourcePage {
+ Q_OBJECT
+
+ public:
+ static FlameShaderPackPage* create(ShaderPackDownloadDialog* dialog, BaseInstance& instance)
+ {
+ return ShaderPackResourcePage::create<FlameShaderPackPage>(dialog, instance);
+ }
+
+ FlameShaderPackPage(ShaderPackDownloadDialog* dialog, BaseInstance& instance);
+ ~FlameShaderPackPage() override = default;
+
+ [[nodiscard]] bool shouldDisplay() const override;
+
+ [[nodiscard]] inline auto displayName() const -> QString override { return Flame::displayName(); }
+ [[nodiscard]] inline auto icon() const -> QIcon override { return Flame::icon(); }
+ [[nodiscard]] inline auto id() const -> QString override { return Flame::id(); }
+ [[nodiscard]] inline auto debugName() const -> QString override { return Flame::debugName(); }
+ [[nodiscard]] inline auto metaEntryBase() const -> QString override { return Flame::metaEntryBase(); }
+
+ [[nodiscard]] inline auto helpPage() const -> QString override { return ""; }
+
+ bool optedOut(ModPlatform::IndexedVersion& ver) const override;
+
+ void openUrl(const QUrl& url) override;
+};
+
} // namespace ResourceDownload
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
index 5c9ff63b..d3ead083 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.cpp
@@ -17,6 +17,7 @@
*/
#include "ImportFTBPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_ImportFTBPage.h"
#include <QWidget>
@@ -32,17 +33,30 @@ ImportFTBPage::ImportFTBPage(NewInstanceDialog* dialog, QWidget* parent) : QWidg
ui->setupUi(this);
{
+ currentModel = new FilterModel(this);
listModel = new ListModel(this);
+ currentModel->setSourceModel(listModel);
- ui->modpackList->setModel(listModel);
+ ui->modpackList->setModel(currentModel);
ui->modpackList->setSortingEnabled(true);
ui->modpackList->header()->hide();
ui->modpackList->setIndentation(0);
ui->modpackList->setIconSize(QSize(42, 42));
+
+ for (int i = 0; i < currentModel->getAvailableSortings().size(); i++) {
+ ui->sortByBox->addItem(currentModel->getAvailableSortings().keys().at(i));
+ }
+
+ ui->sortByBox->setCurrentText(currentModel->translateCurrentSorting());
}
connect(ui->modpackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &ImportFTBPage::onPublicPackSelectionChanged);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &ImportFTBPage::onSortingSelectionChanged);
+
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &ImportFTBPage::triggerSearch);
+
+ ui->modpackList->setItemDelegate(new ProjectItemDelegate(this));
ui->modpackList->selectionModel()->reset();
}
@@ -86,7 +100,7 @@ void ImportFTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex pr
onPackSelectionChanged();
return;
}
- Modpack selectedPack = listModel->data(now, Qt::UserRole).value<Modpack>();
+ Modpack selectedPack = currentModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
@@ -101,4 +115,15 @@ void ImportFTBPage::onPackSelectionChanged(Modpack* pack)
dialog->setSuggestedPack();
}
+void ImportFTBPage::onSortingSelectionChanged(QString sort)
+{
+ FilterModel::Sorting toSet = currentModel->getAvailableSortings().value(sort);
+ currentModel->setSorting(toSet);
+}
+
+void ImportFTBPage::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace FTBImportAPP
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
index 54c49f7b..8e966127 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.h
@@ -53,12 +53,15 @@ class ImportFTBPage : public QWidget, public BasePage {
void suggestCurrent();
void onPackSelectionChanged(Modpack* pack = nullptr);
private slots:
+ void onSortingSelectionChanged(QString data);
void onPublicPackSelectionChanged(QModelIndex first, QModelIndex second);
+ void triggerSearch();
private:
bool initialized = false;
Modpack selected;
ListModel* listModel = nullptr;
+ FilterModel* currentModel = nullptr;
NewInstanceDialog* dialog = nullptr;
Ui::ImportFTBPage* ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
index 32d548b0..5e09fb6d 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
+++ b/launcher/ui/pages/modplatform/import_ftb/ImportFTBPage.ui
@@ -10,8 +10,8 @@
<height>1011</height>
</rect>
</property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="1">
<widget class="QTreeView" name="modpackList">
<property name="maximumSize">
<size>
@@ -21,6 +21,54 @@
</property>
</widget>
</item>
+ <item row="0" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QComboBox" name="sortByBox">
+ <property name="minimumSize">
+ <size>
+ <width>265</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
index dc78f451..134bdc0c 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.cpp
@@ -23,7 +23,9 @@
#include <QIcon>
#include <QProcessEnvironment>
#include "FileSystem.h"
+#include "StringUtils.h"
#include "modplatform/import_ftb/PackHelpers.h"
+#include "ui/widgets/ProjectItem.h"
namespace FTBImportAPP {
@@ -71,18 +73,99 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
auto pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- return pack.icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
- } else if (role == Qt::ToolTipRole) {
- return tr("Minecraft %1").arg(pack.mcVersion);
+ if (role == Qt::ToolTipRole) {
}
- return QVariant();
+ switch (role) {
+ case Qt::ToolTipRole:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case Qt::DecorationRole:
+ return pack.icon;
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return tr("Minecraft %1").arg(pack.mcVersion);
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+FilterModel::FilterModel(QObject* parent) : QSortFilterProxyModel(parent)
+{
+ currentSorting = Sorting::ByGameVersion;
+ sortings.insert(tr("Sort by Name"), Sorting::ByName);
+ sortings.insert(tr("Sort by Game Version"), Sorting::ByGameVersion);
+}
+
+bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) const
+{
+ Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
+ Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
+
+ if (currentSorting == Sorting::ByGameVersion) {
+ Version lv(leftPack.mcVersion);
+ Version rv(rightPack.mcVersion);
+ return lv < rv;
+
+ } else if (currentSorting == Sorting::ByName) {
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // UHM, some inavlid value set?!
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
+{
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting s)
+{
+ currentSorting = s;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
}
} // namespace FTBImportAPP \ No newline at end of file
diff --git a/launcher/ui/pages/modplatform/import_ftb/ListModel.h b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
index c67aa896..11192827 100644
--- a/launcher/ui/pages/modplatform/import_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/import_ftb/ListModel.h
@@ -20,11 +20,33 @@
#include <QAbstractListModel>
#include <QIcon>
+#include <QSortFilterProxyModel>
#include <QVariant>
#include "modplatform/import_ftb/PackHelpers.h"
namespace FTBImportAPP {
+class FilterModel : public QSortFilterProxyModel {
+ Q_OBJECT
+ public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting { ByName, ByGameVersion };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
+
+ protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
+ bool lessThan(const QModelIndex& left, const QModelIndex& right) const override;
+
+ private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+ QString searchTerm;
+};
+
class ListModel : public QAbstractListModel {
Q_OBJECT
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 356d919d..49666cf6 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -41,6 +41,7 @@
#include <Version.h>
#include "StringUtils.h"
+#include "ui/widgets/ProjectItem.h"
#include <QLabel>
#include <QtMath>
@@ -79,7 +80,20 @@ bool FilterModel::lessThan(const QModelIndex& left, const QModelIndex& right) co
bool FilterModel::filterAcceptsRow([[maybe_unused]] int sourceRow, [[maybe_unused]] const QModelIndex& sourceParent) const
{
- return true;
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ Modpack pack = sourceModel()->data(index, Qt::UserRole).value<Modpack>();
+ if (searchTerm.startsWith("#"))
+ return pack.packCode == searchTerm.mid(1);
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
}
const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
@@ -139,39 +153,57 @@ QVariant ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name + "\n" + translatePackType(pack.type);
- } else if (role == Qt::ToolTipRole) {
- if (pack.description.length() > 100) {
- // some magic to prevent to long tooltips and replace html linebreaks
- QString edit = pack.description.left(97);
- edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
- return edit;
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logo)) {
+ return (m_logoMap.value(pack.logo));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel*)this)->requestLogo(pack.logo);
+ return icon;
}
- return pack.description;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logo)) {
- return (m_logoMap.value(pack.logo));
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logo);
- return icon;
- } else if (role == Qt::ForegroundRole) {
- if (pack.broken) {
- // FIXME: Hardcoded color
- return QColor(255, 0, 50);
- } else if (pack.bugged) {
- // FIXME: Hardcoded color
- // bugged pack, currently only indicates bugged xml
- return QColor(244, 229, 66);
+ case Qt::ForegroundRole: {
+ if (pack.broken) {
+ // FIXME: Hardcoded color
+ return QColor(255, 0, 50);
+ } else if (pack.bugged) {
+ // FIXME: Hardcoded color
+ // bugged pack, currently only indicates bugged xml
+ return QColor(244, 229, 66);
+ }
}
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+ return {};
}
void ListModel::fill(ModpackList modpacks_)
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
index 51a58d99..c802a4b5 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.h
@@ -25,6 +25,7 @@ class FilterModel : public QSortFilterProxyModel {
QString translateCurrentSorting();
void setSorting(Sorting sorting);
Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override;
@@ -33,6 +34,7 @@ class FilterModel : public QSortFilterProxyModel {
private:
QMap<QString, Sorting> sortings;
Sorting currentSorting;
+ QString searchTerm;
};
class ListModel : public QAbstractListModel {
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
index 0103bbaa..4104f139 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp
@@ -35,6 +35,7 @@
*/
#include "Page.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_Page.h"
#include <QInputDialog>
@@ -110,6 +111,8 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &Page::triggerSearch);
+
connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
@@ -125,6 +128,9 @@ Page::Page(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), dialog
ui->thirdPartyPackList->selectionModel()->reset();
ui->privatePackList->selectionModel()->reset();
+ ui->publicPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->thirdPartyPackList->setItemDelegate(new ProjectItemDelegate(this));
+ ui->privatePackList->setItemDelegate(new ProjectItemDelegate(this));
onTabChanged(ui->tabWidget->currentIndex());
}
@@ -319,6 +325,8 @@ void Page::onTabChanged(int tab)
currentModpackInfo = ui->publicPackDescription;
}
+ triggerSearch();
+
currentList->selectionModel()->reset();
QModelIndex idx = currentList->currentIndex();
if (idx.isValid()) {
@@ -358,4 +366,9 @@ void Page::onRemovePackClicked()
onPackSelectionChanged();
}
+void Page::triggerSearch()
+{
+ currentModel->setSearchTerm(ui->searchEdit->text());
+}
+
} // namespace LegacyFTB
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
index a12b0745..4d317b7c 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h
@@ -43,7 +43,6 @@
#include "QObjectPtr.h"
#include "modplatform/legacy_ftb/PackFetchTask.h"
#include "modplatform/legacy_ftb/PackHelpers.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
class NewInstanceDialog;
@@ -56,8 +55,6 @@ class Page;
class ListModel;
class FilterModel;
-class PrivatePackListModel;
-class PrivatePackFilterModel;
class PrivatePackManager;
class Page : public QWidget, public BasePage {
@@ -98,6 +95,8 @@ class Page : public QWidget, public BasePage {
void onAddPackClicked();
void onRemovePackClicked();
+ void triggerSearch();
+
private:
FilterModel* currentModel = nullptr;
QTreeView* currentList = nullptr;
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
index ad08dc25..56cba748 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
+++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui
@@ -10,8 +10,29 @@
<height>602</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout_5">
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter...</string>
+ </property>
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="4" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@@ -36,9 +57,9 @@
</item>
<item row="0" column="1">
<widget class="QTextBrowser" name="publicPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -50,10 +71,10 @@
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="1">
<widget class="QTextBrowser" name="thirdPartyPackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
<item row="0" column="0">
<widget class="QTreeView" name="thirdPartyPackList">
@@ -104,16 +125,16 @@
</item>
<item row="0" column="1" rowspan="3">
<widget class="QTextBrowser" name="privatePackDescription">
- <property name="openExternalLinks">
- <bool>true</bool>
- </property>
- </widget>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
</item>
</layout>
</widget>
</widget>
</item>
- <item>
+ <item row="5" column="0">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="label">
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
index ebc5556c..f691a185 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp
@@ -38,8 +38,8 @@
#include "BuildConfig.h"
#include "Json.h"
-#include "minecraft/MinecraftInstance.h"
-#include "minecraft/PackProfile.h"
+#include "modplatform/modrinth/ModrinthAPI.h"
+#include "net/NetJob.h"
#include "ui/widgets/ProjectItem.h"
#include "net/ApiDownload.h"
@@ -130,7 +130,24 @@ bool ModpackListModel::setData(const QModelIndex& index, const QVariant& value,
void ModpackListModel::performPaginatedSearch()
{
- // TODO: Move to standalone API
+ if (hasActiveSearchJob())
+ return;
+
+ if (currentSearchTerm.startsWith("#")) {
+ auto projectId = currentSearchTerm.mid(1);
+ if (!projectId.isEmpty()) {
+ ResourceAPI::ProjectInfoCallbacks callbacks;
+
+ callbacks.on_fail = [this](QString reason) { searchRequestFailed(reason); };
+ callbacks.on_succeed = [this](auto& doc, auto& pack) { searchRequestForOneSucceeded(doc); };
+ static const ModrinthAPI api;
+ if (auto job = api.getProjectInfo({ projectId }, std::move(callbacks)); job) {
+ jobPtr = job;
+ jobPtr->start();
+ }
+ return;
+ }
+ } // TODO: Move to standalone API
auto netJob = makeShared<NetJob>("Modrinth::SearchModpack", APPLICATION->network());
auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL +
"/search?"
@@ -167,16 +184,17 @@ void ModpackListModel::performPaginatedSearch()
void ModpackListModel::refresh()
{
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
nextSearchOffset = 0;
performPaginatedSearch();
}
@@ -307,9 +325,29 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all)
endInsertRows();
}
+void ModpackListModel::searchRequestForOneSucceeded(QJsonDocument& doc)
+{
+ jobPtr.reset();
+
+ auto packObj = doc.object();
+
+ Modrinth::Modpack pack;
+ try {
+ Modrinth::loadIndexedPack(pack, packObj);
+ pack.id = Json::ensureString(packObj, "id", pack.id);
+ } catch (const JSONValidationError& e) {
+ qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause();
+ return;
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + 1);
+ modpacks.append({ pack });
+ endInsertRows();
+}
+
void ModpackListModel::searchRequestFailed(QString reason)
{
- auto failed_action = jobPtr->getFailedActions().at(0);
+ auto failed_action = dynamic_cast<NetJob*>(jobPtr.get())->getFailedActions().at(0);
if (!failed_action->m_reply) {
// Network error
QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks."));
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 721c69f5..2a9d6226 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -73,6 +73,9 @@ class ModpackListModel : public QAbstractListModel {
void refresh();
void searchWithTerm(const QString& term, const int sort);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
inline auto canFetchMore(const QModelIndex& parent) const -> bool override
@@ -83,6 +86,7 @@ class ModpackListModel : public QAbstractListModel {
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
void searchRequestFailed(QString reason);
+ void searchRequestForOneSucceeded(QJsonDocument&);
protected slots:
@@ -111,7 +115,7 @@ class ModpackListModel : public QAbstractListModel {
int nextSearchOffset = 0;
enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None;
- NetJob::Ptr jobPtr;
+ Task::Ptr jobPtr;
std::shared_ptr<QByteArray> m_all_response = std::make_shared<QByteArray>();
QByteArray m_specific_response;
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
index 41fd5003..8e2b9a90 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp
@@ -52,7 +52,8 @@
#include <QKeyEvent>
#include <QPushButton>
-ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog)
+ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
@@ -64,6 +65,17 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &ModrinthPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
ui->sortByBox->addItem(tr("Sort by Relevance"));
ui->sortByBox->addItem(tr("Sort by Total Downloads"));
ui->sortByBox->addItem(tr("Sort by Follows"));
@@ -102,6 +114,11 @@ bool ModrinthPage::eventFilter(QObject* watched, QEvent* event)
this->triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QObject::eventFilter(watched, event);
@@ -200,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;
@@ -309,6 +327,7 @@ void ModrinthPage::suggestCurrent()
void ModrinthPage::triggerSearch()
{
m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ m_fetch_progress.watch(m_model->activeSearchJob().get());
}
void ModrinthPage::onVersionSelectionChanged(QString version)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
index b7054c88..4240dcaf 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h
@@ -41,7 +41,9 @@
#include "ui/pages/BasePage.h"
#include "modplatform/modrinth/ModrinthPackManifest.h"
+#include "ui/widgets/ProgressWidget.h"
+#include <QTimer>
#include <QWidget>
namespace Ui {
@@ -88,4 +90,9 @@ class ModrinthPage : public QWidget, public BasePage {
Modrinth::Modpack current;
QString selectedVersion;
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
index 6d8b2b67..78a25fea 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui
@@ -10,8 +10,8 @@
<height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
@@ -29,7 +29,7 @@
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QLineEdit" name="searchEdit">
@@ -47,7 +47,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="3" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QListView" name="packView">
@@ -77,7 +77,7 @@
</item>
</layout>
</item>
- <item>
+ <item row="4" column="0">
<layout class="QHBoxLayout">
<item>
<widget class="QComboBox" name="sortByBox"/>
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
index ce7475b6..85601829 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourceModels.cpp
@@ -39,12 +39,12 @@ void ModrinthModModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObjec
void ModrinthModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion
{
- return ::Modrinth::loadDependencyVersions(m, arr);
+ return ::Modrinth::loadDependencyVersions(m, arr, &m_base_instance);
}
auto ModrinthModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -66,7 +66,7 @@ void ModrinthResourcePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, Q
void ModrinthResourcePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthResourcePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -88,7 +88,7 @@ void ModrinthTexturePackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJ
void ModrinthTexturePackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthTexturePackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
@@ -110,7 +110,7 @@ void ModrinthShaderPackModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJs
void ModrinthShaderPackModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr)
{
- ::Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), &m_base_instance);
+ ::Modrinth::loadIndexedPackVersions(m, arr, &m_base_instance);
}
auto ModrinthShaderPackModel::documentToArray(QJsonDocument& obj) const -> QJsonArray
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
index 616c1a81..a4197b22 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.cpp
@@ -65,21 +65,9 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance& instan
auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer,
- std::optional<ResourceAPI::ModLoaderTypes> loaders) const -> bool
+ std::optional<ModPlatform::ModLoaderTypes> loaders) const -> bool
{
- auto loaderCompatible = !loaders.has_value();
-
- if (!loaderCompatible) {
- auto loaderStrings = ModrinthAPI::getModLoaderStrings(loaders.value());
- for (auto remoteLoader : ver.loaders) {
- if (loaderStrings.contains(remoteLoader)) {
- loaderCompatible = true;
- break;
- }
- }
- }
-
- return ver.mcVersion.contains(mineVer) && loaderCompatible;
+ return ver.mcVersion.contains(mineVer) && (!loaders.has_value() || !ver.loaders || loaders.value() & ver.loaders);
}
ModrinthResourcePackPage::ModrinthResourcePackPage(ResourcePackDownloadDialog* dialog, BaseInstance& instance)
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
index 86ba1ccb..311bcfe3 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthResourcePages.h
@@ -93,7 +93,7 @@ class ModrinthModPage : public ModPage {
[[nodiscard]] inline auto helpPage() const -> QString override { return "Mod-platform"; }
- auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ResourceAPI::ModLoaderTypes> loaders = {}) const
+ auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, std::optional<ModPlatform::ModLoaderTypes> loaders = {}) const
-> bool override;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index e8c5ac92..3cd1d9a2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -39,6 +39,7 @@
#include "Json.h"
#include "net/ApiDownload.h"
+#include "ui/widgets/ProjectItem.h"
#include <QIcon>
@@ -54,21 +55,47 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
}
Modpack pack = modpacks.at(pos);
- if (role == Qt::DisplayRole) {
- return pack.name;
- } else if (role == Qt::DecorationRole) {
- if (m_logoMap.contains(pack.logoName)) {
- return (m_logoMap.value(pack.logoName));
+ switch (role) {
+ case Qt::ToolTipRole: {
+ if (pack.description.length() > 100) {
+ // some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+ }
+ return pack.description;
+ }
+ case Qt::DecorationRole: {
+ if (m_logoMap.contains(pack.logoName)) {
+ return (m_logoMap.value(pack.logoName));
+ }
+ QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
+ ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
+ }
+ case Qt::UserRole: {
+ QVariant v;
+ v.setValue(pack);
+ return v;
}
- QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder");
- ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl);
- return icon;
- } else if (role == Qt::UserRole) {
- QVariant v;
- v.setValue(pack);
- return v;
+ case Qt::DisplayRole:
+ return pack.name;
+ case Qt::SizeHintRole:
+ return QSize(0, 58);
+ // Custom data
+ case UserDataTypes::TITLE:
+ return pack.name;
+ case UserDataTypes::DESCRIPTION:
+ return pack.description;
+ case UserDataTypes::SELECTED:
+ return false;
+ case UserDataTypes::INSTALLED:
+ return false;
+ default:
+ break;
}
- return QVariant();
+
+ return {};
}
int Technic::ListModel::columnCount(const QModelIndex& parent) const
@@ -87,21 +114,25 @@ void Technic::ListModel::searchWithTerm(const QString& term)
return;
}
currentSearchTerm = term;
- if (jobPtr) {
+ if (hasActiveSearchJob()) {
jobPtr->abort();
searchState = ResetRequested;
return;
- } else {
- beginResetModel();
- modpacks.clear();
- endResetModel();
- searchState = None;
}
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
performSearch();
}
void Technic::ListModel::performSearch()
{
+ if (hasActiveSearchJob())
+ return;
+
auto netJob = makeShared<NetJob>("Technic::Search", APPLICATION->network());
QString searchUrl = "";
if (currentSearchTerm.isEmpty()) {
@@ -113,6 +144,9 @@ void Technic::ListModel::performSearch()
} else if (currentSearchTerm.startsWith("https://api.technicpack.net/modpack/")) {
searchUrl = QString("%1?build=%2").arg(currentSearchTerm, BuildConfig.TECHNIC_API_BUILD);
searchMode = Single;
+ } else if (currentSearchTerm.startsWith("#")) {
+ searchUrl = QString("https://api.technicpack.net/modpack/%1?build=%2").arg(currentSearchTerm.mid(1), BuildConfig.TECHNIC_API_BUILD);
+ searchMode = Single;
} else {
searchUrl =
QString("%1search?build=%2&q=%3").arg(BuildConfig.TECHNIC_API_BASE_URL, BuildConfig.TECHNIC_API_BUILD, currentSearchTerm);
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.h b/launcher/ui/pages/modplatform/technic/TechnicModel.h
index d7a635d4..aeb4f308 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.h
@@ -58,6 +58,9 @@ class ListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
void searchWithTerm(const QString& term);
+ [[nodiscard]] bool hasActiveSearchJob() const { return jobPtr && jobPtr->isRunning(); }
+ [[nodiscard]] Task::Ptr activeSearchJob() { return hasActiveSearchJob() ? jobPtr : nullptr; }
+
private slots:
void searchRequestFinished();
void searchRequestFailed();
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
index 54b86feb..190b7c68 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp
@@ -34,6 +34,7 @@
*/
#include "TechnicPage.h"
+#include "ui/widgets/ProjectItem.h"
#include "ui_TechnicPage.h"
#include <QKeyEvent>
@@ -51,7 +52,8 @@
#include "net/ApiDownload.h"
-TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
+TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent)
+ : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog), m_fetch_progress(this, false)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
@@ -59,8 +61,21 @@ TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(p
model = new Technic::ListModel(this);
ui->packView->setModel(model);
+ m_search_timer.setTimerType(Qt::TimerType::CoarseTimer);
+ m_search_timer.setSingleShot(true);
+
+ connect(&m_search_timer, &QTimer::timeout, this, &TechnicPage::triggerSearch);
+
+ m_fetch_progress.hideIfInactive(true);
+ m_fetch_progress.setFixedHeight(24);
+ m_fetch_progress.progressFormat("");
+
+ ui->gridLayout->addWidget(&m_fetch_progress, 2, 0, 1, ui->gridLayout->columnCount());
+
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &TechnicPage::onVersionSelectionChanged);
+
+ ui->packView->setItemDelegate(new ProjectItemDelegate(this));
}
bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
@@ -71,6 +86,11 @@ bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
triggerSearch();
keyEvent->accept();
return true;
+ } else {
+ if (m_search_timer.isActive())
+ m_search_timer.stop();
+
+ m_search_timer.start(350);
}
}
return QWidget::eventFilter(watched, event);
@@ -100,6 +120,7 @@ void TechnicPage::openedImpl()
void TechnicPage::triggerSearch()
{
model->searchWithTerm(ui->searchEdit->text());
+ m_fetch_progress.watch(model->activeSearchJob().get());
}
void TechnicPage::onSelectionChanged(QModelIndex first, [[maybe_unused]] QModelIndex second)
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.h b/launcher/ui/pages/modplatform/technic/TechnicPage.h
index 91b61eaf..01439337 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.h
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.h
@@ -35,13 +35,14 @@
#pragma once
+#include <QTimer>
#include <QWidget>
#include <Application.h>
#include "TechnicData.h"
#include "net/NetJob.h"
-#include "tasks/Task.h"
#include "ui/pages/BasePage.h"
+#include "ui/widgets/ProgressWidget.h"
namespace Ui {
class TechnicPage;
@@ -91,4 +92,9 @@ class TechnicPage : public QWidget, public BasePage {
NetJob::Ptr jobPtr;
std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+
+ ProgressWidget m_fetch_progress;
+
+ // Used to do instant searching with a delay to cache quick changes
+ QTimer m_search_timer;
};
diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.ui b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
index 15bf645f..b988eda2 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicPage.ui
+++ b/launcher/ui/pages/modplatform/technic/TechnicPage.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="3" column="0" colspan="2">
+ <item row="4" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
@@ -44,7 +44,7 @@
</item>
</layout>
</item>
- <item row="2" column="0" colspan="2">
+ <item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListView" name="packView">
diff --git a/launcher/ui/themes/CatPack.cpp b/launcher/ui/themes/CatPack.cpp
index f0d8ddd5..bbcb58bc 100644
--- a/launcher/ui/themes/CatPack.cpp
+++ b/launcher/ui/themes/CatPack.cpp
@@ -99,18 +99,22 @@ QDate ensureDay(int year, int month, int day)
QString JsonCatPack::path()
{
- const QDate now = QDate::currentDate();
+ return path(QDate::currentDate());
+}
+
+QString JsonCatPack::path(QDate now)
+{
for (auto var : m_variants) {
QDate startDate = ensureDay(now.year(), var.startTime.month, var.startTime.day);
QDate endDate = ensureDay(now.year(), var.endTime.month, var.endTime.day);
if (startDate > endDate) { // it's spans over multiple years
- if (endDate <= now) // end date is in the past so jump one year into the future for endDate
+ if (endDate < now) // end date is in the past so jump one year into the future for endDate
endDate = endDate.addYears(1);
else // end date is in the future so jump one year into the past for startDate
startDate = startDate.addYears(-1);
}
- if (startDate >= now && now >= endDate)
+ if (startDate <= now && now <= endDate)
return var.path;
}
return m_defaultPath;
diff --git a/launcher/ui/themes/CatPack.h b/launcher/ui/themes/CatPack.h
index fdd117a7..1d310e79 100644
--- a/launcher/ui/themes/CatPack.h
+++ b/launcher/ui/themes/CatPack.h
@@ -52,9 +52,9 @@ class BasicCatPack : public CatPack {
public:
BasicCatPack(QString id, QString name) : m_id(id), m_name(name) {}
BasicCatPack(QString id) : BasicCatPack(id, id) {}
- virtual QString id() { return m_id; }
- virtual QString name() { return m_name; }
- virtual QString path();
+ virtual QString id() override { return m_id; }
+ virtual QString name() override { return m_name; }
+ virtual QString path() override;
protected:
QString m_id;
@@ -83,7 +83,8 @@ class JsonCatPack : public BasicCatPack {
PartialDate endTime;
};
JsonCatPack(QFileInfo& manifestInfo);
- virtual QString path();
+ virtual QString path() override;
+ QString path(QDate now);
private:
QString m_defaultPath;
diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp
index 1f03f9ea..69f72fea 100644
--- a/launcher/ui/widgets/InfoFrame.cpp
+++ b/launcher/ui/widgets/InfoFrame.cpp
@@ -158,12 +158,12 @@ QString InfoFrame::renderColorCodes(QString input)
//
// TODO: Wrap links inside <a> tags
- // https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes
+ // https://minecraft.wiki/w/Formatting_codes#Color_codes
const QMap<QChar, QString> color_codes_map = { { '0', "#000000" }, { '1', "#0000AA" }, { '2', "#00AA00" }, { '3', "#00AAAA" },
{ '4', "#AA0000" }, { '5', "#AA00AA" }, { '6', "#FFAA00" }, { '7', "#AAAAAA" },
{ '8', "#555555" }, { '9', "#5555FF" }, { 'a', "#55FF55" }, { 'b', "#55FFFF" },
{ 'c', "#FF5555" }, { 'd', "#FF55FF" }, { 'e', "#FFFF55" }, { 'f', "#FFFFFF" } };
- // https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes
+ // https://minecraft.wiki/w/Formatting_codes#Formatting_codes
const QMap<QChar, QString> formatting_codes_map = { { 'l', "b" }, { 'm', "s" }, { 'n', "u" }, { 'o', "i" } };
QString html("<html>");
diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp
index 42279a66..d2d92191 100644
--- a/launcher/ui/widgets/JavaSettingsWidget.cpp
+++ b/launcher/ui/widgets/JavaSettingsWidget.cpp
@@ -186,12 +186,20 @@ QString JavaSettingsWidget::javaPath() const
int JavaSettingsWidget::maxHeapSize() const
{
- return m_maxMemSpinBox->value();
+ auto min = m_minMemSpinBox->value();
+ auto max = m_maxMemSpinBox->value();
+ if (max < min)
+ max = min;
+ return max;
}
int JavaSettingsWidget::minHeapSize() const
{
- return m_minMemSpinBox->value();
+ auto min = m_minMemSpinBox->value();
+ auto max = m_maxMemSpinBox->value();
+ if (min > max)
+ min = max;
+ return min;
}
bool JavaSettingsWidget::permGenEnabled() const
@@ -214,17 +222,9 @@ void JavaSettingsWidget::memoryValueChanged(int)
if (obj == m_minMemSpinBox && min != observedMinMemory) {
observedMinMemory = min;
actuallyChanged = true;
- if (min > max) {
- observedMaxMemory = min;
- m_maxMemSpinBox->setValue(min);
- }
} else if (obj == m_maxMemSpinBox && max != observedMaxMemory) {
observedMaxMemory = max;
actuallyChanged = true;
- if (min > max) {
- observedMinMemory = max;
- m_minMemSpinBox->setValue(max);
- }
} else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) {
observedPermGenMemory = permgen;
actuallyChanged = true;
@@ -361,8 +361,8 @@ void JavaSettingsWidget::checkJavaPath(const QString& path)
setJavaStatus(JavaStatus::Pending);
m_checker.reset(new JavaChecker());
m_checker->m_path = path;
- m_checker->m_minMem = m_minMemSpinBox->value();
- m_checker->m_maxMem = m_maxMemSpinBox->value();
+ m_checker->m_minMem = minHeapSize();
+ m_checker->m_maxMem = maxHeapSize();
if (m_permGenSpinBox->isVisible()) {
m_checker->m_permGen = m_permGenSpinBox->value();
}
@@ -415,6 +415,9 @@ void JavaSettingsWidget::updateThresholds()
} else if (observedMaxMemory > (m_availableMemory * 0.9)) {
iconName = "status-yellow";
m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity."));
+ } else if (observedMaxMemory < observedMinMemory) {
+ iconName = "status-yellow";
+ m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value"));
} else {
iconName = "status-good";
m_labelMaxMemIcon->setToolTip("");
diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp
index 1481c1b6..60b92b28 100644
--- a/launcher/ui/widgets/ProjectItem.cpp
+++ b/launcher/ui/widgets/ProjectItem.cpp
@@ -34,8 +34,8 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o
icon_width = icon_size.width();
icon_height = icon_size.height();
- icon_x_margin = (rect.height() - icon_width) / 2;
icon_y_margin = (rect.height() - icon_height) / 2;
+ icon_x_margin = icon_y_margin; // use same margins for consistency
}
// Centralize icon with a margin to separate from the other elements