Merge branch 'develop' into feat/launcher-updater

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
TheKodeToad 2023-08-30 11:47:33 +01:00 committed by GitHub
commit befa3baa6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
115 changed files with 1296 additions and 793 deletions

1
.envrc
View File

@ -1 +1,2 @@
use flake use flake
watch_file nix/*.nix

View File

@ -24,7 +24,7 @@ jobs:
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
- name: Create backport PRs - name: Create backport PRs
uses: korthout/backport-action@v1.3.1 uses: korthout/backport-action@v1.4.0
with: with:
# Config README: https://github.com/korthout/backport-action#backport-action # Config README: https://github.com/korthout/backport-action#backport-action
pull_description: |- pull_description: |-

View File

@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: cachix/install-nix-action@v22 - uses: cachix/install-nix-action@v22
- uses: DeterminateSystems/update-flake-lock@v19 - uses: DeterminateSystems/update-flake-lock@v20
with: with:
commit-msg: "chore(nix): update lockfile" commit-msg: "chore(nix): update lockfile"
pr-title: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile"

View File

@ -219,6 +219,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_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") 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 # API Keys
# NOTE: These API keys are here for convenience. If you rebrand this software or intend to break the terms of service # 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. # of these platforms, please change these API keys beforehand.

View File

@ -118,6 +118,9 @@ Config::Config()
FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@"; FLAME_API_KEY = "@Launcher_CURSEFORGE_API_KEY@";
META_URL = "@Launcher_META_URL@"; 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@"; BUG_TRACKER_URL = "@Launcher_BUG_TRACKER_URL@";
TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@"; TRANSLATIONS_URL = "@Launcher_TRANSLATIONS_URL@";
MATRIX_URL = "@Launcher_MATRIX_URL@"; MATRIX_URL = "@Launcher_MATRIX_URL@";

View File

@ -152,6 +152,9 @@ class Config {
*/ */
QString META_URL; QString META_URL;
QString GLFW_LIBRARY_NAME;
QString OPENAL_LIBRARY_NAME;
QString BUG_TRACKER_URL; QString BUG_TRACKER_URL;
QString TRANSLATIONS_URL; QString TRANSLATIONS_URL;
QString MATRIX_URL; QString MATRIX_URL;

View File

@ -67,5 +67,16 @@
<string>Alternate</string> <string>Alternate</string>
</dict> </dict>
</array> </array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Curseforge</string>
<key>CFBundleURLSchemes</key>
<array>
<string>curseforge</string>
</array>
</dict>
</array>
</dict> </dict>
</plist> </plist>

12
flake.lock generated
View File

@ -91,11 +91,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1691853136, "lastModified": 1693145325,
"narHash": "sha256-wTzDsRV4HN8A2Sl0SVQY0q8ILs90CD43Ha//7gNZE+E=", "narHash": "sha256-Gat9xskErH1zOcLjYMhSDBo0JTBZKfGS0xJlIRnj6Rc=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "f0451844bbdf545f696f029d1448de4906c7f753", "rev": "cddebdb60de376c1bdb7a4e6ee3d98355453fe56",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -138,11 +138,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1691747570, "lastModified": 1692274144,
"narHash": "sha256-J3fnIwJtHVQ0tK2JMBv4oAmII+1mCdXdpeCxtIsrL2A=", "narHash": "sha256-BxTQuRUANQ81u8DJznQyPmRsg63t4Yc+0kcyq6OLz8s=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "c5ac3aa3324bd8aebe8622a3fc92eeb3975d317a", "rev": "7e3517c03d46159fdbf8c0e5c97f82d5d4b0c8fa",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -9,7 +9,6 @@
* Copyright (C) 2022 Tayou <git@tayou.org> * Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -227,8 +226,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" }, { { "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" }, { { "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" }, { "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" } }); { "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.addHelpOption();
parser.addVersionOption(); parser.addVersionOption();
@ -241,13 +243,13 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToShowWindowOf = parser.value("show"); m_instanceIdToShowWindowOf = parser.value("show");
for (auto zip_path : parser.values("import")) { for (auto url : parser.values("import")) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath())); m_urlsToImport.append(normalizeImportUrl(url));
} }
// treat unspecified positional arguments as import urls // treat unspecified positional arguments as import urls
for (auto zip_path : parser.positionalArguments()) { for (auto url : parser.positionalArguments()) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath())); m_urlsToImport.append(normalizeImportUrl(url));
} }
// error if --launch is missing with --server or --profile // error if --launch is missing with --server or --profile
@ -347,11 +349,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
activate.command = "activate"; activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout); m_peerInstance->sendMessage(activate.serialize(), timeout);
if (!m_zipsToImport.isEmpty()) { if (!m_urlsToImport.isEmpty()) {
for (auto zip_url : m_zipsToImport) { for (auto url : m_urlsToImport) {
ApplicationMessage import; ApplicationMessage import;
import.command = "import"; import.command = "import";
import.args.insert("path", zip_url.toString()); import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout); m_peerInstance->sendMessage(import.serialize(), timeout);
} }
} }
@ -616,12 +618,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false); m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false); m_settings->registerSetting("IgnoreJavaWizard", false);
// Mod loader settings
m_settings->registerSetting("DisableQuiltBeacon", false);
// Native library workarounds // Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false); m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("CustomOpenALPath", "");
m_settings->registerSetting("UseNativeGLFW", false); m_settings->registerSetting("UseNativeGLFW", false);
m_settings->registerSetting("CustomGLFWPath", "");
// Peformance related options // Peformance related options
m_settings->registerSetting("EnableFeralGamemode", false); m_settings->registerSetting("EnableFeralGamemode", false);
@ -632,6 +633,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true); m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true); m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true); m_settings->registerSetting("RecordGameTime", true);
m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods // Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false); m_settings->registerSetting("ModMetadataDisabled", false);
@ -872,6 +874,8 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
updateCapabilities(); updateCapabilities();
detectLibraries();
// check update locks // check update locks
{ {
auto update_log_path = FS::PathCombine(m_dataPath, "logs", "prism_launcher_update.log"); auto update_log_path = FS::PathCombine(m_dataPath, "logs", "prism_launcher_update.log");
@ -1112,7 +1116,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse; qDebug() << " Launching with account" << m_profileToUse;
} }
launch(inst, true, false, nullptr, serverToJoin, accountToUse); launch(inst, true, false, serverToJoin, accountToUse);
return; return;
} }
} }
@ -1143,9 +1147,9 @@ void Application::performMainStartupAction()
qDebug() << "<> Updater started."; qDebug() << "<> Updater started.";
} }
if (!m_zipsToImport.isEmpty()) { if (!m_urlsToImport.isEmpty()) {
qDebug() << "<> Importing from zip:" << m_zipsToImport; qDebug() << "<> Importing from url:" << m_urlsToImport;
m_mainWindow->processURLs(m_zipsToImport); m_mainWindow->processURLs(m_urlsToImport);
} }
} }
@ -1187,12 +1191,12 @@ void Application::messageReceived(const QByteArray& message)
if (command == "activate") { if (command == "activate") {
showMainWindow(); showMainWindow();
} else if (command == "import") { } else if (command == "import") {
QString path = received.args["path"]; QString url = received.args["url"];
if (path.isEmpty()) { if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL."; qWarning() << "Received" << command << "message without a zip path/URL.";
return; return;
} }
m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) }); m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") { } else if (command == "launch") {
QString id = received.args["id"]; QString id = received.args["id"];
QString server = received.args["server"]; QString server = received.args["server"];
@ -1225,7 +1229,7 @@ void Application::messageReceived(const QByteArray& message)
} }
} }
launch(instance, true, false, nullptr, serverObject, accountObject); launch(instance, true, false, serverObject, accountObject);
} else { } else {
qWarning() << "Received invalid message" << message; qWarning() << "Received invalid message" << message;
} }
@ -1266,7 +1270,6 @@ bool Application::openJsonEditor(const QString& filename)
bool Application::launch(InstancePtr instance, bool Application::launch(InstancePtr instance,
bool online, bool online,
bool demo, bool demo,
BaseProfilerFactory* profiler,
MinecraftServerTargetPtr serverToJoin, MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse) MinecraftAccountPtr accountToUse)
{ {
@ -1274,7 +1277,7 @@ bool Application::launch(InstancePtr instance,
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed."; qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) { } else if (instance->canLaunch()) {
auto& extras = m_instanceExtras[instance->id()]; auto& extras = m_instanceExtras[instance->id()];
auto& window = extras.window; auto window = extras.window;
if (window) { if (window) {
if (!window->saveAll()) { if (!window->saveAll()) {
return false; return false;
@ -1285,7 +1288,7 @@ bool Application::launch(InstancePtr instance,
controller->setInstance(instance); controller->setInstance(instance);
controller->setOnline(online); controller->setOnline(online);
controller->setDemo(demo); controller->setDemo(demo);
controller->setProfiler(profiler); controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin); controller->setServerToJoin(serverToJoin);
controller->setAccountToUse(accountToUse); controller->setAccountToUse(accountToUse);
if (window) { if (window) {
@ -1577,6 +1580,15 @@ void Application::updateCapabilities()
#endif #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) QString Application::getJarPath(QString jarFile)
{ {
QStringList potentialPaths = { QStringList potentialPaths = {
@ -1755,3 +1767,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available."; 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);
}
}

View File

@ -142,6 +142,8 @@ class Application : public QApplication {
void updateCapabilities(); void updateCapabilities();
void detectLibraries();
/*! /*!
* Finds and returns the full path to a jar file. * Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found. * Returns a null-string if it could not be found.
@ -183,6 +185,8 @@ class Application : public QApplication {
bool updaterEnabled(); bool updaterEnabled();
QString updaterBinaryName(); QString updaterBinaryName();
QUrl normalizeImportUrl(QString const& url);
signals: signals:
void updateAllowedChanged(bool status); void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen(); void globalSettingsAboutToOpen();
@ -197,7 +201,6 @@ class Application : public QApplication {
bool launch(InstancePtr instance, bool launch(InstancePtr instance,
bool online = true, bool online = true,
bool demo = false, bool demo = false,
BaseProfilerFactory* profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr, MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr); MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance); bool kill(InstancePtr instance);
@ -282,11 +285,13 @@ class Application : public QApplication {
SetupWizard* m_setupWizard = nullptr; SetupWizard* m_setupWizard = nullptr;
public: public:
QString m_detectedGLFWPath;
QString m_detectedOpenALPath;
QString m_instanceIdToLaunch; QString m_instanceIdToLaunch;
QString m_serverToJoin; QString m_serverToJoin;
QString m_profileToUse; QString m_profileToUse;
bool m_liveCheck = false; bool m_liveCheck = false;
QList<QUrl> m_zipsToImport; QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf; QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile; std::unique_ptr<QFile> logFile;
}; };

View File

@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * 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 * 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 * 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("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", ""); m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", ""); m_settings->registerSetting("ManagedPackVersionName", "");
m_settings->registerSetting("Profiler", "");
} }
QString BaseInstance::getPreLaunchCommand() QString BaseInstance::getPreLaunchCommand()

View File

@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -38,6 +39,7 @@
#include <cassert> #include <cassert>
#include <QDateTime> #include <QDateTime>
#include <QMenu>
#include <QObject> #include <QObject>
#include <QProcess> #include <QProcess>
#include <QSet> #include <QSet>
@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual bool canEdit() const = 0; virtual bool canEdit() const = 0;
virtual bool canExport() const = 0; virtual bool canExport() const = 0;
virtual void populateLaunchMenu(QMenu* menu) = 0;
bool reloadSettings(); bool reloadSettings();
/** /**
@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void runningStatusChanged(bool running); void runningStatusChanged(bool running);
void profilerChanged();
void statusChanged(Status from, Status to); void statusChanged(Status from, Status to);
protected slots: protected slots:

View File

@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{ {
auto fileName = fileInfo.fileName(); return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
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);
} }
bool FileIgnoreProxy::filterFile(const QString& fileName) const bool FileIgnoreProxy::filterFile(const QString& fileName) const

View File

@ -50,6 +50,7 @@
#include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/technic/TechnicPackProcessor.h"
#include "settings/INISettingsObject.h" #include "settings/INISettingsObject.h"
#include "tasks/Task.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
@ -90,25 +91,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true; m_downloadRequired = true;
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path()); downloadFromUrl();
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();
} }
} }
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() void InstanceImportTask::downloadSucceeded()
{ {
processZipPack(); processZipPack();

View File

@ -101,4 +101,5 @@ class InstanceImportTask : public InstanceTask {
// FIXME: nuke // FIXME: nuke
QWidget* m_parent; QWidget* m_parent;
void downloadFromUrl();
}; };

View File

@ -2,6 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -361,22 +362,21 @@ void LaunchController::readyForLaunch()
QString error; QString error;
if (!m_profiler->check(&error)) { if (!m_profiler->check(&error)) {
m_launcher->abort(); m_launcher->abort();
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
emitFailed("Profiler startup failed!"); emitFailed("Profiler startup failed!");
QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Profiler check for %1 failed: %2").arg(m_profiler->name(), error));
return; return;
} }
BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this); BaseProfiler* profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
connect(profilerInstance, &BaseProfiler::readyToLaunch, [this](const QString& message) { 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 " msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the " "button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1") "profiler server is running now.\n\n%1")
.arg(message)); .arg(message));
msg.setWindowTitle(tr("Waiting.")); msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information); msg.setIcon(QMessageBox::Information);
msg.addButton(tr("Launch"), QMessageBox::AcceptRole); msg.addButton(tr("&Launch"), QMessageBox::AcceptRole);
msg.setModal(true);
msg.exec(); msg.exec();
m_launcher->proceed(); m_launcher->proceed();
}); });

View File

@ -16,19 +16,20 @@
*/ */
#include <MMCTime.h> #include <MMCTime.h>
#include <qobject.h>
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QTextStream> #include <QTextStream>
QString Time::prettifyDuration(int64_t duration) QString Time::prettifyDuration(int64_t duration, bool noDays)
{ {
int seconds = (int)(duration % 60); int seconds = (int)(duration % 60);
duration /= 60; duration /= 60;
int minutes = (int)(duration % 60); int minutes = (int)(duration % 60);
duration /= 60; duration /= 60;
int hours = (int)(duration % 24); int hours = (int)(noDays ? duration : (duration % 24));
int days = (int)(duration / 24); int days = (int)(noDays ? 0 : (duration / 24));
if ((hours == 0) && (days == 0)) { if ((hours == 0) && (days == 0)) {
return QObject::tr("%1min %2s").arg(minutes).arg(seconds); return QObject::tr("%1min %2s").arg(minutes).arg(seconds);
} }

View File

@ -20,7 +20,7 @@
namespace Time { 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`. * @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <QDebug>
#include <QDir> #include <QDir>
#include <QString> #include <QString>
#include <QStringList> #include <QStringList>
@ -26,6 +27,15 @@
#include "Json.h" #include "Json.h"
#include "MangoHud.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 { namespace MangoHud {
QString getLibraryString() QString getLibraryString()
@ -106,4 +116,37 @@ QString getLibraryString()
return QString(); 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 } // namespace MangoHud
#ifdef UNDEF_GNU_SOURCE
#undef _GNU_SOURCE
#undef UNDEF_GNU_SOURCE
#endif

View File

@ -24,4 +24,6 @@
namespace MangoHud { namespace MangoHud {
QString getLibraryString(); QString getLibraryString();
}
QString findLibrary(QString libName);
} // namespace MangoHud

View File

@ -2,6 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * 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 canExport() const override { return false; }
bool canEdit() const override { return false; } bool canEdit() const override { return false; }
bool canLaunch() const override { return false; } bool canLaunch() const override { return false; }
void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{ {
QStringList out; QStringList out;

View File

@ -104,14 +104,8 @@ class Version {
QString m_fullString; QString m_fullString;
[[nodiscard]] inline bool isAppendix() const [[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
{ [[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
return m_stringPart.startsWith('+');
}
[[nodiscard]] inline bool isPreRelease() const
{
return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
}
inline bool operator==(const Section& other) const inline bool operator==(const Section& other) const
{ {
@ -157,14 +151,8 @@ class Version {
return m_fullString < other.m_fullString; return m_fullString < other.m_fullString;
} }
inline bool operator!=(const Section& other) const inline bool operator!=(const Section& other) const { return !(*this == other); }
{ inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
return !(*this == other);
}
inline bool operator>(const Section& other) const
{
return !(*this < other || *this == other);
}
}; };
private: private:

View File

@ -30,7 +30,7 @@ class LogModel : public QAbstractListModel {
enum Roles { LevelRole = Qt::UserRole }; enum Roles { LevelRole = Qt::UserRole };
private /* types */: private /* types */:
struct entry { struct entry {
MessageLevel::Enum level; MessageLevel::Enum level;
QString line; QString line;

View File

@ -2,14 +2,14 @@
#include "Component.h" #include "Component.h"
#include "ComponentUpdateTask_p.h" #include "ComponentUpdateTask_p.h"
#include "OneSixVersionFormat.h"
#include "PackProfile.h" #include "PackProfile.h"
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "Version.h" #include "Version.h"
#include "cassert" #include "cassert"
#include "meta/Index.h" #include "meta/Index.h"
#include "meta/Version.h" #include "meta/Version.h"
#include "meta/VersionList.h" #include "minecraft/OneSixVersionFormat.h"
#include "minecraft/ProfileUtils.h"
#include "net/Mode.h" #include "net/Mode.h"
#include "Application.h" #include "Application.h"

View File

@ -3,8 +3,7 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me> * Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (c) 2023 seth <getchoo at tuta dot io>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -88,6 +87,10 @@
#include "minecraft/gameoptions/GameOptions.h" #include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h" #include "minecraft/update/FoldersTask.h"
#include "tools/BaseProfiler.h"
#include <QActionGroup>
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
#include "MangoHud.h" #include "MangoHud.h"
#endif #endif
@ -166,7 +169,9 @@ void MinecraftInstance::loadSpecificSettings()
// Native library workarounds // Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); 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("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
// Peformance related options // Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
@ -179,10 +184,6 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), 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"); m_settings->set("InstanceType", "OneSix");
} }
@ -194,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false); m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", ""); 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!"; qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true); setSpecificSettingsLoaded(true);
@ -229,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const
return profile->getTraits(); 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 QString MinecraftInstance::gameRoot() const
{ {
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft")); QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
@ -385,10 +436,31 @@ QStringList MinecraftInstance::extraArguments()
} }
{ {
const auto loaders = version->getModLoaders(); QString openALPath;
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool()) QString glfwPath;
list.append("-Dloader.disable_beacon=true");
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; return list;
} }
@ -868,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) { if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) { if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch()); QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append(tr(", last played on %1 for %2") description.append(
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat)) tr(", last played on %1 for %2")
.arg(Time::prettifyDuration(lastTimePlayed()))); .arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
} }
if (totalTimePlayed() > 0) { 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()) { if (hasCrashed()) {

View File

@ -2,7 +2,7 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * 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; } bool canExport() const override { return true; }
void populateLaunchMenu(QMenu* menu) override;
////// Directories and files ////// ////// Directories and files //////
QString jarModsDir() const; QString jarModsDir() const;
QString resourcePacksDir() const; QString resourcePacksDir() const;

View File

@ -58,14 +58,14 @@
#include "ComponentUpdateTask.h" #include "ComponentUpdateTask.h"
#include "PackProfile.h" #include "PackProfile.h"
#include "PackProfile_p.h" #include "PackProfile_p.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
#include "Application.h" static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
#include "modplatform/ResourceAPI.h" { "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge }, { "org.quiltmc.quilt-loader", ModPlatform::Quilt },
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric }, { "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel() 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; bool has_any_loader = false;
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping); QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) { while (i.hasNext()) {
i.next(); i.next();
@ -1008,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
return {}; return {};
return result; 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;
}

View File

@ -44,14 +44,11 @@
#include <QList> #include <QList>
#include <QString> #include <QString>
#include <memory> #include <memory>
#include <optional>
#include "BaseVersion.h"
#include "Component.h" #include "Component.h"
#include "LaunchProfile.h" #include "LaunchProfile.h"
#include "Library.h" #include "modplatform/ModIndex.h"
#include "MojangDownloadInfo.h"
#include "ProfileUtils.h"
#include "modplatform/ResourceAPI.h"
#include "net/Mode.h" #include "net/Mode.h"
class MinecraftInstance; class MinecraftInstance;
@ -146,7 +143,9 @@ class PackProfile : public QAbstractListModel {
// todo(merged): is this the best approach // todo(merged): is this the best approach
void appendComponent(ComponentPtr component); 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: private:
void scheduleSave(); void scheduleSave();

View File

@ -415,7 +415,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role) 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; return false;
} }
@ -423,7 +423,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
if (value == Qt::Checked) { if (value == Qt::Checked) {
MinecraftAccountPtr account = at(idx.row()); MinecraftAccountPtr account = at(idx.row());
setDefaultAccount(account); setDefaultAccount(account);
} } else if (m_defaultAccount == at(idx.row()))
setDefaultAccount(nullptr);
} }
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1)); emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));

View File

@ -39,7 +39,7 @@ static QString replaceSuffix(QString target, const QString& suffix, const QStrin
return target + replacement; 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); QuaZip zip(source);
if (!zip.open(QuaZip::mdUnzip)) { if (!zip.open(QuaZip::mdUnzip)) {
@ -52,12 +52,6 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
do { do {
QString name = zip.getCurrentFileName(); QString name = zip.getCurrentFileName();
auto lowercase = name.toLower(); auto lowercase = name.toLower();
if (nativeGLFW && name.contains("glfw")) {
continue;
}
if (nativeOpenAL && name.contains("openal")) {
continue;
}
if (applyJnilibHack) { if (applyJnilibHack) {
name = replaceSuffix(name, ".jnilib", ".dylib"); name = replaceSuffix(name, ".jnilib", ".dylib");
} }
@ -83,14 +77,12 @@ void ExtractNatives::executeTask()
return; return;
} }
auto settings = minecraftInstance->settings(); auto settings = minecraftInstance->settings();
bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
auto outputPath = minecraftInstance->getNativePath(); auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion(); auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8; bool jniHackEnabled = javaVersion.major() >= 8;
for (const auto& source : toExtract) { 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'"); const char* reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal); emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
emitFailed(tr(reason).arg(source, outputPath)); emitFailed(tr(reason).arg(source, outputPath));

View File

@ -51,8 +51,13 @@
#include "Application.h" #include "Application.h"
#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.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) 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) : ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
@ -309,3 +314,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); 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);
}

View File

@ -48,6 +48,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
class LegacyInstance; class LegacyInstance;
class BaseInstance; class BaseInstance;
@ -75,6 +76,7 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override; [[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } 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); bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods /// Deletes all the selected mods

View File

@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion(); 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, GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep, ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName) 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 overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) { 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); 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); pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) { 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 overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; }); [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; return;
} }
} }
removePack(dep.addonId);
qWarning() << "Error while reading mod version empty "; qWarning() << "Error while reading mod version empty ";
qDebug() << doc; qDebug() << doc;
return; return;

View File

@ -80,5 +80,5 @@ class GetModDependenciesTask : public SequentialTask {
Provider m_modrinth_provider; Provider m_modrinth_provider;
Version m_version; Version m_version;
ResourceAPI::ModLoaderTypes m_loaderType; ModPlatform::ModLoaderTypes m_loaderType;
}; };

View File

@ -14,7 +14,7 @@ class CheckUpdateTask : public Task {
public: public:
CheckUpdateTask(QList<Mod*>& mods, CheckUpdateTask(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){}; : Task(nullptr), m_mods(mods), m_game_versions(mcVersions), m_loaders(loaders), m_mods_folder(mods_folder){};
@ -53,7 +53,7 @@ class CheckUpdateTask : public Task {
protected: protected:
QList<Mod*>& m_mods; QList<Mod*>& m_mods;
std::list<Version>& m_game_versions; 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::shared_ptr<ModFolderModel> m_mods_folder;
std::vector<UpdatableMod> m_updatable; std::vector<UpdatableMod> m_updatable;

View File

@ -83,4 +83,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
projectID.toString(); 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 } // namespace ModPlatform

View File

@ -30,6 +30,9 @@ class QIODevice;
namespace ModPlatform { 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 ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK }; enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@ -70,7 +73,7 @@ struct IndexedVersion {
QString downloadUrl; QString downloadUrl;
QString date; QString date;
QString fileName; QString fileName;
QStringList loaders = {}; ModLoaderTypes loaders = {};
QString hash_type; QString hash_type;
QString hash; QString hash;
bool is_preferred = true; bool is_preferred = true;
@ -128,7 +131,6 @@ struct IndexedPack {
return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; }); return std::any_of(versions.constBegin(), versions.constEnd(), [](auto const& v) { return v.is_currently_selected; });
} }
}; };
QString getMetaURL(ResourceProvider provider, QVariant projectID);
struct OverrideDep { struct OverrideDep {
QString quilt; QString quilt;
@ -148,6 +150,14 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
QString getMetaURL(ResourceProvider provider, QVariant projectID); 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 } // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack) Q_DECLARE_METATYPE(ModPlatform::IndexedPack)

View File

@ -54,9 +54,6 @@ class ResourceAPI {
public: public:
virtual ~ResourceAPI() = default; 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 { struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods. // The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request. // Used by Flame in the API request.
@ -74,7 +71,7 @@ class ResourceAPI {
std::optional<QString> search; std::optional<QString> search;
std::optional<SortingMethod> sorting; std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders; std::optional<ModPlatform::ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions; std::optional<std::list<Version> > versions;
}; };
struct SearchCallbacks { struct SearchCallbacks {
@ -87,7 +84,7 @@ class ResourceAPI {
ModPlatform::IndexedPack pack; ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions; std::optional<std::list<Version> > mcVersions;
std::optional<ModLoaderTypes> loaders; std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default; VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other) void operator=(VersionSearchArgs other)
@ -114,7 +111,7 @@ class ResourceAPI {
struct DependencySearchArgs { struct DependencySearchArgs {
ModPlatform::Dependency dependency; ModPlatform::Dependency dependency;
Version mcVersion; Version mcVersion;
ModLoaderTypes loader; ModPlatform::ModLoaderTypes loader;
}; };
struct DependencySearchCallbacks { struct DependencySearchCallbacks {
@ -161,25 +158,6 @@ class ResourceAPI {
return nullptr; 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: protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; } [[nodiscard]] inline QString debugName() const { return "External resource API"; }

View File

@ -1005,15 +1005,30 @@ static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString&
if (!vlist) if (!vlist)
return {}; return {};
if (!vlist->isLoaded()) if (!vlist->isLoaded()) {
vlist->load(Net::Mode::Online); QEventLoop loadVersionLoop;
auto task = vlist->getLoadTask();
QObject::connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
if (!task->isRunning())
task->start();
loadVersionLoop.exec();
}
auto ver = vlist->getVersion(version); auto ver = vlist->getVersion(version);
if (!ver) if (!ver)
return {}; return {};
if (!ver->isLoaded()) if (!ver->isLoaded()) {
QEventLoop loadVersionLoop;
ver->load(Net::Mode::Online); ver->load(Net::Mode::Online);
auto task = ver->getCurrentTask();
QObject::connect(task.get(), &Task::finished, &loadVersionLoop, &QEventLoop::quit);
if (!task->isRunning())
task->start();
loadVersionLoop.exec();
}
return ver; return ver;
} }

View File

@ -1,6 +1,7 @@
#include "FileResolvingTask.h" #include "FileResolvingTask.h"
#include "Json.h" #include "Json.h"
#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
#include "net/Upload.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 url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>(); auto output = std::make_shared<QByteArray>();
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output); 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); m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output); 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 // 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 // which file is relative to each loader, so it's best to not use any one and
// let the user download it manually. // let the user download it manually.
if (file.loaders.size() <= 1) { if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
out->url = file.downloadUrl; out->url = file.downloadUrl;
qDebug() << "Found alternative on modrinth " << out->fileName; qDebug() << "Found alternative on modrinth " << out->fileName;
} else { } else {
@ -175,7 +176,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId); auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::ApiDownload::makeByteArray(url, output); auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName; 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 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 json = QJsonDocument::fromJson(*output);
auto base = auto base =

View File

@ -6,7 +6,6 @@
#include "FlameModIndex.h" #include "FlameModIndex.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "Json.h" #include "Json.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"
#include "net/ApiUpload.h" #include "net/ApiUpload.h"
@ -131,19 +130,13 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto obj = Json::requireObject(doc); auto obj = Json::requireObject(doc);
auto arr = Json::requireArray(obj, "data"); auto arr = Json::requireArray(obj, "data");
QJsonObject latest_file_obj;
ModPlatform::IndexedVersion ver_tmp;
for (auto file : arr) { for (auto file : arr) {
auto file_obj = Json::requireObject(file); auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj); auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
if (file_tmp.date > ver_tmp.date) { if (file_tmp.date > ver.date && (!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders))
ver_tmp = file_tmp; ver = file_tmp;
latest_file_obj = file_obj;
}
} }
ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
} catch (Json::JsonException& e) { } catch (Json::JsonException& e) {
qCritical() << "Failed to parse response from a version request."; qCritical() << "Failed to parse response from a version request.";
qCritical() << e.what(); qCritical() << e.what();
@ -204,6 +197,17 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
return netJob; 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 // https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") }, static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") }, { 2, "Popularity", QObject::tr("Sort by Popularity") },

View File

@ -20,10 +20,14 @@ class FlameAPI : public NetworkResourceAPI {
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; 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 matchFingerprints(const QList<uint>& fingerprints, std::shared_ptr<QByteArray> response);
Task::Ptr getFiles(const QStringList& fileIds, std::shared_ptr<QByteArray> response) const; 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; [[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: private:
static int getClassId(ModPlatform::ResourceType type) static int getClassId(ModPlatform::ResourceType type)
@ -37,19 +41,42 @@ class FlameAPI : public NetworkResourceAPI {
} }
} }
static int getMappedModLoader(ModLoaderTypes loaders) static int getMappedModLoader(ModPlatform::ModLoaderType loaders)
{ {
// https://docs.curseforge.com/?http#tocS_ModLoaderType // https://docs.curseforge.com/?http#tocS_ModLoaderType
if (loaders & Forge) switch (loaders) {
return 1; case ModPlatform::Forge:
if (loaders & Fabric) return 1;
return 4; case ModPlatform::Cauldron:
// TODO: remove this once Quilt drops official Fabric support return 2;
if (loaders & Quilt) // NOTE: Most if not all Fabric mods should work *currently* case ModPlatform::LiteLoader:
return 4; // Quilt would probably be 5 return 3;
case ModPlatform::Fabric:
return 4;
case ModPlatform::Quilt:
return 5;
case ModPlatform::NeoForge:
return 6;
}
return 0; 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: private:
[[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override [[nodiscard]] std::optional<QString> getSearchURL(SearchArgs const& args) const override
{ {
@ -66,7 +93,7 @@ class FlameAPI : public NetworkResourceAPI {
get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index)); get_arguments.append(QString("sortField=%1").arg(args.sorting.value().index));
get_arguments.append("sortOrder=desc"); get_arguments.append("sortOrder=desc");
if (args.loaders.has_value()) 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); get_arguments.append(gameVersionStr);
return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&'); return "https://api.curseforge.com/v1/mods/search?gameId=432&" + get_arguments.join('&');
@ -80,47 +107,27 @@ class FlameAPI : public NetworkResourceAPI {
[[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override [[nodiscard]] std::optional<QString> getVersionsURL(VersionSearchArgs const& args) const override
{ {
auto addonId = args.pack.addonId.toString(); 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()) if (args.mcVersions.has_value())
get_parameters.append(QString("gameVersion=%1").arg(args.mcVersions.value().front().toString())); url += QString("&gameVersion=%1").arg(args.mcVersions.value().front().toString());
if (args.loaders.has_value()) { if (args.loaders.has_value() && ModPlatform::hasSingleModLoaderSelected(args.loaders.value())) {
int mappedModLoader = getMappedModLoader(args.loaders.value()); int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loaders.value())));
url += QString("&modLoaderType=%1").arg(mappedModLoader);
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;
}
}
get_parameters.append(QString("modLoaderType=%1").arg(mappedModLoader));
} }
return url;
return url + get_parameters.join('&');
}; };
[[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{ {
auto mappedModLoader = getMappedModLoader(args.loader);
auto addonId = args.dependency.addonId.toString(); auto addonId = args.dependency.addonId.toString();
if (args.loader & Quilt) { auto url =
auto overide = ModPlatform::getOverrideDeps(); QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2").arg(addonId, args.mcVersion.toString());
auto over = std::find_if(overide.cbegin(), overide.cend(), [addonId](auto dep) { if (args.loader && ModPlatform::hasSingleModLoaderSelected(args.loader)) {
return dep.provider == ModPlatform::ResourceProvider::FLAME && addonId == dep.quilt; int mappedModLoader = getMappedModLoader(static_cast<ModPlatform::ModLoaderType>(static_cast<int>(args.loader)));
}); url += QString("&modLoaderType=%1").arg(mappedModLoader);
if (over != overide.cend()) {
mappedModLoader = 5;
}
} }
return QString("https://api.curseforge.com/v1/mods/%1/files?pageSize=10000&gameVersion=%2&modLoaderType=%3") return url;
.arg(addonId)
.arg(args.mcVersion.toString())
.arg(mappedModLoader);
}; };
}; };

View File

@ -5,13 +5,11 @@
#include <MurmurHash2.h> #include <MurmurHash2.h>
#include <memory> #include <memory>
#include "FileSystem.h"
#include "Json.h" #include "Json.h"
#include "ResourceDownloadTask.h" #include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h" #include "net/ApiDownload.h"

View File

@ -10,7 +10,7 @@ class FlameCheckUpdate : public CheckUpdateTask {
public: public:
FlameCheckUpdate(QList<Mod*>& mods, FlameCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{} {}

View File

@ -284,7 +284,7 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
// filter by minecraft version, if the loader depends on a certain version. // 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 // not all mod loaders depend on a given Minecraft version, so we won't do this
// filtering for those loaders. // 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) { auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion; return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
}); });
@ -350,7 +350,11 @@ bool FlameCreationTask::createInstance()
for (auto& loader : m_pack.minecraft.modLoaders) { for (auto& loader : m_pack.minecraft.modLoaders) {
auto id = loader.id; 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-"); id.remove("forge-");
loaderType = "forge"; loaderType = "forge";
loaderUid = "net.minecraftforge"; loaderUid = "net.minecraftforge";

View File

@ -81,6 +81,7 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QVector<ModPlatform::IndexedVersion> unsortedVersions; QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile(); auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft"); QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
if (!file.addonId.isValid()) if (!file.addonId.isValid())
file.addonId = pack.addonId; 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); unsortedVersions.append(file);
} }
@ -115,6 +117,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
if (str.contains('.')) if (str.contains('.'))
file.mcVersion.append(str); 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"); file.addonId = Json::requireInteger(obj, "modId");
@ -173,8 +188,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
return file; 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; QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
@ -183,7 +201,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
if (!file.addonId.isValid()) if (!file.addonId.isValid())
file.addonId = m.addonId; 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); versions.append(file);
} }
@ -192,5 +211,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
return a.date > b.date; return a.date > b.date;
}; };
std::sort(versions.begin(), versions.end(), orderSortPredicate); std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.front(); if (versions.size() != 0)
return versions.front();
return {};
} }

View File

@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network, const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst); const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion; 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 } // namespace FlameMod

View File

@ -43,12 +43,14 @@ const QStringList FlamePackExportTask::FILE_EXTENSIONS({ "jar", "zip" });
FlamePackExportTask::FlamePackExportTask(const QString& name, FlamePackExportTask::FlamePackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& author, const QString& author,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter) MMCZip::FilterFunction filter)
: name(name) : name(name)
, version(version) , version(version)
, author(author) , author(author)
, optionalFiles(optionalFiles)
, instance(instance) , instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot()) , gameRoot(instance->gameRoot())
@ -381,6 +383,7 @@ QByteArray FlamePackExportTask::generateIndex()
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
const ComponentPtr forge = profile->getComponent("net.minecraftforge"); const ComponentPtr forge = profile->getComponent("net.minecraftforge");
const ComponentPtr neoforge = profile->getComponent("net.neoforged");
// convert all available components to mrpack dependencies // convert all available components to mrpack dependencies
if (minecraft != nullptr) if (minecraft != nullptr)
@ -392,6 +395,8 @@ QByteArray FlamePackExportTask::generateIndex()
id = "fabric-" + fabric->getVersion(); id = "fabric-" + fabric->getVersion();
else if (forge != nullptr) else if (forge != nullptr)
id = "forge-" + forge->getVersion(); id = "forge-" + forge->getVersion();
else if (neoforge != nullptr)
id = "neoforge-" + neoforge->getVersion();
version["modLoaders"] = QJsonArray(); version["modLoaders"] = QJsonArray();
if (!id.isEmpty()) { if (!id.isEmpty()) {
QJsonObject loader; QJsonObject loader;
@ -407,7 +412,7 @@ QByteArray FlamePackExportTask::generateIndex()
QJsonObject file; QJsonObject file;
file["projectID"] = mod.addonId; file["projectID"] = mod.addonId;
file["fileID"] = mod.version; file["fileID"] = mod.version;
file["required"] = mod.enabled; file["required"] = mod.enabled || !optionalFiles;
files << file; files << file;
} }
obj["files"] = files; obj["files"] = files;

View File

@ -30,6 +30,7 @@ class FlamePackExportTask : public Task {
FlamePackExportTask(const QString& name, FlamePackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& author, const QString& author,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter); MMCZip::FilterFunction filter);
@ -44,6 +45,7 @@ class FlamePackExportTask : public Task {
// inputs // inputs
const QString name, version, author; const QString name, version, author;
const bool optionalFiles;
const InstancePtr instance; const InstancePtr instance;
MinecraftInstance* mcInstance; MinecraftInstance* mcInstance;
const QDir gameRoot; const QDir gameRoot;

View File

@ -131,7 +131,7 @@ Task::Ptr NetworkResourceAPI::getDependencyVersion(DependencySearchArgs&& args,
auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("%1::Dependency").arg(args.dependency.addonId.toString()), APPLICATION->network());
auto response = std::make_shared<QByteArray>(); 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, [=] { QObject::connect(netJob.get(), &NetJob::succeeded, [=] {
QJsonParseError parse_error{}; QJsonParseError parse_error{};

View File

@ -59,16 +59,20 @@ Modpack parseDirectory(QString path)
auto obj = Json::requireObject(target, "target"); auto obj = Json::requireObject(target, "target");
auto name = Json::requireString(obj, "name", "name"); auto name = Json::requireString(obj, "name", "name");
auto version = Json::requireString(obj, "version", "version"); auto version = Json::requireString(obj, "version", "version");
if (name == "forge") { if (name == "neoforge") {
modpack.loaderType = ResourceAPI::Forge; modpack.loaderType = ModPlatform::NeoForge;
modpack.version = version;
break;
} else if (name == "forge") {
modpack.loaderType = ModPlatform::Forge;
modpack.version = version; modpack.version = version;
break; break;
} else if (name == "fabric") { } else if (name == "fabric") {
modpack.loaderType = ResourceAPI::Fabric; modpack.loaderType = ModPlatform::Fabric;
modpack.version = version; modpack.version = version;
break; break;
} else if (name == "quilt") { } else if (name == "quilt") {
modpack.loaderType = ResourceAPI::Quilt; modpack.loaderType = ModPlatform::Quilt;
modpack.version = version; modpack.version = version;
break; break;
} }

View File

@ -39,7 +39,7 @@ struct Modpack {
// not needed for instance creation // not needed for instance creation
QVariant jvmArgs; QVariant jvmArgs;
std::optional<ResourceAPI::ModLoaderType> loaderType; std::optional<ModPlatform::ModLoaderType> loaderType;
QString loaderVersion; QString loaderVersion;
QIcon icon; QIcon icon;

View File

@ -68,21 +68,25 @@ void PackInstallTask::copySettings()
auto modloader = m_pack.loaderType; auto modloader = m_pack.loaderType;
if (modloader.has_value()) if (modloader.has_value())
switch (modloader.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); components->setComponentVersion("net.minecraftforge", m_pack.version, true);
break; break;
} }
case ResourceAPI::Fabric: { case ModPlatform::Fabric: {
components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true); components->setComponentVersion("net.fabricmc.fabric-loader", m_pack.version, true);
break; break;
} }
case ResourceAPI::Quilt: { case ModPlatform::Quilt: {
components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true); components->setComponentVersion("org.quiltmc.quilt-loader", m_pack.version, true);
break; break;
} }
case ResourceAPI::Cauldron: case ModPlatform::Cauldron:
break; break;
case ResourceAPI::LiteLoader: case ModPlatform::LiteLoader:
break; break;
} }
components->saveNow(); components->saveNow();

View File

@ -42,7 +42,7 @@ Task::Ptr ModrinthAPI::currentVersions(const QStringList& hashes, QString hash_f
Task::Ptr ModrinthAPI::latestVersion(QString hash, Task::Ptr ModrinthAPI::latestVersion(QString hash,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersion"), APPLICATION->network());
@ -72,7 +72,7 @@ Task::Ptr ModrinthAPI::latestVersion(QString hash,
Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes, Task::Ptr ModrinthAPI::latestVersions(const QStringList& hashes,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) std::shared_ptr<QByteArray> response)
{ {
auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network()); auto netJob = makeShared<NetJob>(QString("Modrinth::GetLatestVersions"), APPLICATION->network());

View File

@ -19,13 +19,13 @@ class ModrinthAPI : public NetworkResourceAPI {
auto latestVersion(QString hash, auto latestVersion(QString hash,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr; std::shared_ptr<QByteArray> response) -> Task::Ptr;
auto latestVersions(const QStringList& hashes, auto latestVersions(const QStringList& hashes,
QString hash_format, QString hash_format,
std::optional<std::list<Version>> mcVersions, std::optional<std::list<Version>> mcVersions,
std::optional<ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<QByteArray> response) -> Task::Ptr; std::shared_ptr<QByteArray> response) -> Task::Ptr;
Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const override; 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; }; 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; 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) { if (types & loader) {
l << getModLoaderString(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; return l;
} }
static auto getModLoaderFilters(ModLoaderTypes types) -> const QString static auto getModLoaderFilters(ModPlatform::ModLoaderTypes types) -> const QString
{ {
QStringList l; QStringList l;
for (auto loader : getModLoaderStrings(types)) { for (auto loader : getModLoaderStrings(types)) {
@ -141,7 +140,10 @@ class ModrinthAPI : public NetworkResourceAPI {
return s.isEmpty() ? QString() : s; 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 [[nodiscard]] std::optional<QString> getDependencyURL(DependencySearchArgs const& args) const override
{ {

View File

@ -11,7 +11,6 @@
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
static ModrinthAPI api; static ModrinthAPI api;
static ModPlatform::ProviderCapabilities ProviderCaps; static ModPlatform::ProviderCapabilities ProviderCaps;
@ -111,11 +110,11 @@ void ModrinthCheckUpdate::executeTask()
// so we may want to filter it // so we may want to filter it
QString loader_filter; QString loader_filter;
if (m_loaders.has_value()) { if (m_loaders.has_value()) {
static auto flags = { ResourceAPI::ModLoaderType::Forge, ResourceAPI::ModLoaderType::Fabric, static auto flags = { ModPlatform::ModLoaderType::NeoForge, ModPlatform::ModLoaderType::Forge,
ResourceAPI::ModLoaderType::Quilt }; ModPlatform::ModLoaderType::Fabric, ModPlatform::ModLoaderType::Quilt };
for (auto flag : flags) { for (auto flag : flags) {
if (m_loaders.value().testFlag(flag)) { if (m_loaders.value().testFlag(flag)) {
loader_filter = api.getModLoaderString(flag); loader_filter = ModPlatform::getModLoaderString(flag);
break; break;
} }
} }

View File

@ -10,7 +10,7 @@ class ModrinthCheckUpdate : public CheckUpdateTask {
public: public:
ModrinthCheckUpdate(QList<Mod*>& mods, ModrinthCheckUpdate(QList<Mod*>& mods,
std::list<Version>& mcVersions, std::list<Version>& mcVersions,
std::optional<ResourceAPI::ModLoaderTypes> loaders, std::optional<ModPlatform::ModLoaderTypes> loaders,
std::shared_ptr<ModFolderModel> mods_folder) std::shared_ptr<ModFolderModel> mods_folder)
: CheckUpdateTask(mods, mcVersions, loaders, mods_folder) : CheckUpdateTask(mods, mcVersions, loaders, mods_folder)
{} {}

View File

@ -211,6 +211,8 @@ bool ModrinthCreationTask::createInstance()
components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version); components->setComponentVersion("org.quiltmc.quilt-loader", m_quilt_version);
if (!m_forge_version.isEmpty()) if (!m_forge_version.isEmpty())
components->setComponentVersion("net.minecraftforge", m_forge_version); components->setComponentVersion("net.minecraftforge", m_forge_version);
if (!m_neoForge_version.isEmpty())
components->setComponentVersion("net.neoforged", m_neoForge_version);
if (m_instIcon != "default") { if (m_instIcon != "default") {
instance.setIconKey(m_instIcon); instance.setIconKey(m_instIcon);
@ -398,6 +400,8 @@ bool ModrinthCreationTask::parseManifest(const QString& index_path,
m_quilt_version = Json::requireString(*it, "Quilt Loader version"); m_quilt_version = Json::requireString(*it, "Quilt Loader version");
} else if (name == "forge") { } else if (name == "forge") {
m_forge_version = Json::requireString(*it, "Forge version"); m_forge_version = Json::requireString(*it, "Forge version");
} else if (name == "neoforge") {
m_neoForge_version = Json::requireString(*it, "NeoForge version");
} else { } else {
throw JSONValidationError("Unknown dependency type: " + name); throw JSONValidationError("Unknown dependency type: " + name);
} }

View File

@ -39,7 +39,7 @@ class ModrinthCreationTask final : public InstanceCreationTask {
private: private:
QWidget* m_parent = nullptr; 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; QString m_managed_id, m_managed_version_id, m_managed_name;
std::vector<Modrinth::File> m_files; std::vector<Modrinth::File> m_files;

View File

@ -33,12 +33,14 @@ const QStringList ModrinthPackExportTask::FILE_EXTENSIONS({ "jar", "litemod", "z
ModrinthPackExportTask::ModrinthPackExportTask(const QString& name, ModrinthPackExportTask::ModrinthPackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& summary, const QString& summary,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter) MMCZip::FilterFunction filter)
: name(name) : name(name)
, version(version) , version(version)
, summary(summary) , summary(summary)
, optionalFiles(optionalFiles)
, instance(instance) , instance(instance)
, mcInstance(dynamic_cast<MinecraftInstance*>(instance.get())) , mcInstance(dynamic_cast<MinecraftInstance*>(instance.get()))
, gameRoot(instance->gameRoot()) , gameRoot(instance->gameRoot())
@ -245,6 +247,7 @@ QByteArray ModrinthPackExportTask::generateIndex()
const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader"); const ComponentPtr quilt = profile->getComponent("org.quiltmc.quilt-loader");
const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader"); const ComponentPtr fabric = profile->getComponent("net.fabricmc.fabric-loader");
const ComponentPtr forge = profile->getComponent("net.minecraftforge"); const ComponentPtr forge = profile->getComponent("net.minecraftforge");
const ComponentPtr neoForge = profile->getComponent("net.neoforged");
// convert all available components to mrpack dependencies // convert all available components to mrpack dependencies
QJsonObject dependencies; QJsonObject dependencies;
@ -256,6 +259,8 @@ QByteArray ModrinthPackExportTask::generateIndex()
dependencies["fabric-loader"] = fabric->m_version; dependencies["fabric-loader"] = fabric->m_version;
if (forge != nullptr) if (forge != nullptr)
dependencies["forge"] = forge->m_version; dependencies["forge"] = forge->m_version;
if (neoForge != nullptr)
dependencies["neoforge"] = neoForge->m_version;
out["dependencies"] = dependencies; out["dependencies"] = dependencies;
} }
@ -267,16 +272,18 @@ QByteArray ModrinthPackExportTask::generateIndex()
QString path = iterator.key(); QString path = iterator.key();
const ResolvedFile& value = iterator.value(); const ResolvedFile& value = iterator.value();
// detect disabled mod if (optionalFiles) {
const QFileInfo pathInfo(path); // detect disabled mod
if (pathInfo.suffix() == "disabled") { const QFileInfo pathInfo(path);
// rename it if (pathInfo.suffix() == "disabled") {
path = pathInfo.dir().filePath(pathInfo.completeBaseName()); // rename it
// ...and make it optional path = pathInfo.dir().filePath(pathInfo.completeBaseName());
QJsonObject env; // ...and make it optional
env["client"] = "optional"; QJsonObject env;
env["server"] = "optional"; env["client"] = "optional";
fileOut["env"] = env; env["server"] = "optional";
fileOut["env"] = env;
}
} }
fileOut["path"] = path; fileOut["path"] = path;

View File

@ -31,6 +31,7 @@ class ModrinthPackExportTask : public Task {
ModrinthPackExportTask(const QString& name, ModrinthPackExportTask(const QString& name,
const QString& version, const QString& version,
const QString& summary, const QString& summary,
bool optionalFiles,
InstancePtr instance, InstancePtr instance,
const QString& output, const QString& output,
MMCZip::FilterFunction filter); MMCZip::FilterFunction filter);
@ -50,6 +51,7 @@ class ModrinthPackExportTask : public Task {
// inputs // inputs
const QString name, version, summary; const QString name, version, summary;
const bool optionalFiles;
const InstancePtr instance; const InstancePtr instance;
MinecraftInstance* mcInstance; MinecraftInstance* mcInstance;
const QDir gameRoot; const QDir gameRoot;

View File

@ -93,19 +93,19 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob
pack.extraDataLoaded = true; pack.extraDataLoaded = true;
} }
void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void Modrinth::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst)
QJsonArray& arr,
[[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst)
{ {
QVector<ModPlatform::IndexedVersion> unsortedVersions; 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) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj); 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); unsortedVersions.append(file);
} }
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {
@ -134,7 +134,18 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
} }
auto loaders = Json::requireArray(obj, "loaders"); auto loaders = Json::requireArray(obj, "loaders");
for (auto loader : 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 = Json::requireString(obj, "name");
file.version_number = Json::requireString(obj, "version_number"); file.version_number = Json::requireString(obj, "version_number");
@ -218,15 +229,20 @@ auto Modrinth::loadIndexedPackVersion(QJsonObject& obj, QString preferred_hash_t
return {}; 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) { for (auto versionIter : arr) {
auto obj = versionIter.toObject(); auto obj = versionIter.toObject();
auto file = loadIndexedPackVersion(obj); 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); versions.append(file);
} }
auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool { auto orderSortPredicate = [](const ModPlatform::IndexedVersion& a, const ModPlatform::IndexedVersion& b) -> bool {

View File

@ -26,11 +26,8 @@ namespace Modrinth {
void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj);
void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const BaseInstance* inst);
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, QString hash_type = "sha512", QString filename_prefer = "") -> ModPlatform::IndexedVersion; 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 } // namespace Modrinth

View File

@ -218,9 +218,24 @@ void HttpMetaCache::Load()
if (!index.open(QIODevice::ReadOnly)) if (!index.open(QIODevice::ReadOnly))
return; return;
QJsonDocument json = QJsonDocument::fromJson(index.readAll()); QJsonParseError parseError;
QJsonDocument json = QJsonDocument::fromJson(index.readAll(), &parseError);
auto root = Json::requireObject(json, "HttpMetaCache root"); // 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.object();
// check file version first // check file version first
auto version_val = Json::ensureString(root, "version"); auto version_val = Json::ensureString(root, "version");

View File

@ -350,6 +350,7 @@
<file>scalable/instances/quiltmc.svg</file> <!-- CC0 QuiltMC --> <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/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/forge.png</file> <!-- LGPL3 Forge Development LLC -->
<file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader --> <file>128x128/instances/liteloader.png</file> <!-- CC-BY-SA 4.0 LiteLoader -->
</qresource> </qresource>

View File

@ -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>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -44,8 +44,6 @@
#include <QPushButton> #include <QPushButton>
#include <QScrollBar> #include <QScrollBar>
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui/widgets/PageContainer.h" #include "ui/widgets/PageContainer.h"
#include "InstancePageProvider.h" #include "InstancePageProvider.h"
@ -76,40 +74,44 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget* parent) : QMainWin
// Add custom buttons to the page container layout. // Add custom buttons to the page container layout.
{ {
auto horizontalLayout = new QHBoxLayout(); auto horizontalLayout = new QHBoxLayout(this);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setContentsMargins(6, -1, 6, -1); horizontalLayout->setContentsMargins(6, -1, 6, -1);
auto btnHelp = new QPushButton(); auto btnHelp = new QPushButton(this);
btnHelp->setText(tr("Help")); btnHelp->setText(tr("Help"));
horizontalLayout->addWidget(btnHelp); 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); auto spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout->addSpacerItem(spacer); horizontalLayout->addSpacerItem(spacer);
m_killButton = new QPushButton(); 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_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); horizontalLayout->addWidget(m_killButton);
connect(m_killButton, SIGNAL(clicked(bool)), SLOT(on_btnKillMinecraft_clicked())); connect(m_killButton, &QPushButton::clicked, this, [this] { APPLICATION->kill(m_instance); });
m_launchOfflineButton = new QPushButton(); updateButtons();
horizontalLayout->addWidget(m_launchOfflineButton);
m_launchOfflineButton->setText(tr("Launch Offline"));
m_launchDemoButton = new QPushButton(); m_closeButton = new QPushButton(this);
horizontalLayout->addWidget(m_launchDemoButton);
m_launchDemoButton->setText(tr("Launch Demo"));
updateLaunchButtons();
connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked()));
connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked()));
m_closeButton = new QPushButton();
m_closeButton->setText(tr("Close")); m_closeButton->setText(tr("Close"));
horizontalLayout->addWidget(m_closeButton); 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); m_container->addButtons(horizontalLayout);
connect(m_instance.get(), &BaseInstance::profilerChanged, this, &InstanceWindow::updateButtons);
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceWindow::updateButtons);
} }
// restore window state // restore window state
@ -149,47 +151,18 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance
} }
} }
void InstanceWindow::updateLaunchButtons() void InstanceWindow::updateButtons()
{ {
if (m_instance->isRunning()) { m_launchButton->setEnabled(m_instance->canLaunch());
m_launchOfflineButton->setEnabled(false); m_killButton->setEnabled(m_instance->isRunning());
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. QMenu* launchMenu = m_launchButton->menu();
auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get()); if (launchMenu)
if (instance) { launchMenu->clear();
m_launchDemoButton->setEnabled(instance->supportsDemo()); else
} launchMenu = new QMenu(this);
m_instance->populateLaunchMenu(launchMenu);
m_killButton->setText(tr("Launch")); m_launchButton->setMenu(launchMenu);
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()
{
APPLICATION->launch(m_instance, false, false, nullptr);
}
void InstanceWindow::on_btnLaunchMinecraftDemo_clicked()
{
APPLICATION->launch(m_instance, false, true, nullptr);
} }
void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc) 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) void InstanceWindow::runningStateChanged(bool running)
{ {
updateLaunchButtons(); updateButtons();
m_container->refreshContainer(); m_container->refreshContainer();
if (running) { if (running) {
selectPage("log"); selectPage("log");
} }
} }
void InstanceWindow::on_closeButton_clicked()
{
close();
}
void InstanceWindow::closeEvent(QCloseEvent* event) void InstanceWindow::closeEvent(QCloseEvent* event)
{ {
bool proceed = true; bool proceed = true;
@ -233,15 +201,6 @@ bool InstanceWindow::saveAll()
return m_container->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() QString InstanceWindow::instanceId()
{ {
return m_instance->id(); return m_instance->id();
@ -252,17 +211,15 @@ bool InstanceWindow::selectPage(QString pageId)
return m_container->selectPage(pageId); return m_container->selectPage(pageId);
} }
BasePage* InstanceWindow::selectedPage() const
{
return m_container->selectedPage();
}
void InstanceWindow::refreshContainer() void InstanceWindow::refreshContainer()
{ {
m_container->refreshContainer(); m_container->refreshContainer();
} }
InstanceWindow::~InstanceWindow() {} BasePage* InstanceWindow::selectedPage() const
{
return m_container->selectedPage();
}
bool InstanceWindow::requestClose() bool InstanceWindow::requestClose()
{ {

View File

@ -38,6 +38,7 @@
#include <QMainWindow> #include <QMainWindow>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include <QToolButton>
#include "LaunchController.h" #include "LaunchController.h"
#include "launch/LaunchTask.h" #include "launch/LaunchTask.h"
@ -53,7 +54,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
public: public:
explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0); explicit InstanceWindow(InstancePtr proc, QWidget* parent = 0);
virtual ~InstanceWindow(); virtual ~InstanceWindow() = default;
bool selectPage(QString pageId) override; bool selectPage(QString pageId) override;
BasePage* selectedPage() const override; BasePage* selectedPage() const override;
@ -71,11 +72,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void isClosing(); void isClosing();
private slots: 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 instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
void runningStateChanged(bool running); void runningStateChanged(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus); void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
@ -84,7 +80,7 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
void closeEvent(QCloseEvent*) override; void closeEvent(QCloseEvent*) override;
private: private:
void updateLaunchButtons(); void updateButtons();
private: private:
shared_qobject_ptr<LaunchTask> m_proc; shared_qobject_ptr<LaunchTask> m_proc;
@ -92,7 +88,6 @@ class InstanceWindow : public QMainWindow, public BasePageContainer {
bool m_doNotSave = false; bool m_doNotSave = false;
PageContainer* m_container = nullptr; PageContainer* m_container = nullptr;
QPushButton* m_closeButton = nullptr; QPushButton* m_closeButton = nullptr;
QToolButton* m_launchButton = nullptr;
QPushButton* m_killButton = nullptr; QPushButton* m_killButton = nullptr;
QPushButton* m_launchOfflineButton = nullptr;
QPushButton* m_launchDemoButton = nullptr;
}; };

View File

@ -43,7 +43,6 @@
#include "FileSystem.h" #include "FileSystem.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui_MainWindow.h" #include "ui_MainWindow.h"
#include <QDir> #include <QDir>
@ -85,22 +84,19 @@
#include <launch/LaunchTask.h> #include <launch/LaunchTask.h>
#include <minecraft/MinecraftInstance.h> #include <minecraft/MinecraftInstance.h>
#include <minecraft/auth/AccountList.h> #include <minecraft/auth/AccountList.h>
#include <net/Download.h> #include <net/ApiDownload.h>
#include <net/NetJob.h> #include <net/NetJob.h>
#include <news/NewsChecker.h> #include <news/NewsChecker.h>
#include <tools/BaseProfiler.h> #include <tools/BaseProfiler.h>
#include <updater/ExternalUpdater.h> #include <updater/ExternalUpdater.h>
#include "InstancePageProvider.h"
#include "InstanceWindow.h" #include "InstanceWindow.h"
#include "JavaCommon.h"
#include "LaunchController.h"
#include "ui/dialogs/AboutDialog.h" #include "ui/dialogs/AboutDialog.h"
#include "ui/dialogs/CopyInstanceDialog.h" #include "ui/dialogs/CopyInstanceDialog.h"
#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h" #include "ui/dialogs/ExportInstanceDialog.h"
#include "ui/dialogs/ExportPackDialog.h" #include "ui/dialogs/ExportPackDialog.h"
#include "ui/dialogs/ExportToModListDialog.h"
#include "ui/dialogs/IconPickerDialog.h" #include "ui/dialogs/IconPickerDialog.h"
#include "ui/dialogs/ImportResourceDialog.h" #include "ui/dialogs/ImportResourceDialog.h"
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
@ -113,15 +109,24 @@
#include "ui/themes/ThemeManager.h" #include "ui/themes/ThemeManager.h"
#include "ui/widgets/LabeledToolButton.h" #include "ui/widgets/LabeledToolButton.h"
#include "minecraft/PackProfile.h"
#include "minecraft/VersionFile.h"
#include "minecraft/WorldList.h" #include "minecraft/WorldList.h"
#include "minecraft/mod/ModFolderModel.h" #include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourcePackFolderModel.h"
#include "minecraft/mod/ShaderPackFolderModel.h" #include "minecraft/mod/ShaderPackFolderModel.h"
#include "minecraft/mod/TexturePackFolderModel.h"
#include "minecraft/mod/tasks/LocalResourceParse.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 "KonamiCode.h"
#include "InstanceCopyTask.h" #include "InstanceCopyTask.h"
#include "InstanceImportTask.h"
#include "Json.h"
#include "MMCTime.h" #include "MMCTime.h"
@ -549,71 +554,15 @@ void MainWindow::updateMainToolBar()
ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); 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(); QMenu* launchMenu = ui->actionLaunchInstance->menu();
if (launchMenu) { if (launchMenu)
launchMenu->clear(); launchMenu->clear();
} else { else
launchMenu = new QMenu(this); launchMenu = new QMenu(this);
} if (m_selectedInstance)
QAction* normalLaunch = launchMenu->addAction(tr("Launch")); m_selectedInstance->populateLaunchMenu(launchMenu);
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);
}
}
ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstance->setMenu(launchMenu);
} }
@ -924,13 +873,13 @@ void MainWindow::finalizeInstance(InstancePtr inst)
} else { } else {
CustomMessageBox::selectable(this, tr("Error"), CustomMessageBox::selectable(this, tr("Error"),
tr("The launcher cannot download Minecraft or update instances unless you have at least " 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 your Microsoft or Mojang account."),
QMessageBox::Warning) QMessageBox::Warning)
->show(); ->show();
} }
} }
void MainWindow::addInstance(QString url) void MainWindow::addInstance(const QString& url, const QMap<QString, QString>& extra_info)
{ {
QString groupName; QString groupName;
do { do {
@ -950,7 +899,7 @@ void MainWindow::addInstance(QString url)
groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString(); groupName = APPLICATION->settings()->get("LastUsedGroupForNewInstance").toString();
} }
NewInstanceDialog newInstDlg(groupName, url, this); NewInstanceDialog newInstDlg(groupName, url, extra_info, this);
if (!newInstDlg.exec()) if (!newInstDlg.exec())
return; return;
@ -977,18 +926,105 @@ void MainWindow::processURLs(QList<QUrl> urls)
if (url.scheme().isEmpty()) if (url.scheme().isEmpty())
url.setScheme("file"); url.setScheme("file");
if (!url.isLocalFile()) { // probably instance/modpack ModPlatform::IndexedVersion version;
addInstance(url.toString()); QMap<QString, QString> extra_info;
break; 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);
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];
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);
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, &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
version = FlameMod::loadIndexedPackVersion(data);
auto fileName = version.fileName;
// Have to use ensureString then use QUrl to get proper url encoding
dl_url = QUrl(version.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());
});
{ // 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); QFileInfo localFileInfo(localFileName);
auto type = ResourceUtils::identify(localFileInfo); auto type = ResourceUtils::identify(localFileInfo);
if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack if (ResourceUtils::ValidResourceTypes.count(type) == 0) { // probably instance/modpack
addInstance(localFileName); addInstance(localFileName, extra_info);
continue; continue;
} }
@ -1013,7 +1049,7 @@ void MainWindow::processURLs(QList<QUrl> urls)
qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName; qWarning() << "Importing of Data Packs not supported at this time. Ignoring" << localFileName;
break; break;
case PackedResourceType::Mod: case PackedResourceType::Mod:
minecraftInst->loaderModList()->installMod(localFileName); minecraftInst->loaderModList()->installMod(localFileName, version);
break; break;
case PackedResourceType::ShaderPack: case PackedResourceType::ShaderPack:
minecraftInst->shaderPackList()->installResource(localFileName); minecraftInst->shaderPackList()->installResource(localFileName);
@ -1193,7 +1229,7 @@ void MainWindow::globalSettingsClosed()
proxymodel->invalidate(); proxymodel->invalidate();
proxymodel->sort(0); proxymodel->sort(0);
updateMainToolBar(); updateMainToolBar();
updateToolsMenu(); updateLaunchButton();
updateThemeMenu(); updateThemeMenu();
updateStatusCenter(); updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed // This needs to be done to prevent UI elements disappearing in the event the config is changed
@ -1323,10 +1359,11 @@ void MainWindow::on_actionDeleteInstance_triggered()
if (APPLICATION->instances()->trashInstance(id)) { if (APPLICATION->instances()->trashInstance(id)) {
ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); ui->actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething());
return; } else {
APPLICATION->instances()->deleteInstance(id);
} }
APPLICATION->settings()->set("SelectedInstance", QString());
APPLICATION->instances()->deleteInstance(id); selectionBad();
} }
void MainWindow::on_actionExportInstanceZip_triggered() void MainWindow::on_actionExportInstanceZip_triggered()
@ -1428,20 +1465,6 @@ void MainWindow::activateInstance(InstancePtr instance)
APPLICATION->launch(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() void MainWindow::on_actionKillInstance_triggered()
{ {
if (m_selectedInstance && m_selectedInstance->isRunning()) { if (m_selectedInstance && m_selectedInstance->isRunning()) {
@ -1615,6 +1638,7 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
} }
if (m_selectedInstance) { if (m_selectedInstance) {
disconnect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); 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(); QString id = current.data(InstanceList::InstanceIDRole).toString();
m_selectedInstance = APPLICATION->instances()->getInstanceById(id); m_selectedInstance = APPLICATION->instances()->getInstanceById(id);
@ -1622,14 +1646,6 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
ui->instanceToolBar->setEnabled(true); ui->instanceToolBar->setEnabled(true);
setInstanceActionsEnabled(true); setInstanceActionsEnabled(true);
ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); 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->actionKillInstance->setEnabled(m_selectedInstance->isRunning());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
@ -1638,18 +1654,13 @@ void MainWindow::instanceChanged(const QModelIndex& current, [[maybe_unused]] co
updateStatusCenter(); updateStatusCenter();
updateInstanceToolIcon(m_selectedInstance->iconKey()); updateInstanceToolIcon(m_selectedInstance->iconKey());
updateToolsMenu(); updateLaunchButton();
APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id()); APPLICATION->settings()->set("SelectedInstance", m_selectedInstance->id());
connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance); connect(m_selectedInstance.get(), &BaseInstance::runningStatusChanged, this, &MainWindow::refreshCurrentInstance);
connect(m_selectedInstance.get(), &BaseInstance::profilerChanged, this, &MainWindow::refreshCurrentInstance);
} else { } 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()); APPLICATION->settings()->set("SelectedInstance", QString());
selectionBad(); selectionBad();
return; return;
@ -1674,11 +1685,12 @@ void MainWindow::selectionBad()
{ {
// start by reseting everything... // start by reseting everything...
m_selectedInstance = nullptr; m_selectedInstance = nullptr;
m_statusLeft->setText(tr("No instance selected"));
statusBar()->clearMessage(); statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false); ui->instanceToolBar->setEnabled(false);
setInstanceActionsEnabled(false); setInstanceActionsEnabled(false);
updateToolsMenu(); updateLaunchButton();
renameButton->setText(tr("Rename Instance")); renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("grass"); updateInstanceToolIcon("grass");
@ -1725,7 +1737,9 @@ void MainWindow::updateStatusCenter()
int timePlayed = APPLICATION->instances()->getTotalPlayTime(); int timePlayed = APPLICATION->instances()->getTotalPlayTime();
if (timePlayed > 0) { 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) // "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here)
@ -1741,7 +1755,7 @@ void MainWindow::setInstanceActionsEnabled(bool enabled)
ui->actionCreateInstanceShortcut->setEnabled(enabled); ui->actionCreateInstanceShortcut->setEnabled(enabled);
} }
void MainWindow::refreshCurrentInstance([[maybe_unused]] bool running) void MainWindow::refreshCurrentInstance()
{ {
auto current = view->selectionModel()->currentIndex(); auto current = view->selectionModel()->currentIndex();
instanceChanged(current, current); instanceChanged(current, current);

View File

@ -144,10 +144,6 @@ class MainWindow : public QMainWindow {
void on_actionLaunchInstance_triggered(); void on_actionLaunchInstance_triggered();
void on_actionLaunchInstanceOffline_triggered();
void on_actionLaunchInstanceDemo_triggered();
void on_actionKillInstance_triggered(); void on_actionKillInstance_triggered();
void on_actionDeleteInstance_triggered(); void on_actionDeleteInstance_triggered();
@ -155,10 +151,7 @@ class MainWindow : public QMainWindow {
void deleteGroup(); void deleteGroup();
void undoTrashInstance(); void undoTrashInstance();
inline void on_actionExportInstance_triggered() inline void on_actionExportInstance_triggered() { on_actionExportInstanceZip_triggered(); }
{
on_actionExportInstanceZip_triggered();
}
void on_actionExportInstanceZip_triggered(); void on_actionExportInstanceZip_triggered();
void on_actionExportInstanceMrPack_triggered(); void on_actionExportInstanceMrPack_triggered();
void on_actionExportInstanceFlamePack_triggered(); void on_actionExportInstanceFlamePack_triggered();
@ -181,7 +174,7 @@ class MainWindow : public QMainWindow {
void updateMainToolBar(); void updateMainToolBar();
void updateToolsMenu(); void updateLaunchButton();
void updateThemeMenu(); void updateThemeMenu();
@ -215,12 +208,12 @@ class MainWindow : public QMainWindow {
void keyReleaseEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override;
#endif #endif
void refreshCurrentInstance(bool running); void refreshCurrentInstance();
private: private:
void retranslateUi(); void retranslateUi();
void addInstance(QString url = QString()); void addInstance(const QString& url = QString(), const QMap<QString, QString>& extra_info = {});
void activateInstance(InstancePtr instance); void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled); void setCatBackground(bool enabled);
void updateInstanceToolIcon(QString new_icon); void updateInstanceToolIcon(QString new_icon);

View File

@ -440,22 +440,6 @@
<string>Ctrl+D</string> <string>Ctrl+D</string>
</property> </property>
</action> </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"> <action name="actionExportInstance">
<property name="icon"> <property name="icon">
<iconset theme="export"> <iconset theme="export">

View File

@ -37,15 +37,21 @@
ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider) ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPlatform::ResourceProvider provider)
: QDialog(parent), instance(instance), ui(new Ui::ExportPackDialog), m_provider(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->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) { if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
ui->summary->setText(instance->notes().split(QRegularExpression("\\r?\\n"))[0]); setWindowTitle(tr("Export Modrinth Pack"));
setWindowTitle("Export Modrinth Pack"); ui->summary->setText(instance->settings()->get("ExportSummary").toString());
} else { } else {
setWindowTitle("Export CurseForge Pack"); setWindowTitle(tr("Export CurseForge Pack"));
ui->version->setText(""); ui->summaryLabel->setText(tr("&Author"));
ui->summaryLabel->setText("Author"); ui->summary->setText(instance->settings()->get("ExportAuthor").toString());
} }
// ensure a valid pack is generated // ensure a valid pack is generated
@ -75,20 +81,19 @@ ExportPackDialog::ExportPackDialog(InstancePtr instance, QWidget* parent, ModPla
MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get()); MinecraftInstance* mcInstance = dynamic_cast<MinecraftInstance*>(instance.get());
if (mcInstance) { if (mcInstance) {
mcInstance->loaderModList()->update();
const QDir index = mcInstance->loaderModList()->indexDir(); const QDir index = mcInstance->loaderModList()->indexDir();
if (index.exists()) if (index.exists())
proxy->blockedPaths().insert(root.relativeFilePath(index.absolutePath())); proxy->ignoreFilesWithPath().insert(root.relativeFilePath(index.absolutePath()));
} }
ui->treeView->setModel(proxy); ui->files->setModel(proxy);
ui->treeView->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot()))); ui->files->setRootIndex(proxy->mapFromSource(model->index(instance->gameRoot())));
ui->treeView->sortByColumn(0, Qt::AscendingOrder); ui->files->sortByColumn(0, Qt::AscendingOrder);
model->setFilter(filter); model->setFilter(filter);
model->setRootPath(instance->gameRoot()); model->setRootPath(instance->gameRoot());
QHeaderView* headerView = ui->treeView->header(); QHeaderView* headerView = ui->files->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents); headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
headerView->setSectionResizeMode(0, QHeaderView::Stretch); headerView->setSectionResizeMode(0, QHeaderView::Stretch);
} }
@ -100,26 +105,41 @@ ExportPackDialog::~ExportPackDialog()
void ExportPackDialog::done(int result) void ExportPackDialog::done(int result)
{ {
if (result == Accepted) { auto settings = instance->settings();
const QString filename = FS::RemoveInvalidFilenameChars(ui->name->text()); settings->set("ExportName", ui->name->text());
QString output; settings->set("ExportVersion", ui->version->text());
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) settings->set(m_provider == ModPlatform::ResourceProvider::FLAME ? "ExportAuthor" : "ExportSummary", ui->summary->text());
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), settings->set("ExportOptionalFiles", ui->optionalFiles->isChecked());
FS::PathCombine(QDir::homePath(), filename + ".mrpack"), "Modrinth pack (*.mrpack *.zip)",
nullptr); if (result == Accepted) {
else const QString name = ui->name->text().isEmpty() ? instance->name() : ui->name->text();
output = QFileDialog::getSaveFileName(this, tr("Export %1").arg(ui->name->text()), const QString filename = FS::RemoveInvalidFilenameChars(name);
FS::PathCombine(QDir::homePath(), filename + ".zip"), "CurseForge pack (*.zip)", nullptr);
QString output;
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");
}
if (output.isEmpty())
return;
Task* task; Task* task;
if (m_provider == ModPlatform::ResourceProvider::MODRINTH) if (m_provider == ModPlatform::ResourceProvider::MODRINTH) {
task = new ModrinthPackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, task = new ModrinthPackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); output, std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
else } else {
task = new FlamePackExportTask(ui->name->text(), ui->version->text(), ui->summary->text(), instance, output, task = new FlamePackExportTask(name, ui->version->text(), ui->summary->text(), ui->optionalFiles->isChecked(), instance, output,
std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1)); std::bind(&FileIgnoreProxy::filterFile, proxy, std::placeholders::_1));
}
connect(task, &Task::failed, connect(task, &Task::failed,
[this](const QString reason) { CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); }); [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() void ExportPackDialog::validate()
{ {
const bool invalid = ui->buttonBox->button(QDialogButtonBox::Ok)
ui->name->text().isEmpty() || ((m_provider == ModPlatform::ResourceProvider::MODRINTH) && ui->version->text().isEmpty()); ->setDisabled(m_provider == ModPlatform::ResourceProvider::MODRINTH && ui->version->text().isEmpty());
ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(invalid);
} }

View File

@ -7,12 +7,9 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>650</width> <width>650</width>
<height>413</height> <height>510</height>
</rect> </rect>
</property> </property>
<property name="windowTitle">
<string>Export Pack</string>
</property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -20,13 +17,16 @@
<item> <item>
<widget class="QGroupBox" name="information"> <widget class="QGroupBox" name="information">
<property name="title"> <property name="title">
<string>Information</string> <string>&amp;Description</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="summaryLabel"> <widget class="QLabel" name="summaryLabel">
<property name="text"> <property name="text">
<string>Summary</string> <string>&amp;Summary</string>
</property>
<property name="buddy">
<cstring>summary</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -36,14 +36,20 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="nameLabel"> <widget class="QLabel" name="nameLabel">
<property name="text"> <property name="text">
<string>Name</string> <string>&amp;Name</string>
</property>
<property name="buddy">
<cstring>name</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="versionLabel"> <widget class="QLabel" name="versionLabel">
<property name="text"> <property name="text">
<string>Version</string> <string>&amp;Version</string>
</property>
<property name="buddy">
<cstring>version</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -57,31 +63,52 @@
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QLabel" name="filesLabel"> <widget class="QGroupBox" name="options">
<property name="text"> <property name="title">
<string>Files</string> <string>&amp;Options</string>
</property> </property>
</widget> <layout class="QVBoxLayout" name="verticalLayout">
</item> <item>
<item> <widget class="QLabel" name="filesLabel">
<widget class="QTreeView" name="treeView"> <property name="text">
<property name="alternatingRowColors"> <string>&amp;Files</string>
<bool>true</bool> </property>
</property> <property name="buddy">
<property name="selectionMode"> <cstring>files</cstring>
<enum>QAbstractItemView::ExtendedSelection</enum> </property>
</property> </widget>
<property name="sortingEnabled"> </item>
<bool>true</bool> <item>
</property> <widget class="QTreeView" name="files">
<attribute name="headerStretchLastSection"> <property name="alternatingRowColors">
<bool>false</bool> <bool>true</bool>
</attribute> </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> </widget>
</item> </item>
<item> <item>
@ -97,7 +124,8 @@
<tabstop>name</tabstop> <tabstop>name</tabstop>
<tabstop>version</tabstop> <tabstop>version</tabstop>
<tabstop>summary</tabstop> <tabstop>summary</tabstop>
<tabstop>treeView</tabstop> <tabstop>files</tabstop>
<tabstop>optionalFiles</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections> <connections>

View File

@ -129,7 +129,9 @@ InstallLoaderDialog::InstallLoaderDialog(std::shared_ptr<PackProfile> profile, c
QList<BasePage*> InstallLoaderDialog::getPages() 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), new InstallLoaderPage("net.minecraftforge", "forge", tr("Forge"), {}, profile),
// Fabric // Fabric
new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile), new InstallLoaderPage("net.fabricmc.fabric-loader", "fabricmc", tr("Fabric"), Version("1.14"), profile),

View File

@ -5,8 +5,6 @@
#include "ScrollMessageBox.h" #include "ScrollMessageBox.h"
#include "ui_ReviewMessageBox.h" #include "ui_ReviewMessageBox.h"
#include "FileSystem.h"
#include "Json.h"
#include "Markdown.h" #include "Markdown.h"
#include "tasks/ConcurrentTask.h" #include "tasks/ConcurrentTask.h"
@ -30,9 +28,9 @@ static std::list<Version> mcVersions(BaseInstance* inst)
return { static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion() }; 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, ModUpdateDialog::ModUpdateDialog(QWidget* parent,

View File

@ -62,8 +62,10 @@
#include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/modrinth/ModrinthPage.h"
#include "ui/pages/modplatform/technic/TechnicPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h"
#include "ui/widgets/PageContainer.h" #include "ui/widgets/PageContainer.h"
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup,
NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString& url, QWidget* parent) const QString& url,
const QMap<QString, QString>& extra_info,
QWidget* parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog) : QDialog(parent), ui(new Ui::NewInstanceDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
@ -125,6 +127,7 @@ NewInstanceDialog::NewInstanceDialog(const QString& initialGroup, const QString&
QUrl actualUrl(url); QUrl actualUrl(url);
m_container->selectPage("import"); m_container->selectPage("import");
importPage->setUrl(url); importPage->setUrl(url);
importPage->setExtraInfo(extra_info);
} }
updateDialogState(); updateDialogState();

View File

@ -53,7 +53,10 @@ class NewInstanceDialog : public QDialog, public BasePageProvider {
Q_OBJECT Q_OBJECT
public: 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(); ~NewInstanceDialog();
void updateDialogState(); void updateDialogState();

View File

@ -279,7 +279,7 @@ QList<BasePage*> ModDownloadDialog::getPages()
{ {
QList<BasePage*> pages; 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)) if (ModrinthAPI::validateModLoaders(loaders))
pages.append(ModrinthModPage::create(this, *m_instance)); pages.append(ModrinthModPage::create(this, *m_instance));

View File

@ -35,12 +35,6 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize">
<size>
<width>28</width>
<height>16777215</height>
</size>
</property>
<property name="text"> <property name="text">
<string>Browse</string> <string>Browse</string>
</property> </property>

View File

@ -64,7 +64,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
ui->setupUi(this); ui->setupUi(this);
ui->listView->setEmptyString( ui->listView->setEmptyString(
tr("Welcome!\n" 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\" or \"Add Mojang\" buttons to link your Microsoft and/or Mojang "
"accounts."));
ui->listView->setEmptyMode(VersionListView::String); ui->listView->setEmptyMode(VersionListView::String);
ui->listView->setContextMenuPolicy(Qt::CustomContextMenu); ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
@ -85,6 +86,8 @@ AccountListPage::AccountListPage(QWidget* parent) : QMainWindow(parent), ui(new
connect(selectionModel, &QItemSelectionModel::selectionChanged, connect(selectionModel, &QItemSelectionModel::selectionChanged,
[this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); }); [this]([[maybe_unused]] const QItemSelection& sel, [[maybe_unused]] const QItemSelection& dsel) { updateButtonStates(); });
connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu); 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::listChanged, this, &AccountListPage::listChanged);
connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged); connect(m_accounts.get(), &AccountList::listActivityChanged, this, &AccountListPage::listChanged);

View File

@ -185,6 +185,7 @@ void JavaPage::updateThresholds()
{ {
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value(); unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName; QString iconName;
@ -194,6 +195,9 @@ void JavaPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) { } else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow"; iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); 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 { } else {
iconName = "status-good"; iconName = "status-good";
ui->labelMaxMemIcon->setToolTip(""); ui->labelMaxMemIcon->setToolTip("");

View File

@ -2,7 +2,6 @@
/* /*
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -35,6 +34,7 @@
*/ */
#include "MinecraftPage.h" #include "MinecraftPage.h"
#include "BuildConfig.h"
#include "ui_MinecraftPage.h" #include "ui_MinecraftPage.h"
#include <QDir> #include <QDir>
@ -44,9 +44,15 @@
#include "Application.h" #include "Application.h"
#include "settings/SettingsObject.h" #include "settings/SettingsObject.h"
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage) MinecraftPage::MinecraftPage(QWidget* parent) : QWidget(parent), ui(new Ui::MinecraftPage)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeGLFWChanged);
connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &MinecraftPage::onUseNativeOpenALChanged);
loadSettings(); loadSettings();
updateCheckboxStuff(); updateCheckboxStuff();
} }
@ -74,6 +80,16 @@ void MinecraftPage::on_maximizedCheckBox_clicked(bool checked)
updateCheckboxStuff(); updateCheckboxStuff();
} }
void MinecraftPage::onUseNativeGLFWChanged(bool checked)
{
ui->lineEditGLFWPath->setEnabled(checked);
}
void MinecraftPage::onUseNativeOpenALChanged(bool checked)
{
ui->lineEditOpenALPath->setEnabled(checked);
}
void MinecraftPage::applySettings() void MinecraftPage::applySettings()
{ {
auto s = APPLICATION->settings(); auto s = APPLICATION->settings();
@ -84,8 +100,10 @@ void MinecraftPage::applySettings()
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value()); s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
// Native library workarounds // Native library workarounds
s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
s->set("UseNativeGLFW", ui->useNativeGLFWCheck->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 // Peformance related options
s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked()); s->set("EnableFeralGamemode", ui->enableFeralGamemodeCheck->isChecked());
@ -96,13 +114,11 @@ void MinecraftPage::applySettings()
s->set("ShowGameTime", ui->showGameTime->isChecked()); s->set("ShowGameTime", ui->showGameTime->isChecked());
s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked()); s->set("ShowGlobalGameTime", ui->showGlobalGameTime->isChecked());
s->set("RecordGameTime", ui->recordGameTime->isChecked()); s->set("RecordGameTime", ui->recordGameTime->isChecked());
s->set("ShowGameTimeWithoutDays", ui->showGameTimeWithoutDays->isChecked());
// Miscellaneous // Miscellaneous
s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); s->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked());
s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); s->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked());
// Mod loader settings
s->set("DisableQuiltBeacon", ui->disableQuiltBeaconCheckBox->isChecked());
} }
void MinecraftPage::loadSettings() void MinecraftPage::loadSettings()
@ -114,8 +130,20 @@ void MinecraftPage::loadSettings()
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt()); ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt()); ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").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->enableFeralGamemodeCheck->setChecked(s->get("EnableFeralGamemode").toBool());
ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool()); ui->enableMangoHud->setChecked(s->get("EnableMangoHud").toBool());
@ -138,11 +166,10 @@ void MinecraftPage::loadSettings()
ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool());
ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
ui->showGameTimeWithoutDays->setChecked(s->get("ShowGameTimeWithoutDays").toBool());
ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool()); ui->closeAfterLaunchCheck->setChecked(s->get("CloseAfterLaunch").toBool());
ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool()); ui->quitAfterGameStopCheck->setChecked(s->get("QuitAfterGameStop").toBool());
ui->disableQuiltBeaconCheckBox->setChecked(s->get("DisableQuiltBeacon").toBool());
} }
void MinecraftPage::retranslate() void MinecraftPage::retranslate()

View File

@ -70,6 +70,9 @@ class MinecraftPage : public QWidget, public BasePage {
private slots: private slots:
void on_maximizedCheckBox_clicked(bool checked); void on_maximizedCheckBox_clicked(bool checked);
void onUseNativeGLFWChanged(bool checked);
void onUseNativeOpenALChanged(bool checked);
private: private:
Ui::MinecraftPage* ui; Ui::MinecraftPage* ui;
}; };

View File

@ -138,6 +138,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="showGameTimeWithoutDays">
<property name="text">
<string>Show time spent playing in hours</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@ -190,45 +197,60 @@
<string>Tweaks</string> <string>Tweaks</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_12"> <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> <item>
<widget class="QGroupBox" name="nativeLibWorkaroundGroupBox"> <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
<property name="title"> <property name="title">
<string>Native library workarounds</string> <string>Native library workarounds</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_11"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0">
<widget class="QCheckBox" name="useNativeGLFWCheck"> <widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text"> <property name="text">
<string>Use system installation of &amp;GLFW</string> <string>Use system installation of &amp;GLFW</string>
</property> </property>
</widget> </widget>
</item> </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"> <widget class="QCheckBox" name="useNativeOpenALCheck">
<property name="text"> <property name="text">
<string>Use system installation of &amp;OpenAL</string> <string>Use system installation of &amp;OpenAL</string>
</property> </property>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
</item> </item>

View File

@ -152,6 +152,7 @@ void ExternalResourcesPage::retranslate()
void ExternalResourcesPage::itemActivated(const QModelIndex&) void ExternalResourcesPage::itemActivated(const QModelIndex&)
{ {
auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection());
m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE);
} }
void ExternalResourcesPage::filterTextChanged(const QString& newContents) void ExternalResourcesPage::filterTextChanged(const QString& newContents)

View File

@ -3,7 +3,6 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * it under the terms of the GNU General Public License as published by
@ -48,6 +47,7 @@
#include "ui/widgets/CustomCommands.h" #include "ui/widgets/CustomCommands.h"
#include "Application.h" #include "Application.h"
#include "BuildConfig.h"
#include "JavaCommon.h" #include "JavaCommon.h"
#include "minecraft/auth/AccountList.h" #include "minecraft/auth/AccountList.h"
@ -66,6 +66,10 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance* inst, QWidget* parent)
connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings); connect(APPLICATION, &Application::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, connect(ui->instanceAccountSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&InstanceSettingsPage::changeInstanceAccount); &InstanceSettingsPage::changeInstanceAccount);
connect(ui->useNativeGLFWCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeGLFWChanged);
connect(ui->useNativeOpenALCheck, &QAbstractButton::toggled, this, &InstanceSettingsPage::onUseNativeOpenALChanged);
loadSettings(); loadSettings();
updateThresholds(); updateThresholds();
@ -198,11 +202,15 @@ void InstanceSettingsPage::applySettings()
bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked(); bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
m_settings->set("OverrideNativeWorkarounds", workarounds); m_settings->set("OverrideNativeWorkarounds", workarounds);
if (workarounds) { if (workarounds) {
m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->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 { } else {
m_settings->reset("UseNativeOpenAL");
m_settings->reset("UseNativeGLFW"); m_settings->reset("UseNativeGLFW");
m_settings->reset("CustomGLFWPath");
m_settings->reset("UseNativeOpenAL");
m_settings->reset("CustomOpenALPath");
} }
// Performance // Performance
@ -245,14 +253,6 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("InstanceAccountId"); 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 // FIXME: This should probably be called by a signal instead
m_instance->updateRuntimeContext(); m_instance->updateRuntimeContext();
} }
@ -312,7 +312,19 @@ void InstanceSettingsPage::loadSettings()
// Workarounds // Workarounds
ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool()); ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").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->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 // Performance
ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool()); ui->perfomanceGroupBox->setChecked(m_settings->get("OverridePerformance").toBool());
@ -344,10 +356,6 @@ void InstanceSettingsPage::loadSettings()
ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool()); ui->instanceAccountGroupBox->setChecked(m_settings->get("UseAccountForInstance").toBool());
updateAccountsMenu(); 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() void InstanceSettingsPage::on_javaDetectBtn_clicked()
@ -408,6 +416,16 @@ void InstanceSettingsPage::on_javaTestBtn_clicked()
checker->run(); checker->run();
} }
void InstanceSettingsPage::onUseNativeGLFWChanged(bool checked)
{
ui->lineEditGLFWPath->setEnabled(checked);
}
void InstanceSettingsPage::onUseNativeOpenALChanged(bool checked)
{
ui->lineEditOpenALPath->setEnabled(checked);
}
void InstanceSettingsPage::updateAccountsMenu() void InstanceSettingsPage::updateAccountsMenu()
{ {
ui->instanceAccountSelector->clear(); ui->instanceAccountSelector->clear();
@ -460,6 +478,7 @@ void InstanceSettingsPage::updateThresholds()
{ {
auto sysMiB = Sys::getSystemRam() / Sys::mebibyte; auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
unsigned int maxMem = ui->maxMemSpinBox->value(); unsigned int maxMem = ui->maxMemSpinBox->value();
unsigned int minMem = ui->minMemSpinBox->value();
QString iconName; QString iconName;
@ -469,6 +488,9 @@ void InstanceSettingsPage::updateThresholds()
} else if (maxMem > (sysMiB * 0.9)) { } else if (maxMem > (sysMiB * 0.9)) {
iconName = "status-yellow"; iconName = "status-yellow";
ui->labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation approaches your system memory capacity.")); 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 { } else {
iconName = "status-good"; iconName = "status-good";
ui->labelMaxMemIcon->setToolTip(""); ui->labelMaxMemIcon->setToolTip("");

View File

@ -71,6 +71,9 @@ class InstanceSettingsPage : public QWidget, public BasePage {
void on_javaBrowseBtn_clicked(); void on_javaBrowseBtn_clicked();
void on_maxMemSpinBox_valueChanged(int i); void on_maxMemSpinBox_valueChanged(int i);
void onUseNativeGLFWChanged(bool checked);
void onUseNativeOpenALChanged(bool checked);
void applySettings(); void applySettings();
void loadSettings(); void loadSettings();

View File

@ -443,18 +443,52 @@
<property name="checked"> <property name="checked">
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QGridLayout" name="gridLayout_3">
<item> <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"> <widget class="QCheckBox" name="useNativeGLFWCheck">
<property name="text"> <property name="text">
<string>Use system installation of GLFW</string> <string>Use system installation of GLFW</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="1" column="1">
<widget class="QCheckBox" name="useNativeOpenALCheck"> <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"> <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> </property>
</widget> </widget>
</item> </item>
@ -549,31 +583,6 @@
<string>Miscellaneous</string> <string>Miscellaneous</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_9"> <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> <item>
<widget class="QGroupBox" name="gameTimeGroupBox"> <widget class="QGroupBox" name="gameTimeGroupBox">
<property name="enabled"> <property name="enabled">

View File

@ -3,7 +3,7 @@
* Prism Launcher - Minecraft Launcher * Prism Launcher - Minecraft Launcher
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org> * Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net> * 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 * 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 * 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 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; }
return parent.isValid() ? 0 : m_servers.size();
}
int columnCount(const QModelIndex& parent) const override
{
return parent.isValid() ? 0 : COLUMN_COUNT;
}
Server* at(int index) Server* at(int index)
{ {
@ -445,10 +439,7 @@ class ServersModel : public QAbstractListModel {
qDebug() << "Changed:" << path; qDebug() << "Changed:" << path;
load(); load();
} }
void fileChanged(const QString& path) void fileChanged(const QString& path) { qDebug() << "Changed:" << path; }
{
qDebug() << "Changed:" << path;
}
private slots: private slots:
void save_internal() void save_internal()
@ -492,10 +483,7 @@ class ServersModel : public QAbstractListModel {
m_saveTimer.stop(); m_saveTimer.stop();
} }
bool saveIsScheduled() const bool saveIsScheduled() const { return m_dirty; }
{
return m_dirty;
}
void updateFSObserver() void updateFSObserver()
{ {
@ -743,7 +731,7 @@ void ServersPage::on_actionMove_Down_triggered()
void ServersPage::on_actionJoin_triggered() void ServersPage::on_actionJoin_triggered()
{ {
const auto& address = m_model->at(currentServer)->m_address; 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" #include "ServersPage.moc"

View File

@ -166,14 +166,17 @@ VersionPage::VersionPage(MinecraftInstance* inst, QWidget* parent) : QMainWindow
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection); ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu); ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel(); auto smodel = ui->packageView->selectionModel();
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent); connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls); connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
updateVersionControls(); updateVersionControls();
preselect(0); preselect(0);
connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu); 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); connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
} }
@ -408,7 +411,7 @@ void VersionPage::on_actionDownload_All_triggered()
if (!APPLICATION->accounts()->anyAccountIsValid()) { if (!APPLICATION->accounts()->anyAccountIsValid()) {
CustomMessageBox::selectable(this, tr("Error"), CustomMessageBox::selectable(this, tr("Error"),
tr("Cannot download Minecraft or update instances unless you have at least " 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 your Microsoft or Mojang account."),
QMessageBox::Warning) QMessageBox::Warning)
->show(); ->show();
return; return;

View File

@ -127,6 +127,9 @@ void CustomPage::loaderFilterChanged()
ui->loaderVersionList->setEmptyString(tr("No mod loader is selected.")); ui->loaderVersionList->setEmptyString(tr("No mod loader is selected."));
ui->loaderVersionList->setEmptyMode(VersionListView::String); ui->loaderVersionList->setEmptyMode(VersionListView::String);
return; return;
} else if (ui->neoForgeFilter->isChecked()) {
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
m_selectedLoader = "net.neoforged";
} else if (ui->forgeFilter->isChecked()) { } else if (ui->forgeFilter->isChecked()) {
ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); ui->loaderVersionList->setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
m_selectedLoader = "net.minecraftforge"; m_selectedLoader = "net.minecraftforge";

View File

@ -194,6 +194,16 @@
</attribute> </attribute>
</widget> </widget>
</item> </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> <item>
<widget class="QRadioButton" name="forgeFilter"> <widget class="QRadioButton" name="forgeFilter">
<property name="text"> <property name="text">

View File

@ -35,13 +35,21 @@
*/ */
#include "ImportPage.h" #include "ImportPage.h"
#include "ui/dialogs/ProgressDialog.h"
#include "ui_ImportPage.h" #include "ui_ImportPage.h"
#include <QFileDialog> #include <QFileDialog>
#include <QValidator> #include <QValidator>
#include <utility>
#include "ui/dialogs/CustomMessageBox.h"
#include "ui/dialogs/NewInstanceDialog.h" #include "ui/dialogs/NewInstanceDialog.h"
#include "modplatform/flame/FlameAPI.h"
#include "Json.h"
#include "InstanceImportTask.h" #include "InstanceImportTask.h"
class UrlValidator : public QValidator { class UrlValidator : public QValidator {
@ -106,10 +114,61 @@ void ImportPage::updateState()
bool isMRPack = fi.suffix() == "mrpack"; bool isMRPack = fi.suffix() == "mrpack";
if (fi.exists() && (isZip || isMRPack)) { if (fi.exists() && (isZip || isMRPack)) {
QFileInfo file_info(url.fileName()); auto extra_info = QMap(m_extra_info);
dialog->setSuggestedPack(file_info.completeBaseName(), new InstanceImportTask(url, this)); qDebug() << "Pack Extra Info" << extra_info << m_extra_info;
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url, this, std::move(extra_info)));
dialog->setSuggestedIcon("default"); 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 { } else {
if (input.endsWith("?client=y")) { if (input.endsWith("?client=y")) {
input.chop(9); input.chop(9);
@ -118,7 +177,8 @@ void ImportPage::updateState()
} }
// hook, line and sinker. // hook, line and sinker.
QFileInfo fi(url.fileName()); 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"); dialog->setSuggestedIcon("default");
} }
} else { } else {
@ -132,6 +192,12 @@ void ImportPage::setUrl(const QString& url)
updateState(); updateState();
} }
void ImportPage::setExtraInfo(const QMap<QString, QString>& extra_info)
{
m_extra_info = extra_info;
updateState();
}
void ImportPage::on_modpackBtn_clicked() void ImportPage::on_modpackBtn_clicked()
{ {
auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString();

View File

@ -62,7 +62,7 @@ class ImportPage : public QWidget, public BasePage {
void setUrl(const QString& url); void setUrl(const QString& url);
void openedImpl() override; void openedImpl() override;
void setExtraInfo(const QMap<QString, QString>& extra_info);
private slots: private slots:
void on_modpackBtn_clicked(); void on_modpackBtn_clicked();
void updateState(); void updateState();
@ -73,4 +73,5 @@ class ImportPage : public QWidget, public BasePage {
private: private:
Ui::ImportPage* ui = nullptr; Ui::ImportPage* ui = nullptr;
NewInstanceDialog* dialog = nullptr; NewInstanceDialog* dialog = nullptr;
QMap<QString, QString> m_extra_info = {};
}; };

View File

@ -40,7 +40,7 @@
<item> <item>
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>- CurseForge modpacks (ZIP)</string> <string>- CurseForge modpacks (ZIP / curseforge:// URL)</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>

View File

@ -33,7 +33,7 @@ ResourceAPI::SearchArgs ModModel::createSearchArguments()
auto sort = getCurrentSortingMethodByIndex(); 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) ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& entry)
@ -48,7 +48,7 @@ ResourceAPI::VersionSearchArgs ModModel::createVersionsArguments(QModelIndex& en
if (!m_filter->versions.empty()) if (!m_filter->versions.empty())
versions = m_filter->versions; versions = m_filter->versions;
return { pack, versions, profile->getModLoaders() }; return { pack, versions, profile->getSupportedModLoaders() };
} }
ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry) ResourceAPI::ProjectInfoArgs ModModel::createInfoArguments(QModelIndex& entry)

View File

@ -124,8 +124,7 @@ void ModPage::updateVersionList()
auto version = current_pack->versions[i]; auto version = current_pack->versions[i];
bool valid = false; bool valid = false;
for (auto& mcVer : m_filter->versions) { for (auto& mcVer : m_filter->versions) {
// NOTE: Flame doesn't care about loader, so passing it changes nothing. if (validateVersion(version, mcVer.toString(), packProfile->getSupportedModLoaders())) {
if (validateVersion(version, mcVer.toString(), packProfile->getModLoaders())) {
valid = true; valid = true;
break; break;
} }

View File

@ -55,7 +55,7 @@ class ModPage : public ResourcePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, virtual auto validateVersion(ModPlatform::IndexedVersion& ver,
QString mineVer, 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; }; [[nodiscard]] bool supportsFiltering() const override { return true; };
auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; } auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
#include "ShaderPackPage.h" #include "ShaderPackPage.h"
#include "modplatform/ModIndex.h"
#include "ui_ResourcePage.h" #include "ui_ResourcePage.h"
#include "ShaderPackModel.h" #include "ShaderPackModel.h"
@ -48,7 +49,7 @@ void ShaderPackResourcePage::addResourceToPage(ModPlatform::IndexedPack::Ptr pac
const std::shared_ptr<ResourceFolderModel> base_model) const std::shared_ptr<ResourceFolderModel> base_model)
{ {
QString custom_target_folder; QString custom_target_folder;
if (version.loaders.contains(QStringLiteral("canvas"))) if (version.loaders & ModPlatform::Cauldron)
custom_target_folder = QStringLiteral("resourcepacks"); custom_target_folder = QStringLiteral("resourcepacks");
m_model->addPack(pack, version, base_model, false, custom_target_folder); m_model->addPack(pack, version, base_model, false, custom_target_folder);
} }

View File

@ -31,7 +31,7 @@ void FlameModModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonAr
auto FlameModModel::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion 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 auto FlameModModel::documentToArray(QJsonDocument& obj) const -> QJsonArray

Some files were not shown because too many files have changed in this diff Show More