aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt12
-rw-r--r--buildconfig/BuildConfig.cpp.in3
-rw-r--r--buildconfig/BuildConfig.h3
-rw-r--r--cmake/MacOSXBundleInfo.plist.in11
-rw-r--r--launcher/Application.cpp54
-rw-r--r--launcher/Application.h8
-rw-r--r--launcher/InstanceImportTask.cpp35
-rw-r--r--launcher/InstanceImportTask.h1
-rw-r--r--launcher/MangoHud.cpp43
-rw-r--r--launcher/MangoHud.h4
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp29
-rw-r--r--launcher/minecraft/PackProfile.cpp3
-rw-r--r--launcher/minecraft/launch/ExtractNatives.cpp12
-rw-r--r--launcher/modplatform/ResourceAPI.h4
-rw-r--r--launcher/modplatform/flame/FlameAPI.cpp11
-rw-r--r--launcher/modplatform/flame/FlameAPI.h7
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp8
-rw-r--r--launcher/modplatform/flame/FlamePackExportTask.cpp3
-rw-r--r--launcher/modplatform/import_ftb/PackHelpers.cpp6
-rw-r--r--launcher/modplatform/import_ftb/PackInstallTask.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h7
-rw-r--r--launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp4
-rw-r--r--launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h2
-rw-r--r--launcher/modplatform/modrinth/ModrinthPackExportTask.cpp3
-rw-r--r--launcher/resources/multimc/multimc.qrc1
-rw-r--r--launcher/resources/multimc/scalable/instances/neoforged.svg3
-rw-r--r--launcher/ui/MainWindow.cpp103
-rw-r--r--launcher/ui/MainWindow.h2
-rw-r--r--launcher/ui/dialogs/InstallLoaderDialog.cpp4
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.cpp7
-rw-r--r--launcher/ui/dialogs/NewInstanceDialog.h5
-rw-r--r--launcher/ui/dialogs/SkinUploadDialog.ui6
-rw-r--r--launcher/ui/pages/global/MinecraftPage.cpp35
-rw-r--r--launcher/ui/pages/global/MinecraftPage.h3
-rw-r--r--launcher/ui/pages/global/MinecraftPage.ui40
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.cpp35
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.h3
-rw-r--r--launcher/ui/pages/instance/InstanceSettingsPage.ui44
-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/ImportPage.cpp72
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.h3
-rw-r--r--launcher/ui/pages/modplatform/ImportPage.ui2
-rw-r--r--program_info/org.prismlauncher.PrismLauncher.desktop.in2
-rw-r--r--program_info/win_install.nsi.in4
46 files changed, 577 insertions, 91 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a2853cb5..638fba05 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -216,6 +216,18 @@ set(Launcher_SUBREDDIT_URL "https://prismlauncher.org/reddit" CACHE STRING "URL
set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules")
set(Launcher_QT_VERSION_MAJOR "6" CACHE STRING "Major Qt version to build against")
+# Native libraries
+if(UNIX AND APPLE)
+ set(Launcher_GLFW_LIBRARY_NAME "libglfw.dylib" CACHE STRING "Name of native glfw library")
+ set(Launcher_OPENAL_LIBRARY_NAME "libopenal.dylib" CACHE STRING "Name of native openal library")
+elseif(UNIX)
+ set(Launcher_GLFW_LIBRARY_NAME "libglfw.so" CACHE STRING "Name of native glfw library")
+ set(Launcher_OPENAL_LIBRARY_NAME "libopenal.so" CACHE STRING "Name of native openal library")
+elseif(WIN32)
+ set(Launcher_GLFW_LIBRARY_NAME "glfw.dll" CACHE STRING "Name of native glfw library")
+ set(Launcher_OPENAL_LIBRARY_NAME "OpenAL.dll" CACHE STRING "Name of native openal library")
+endif()
+
# API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service
# of these platforms, please change these API keys beforehand.
diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index d7662a7a..1eb0022b 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -110,6 +110,9 @@ Config::Config()
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@";
+ GLFW_LIBRARY_NAME = "@Launcher_GLFW_LIBRARY_NAME@";
+ OPENAL_LIBRARY_NAME = "@Launcher_OPENAL_LIBRARY_NAME@";
+
BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@";
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index 387f494f..a5649b98 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -134,6 +134,9 @@ class Config {
*/
QString META_URL;
+ QString GLFW_LIBRARY_NAME;
+ QString OPENAL_LIBRARY_NAME;
+
QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL;
QString MATRIX_URL;
diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in
index 400e482f..d36ac3e8 100644
--- a/cmake/MacOSXBundleInfo.plist.in
+++ b/cmake/MacOSXBundleInfo.plist.in
@@ -67,5 +67,16 @@
<string>Alternate</string>
</dict>
</array>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>Curseforge</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>curseforge</string>
+ </array>
+ </dict>
+ </array>
</dict>
</plist>
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 4da7629c..5a952c74 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -194,8 +194,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
- { { "I", "import" }, "Import instance from specified zip (local path or URL)", "file" },
+ { { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
+ // Has to be positional for some OS to handle that properly
+ parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]");
+
parser.addHelpOption();
parser.addVersionOption();
@@ -208,13 +211,13 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToShowWindowOf = parser.value("show");
- for (auto zip_path : parser.values("import")) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.values("import")) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// treat unspecified positional arguments as import urls
- for (auto zip_path : parser.positionalArguments()) {
- m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
+ for (auto url : parser.positionalArguments()) {
+ m_urlsToImport.append(normalizeImportUrl(url));
}
// error if --launch is missing with --server or --profile
@@ -313,11 +316,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);
- if (!m_zipsToImport.isEmpty()) {
- for (auto zip_url : m_zipsToImport) {
+ if (!m_urlsToImport.isEmpty()) {
+ for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
- import.args.insert("path", zip_url.toString());
+ import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
@@ -582,7 +585,9 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
+ m_settings->registerSetting("CustomOpenALPath", "");
m_settings->registerSetting("UseNativeGLFW", false);
+ m_settings->registerSetting("CustomGLFWPath", "");
// Peformance related options
m_settings->registerSetting("EnableFeralGamemode", false);
@@ -842,6 +847,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
updateCapabilities();
+ detectLibraries();
+
if (createSetupWizard()) {
return;
}
@@ -978,9 +985,9 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
- if (!m_zipsToImport.isEmpty()) {
- qDebug() << "<> Importing from zip:" << m_zipsToImport;
- m_mainWindow->processURLs(m_zipsToImport);
+ if (!m_urlsToImport.isEmpty()) {
+ qDebug() << "<> Importing from url:" << m_urlsToImport;
+ m_mainWindow->processURLs(m_urlsToImport);
}
}
@@ -1022,12 +1029,12 @@ void Application::messageReceived(const QByteArray& message)
if (command == "activate") {
showMainWindow();
} else if (command == "import") {
- QString path = received.args["path"];
- if (path.isEmpty()) {
+ QString url = received.args["url"];
+ if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
- m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
+ m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
@@ -1411,6 +1418,15 @@ void Application::updateCapabilities()
#endif
}
+void Application::detectLibraries()
+{
+#ifdef Q_OS_LINUX
+ m_detectedGLFWPath = MangoHud::findLibrary(BuildConfig.GLFW_LIBRARY_NAME);
+ m_detectedOpenALPath = MangoHud::findLibrary(BuildConfig.OPENAL_LIBRARY_NAME);
+ qDebug() << "Detected native libraries:" << m_detectedGLFWPath << m_detectedOpenALPath;
+#endif
+}
+
QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
@@ -1589,3 +1605,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available.";
}
}
+
+QUrl Application::normalizeImportUrl(QString const& url)
+{
+ auto local_file = QFileInfo(url);
+ if (local_file.exists()) {
+ return QUrl::fromLocalFile(local_file.absoluteFilePath());
+ } else {
+ return QUrl::fromUserInput(url);
+ }
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index 8a85fd95..b227bb81 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -142,6 +142,8 @@ class Application : public QApplication {
void updateCapabilities();
+ void detectLibraries();
+
/*!
* Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found.
@@ -177,6 +179,8 @@ class Application : public QApplication {
int suitableMaxMem();
+ QUrl normalizeImportUrl(QString const& url);
+
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@@ -274,11 +278,13 @@ class Application : public QApplication {
SetupWizard* m_setupWizard = nullptr;
public:
+ QString m_detectedGLFWPath;
+ QString m_detectedOpenALPath;
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
- QList<QUrl> m_zipsToImport;
+ QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};
diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp
index 3f948a33..bc139e4f 100644
--- a/launcher/InstanceImportTask.cpp
+++ b/launcher/InstanceImportTask.cpp
@@ -50,6 +50,7 @@
#include "modplatform/technic/TechnicPackProcessor.h"
#include "settings/INISettingsObject.h"
+#include "tasks/Task.h"
#include "net/ApiDownload.h"
@@ -90,25 +91,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
- const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
-
- auto entry = APPLICATION->metacache()->resolveEntry("general", path);
- entry->setStale(true);
- m_archivePath = entry->getFullPath();
-
- m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
- m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
-
- connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
- connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
- connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
- connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
- connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
-
- m_filesNetJob->start();
+ downloadFromUrl();
}
}
+void InstanceImportTask::downloadFromUrl()
+{
+ const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
+ m_archivePath = entry->getFullPath();
+
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
+ connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
+ connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
+ connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
+ connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
+ m_filesNetJob->start();
+}
+
void InstanceImportTask::downloadSucceeded()
{
processZipPack();
diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h
index 4459e440..ca3d30ad 100644
--- a/launcher/InstanceImportTask.h
+++ b/launcher/InstanceImportTask.h
@@ -101,4 +101,5 @@ class InstanceImportTask : public InstanceTask {
// FIXME: nuke
QWidget* m_parent;
+ void downloadFromUrl();
};
diff --git a/launcher/MangoHud.cpp b/launcher/MangoHud.cpp
index 5758da3a..ab79f418 100644
--- a/launcher/MangoHud.cpp
+++ b/launcher/MangoHud.cpp
@@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+#include <QDebug>
#include <QDir>
#include <QString>
#include <QStringList>
@@ -26,6 +27,15 @@
#include "Json.h"
#include "MangoHud.h"
+#ifdef __GLIBC__
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#define UNDEF_GNU_SOURCE
+#endif
+#include <dlfcn.h>
+#include <linux/limits.h>
+#endif
+
namespace MangoHud {
QString getLibraryString()
@@ -106,4 +116,37 @@ QString getLibraryString()
return QString();
}
+
+QString findLibrary(QString libName)
+{
+#ifdef __GLIBC__
+ const char* library = libName.toLocal8Bit().constData();
+
+ void* handle = dlopen(library, RTLD_NOW);
+ if (!handle) {
+ qCritical() << "dlopen() failed:" << dlerror();
+ return {};
+ }
+
+ char path[PATH_MAX];
+ if (dlinfo(handle, RTLD_DI_ORIGIN, path) == -1) {
+ qCritical() << "dlinfo() failed:" << dlerror();
+ dlclose(handle);
+ return {};
+ }
+
+ auto fullPath = FS::PathCombine(QString(path), libName);
+
+ dlclose(handle);
+ return fullPath;
+#else
+ qWarning() << "MangoHud::findLibrary is not implemented on this platform";
+ return {};
+#endif
+}
} // namespace MangoHud
+
+#ifdef UNDEF_GNU_SOURCE
+#undef _GNU_SOURCE
+#undef UNDEF_GNU_SOURCE
+#endif
diff --git a/launcher/MangoHud.h b/launcher/MangoHud.h
index 7b7c2849..5361999b 100644
--- a/launcher/MangoHud.h
+++ b/launcher/MangoHud.h
@@ -24,4 +24,6 @@
namespace MangoHud {
QString getLibraryString();
-}
+
+QString findLibrary(QString libName);
+} // namespace MangoHud
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 7e7b3554..699aaffa 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -170,7 +170,9 @@ void MinecraftInstance::loadSpecificSettings()
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("CustomOpenALPath"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
@@ -437,6 +439,33 @@ QStringList MinecraftInstance::extraArguments()
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
list.append("-Dloader.disable_beacon=true");
}
+
+ {
+ QString openALPath;
+ QString glfwPath;
+
+ if (settings()->get("UseNativeOpenAL").toBool()) {
+ openALPath = APPLICATION->m_detectedOpenALPath;
+ auto customPath = settings()->get("CustomOpenALPath").toString();
+ if (!customPath.isEmpty())
+ openALPath = customPath;
+ }
+ if (settings()->get("UseNativeGLFW").toBool()) {
+ glfwPath = APPLICATION->m_detectedGLFWPath;
+ auto customPath = settings()->get("CustomGLFWPath").toString();
+ if (!customPath.isEmpty())
+ glfwPath = customPath;
+ }
+
+ QFileInfo openALInfo(openALPath);
+ QFileInfo glfwInfo(glfwPath);
+
+ if (!openALPath.isEmpty() && openALInfo.exists())
+ list.append("-Dorg.lwjgl.openal.libname=" + openALInfo.absoluteFilePath());
+ if (!glfwPath.isEmpty() && glfwInfo.exists())
+ list.append("-Dorg.lwjgl.glfw.libname=" + glfwInfo.absoluteFilePath());
+ }
+
return list;
}
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index cf8270cd..92988808 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -62,7 +62,8 @@
#include "Application.h"
#include "modplatform/ResourceAPI.h"
-static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge },
+static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.neoforged", ResourceAPI::NeoForge },
+ { "net.minecraftforge", ResourceAPI::Forge },
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
diff --git a/launcher/minecraft/launch/ExtractNatives.cpp b/launcher/minecraft/launch/ExtractNatives.cpp
index cebeaedd..8f3cac4d 100644
--- a/launcher/minecraft/launch/ExtractNatives.cpp
+++ b/launcher/minecraft/launch/ExtractNatives.cpp
@@ -39,7 +39,7 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin
return target + replacement;
}
-static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW)
+static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack)
{
QuaZip zip(source);
if (!zip.open(QuaZip::mdUnzip)) {
@@ -52,12 +52,6 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
do {
QString name = zip.getCurrentFileName();
auto lowercase = name.toLower();
- if (nativeGLFW && name.contains("glfw")) {
- continue;
- }
- if (nativeOpenAL && name.contains("openal")) {
- continue;
- }
if (applyJnilibHack) {
name = replaceSuffix(name, ".jnilib", ".dylib");
}
@@ -83,14 +77,12 @@ void ExtractNatives::executeTask()
return;
}
auto settings = minecraftInstance->settings();
- bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
- bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for (const auto& source : toExtract) {
- if (!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW)) {
+ if (!unzipNatives(source, outputPath, jniHackEnabled)) {
const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
emitFailed(tr(reason).arg(source, outputPath));
diff --git a/launcher/modplatform/ResourceAPI.h b/launcher/modplatform/ResourceAPI.h
index a92217a0..f6ccb426 100644
--- a/launcher/modplatform/ResourceAPI.h
+++ b/launcher/modplatform/ResourceAPI.h
@@ -54,7 +54,7 @@ class ResourceAPI {
public:
virtual ~ResourceAPI() = default;
- enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
+ 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)
struct SortingMethod {
@@ -164,6 +164,8 @@ class ResourceAPI {
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
+ case NeoForge:
+ return "neoforge";
case Forge:
return "forge";
case Cauldron:
diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp
index 73ed1011..74d7db97 100644
--- a/launcher/modplatform/flame/FlameAPI.cpp
+++ b/launcher/modplatform/flame/FlameAPI.cpp
@@ -204,6 +204,17 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
return netJob;
}
+Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const
+{
+ auto netJob = makeShared<NetJob>(QString("Flame::GetFile"), APPLICATION->network());
+ netJob->addNetAction(
+ Net::ApiDownload::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), response));
+
+ QObject::connect(netJob.get(), &NetJob::failed, [addonId, fileId] { qDebug() << "Flame API file failure" << addonId << fileId; });
+
+ return netJob;
+}
+
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 49bc316f..a1256e17 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -20,10 +20,11 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override;
Task::Ptr matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const;
+ Task::Ptr getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const;
[[nodiscard]] auto getSortingMethods() const -> QList<ResourceAPI::SortingMethod> override;
- static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (Forge | Fabric | Quilt); }
+ static inline auto validateModLoaders(ModLoaderTypes loaders) -> bool { return loaders & (NeoForge | Forge | Fabric | Quilt); }
private:
static int getClassId(ModPlatform::ResourceType type)
@@ -46,7 +47,9 @@ class FlameAPI : public NetworkResourceAPI {
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
+ return 4; // FIXME: implement multiple loaders filter
+ if (loaders & NeoForge)
+ return 6;
return 0;
}
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/FlamePackExportTask.cpp b/launcher/modplatform/flame/FlamePackExportTask.cpp
index f5f3af37..0863f0b2 100644
--- a/launcher/modplatform/flame/FlamePackExportTask.cpp
+++ b/launcher/modplatform/flame/FlamePackExportTask.cpp
@@ -381,6 +381,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 +393,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;
diff --git a/launcher/modplatform/import_ftb/PackHelpers.cpp b/launcher/modplatform/import_ftb/PackHelpers.cpp
index 4a1bbef9..118bdd15 100644
--- a/launcher/modplatform/import_ftb/PackHelpers.cpp
+++ b/launcher/modplatform/import_ftb/PackHelpers.cpp
@@ -59,7 +59,11 @@ 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") {
+ if (name == "neoforge") {
+ modpack.loaderType = ResourceAPI::NeoForge;
+ modpack.version = version;
+ break;
+ } else if (name == "forge") {
modpack.loaderType = ResourceAPI::Forge;
modpack.version = version;
break;
diff --git a/launcher/modplatform/import_ftb/PackInstallTask.cpp b/launcher/modplatform/import_ftb/PackInstallTask.cpp
index b5e424d1..9e4decb0 100644
--- a/launcher/modplatform/import_ftb/PackInstallTask.cpp
+++ b/launcher/modplatform/import_ftb/PackInstallTask.cpp
@@ -68,6 +68,10 @@ void PackInstallTask::copySettings()
auto modloader = m_pack.loaderType;
if (modloader.has_value())
switch (modloader.value()) {
+ case ResourceAPI::NeoForge: {
+ components->setComponentVersion("net.neoforged", m_pack.version, true);
+ break;
+ }
case ResourceAPI::Forge: {
components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break;
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index 58af14cc..0f150e97 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -38,7 +38,7 @@ class ModrinthAPI : public NetworkResourceAPI {
static auto getModLoaderStrings(const ModLoaderTypes types) -> const QStringList
{
QStringList l;
- for (auto loader : { Forge, Fabric, Quilt, LiteLoader }) {
+ for (auto loader : { NeoForge, Forge, Fabric, Quilt, LiteLoader }) {
if (types & loader) {
l << getModLoaderString(loader);
}
@@ -141,7 +141,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(ModLoaderTypes loaders) -> bool
+ {
+ return loaders & (NeoForge | Forge | Fabric | Quilt | 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..bff8fa2f 100644
--- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
+++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp
@@ -111,8 +111,8 @@ 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 = { ResourceAPI::ModLoaderType::NeoForge, ResourceAPI::ModLoaderType::Forge,
+ ResourceAPI::ModLoaderType::Fabric, ResourceAPI::ModLoaderType::Quilt };
for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) {
loader_filter = api.getModLoaderString(flag);
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..ad8fefac 100644
--- a/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
+++ b/launcher/modplatform/modrinth/ModrinthPackExportTask.cpp
@@ -245,6 +245,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 +257,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;
}
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/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 36a6e379..73b8dbe5 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -85,7 +85,7 @@
#include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h>
#include <minecraft/auth/AccountList.h>
-#include <net/Download.h>
+#include <net/ApiDownload.h>
#include <net/NetJob.h>
#include <news/NewsChecker.h>
#include <tools/BaseProfiler.h>
@@ -118,11 +118,15 @@
#include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
+#include "modplatform/flame/FlameAPI.h"
+
#include "KonamiCode.h"
#include "InstanceCopyTask.h"
#include "InstanceImportTask.h"
+#include "Json.h"
+
#include "MMCTime.h"
namespace {
@@ -872,7 +876,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
}
}
-void MainWindow::addInstance(QString url)
+void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
{
QString groupName;
do {
@@ -892,7 +896,7 @@ void MainWindow::addInstance(QString url)
groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString();
}
- NewInstanceDialog newInstDlg(groupName, url, this);
+ NewInstanceDialog newInstDlg(groupName, url, extra_info, this);
if (!newInstDlg.exec())
return;
@@ -919,18 +923,101 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty())
url.setScheme("file");
- if (!url.isLocalFile()) { // probably instance/modpack
- addInstance(url.toString());
- break;
+ QMap<QString, QString> extra_info;
+ QUrl local_url;
+ if (!url.isLocalFile()) { // download the remote resource and identify
+ QUrl dl_url;
+ if (url.scheme() == "curseforge") {
+ // need to find the download link for the modpack / resource
+ // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
+ QUrlQuery query(url);
+
+ auto addonId = query.allQueryItemValues("addonId")[0];
+ auto fileId = query.allQueryItemValues("fileId")[0];
+
+ extra_info.insert("pack_id", addonId);
+ extra_info.insert("pack_version_id", fileId);
+
+ auto array = std::make_shared<QByteArray>();
+
+ 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] {
+ 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");
+
+ // Have to use ensureString then use QUrl to get proper url encoding
+ dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ if (!dl_url.isValid()) {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("The modpack, mod, or resource %1 is blocked for third-parties! Please download it manually.").arg(fileName),
+ QMessageBox::Critical)
+ ->show();
+ return;
+ }
+
+ QFileInfo dl_file(dl_url.fileName());
+ resource_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
+ });
+
+ { // drop stack
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(job.get());
+ }
+
+ } else {
+ dl_url = url;
+ }
+
+ if (!dl_url.isValid()) {
+ continue; // no valid url to download this resource
+ }
+
+ const QString path = dl_url.host() + '/' + dl_url.path();
+ auto entry = APPLICATION->metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ auto dl_job = unique_qobject_ptr<NetJob>(new NetJob(tr("Modpack download"), APPLICATION->network()));
+ dl_job->addNetAction(Net::ApiDownload::makeCached(dl_url, entry));
+ auto archivePath = entry->getFullPath();
+
+ bool dl_success = false;
+ connect(dl_job.get(), &Task::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(dl_job.get(), &Task::succeeded, this, [&dl_success] { dl_success = true; });
+
+ { // drop stack
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(dl_job.get());
+ }
+
+ if (!dl_success) {
+ continue; // no local file to identify
+ }
+ local_url = QUrl::fromLocalFile(archivePath);
+
+ } else {
+ local_url = url;
}
- auto localFileName = QDir::toNativeSeparators(url.toLocalFile());
+ auto localFileName = QDir::toNativeSeparators(local_url.toLocalFile());
QFileInfo localFileInfo(localFileName);
auto type = ResourceUtils::identify(localFileInfo);
if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack
- addInstance(localFileName);
+ addInstance(localFileName, extra_info);
continue;
}
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index aa5b6b90..b6f45da2 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -216,7 +216,7 @@ class MainWindow : public QMainWindow {
private:
void retranslateUi();
- void addInstance(QString url = QString());
+ void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {});
void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon);
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/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp
index 1daaa3ce..9613c6b0 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.cpp
+++ b/launcher/ui/dialogs/NewInstanceDialog.cpp
@@ -62,8 +62,10 @@
#include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h"
#include "ui/widgets/PageContainer.h"
-
-NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent)
+NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
+ const QString& url,
+ const QMap<QString, QString>& extra_info,
+ QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
{
ui->setupUi(this);
@@ -125,6 +127,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString&
QUrl actualUrl(url);
m_container->selectPage("import");
importPage->setUrl(url);
+ importPage->setExtraInfo(extra_info);
}
updateDialogState();
diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h
index b348649f..92357956 100644
--- a/launcher/ui/dialogs/NewInstanceDialog.h
+++ b/launcher/ui/dialogs/NewInstanceDialog.h
@@ -53,7 +53,10 @@ class NewInstanceDialog : public QDialog, public BasePageProvider {
Q_OBJECT
public:
- explicit NewInstanceDialog(const QString& initialGroup, const QString& url = QString(), QWidget* parent = 0);
+ explicit NewInstanceDialog(const QString& initialGroup,
+ const QString& url = QString(),
+ const QMap<QString, QString>& extra_info = {},
+ QWidget* parent = 0);
~NewInstanceDialog();
void updateDialogState();
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/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp
index 866a4121..1c774721 100644
--- a/launcher/ui/pages/global/MinecraftPage.cpp
+++ b/launcher/ui/pages/global/MinecraftPage.cpp
@@ -35,6 +35,7 @@
*/
#include "MinecraftPage.h"
+#include "BuildConfig.h"
#include "ui_MinecraftPage.h"
#include <QDir>
@@ -44,9 +45,15 @@
#include "Application.h"
#include "settings/SettingsObject.h"
+#ifdef Q_OS_LINUX
+#include "MangoHud.h"
+#endif
+
MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage)
{
ui->setupUi(this);
+ connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged);
+ connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged);
loadSettings();
updateCheckboxStuff();
}
@@ -74,6 +81,16 @@ void MinecraftPage::on_maximizedCheckBox_clicked(bool checked)
updateCheckboxStuff();
}
+void MinecraftPage::onUseNativeGLFWChanged(bool checked)
+{
+ ui->lineEditGLFWPath->setEnabled(checked);
+}
+
+void MinecraftPage::onUseNativeOpenALChanged(bool checked)
+{
+ ui->lineEditOpenALPath->setEnabled(checked);
+}
+
void MinecraftPage::applySettings()
{
auto s = APPLICATION->settings();
@@ -84,8 +101,10 @@ void MinecraftPage::applySettings()
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
// Native library workarounds
- s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+ s->set("CustomGLFWPath", ui->lineEditGLFWPath->text());
+ s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
+ s->set("CustomOpenALPath", ui->lineEditOpenALPath->text());
// Peformance related options
s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
@@ -114,8 +133,20 @@ void MinecraftPage::loadSettings()
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
- ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool());
+ ui->lineEditGLFWPath->setText(s->get("CustomGLFWPath").toString());
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME));
+#ifdef Q_OS_LINUX
+ if (!APPLICATION->m_detectedGLFWPath.isEmpty())
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedGLFWPath));
+#endif
+ ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
+ ui->lineEditOpenALPath->setText(s->get("CustomOpenALPath").toString());
+ ui->lineEditOpenALPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
+#ifdef Q_OS_LINUX
+ if (!APPLICATION->m_detectedOpenALPath.isEmpty())
+ ui->lineEditOpenALPath->setPlaceholderText(tr("Auto detected path: %1").arg(APPLICATION->m_detectedOpenALPath));
+#endif
ui->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
diff --git a/launcher/ui/pages/global/MinecraftPage.h b/launcher/ui/pages/global/MinecraftPage.h
index 28c31b5d..5facfbb3 100644
--- a/launcher/ui/pages/global/MinecraftPage.h
+++ b/launcher/ui/pages/global/MinecraftPage.h
@@ -70,6 +70,9 @@ class MinecraftPage : public QWidget, public BasePage {
private slots:
void on_maximizedCheckBox_clicked(bool checked);
+ void onUseNativeGLFWChanged(bool checked);
+ void onUseNativeOpenALChanged(bool checked);
+
private:
Ui::MinecraftPage* ui;
};
diff --git a/launcher/ui/pages/global/MinecraftPage.ui b/launcher/ui/pages/global/MinecraftPage.ui
index 393b0f35..98e90e4e 100644
--- a/launcher/ui/pages/global/MinecraftPage.ui
+++ b/launcher/ui/pages/global/MinecraftPage.ui
@@ -214,21 +214,55 @@
<property name="title">
<string>Native library workarounds</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_11">
- <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of &amp;GLFW</string>
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelGLFWPath">
+ <property name="text">
+ <string>&amp;GLFW library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditGLFWPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
<widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text">
<string>Use system installation of &amp;OpenAL</string>
</property>
</widget>
</item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelOpenALPath">
+ <property name="text">
+ <string>&amp;OpenAL library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditOpenALPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lineEditGLFWPath">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="lineEditOpenALPath">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
</item>
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
index f7be9168..108f2c27 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp
@@ -48,6 +48,7 @@
#include "ui/widgets/CustomCommands.h"
#include "Application.h"
+#include "BuildConfig.h"
#include "JavaCommon.h"
#include "minecraft/auth/AccountList.h"
@@ -66,6 +67,10 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&InstanceSettingsPage::changeInstanceAccount);
+
+ connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged);
+ connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged);
+
loadSettings();
updateThresholds();
@@ -198,11 +203,15 @@ void InstanceSettingsPage::applySettings()
bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
m_settings->set("OverrideNativeWorkarounds", workarounds);
if (workarounds) {
- m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+ m_settings->set("CustomGLFWPath", ui->lineEditGLFWPath->text());
+ m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
+ m_settings->set("CustomOpenALPath", ui->lineEditOpenALPath->text());
} else {
- m_settings->reset("UseNativeOpenAL");
m_settings->reset("UseNativeGLFW");
+ m_settings->reset("CustomGLFWPath");
+ m_settings->reset("UseNativeOpenAL");
+ m_settings->reset("CustomOpenALPath");
}
// Performance
@@ -312,7 +321,19 @@ void InstanceSettingsPage::loadSettings()
// Workarounds
ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
+ ui->lineEditGLFWPath->setText(m_settings->get("CustomGLFWPath").toString());
+#ifdef Q_OS_LINUX
+ ui->lineEditGLFWPath->setPlaceholderText(APPLICATION->m_detectedGLFWPath);
+#else
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.GLFW_LIBRARY_NAME));
+#endif
ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
+ ui->lineEditOpenALPath->setText(m_settings->get("CustomOpenALPath").toString());
+#ifdef Q_OS_LINUX
+ ui->lineEditOpenALPath->setPlaceholderText(APPLICATION->m_detectedOpenALPath);
+#else
+ ui->lineEditGLFWPath->setPlaceholderText(tr("Path to %1 library file").arg(BuildConfig.OPENAL_LIBRARY_NAME));
+#endif
// Performance
ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool());
@@ -408,6 +429,16 @@ void InstanceSettingsPage::on_javaTestBtn_clicked()
checker->run();
}
+void InstanceSettingsPage::onUseNativeGLFWChanged(bool checked)
+{
+ ui->lineEditGLFWPath->setEnabled(checked);
+}
+
+void InstanceSettingsPage::onUseNativeOpenALChanged(bool checked)
+{
+ ui->lineEditOpenALPath->setEnabled(checked);
+}
+
void InstanceSettingsPage::updateAccountsMenu()
{
ui->instanceAccountSelector->clear();
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.h b/launcher/ui/pages/instance/InstanceSettingsPage.h
index 21ecbaf8..8b78dcb7 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.h
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.h
@@ -71,6 +71,9 @@ class InstanceSettingsPage : public QWidget, public BasePage {
void on_javaBrowseBtn_clicked();
void on_maxMemSpinBox_valueChanged(int i);
+ void onUseNativeGLFWChanged(bool checked);
+ void onUseNativeOpenALChanged(bool checked);
+
void applySettings();
void loadSettings();
diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui
index 380d8c88..fcc2a5d0 100644
--- a/launcher/ui/pages/instance/InstanceSettingsPage.ui
+++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui
@@ -443,18 +443,52 @@
<property name="checked">
<bool>false</bool>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_7">
- <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <property name="text">
+ <string>Use system installation of OpenAL</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelGLFWPath">
+ <property name="text">
+ <string>&amp;GLFW library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditGLFWPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
<widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text">
<string>Use system installation of GLFW</string>
</property>
</widget>
</item>
- <item>
- <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="lineEditGLFWPath">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="labelOpenALPath">
<property name="text">
- <string>Use system installation of OpenAL</string>
+ <string>&amp;OpenAL library path</string>
+ </property>
+ <property name="buddy">
+ <cstring>lineEditOpenALPath</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="lineEditOpenALPath">
+ <property name="enabled">
+ <bool>false</bool>
</property>
</widget>
</item>
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/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp
index ce2777b3..3e3c36b7 100644
--- a/launcher/ui/pages/modplatform/ImportPage.cpp
+++ b/launcher/ui/pages/modplatform/ImportPage.cpp
@@ -35,13 +35,21 @@
*/
#include "ImportPage.h"
+
+#include "ui/dialogs/ProgressDialog.h"
#include "ui_ImportPage.h"
#include <QFileDialog>
#include <QValidator>
+#include <utility>
+#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/NewInstanceDialog.h"
+#include "modplatform/flame/FlameAPI.h"
+
+#include "Json.h"
+
#include "InstanceImportTask.h"
class UrlValidator : public QValidator {
@@ -106,10 +114,61 @@ void ImportPage::updateState()
bool isMRPack = fi.suffix() == "mrpack";
if (fi.exists() && (isZip || isMRPack)) {
- QFileInfo file_info(url.fileName());
- dialog->setSuggestedPack(file_info.completeBaseName(), new InstanceImportTask(url, this));
+ auto extra_info = QMap(m_extra_info);
+ qDebug() << "Pack Extra Info" << extra_info << m_extra_info;
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default");
}
+ } else if (url.scheme() == "curseforge") {
+ // need to find the download link for the modpack
+ // format of url curseforge://install?addonId=IDHERE&fileId=IDHERE
+ QUrlQuery query(url);
+ auto addonId = query.allQueryItemValues("addonId")[0];
+ auto fileId = query.allQueryItemValues("fileId")[0];
+ auto array = std::make_shared<QByteArray>();
+
+ auto api = FlameAPI();
+ auto job = api.getFile(addonId, fileId, array);
+
+ connect(job.get(), &NetJob::failed, this,
+ [this](QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); });
+ connect(job.get(), &NetJob::succeeded, this, [this, array, addonId, fileId] {
+ 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");
+ if (fileName.endsWith(".zip")) {
+ // Have to use ensureString then use QUrl to get proper url encoding
+ auto dl_url = QUrl(Json::ensureString(data, "downloadUrl", "", "downloadUrl"));
+ if (!dl_url.isValid()) {
+ CustomMessageBox::selectable(
+ this, tr("Error"),
+ tr("The modpack %1 is blocked for third-parties! Please download it manually.").arg(fileName),
+ QMessageBox::Critical)
+ ->show();
+ return;
+ }
+
+ QFileInfo dl_file(dl_url.fileName());
+ QString pack_name = Json::ensureString(data, "displayName", dl_file.completeBaseName(), "displayName");
+
+ QMap<QString, QString> extra_info;
+ extra_info.insert("pack_id", addonId);
+ extra_info.insert("pack_version_id", fileId);
+
+ dialog->setSuggestedPack(pack_name, new InstanceImportTask(dl_url, this, std::move(extra_info)));
+ dialog->setSuggestedIcon("default");
+
+ } else {
+ CustomMessageBox::selectable(this, tr("Error"), tr("This url isn't a valid modpack !"), QMessageBox::Critical)->show();
+ }
+ });
+ ProgressDialog dlUrlDialod(this);
+ dlUrlDialod.setSkipButton(true, tr("Abort"));
+ dlUrlDialod.execWithTask(job.get());
+ return;
} else {
if (input.endsWith("?client=y")) {
input.chop(9);
@@ -118,7 +177,8 @@ void ImportPage::updateState()
}
// hook, line and sinker.
QFileInfo fi(url.fileName());
- dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this));
+ auto extra_info = QMap(m_extra_info);
+ dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default");
}
} else {
@@ -132,6 +192,12 @@ void ImportPage::setUrl(const QString& url)
updateState();
}
+void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
+{
+ m_extra_info = extra_info;
+ updateState();
+}
+
void ImportPage::on_modpackBtn_clicked()
{
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();
diff --git a/launcher/ui/pages/modplatform/ImportPage.h b/launcher/ui/pages/modplatform/ImportPage.h
index d846d566..70d7736e 100644
--- a/launcher/ui/pages/modplatform/ImportPage.h
+++ b/launcher/ui/pages/modplatform/ImportPage.h
@@ -62,7 +62,7 @@ class ImportPage : public QWidget, public BasePage {
void setUrl(const QString& url);
void openedImpl() override;
-
+ void setExtraInfo(const QMap<QString, QString>& extra_info);
private slots:
void on_modpackBtn_clicked();
void updateState();
@@ -73,4 +73,5 @@ class ImportPage : public QWidget, public BasePage {
private:
Ui::ImportPage* ui = nullptr;
NewInstanceDialog* dialog = nullptr;
+ QMap<QString, QString> m_extra_info = {};
};
diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui
index 3583cf90..9a9736b8 100644
--- a/launcher/ui/pages/modplatform/ImportPage.ui
+++ b/launcher/ui/pages/modplatform/ImportPage.ui
@@ -40,7 +40,7 @@
<item>
<widget class="QLabel" name="label_5">
<property name="text">
- <string>- CurseForge modpacks (ZIP)</string>
+ <string>- CurseForge modpacks (ZIP / curseforge:// URL)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/program_info/org.prismlauncher.PrismLauncher.desktop.in b/program_info/org.prismlauncher.PrismLauncher.desktop.in
index 20fabe9d..816c0059 100644
--- a/program_info/org.prismlauncher.PrismLauncher.desktop.in
+++ b/program_info/org.prismlauncher.PrismLauncher.desktop.in
@@ -10,4 +10,4 @@ Icon=org.prismlauncher.PrismLauncher
Categories=Game;ActionGame;AdventureGame;Simulation;
Keywords=game;minecraft;mc;
StartupWMClass=PrismLauncher
-MimeType=application/zip;application/x-modrinth-modpack+zip
+MimeType=application/zip;application/x-modrinth-modpack+zip;x-scheme-handler/curseforge;
diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in
index d3b5c256..cb7b0935 100644
--- a/program_info/win_install.nsi.in
+++ b/program_info/win_install.nsi.in
@@ -363,6 +363,10 @@ Section "@Launcher_DisplayName@"
; Write the installation path into the registry
WriteRegStr HKCU Software\@Launcher_CommonName@ "InstallDir" "$INSTDIR"
+ ; Write the URL Handler into registry for curseforge
+ WriteRegStr HKCU Software\Classes\curseforge "URL Protocol" ""
+ WriteRegStr HKCU Software\Classes\curseforge\shell\open\command "" '"$INSTDIR\@Launcher_APP_BINARY_NAME@.exe" "%1"'
+
; Write the uninstall keys for Windows
${GetParameters} $R0
${GetOptions} $R0 "/NoUninstaller" $R1