Merge branch 'develop' of https://github.com/PrismLauncher/PrismLauncher into feat/acknowledge_release_type

Signed-off-by: Trial97 <alexandru.tripon97@gmail.com>
This commit is contained in:
Trial97
2023-09-28 22:50:12 +03:00
323 changed files with 5800 additions and 2912 deletions

View File

@ -9,7 +9,6 @@
* Copyright (C) 2022 Tayou <git@tayou.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com>
* Copyright (C) 2023 seth <getchoo at tuta dot io>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -136,11 +135,7 @@
#endif
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
#include <windows.h>
#include "WindowsConsole.h"
#endif
#define STRINGIFY(x) #x
@ -172,22 +167,8 @@ void appDebugOutput(QtMsgType type, const QMessageLogContext& context, const QSt
Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
// attach the parent console
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// if attach succeeds, reopen and sync all the i/o
if (freopen("CON", "w", stdout)) {
std::cout.sync_with_stdio();
}
if (freopen("CON", "w", stderr)) {
std::cerr.sync_with_stdio();
}
if (freopen("CON", "r", stdin)) {
std::cin.sync_with_stdio();
}
auto out = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written;
const char* endline = "\n";
WriteConsole(out, endline, strlen(endline), &written, NULL);
// attach the parent console if stdout not already captured
if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@ -212,8 +193,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
{ { "s", "server" }, "Join the specified server on launch (only valid in combination with --launch)", "address" },
{ { "a", "profile" }, "Use the account specified by its profile name (only valid in combination with --launch)", "profile" },
{ "alive", "Write a small '" + liveCheckFile + "' file after the launcher starts" },
{ { "I", "import" }, "Import instance from specified zip (local path or URL)", "file" },
{ { "I", "import" }, "Import instance or resource from specified local path or URL", "url" },
{ "show", "Opens the window for the specified instance (by instance ID)", "show" } });
// Has to be positional for some OS to handle that properly
parser.addPositionalArgument("URL", "Import the resource(s) at the given URL(s) (same as -I / --import)", "[URL...]");
parser.addHelpOption();
parser.addVersionOption();
@ -226,13 +210,13 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_instanceIdToShowWindowOf = parser.value("show");
for (auto zip_path : parser.values("import")) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
for (auto url : parser.values("import")) {
m_urlsToImport.append(normalizeImportUrl(url));
}
// treat unspecified positional arguments as import urls
for (auto zip_path : parser.positionalArguments()) {
m_zipsToImport.append(QUrl::fromLocalFile(QFileInfo(zip_path).absoluteFilePath()));
for (auto url : parser.positionalArguments()) {
m_urlsToImport.append(normalizeImportUrl(url));
}
// error if --launch is missing with --server or --profile
@ -331,11 +315,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
activate.command = "activate";
m_peerInstance->sendMessage(activate.serialize(), timeout);
if (!m_zipsToImport.isEmpty()) {
for (auto zip_url : m_zipsToImport) {
if (!m_urlsToImport.isEmpty()) {
for (auto url : m_urlsToImport) {
ApplicationMessage import;
import.command = "import";
import.args.insert("path", zip_url.toString());
import.args.insert("url", url.toString());
m_peerInstance->sendMessage(import.serialize(), timeout);
}
}
@ -510,7 +494,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings.reset(new INISettingsObject({ BuildConfig.LAUNCHER_CONFIGFILE, "polymc.cfg", "multimc.cfg" }, this));
// Theming
m_settings->registerSetting("IconTheme", QString("pe_colored"));
m_settings->registerSetting("IconTheme", QString());
m_settings->registerSetting("ApplicationTheme", QString());
m_settings->registerSetting("BackgroundCat", QString("kitteh"));
@ -595,12 +579,11 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("IgnoreJavaCompatibility", false);
m_settings->registerSetting("IgnoreJavaWizard", false);
// Mod loader settings
m_settings->registerSetting("DisableQuiltBeacon", false);
// Native library workarounds
m_settings->registerSetting("UseNativeOpenAL", false);
m_settings->registerSetting("CustomOpenALPath", "");
m_settings->registerSetting("UseNativeGLFW", false);
m_settings->registerSetting("CustomGLFWPath", "");
// Peformance related options
m_settings->registerSetting("EnableFeralGamemode", false);
@ -611,6 +594,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
m_settings->registerSetting("ShowGameTime", true);
m_settings->registerSetting("ShowGlobalGameTime", true);
m_settings->registerSetting("RecordGameTime", true);
m_settings->registerSetting("ShowGameTimeWithoutDays", false);
// Minecraft mods
m_settings->registerSetting("ModMetadataDisabled", false);
@ -772,7 +756,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
// Themes
m_themeManager = std::make_unique<ThemeManager>(m_mainWindow);
m_themeManager = std::make_unique<ThemeManager>();
// initialize and load all instances
{
@ -858,14 +842,15 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv)
}
});
applyCurrentlySelectedTheme(true);
updateCapabilities();
detectLibraries();
if (createSetupWizard()) {
return;
}
m_themeManager->applyCurrentlySelectedTheme(true);
performMainStartupAction();
}
@ -891,10 +876,20 @@ bool Application::createSetupWizard()
}();
bool languageRequired = settings()->get("Language").toString().isEmpty();
bool pasteInterventionRequired = settings()->get("PastebinURL") != "";
bool themeInterventionRequired = settings()->get("ApplicationTheme") == "";
bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString());
bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString());
bool themeInterventionRequired = !validWidgets || !validIcons;
bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired;
if (wizardRequired) {
// set default theme after going into theme wizard
if (!validIcons)
settings()->set("IconTheme", QString("pe_colored"));
if (!validWidgets)
settings()->set("ApplicationTheme", QString("system"));
m_themeManager->applyCurrentlySelectedTheme(true);
m_setupWizard = new SetupWizard(nullptr);
if (languageRequired) {
m_setupWizard->addPage(new LanguageWizardPage(m_setupWizard));
@ -909,9 +904,9 @@ bool Application::createSetupWizard()
}
if (themeInterventionRequired) {
settings()->set("ApplicationTheme", QString("system")); // set default theme after going into theme wizard
m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard));
}
connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished);
m_setupWizard->show();
return true;
@ -970,7 +965,7 @@ void Application::performMainStartupAction()
qDebug() << " Launching with account" << m_profileToUse;
}
launch(inst, true, false, nullptr, serverToJoin, accountToUse);
launch(inst, true, false, serverToJoin, accountToUse);
return;
}
}
@ -987,9 +982,9 @@ void Application::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
if (!m_zipsToImport.isEmpty()) {
qDebug() << "<> Importing from zip:" << m_zipsToImport;
m_mainWindow->processURLs(m_zipsToImport);
if (!m_urlsToImport.isEmpty()) {
qDebug() << "<> Importing from url:" << m_urlsToImport;
m_mainWindow->processURLs(m_urlsToImport);
}
}
@ -1031,12 +1026,12 @@ void Application::messageReceived(const QByteArray& message)
if (command == "activate") {
showMainWindow();
} else if (command == "import") {
QString path = received.args["path"];
if (path.isEmpty()) {
QString url = received.args["url"];
if (url.isEmpty()) {
qWarning() << "Received" << command << "message without a zip path/URL.";
return;
}
m_mainWindow->processURLs({ QUrl::fromLocalFile(QFileInfo(path).absoluteFilePath()) });
m_mainWindow->processURLs({ normalizeImportUrl(url) });
} else if (command == "launch") {
QString id = received.args["id"];
QString server = received.args["server"];
@ -1069,7 +1064,7 @@ void Application::messageReceived(const QByteArray& message)
}
}
launch(instance, true, false, nullptr, serverObject, accountObject);
launch(instance, true, false, serverObject, accountObject);
} else {
qWarning() << "Received invalid message" << message;
}
@ -1088,26 +1083,6 @@ std::shared_ptr<JavaInstallList> Application::javalist()
return m_javalist;
}
QList<ITheme*> Application::getValidApplicationThemes()
{
return m_themeManager->getValidApplicationThemes();
}
void Application::applyCurrentlySelectedTheme(bool initial)
{
m_themeManager->applyCurrentlySelectedTheme(initial);
}
void Application::setApplicationTheme(const QString& name)
{
m_themeManager->setApplicationTheme(name);
}
void Application::setIconTheme(const QString& name)
{
m_themeManager->setIconTheme(name);
}
QIcon Application::getThemedIcon(const QString& name)
{
if (name == "logo") {
@ -1116,16 +1091,6 @@ QIcon Application::getThemedIcon(const QString& name)
return QIcon::fromTheme(name);
}
QList<CatPack*> Application::getValidCatPacks()
{
return m_themeManager->getValidCatPacks();
}
QString Application::getCatPack(QString catName)
{
return m_themeManager->getCatPack(catName);
}
bool Application::openJsonEditor(const QString& filename)
{
const QString file = QDir::current().absoluteFilePath(filename);
@ -1140,7 +1105,6 @@ bool Application::openJsonEditor(const QString& filename)
bool Application::launch(InstancePtr instance,
bool online,
bool demo,
BaseProfilerFactory* profiler,
MinecraftServerTargetPtr serverToJoin,
MinecraftAccountPtr accountToUse)
{
@ -1148,7 +1112,7 @@ bool Application::launch(InstancePtr instance,
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
} else if (instance->canLaunch()) {
auto& extras = m_instanceExtras[instance->id()];
auto& window = extras.window;
auto window = extras.window;
if (window) {
if (!window->saveAll()) {
return false;
@ -1159,7 +1123,7 @@ bool Application::launch(InstancePtr instance,
controller->setInstance(instance);
controller->setOnline(online);
controller->setDemo(demo);
controller->setProfiler(profiler);
controller->setProfiler(profilers().value(instance->settings()->get("Profiler").toString(), nullptr).get());
controller->setServerToJoin(serverToJoin);
controller->setAccountToUse(accountToUse);
if (window) {
@ -1451,6 +1415,15 @@ void Application::updateCapabilities()
#endif
}
void Application::detectLibraries()
{
#ifdef Q_OS_LINUX
m_detectedGLFWPath = MangoHud::findLibrary(BuildConfig.GLFW_LIBRARY_NAME);
m_detectedOpenALPath = MangoHud::findLibrary(BuildConfig.OPENAL_LIBRARY_NAME);
qDebug() << "Detected native libraries:" << m_detectedGLFWPath << m_detectedOpenALPath;
#endif
}
QString Application::getJarPath(QString jarFile)
{
QStringList potentialPaths = {
@ -1629,3 +1602,13 @@ void Application::triggerUpdateCheck()
qDebug() << "Updater not available.";
}
}
QUrl Application::normalizeImportUrl(QString const& url)
{
auto local_file = QFileInfo(url);
if (local_file.exists()) {
return QUrl::fromLocalFile(local_file.absoluteFilePath());
} else {
return QUrl::fromUserInput(url);
}
}

View File

@ -71,6 +71,7 @@ class TranslationsModel;
class ITheme;
class MCEditTool;
class ThemeManager;
class IconTheme;
namespace Meta {
class Index;
@ -109,17 +110,7 @@ class Application : public QApplication {
QIcon getThemedIcon(const QString& name);
void setIconTheme(const QString& name);
void applyCurrentlySelectedTheme(bool initial = false);
QList<ITheme*> getValidApplicationThemes();
void setApplicationTheme(const QString& name);
QList<CatPack*> getValidCatPacks();
QString getCatPack(QString catName = "");
ThemeManager* themeManager() { return m_themeManager.get(); }
shared_qobject_ptr<ExternalUpdater> updater() { return m_updater; }
@ -151,6 +142,8 @@ class Application : public QApplication {
void updateCapabilities();
void detectLibraries();
/*!
* Finds and returns the full path to a jar file.
* Returns a null-string if it could not be found.
@ -186,6 +179,8 @@ class Application : public QApplication {
int suitableMaxMem();
QUrl normalizeImportUrl(QString const& url);
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@ -200,7 +195,6 @@ class Application : public QApplication {
bool launch(InstancePtr instance,
bool online = true,
bool demo = false,
BaseProfilerFactory* profiler = nullptr,
MinecraftServerTargetPtr serverToJoin = nullptr,
MinecraftAccountPtr accountToUse = nullptr);
bool kill(InstancePtr instance);
@ -284,11 +278,13 @@ class Application : public QApplication {
SetupWizard* m_setupWizard = nullptr;
public:
QString m_detectedGLFWPath;
QString m_detectedOpenALPath;
QString m_instanceIdToLaunch;
QString m_serverToJoin;
QString m_profileToUse;
bool m_liveCheck = false;
QList<QUrl> m_zipsToImport;
QList<QUrl> m_urlsToImport;
QString m_instanceIdToShowWindowOf;
std::unique_ptr<QFile> logFile;
};

View File

@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -100,6 +101,8 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("ManagedPackName", "");
m_settings->registerSetting("ManagedPackVersionID", "");
m_settings->registerSetting("ManagedPackVersionName", "");
m_settings->registerSetting("Profiler", "");
}
QString BaseInstance::getPreLaunchCommand()

View File

@ -3,6 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -38,6 +39,7 @@
#include <cassert>
#include <QDateTime>
#include <QMenu>
#include <QObject>
#include <QProcess>
#include <QSet>
@ -86,7 +88,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance(){};
virtual ~BaseInstance() {}
virtual void saveNow() = 0;
@ -146,7 +148,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; };
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString& line, MessageLevel::Enum level) { return level; }
virtual QStringList extraArguments();
@ -246,6 +248,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
virtual void populateLaunchMenu(QMenu* menu) = 0;
bool reloadSettings();
/**
@ -267,7 +271,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
protected:
void changeStatus(Status newStatus);
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); };
SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); }
bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; }
void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; }
@ -282,6 +286,8 @@ class BaseInstance : public QObject, public std::enable_shared_from_this<BaseIns
void runningStatusChanged(bool running);
void profilerChanged();
void statusChanged(Status from, Status to);
protected slots:

View File

@ -43,9 +43,8 @@ class BaseVersion {
* the kind of version this is (Stable, Beta, Snapshot, whatever)
*/
virtual QString typeString() const = 0;
virtual bool operator<(BaseVersion& a) { return name() < a.name(); };
virtual bool operator>(BaseVersion& a) { return name() > a.name(); };
virtual bool operator<(BaseVersion& a) { return name() < a.name(); }
virtual bool operator>(BaseVersion& a) { return name() > a.name(); }
};
Q_DECLARE_METATYPE(BaseVersion::Ptr)

View File

@ -136,6 +136,15 @@ set(NET_SOURCES
net/Validator.h
net/Upload.cpp
net/Upload.h
net/HeaderProxy.h
net/RawHeaderProxy.h
net/ApiHeaderProxy.h
net/ApiDownload.h
net/ApiDownload.cpp
net/ApiUpload.cpp
net/ApiUpload.h
net/NetRequest.cpp
net/NetRequest.h
)
# Game launch logic
@ -566,6 +575,9 @@ set(ATLAUNCHER_SOURCES
)
set(LINKEXE_SOURCES
WindowsConsole.cpp
WindowsConsole.h
filelink/FileLink.h
filelink/FileLink.cpp
FileSystem.h
@ -762,6 +774,8 @@ SET(LAUNCHER_SOURCES
ui/themes/ITheme.h
ui/themes/SystemTheme.cpp
ui/themes/SystemTheme.h
ui/themes/IconTheme.cpp
ui/themes/IconTheme.h
ui/themes/ThemeManager.cpp
ui/themes/ThemeManager.h
ui/themes/CatPack.cpp
@ -962,6 +976,8 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ChooseProviderDialog.cpp
ui/dialogs/ModUpdateDialog.cpp
ui/dialogs/ModUpdateDialog.h
ui/dialogs/InstallLoaderDialog.cpp
ui/dialogs/InstallLoaderDialog.h
# GUI - widgets
ui/widgets/Common.cpp
@ -1026,6 +1042,14 @@ SET(LAUNCHER_SOURCES
ui/instanceview/VisualGroup.h
)
if(WIN32)
set(LAUNCHER_SOURCES
WindowsConsole.cpp
WindowsConsole.h
${LAUNCHER_SOURCES}
)
endif()
qt_wrap_ui(LAUNCHER_UI
ui/MainWindow.ui
ui/setupwizard/PasteWizardPage.ui
@ -1109,8 +1133,15 @@ if(WIN32)
set(LAUNCHER_RCS ${CMAKE_CURRENT_BINARY_DIR}/../${Launcher_Branding_WindowsRC})
endif()
include(CompilerWarnings)
# Add executable
add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES})
set_project_warnings(Launcher_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}")
target_compile_definitions(Launcher_logic PUBLIC LAUNCHER_APPLICATION)
target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(Launcher_logic
systeminfo
@ -1195,6 +1226,11 @@ install(TARGETS ${Launcher_Name}
if(WIN32)
add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
set_project_warnings(filelink_logic
"${Launcher_MSVC_WARNINGS}"
"${Launcher_CLANG_WARNINGS}"
"${Launcher_GCC_WARNINGS}")
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
systeminfo

View File

@ -96,12 +96,12 @@ bool IndirectOpen(T callable, qint64* pid_forked = nullptr)
#endif
namespace DesktopServices {
bool openDirectory(const QString& path, bool ensureExists)
bool openDirectory(const QString& path, [[maybe_unused]] bool ensureExists)
{
qDebug() << "Opening directory" << path;
QDir parentPath;
QDir dir(path);
if (!dir.exists()) {
if (ensureExists && !dir.exists()) {
parentPath.mkpath(dir.absolutePath());
}
auto f = [&]() { return QDesktopServices::openUrl(QUrl::fromLocalFile(dir.absolutePath())); };

View File

@ -267,10 +267,7 @@ bool FileIgnoreProxy::filterAcceptsRow(int sourceRow, const QModelIndex& sourceP
bool FileIgnoreProxy::ignoreFile(QFileInfo fileInfo) const
{
auto fileName = fileInfo.fileName();
auto path = relPath(fileInfo.absoluteFilePath());
return std::any_of(m_ignoreFiles.cbegin(), m_ignoreFiles.cend(), [fileName](auto iFileName) { return fileName == iFileName; }) ||
m_ignoreFilePaths.covers(path);
return m_ignoreFiles.contains(fileInfo.fileName()) || m_ignoreFilePaths.covers(relPath(fileInfo.absoluteFilePath()));
}
bool FileIgnoreProxy::filterFile(const QString& fileName) const

View File

@ -112,8 +112,8 @@ class copy : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalCopied() { return m_copied; }
int totalFailed() { return m_failedPaths.length(); }
qsizetype totalCopied() { return m_copied; }
qsizetype totalFailed() { return m_failedPaths.length(); }
QStringList failed() { return m_failedPaths; }
signals:
@ -130,7 +130,7 @@ class copy : public QObject {
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
int m_copied;
qsizetype m_copied;
QStringList m_failedPaths;
};
@ -474,8 +474,8 @@ class clone : public QObject {
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
int totalCloned() { return m_cloned; }
int totalFailed() { return m_failedClones.length(); }
qsizetype totalCloned() { return m_cloned; }
qsizetype totalFailed() { return m_failedClones.length(); }
QList<QPair<QString, QString>> failed() { return m_failedClones; }
@ -491,7 +491,7 @@ class clone : public QObject {
bool m_whitelist = false;
QDir m_src;
QDir m_dst;
int m_cloned;
qsizetype m_cloned;
QList<QPair<QString, QString>> m_failedClones;
};

View File

@ -16,6 +16,12 @@ bool ExactFilter::accepts(const QString& value)
return value == pattern;
}
ExactIfPresentFilter::ExactIfPresentFilter(const QString& pattern) : pattern(pattern) {}
bool ExactIfPresentFilter::accepts(const QString& value)
{
return value.isEmpty() || value == pattern;
}
RegexpFilter::RegexpFilter(const QString& regexp, bool invert) : invert(invert)
{
pattern.setPattern(regexp);

View File

@ -29,6 +29,16 @@ class ExactFilter : public Filter {
QString pattern;
};
class ExactIfPresentFilter : public Filter {
public:
ExactIfPresentFilter(const QString& pattern);
~ExactIfPresentFilter() override = default;
bool accepts(const QString& value) override;
private:
QString pattern;
};
class RegexpFilter : public Filter {
public:
RegexpFilter(const QString& regexp, bool invert);

View File

@ -50,6 +50,9 @@
#include "modplatform/technic/TechnicPackProcessor.h"
#include "settings/INISettingsObject.h"
#include "tasks/Task.h"
#include "net/ApiDownload.h"
#include <QtConcurrentRun>
#include <algorithm>
@ -88,25 +91,27 @@ void InstanceImportTask::executeTask()
setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
m_downloadRequired = true;
const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path());
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_archivePath = entry->getFullPath();
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
m_filesNetJob->start();
downloadFromUrl();
}
}
void InstanceImportTask::downloadFromUrl()
{
const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
auto entry = APPLICATION->metacache()->resolveEntry("general", path);
entry->setStale(true);
m_filesNetJob.reset(new NetJob(tr("Modpack download"), APPLICATION->network()));
m_filesNetJob->addNetAction(Net::ApiDownload::makeCached(m_sourceUrl, entry));
m_archivePath = entry->getFullPath();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &InstanceImportTask::propagateStepProgress);
connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed);
connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted);
m_filesNetJob->start();
}
void InstanceImportTask::downloadSucceeded()
{
processZipPack();

View File

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

View File

@ -96,7 +96,11 @@ Qt::DropActions InstanceList::supportedDropActions() const
return Qt::MoveAction;
}
bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const
bool InstanceList::canDropMimeData(const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent) const
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@ -104,7 +108,11 @@ bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action,
return false;
}
bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
bool InstanceList::dropMimeData(const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
[[maybe_unused]] const QModelIndex& parent)
{
if (data && data->hasFormat("application/x-instanceid")) {
return true;
@ -751,7 +759,7 @@ void InstanceList::instanceDirContentsChanged(const QString& path)
emit instancesChanged();
}
void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value)
void InstanceList::on_InstFolderChanged([[maybe_unused]] const Setting& setting, QVariant value)
{
QString newInstDir = QDir(value.toString()).canonicalPath();
if (newInstDir != m_instDir) {
@ -789,7 +797,7 @@ class InstanceStaging : public Task {
, m_groupName(std::move(groupName))
{
m_child.reset(child);
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded);
connect(child, &Task::succeeded, this, &InstanceStaging::childSucceeded);
connect(child, &Task::failed, this, &InstanceStaging::childFailed);
connect(child, &Task::aborted, this, &InstanceStaging::childAborted);
connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable);
@ -797,7 +805,7 @@ class InstanceStaging : public Task {
connect(child, &Task::details, this, &InstanceStaging::setDetails);
connect(child, &Task::progress, this, &InstanceStaging::setProgress);
connect(child, &Task::stepProgress, this, &InstanceStaging::propagateStepProgress);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded);
connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceeded);
}
virtual ~InstanceStaging(){};
@ -819,7 +827,7 @@ class InstanceStaging : public Task {
QStringList warnings() const override { return m_child->warnings(); }
private slots:
void childSucceded()
void childSucceeded()
{
unsigned sleepTime = backoff();
if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, *m_child.get())) {

View File

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

View File

@ -65,14 +65,9 @@ QStringList LoggedProcess::reprocess(const QByteArray& data, QTextDecoder& decod
m_leftover_line = "";
}
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts);
#else
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts);
#endif
auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed);
if (!str.endsWith(QChar::LineFeed))
m_leftover_line = lines.takeLast();
m_leftover_line = lines.takeLast();
return lines;
}

View File

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

View File

@ -20,7 +20,7 @@
namespace Time {
QString prettifyDuration(int64_t duration);
QString prettifyDuration(int64_t duration, bool noDays = false);
/**
* @brief Returns a string with short form time duration ie. `2days 1h3m4s56.0ms`.

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QDebug>
#include <QDir>
#include <QString>
#include <QStringList>
@ -26,6 +27,15 @@
#include "Json.h"
#include "MangoHud.h"
#ifdef __GLIBC__
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#define UNDEF_GNU_SOURCE
#endif
#include <dlfcn.h>
#include <linux/limits.h>
#endif
namespace MangoHud {
QString getLibraryString()
@ -106,4 +116,37 @@ QString getLibraryString()
return QString();
}
QString findLibrary(QString libName)
{
#ifdef __GLIBC__
const char* library = libName.toLocal8Bit().constData();
void* handle = dlopen(library, RTLD_NOW);
if (!handle) {
qCritical() << "dlopen() failed:" << dlerror();
return {};
}
char path[PATH_MAX];
if (dlinfo(handle, RTLD_DI_ORIGIN, path) == -1) {
qCritical() << "dlinfo() failed:" << dlerror();
dlclose(handle);
return {};
}
auto fullPath = FS::PathCombine(QString(path), libName);
dlclose(handle);
return fullPath;
#else
qWarning() << "MangoHud::findLibrary is not implemented on this platform";
return {};
#endif
}
} // namespace MangoHud
#ifdef UNDEF_GNU_SOURCE
#undef _GNU_SOURCE
#undef UNDEF_GNU_SOURCE
#endif

View File

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

View File

@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -52,7 +53,7 @@ class NullInstance : public BaseInstance {
QSet<QString> traits() const override { return {}; };
QString instanceConfigFolder() const override { return instanceRoot(); };
shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override { return nullptr; }
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override { return nullptr; }
shared_qobject_ptr<Task> createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; }
QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); }
QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); }
QMap<QString, QString> getVariables() override { return QMap<QString, QString>(); }
@ -62,6 +63,7 @@ class NullInstance : public BaseInstance {
bool canExport() const override { return false; }
bool canEdit() const override { return false; }
bool canLaunch() const override { return false; }
void populateLaunchMenu(QMenu* menu) override {}
QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{
QStringList out;

View File

@ -12,7 +12,7 @@ struct PatchProblem {
class ProblemProvider {
public:
virtual ~ProblemProvider(){};
virtual ~ProblemProvider() {}
virtual const QList<PatchProblem> getProblems() const = 0;
virtual ProblemSeverity getProblemSeverity() const = 0;
};

View File

@ -90,7 +90,7 @@ void RecursiveFileSystemWatcher::fileChange(const QString& path)
{
emit fileChanged(path);
}
void RecursiveFileSystemWatcher::directoryChange(const QString& path)
void RecursiveFileSystemWatcher::directoryChange([[maybe_unused]] const QString& path)
{
setFiles(scanRecursive(m_root));
}

View File

@ -24,6 +24,8 @@
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h"
ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
ModPlatform::IndexedVersion version,
const std::shared_ptr<ResourceFolderModel> packs,
@ -51,7 +53,7 @@ ResourceDownloadTask::ResourceDownloadTask(ModPlatform::IndexedPack::Ptr pack,
}
}
m_filesNetJob->addNetAction(Net::Download::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
m_filesNetJob->addNetAction(Net::ApiDownload::makeFile(m_pack_version.downloadUrl, dir.absoluteFilePath(getFilename())));
connect(m_filesNetJob.get(), &NetJob::succeeded, this, &ResourceDownloadTask::downloadSucceeded);
connect(m_filesNetJob.get(), &NetJob::progress, this, &ResourceDownloadTask::downloadProgressChanged);
connect(m_filesNetJob.get(), &NetJob::stepProgress, this, &ResourceDownloadTask::propagateStepProgress);

View File

@ -35,6 +35,11 @@ QPixmap getFaceFromCache(QString username, int height, int width)
QPixmap skinTexture(fskin.fileName());
if (!skinTexture.isNull()) {
QPixmap skin = QPixmap(8, 8);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
skin.fill(QColorConstants::Transparent);
#else
skin.fill(QColor(0, 0, 0, 0));
#endif
QPainter painter(&skin);
painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));

View File

@ -16,6 +16,8 @@ class Usable {
friend class UseLock;
public:
virtual ~Usable() {}
std::size_t useCount() const { return m_useCount; }
bool isInUse() const { return m_useCount > 0; }

View File

@ -63,7 +63,7 @@ class Version {
struct Section {
explicit Section(QString fullString) : m_fullString(std::move(fullString))
{
int cutoff = m_fullString.size();
qsizetype cutoff = m_fullString.size();
for (int i = 0; i < m_fullString.size(); i++) {
if (!m_fullString[i].isDigit()) {
cutoff = i;
@ -103,14 +103,8 @@ class Version {
QString m_fullString;
[[nodiscard]] inline bool isAppendix() const
{
return m_stringPart.startsWith('+');
}
[[nodiscard]] inline bool isPreRelease() const
{
return m_stringPart.startsWith('-') && m_stringPart.length() > 1;
}
[[nodiscard]] inline bool isAppendix() const { return m_stringPart.startsWith('+'); }
[[nodiscard]] inline bool isPreRelease() const { return m_stringPart.startsWith('-') && m_stringPart.length() > 1; }
inline bool operator==(const Section& other) const
{
@ -156,14 +150,8 @@ class Version {
return m_fullString < other.m_fullString;
}
inline bool operator!=(const Section& other) const
{
return !(*this == other);
}
inline bool operator>(const Section& other) const
{
return !(*this < other || *this == other);
}
inline bool operator!=(const Section& other) const { return !(*this == other); }
inline bool operator>(const Section& other) const { return !(*this < other || *this == other); }
};
private:

View File

@ -194,12 +194,12 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
switch (column) {
case Name: {
if (hasRecommended) {
auto value = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
if (value.toBool()) {
auto recommenced = sourceModel()->data(parentIndex, BaseVersionList::RecommendedRole);
if (recommenced.toBool()) {
return APPLICATION->getThemedIcon("star");
} else if (hasLatest) {
auto value = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
if (value.toBool()) {
auto latest = sourceModel()->data(parentIndex, BaseVersionList::LatestRole);
if (latest.toBool()) {
return APPLICATION->getThemedIcon("bug");
}
}
@ -228,7 +228,7 @@ QVariant VersionProxyModel::data(const QModelIndex& index, int role) const
}
}
QModelIndex VersionProxyModel::parent(const QModelIndex& child) const
QModelIndex VersionProxyModel::parent([[maybe_unused]] const QModelIndex& child) const
{
return QModelIndex();
}
@ -408,7 +408,9 @@ void VersionProxyModel::sourceRowsAboutToBeInserted(const QModelIndex& parent, i
beginInsertRows(parent, first, last);
}
void VersionProxyModel::sourceRowsInserted(const QModelIndex& parent, int first, int last)
void VersionProxyModel::sourceRowsInserted([[maybe_unused]] const QModelIndex& parent,
[[maybe_unused]] int first,
[[maybe_unused]] int last)
{
endInsertRows();
}
@ -418,7 +420,7 @@ void VersionProxyModel::sourceRowsAboutToBeRemoved(const QModelIndex& parent, in
beginRemoveRows(parent, first, last);
}
void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first, int last)
void VersionProxyModel::sourceRowsRemoved([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int first, [[maybe_unused]] int last)
{
endRemoveRows();
}

128
launcher/WindowsConsole.cpp Normal file
View File

@ -0,0 +1,128 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <windows.h>
#include <iostream>
void RedirectHandle(DWORD handle, FILE* stream, const char* mode)
{
HANDLE stdHandle = GetStdHandle(handle);
if (stdHandle != INVALID_HANDLE_VALUE) {
int fileDescriptor = _open_osfhandle((intptr_t)stdHandle, _O_TEXT);
if (fileDescriptor != -1) {
FILE* file = _fdopen(fileDescriptor, mode);
if (file != NULL) {
int dup2Result = _dup2(_fileno(file), _fileno(stream));
if (dup2Result == 0) {
setvbuf(stream, NULL, _IONBF, 0);
}
}
}
}
}
// taken from https://stackoverflow.com/a/25927081
// getting a proper output to console with redirection support on windows is apparently hell
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr)
{
// Re-initialize the C runtime "FILE" handles with clean handles bound to "nul". We do this because it has been
// observed that the file number of our standard handle file objects can be assigned internally to a value of -2
// when not bound to a valid target, which represents some kind of unknown internal invalid state. In this state our
// call to "_dup2" fails, as it specifically tests to ensure that the target file number isn't equal to this value
// before allowing the operation to continue. We can resolve this issue by first "re-opening" the target files to
// use the "nul" device, which will place them into a valid state, after which we can redirect them to our target
// using the "_dup2" function.
if (bindStdIn) {
FILE* dummyFile;
freopen_s(&dummyFile, "nul", "r", stdin);
}
if (bindStdOut) {
FILE* dummyFile;
freopen_s(&dummyFile, "nul", "w", stdout);
}
if (bindStdErr) {
FILE* dummyFile;
freopen_s(&dummyFile, "nul", "w", stderr);
}
// Redirect unbuffered stdin from the current standard input handle
if (bindStdIn) {
RedirectHandle(STD_INPUT_HANDLE, stdin, "r");
}
// Redirect unbuffered stdout to the current standard output handle
if (bindStdOut) {
RedirectHandle(STD_OUTPUT_HANDLE, stdout, "w");
}
// Redirect unbuffered stderr to the current standard error handle
if (bindStdErr) {
RedirectHandle(STD_ERROR_HANDLE, stderr, "w");
}
// Clear the error state for each of the C++ standard stream objects. We need to do this, as attempts to access the
// standard streams before they refer to a valid target will cause the iostream objects to enter an error state. In
// versions of Visual Studio after 2005, this seems to always occur during startup regardless of whether anything
// has been read from or written to the targets or not.
if (bindStdIn) {
std::wcin.clear();
std::cin.clear();
}
if (bindStdOut) {
std::wcout.clear();
std::cout.clear();
}
if (bindStdErr) {
std::wcerr.clear();
std::cerr.clear();
}
}
bool AttachWindowsConsole()
{
auto stdinType = GetFileType(GetStdHandle(STD_INPUT_HANDLE));
auto stdoutType = GetFileType(GetStdHandle(STD_OUTPUT_HANDLE));
auto stderrType = GetFileType(GetStdHandle(STD_ERROR_HANDLE));
bool bindStdIn = false;
bool bindStdOut = false;
bool bindStdErr = false;
if (stdinType == FILE_TYPE_CHAR || stdinType == FILE_TYPE_UNKNOWN) {
bindStdIn = true;
}
if (stdoutType == FILE_TYPE_CHAR || stdoutType == FILE_TYPE_UNKNOWN) {
bindStdOut = true;
}
if (stderrType == FILE_TYPE_CHAR || stderrType == FILE_TYPE_UNKNOWN) {
bindStdErr = true;
}
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
BindCrtHandlesToStdHandles(bindStdIn, bindStdOut, bindStdErr);
return true;
}
return false;
}

25
launcher/WindowsConsole.h Normal file
View File

@ -0,0 +1,25 @@
//
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
*
* 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
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#pragma once
void BindCrtHandlesToStdHandles(bool bindStdIn, bool bindStdOut, bool bindStdErr);
bool AttachWindowsConsole();

View File

@ -37,11 +37,7 @@
#include <sys.h>
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <stdio.h>
#include <windows.h>
#include "WindowsConsole.h"
#endif
// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
@ -67,21 +63,7 @@ FileLinkApp::FileLinkApp(int& argc, char** argv) : QCoreApplication(argc, argv),
{
#if defined Q_OS_WIN32
// attach the parent console
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
// if attach succeeds, reopen and sync all the i/o
if (freopen("CON", "w", stdout)) {
std::cout.sync_with_stdio();
}
if (freopen("CON", "w", stderr)) {
std::cerr.sync_with_stdio();
}
if (freopen("CON", "r", stdin)) {
std::cin.sync_with_stdio();
}
auto out = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written;
const char* endline = "\n";
WriteConsole(out, endline, strlen(endline), &written, NULL);
if (AttachWindowsConsole()) {
consoleAttached = true;
}
#endif
@ -188,7 +170,7 @@ void FileLinkApp::runLink()
FS::LinkResult result = { src_path, dst_path, QString::fromStdString(os_err.message()), os_err.value() };
m_path_results.append(result);
} else {
FS::LinkResult result = { src_path, dst_path };
FS::LinkResult result = { src_path, dst_path, "", 0 };
m_path_results.append(result);
}
}
@ -248,7 +230,7 @@ void FileLinkApp::readPathPairs()
in >> numLinks;
qDebug() << "numLinks" << numLinks;
for (int i = 0; i < numLinks; i++) {
for (quint32 i = 0; i < numLinks; i++) {
FS::LinkPair pair;
in >> pair.src;
in >> pair.dst;
@ -271,7 +253,6 @@ FileLinkApp::~FileLinkApp()
fclose(stdout);
fclose(stdin);
fclose(stderr);
FreeConsole();
}
#endif
}

View File

@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
@ -42,6 +43,7 @@
#include <QMimeData>
#include <QSet>
#include <QUrl>
#include "icons/IconUtils.h"
#define MAX_SIZE 1024
@ -128,7 +130,7 @@ void IconList::directoryChanged(const QString& path)
QString suffix = rmfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
if (!IconUtils::isIconSuffix(suffix))
key = rmfile.fileName();
int idx = getIconIndex(key);
@ -155,7 +157,7 @@ void IconList::directoryChanged(const QString& path)
QString suffix = addfile.suffix();
// The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
if (!IconUtils::isIconSuffix(suffix))
key = addfile.fileName();
if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) {
@ -255,10 +257,7 @@ bool IconList::dropMimeData(const QMimeData* data,
Qt::ItemFlags IconList::flags(const QModelIndex& index) const
{
Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
return Qt::ItemIsDropEnabled | defaultFlags;
}
QVariant IconList::data(const QModelIndex& index, int role) const
@ -290,19 +289,8 @@ int IconList::rowCount(const QModelIndex& parent) const
void IconList::installIcons(const QStringList& iconFiles)
{
for (QString file : iconFiles) {
QFileInfo fileinfo(file);
if (!fileinfo.isReadable() || !fileinfo.isFile())
continue;
QString target = FS::PathCombine(getDirectory(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif")
continue;
if (!QFile::copy(file, target))
continue;
}
for (QString file : iconFiles)
installIcon(file, {});
}
void IconList::installIcon(const QString& file, const QString& name)
@ -311,18 +299,17 @@ void IconList::installIcon(const QString& file, const QString& name)
if (!fileinfo.isReadable() || !fileinfo.isFile())
return;
QString target = FS::PathCombine(getDirectory(), name);
if (!IconUtils::isIconSuffix(fileinfo.suffix()))
return;
QString target = FS::PathCombine(getDirectory(), name.isEmpty() ? fileinfo.fileName() : name);
QFile::copy(file, target);
}
bool IconList::iconFileExists(const QString& key) const
{
auto iconEntry = icon(key);
if (!iconEntry) {
return false;
}
return iconEntry->has(IconType::FileBased);
return iconEntry && iconEntry->has(IconType::FileBased);
}
const MMCIcon* IconList::icon(const QString& key) const
@ -335,18 +322,12 @@ const MMCIcon* IconList::icon(const QString& key) const
bool IconList::deleteIcon(const QString& key)
{
if (!iconFileExists(key))
return false;
return QFile::remove(icon(key)->getFilePath());
return iconFileExists(key) && QFile::remove(icon(key)->getFilePath());
}
bool IconList::trashIcon(const QString& key)
{
if (!iconFileExists(key))
return false;
return FS::trash(icon(key)->getFilePath(), nullptr);
return iconFileExists(key) && FS::trash(icon(key)->getFilePath(), nullptr);
}
bool IconList::addThemeIcon(const QString& key)
@ -357,20 +338,19 @@ bool IconList::addThemeIcon(const QString& key)
oldOne.replace(Builtin, key);
dataChanged(index(*iter), index(*iter));
return true;
} else {
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = key;
mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = key;
mmc_icon.m_key = key;
mmc_icon.replace(Builtin, key);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
bool IconList::addIcon(const QString& key, const QString& name, const QString& path, const IconType type)
@ -385,20 +365,19 @@ bool IconList::addIcon(const QString& key, const QString& name, const QString& p
oldOne.replace(type, icon, path);
dataChanged(index(*iter), index(*iter));
return true;
} else {
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
// add a new icon
beginInsertRows(QModelIndex(), icons.size(), icons.size());
{
MMCIcon mmc_icon;
mmc_icon.m_name = name;
mmc_icon.m_key = key;
mmc_icon.replace(type, icon, path);
icons.push_back(mmc_icon);
name_index[key] = icons.size() - 1;
}
endInsertRows();
return true;
}
void IconList::saveIcon(const QString& key, const QString& path, const char* format) const
@ -446,5 +425,3 @@ QString IconList::getDirectory() const
{
return m_dir.absolutePath();
}
//#include "IconList.moc"

View File

@ -1,18 +1,37 @@
/* Copyright 2013-2021 MultiMC Contributors
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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
* the Free Software Foundation, version 3.
*
* http://www.apache.org/licenses/LICENSE-2.0
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QAbstractListModel>

View File

@ -1,19 +1,51 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "IconUtils.h"
#include <QDirIterator>
#include "FileSystem.h"
#include <array>
namespace {
std::array<const char*, 6> validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } };
}
namespace IconUtils {
QString findBestIconIn(const QString& folder, const QString& iconKey)
{
int best_found = validIconExtensions.size();
QString best_filename;
QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
@ -21,36 +53,20 @@ QString findBestIconIn(const QString& folder, const QString& iconKey)
it.next();
auto fileInfo = it.fileInfo();
if (fileInfo.completeBaseName() != iconKey)
continue;
auto extension = fileInfo.suffix();
for (int i = 0; i < best_found; i++) {
if (extension == validIconExtensions[i]) {
best_found = i;
qDebug() << i << " : " << fileInfo.fileName();
best_filename = fileInfo.fileName();
}
}
if (fileInfo.completeBaseName() == iconKey && isIconSuffix(fileInfo.suffix()))
return fileInfo.absoluteFilePath();
}
return FS::PathCombine(folder, best_filename);
return {};
}
QString getIconFilter()
{
QString out;
QTextStream stream(&out);
stream << '(';
for (size_t i = 0; i < validIconExtensions.size() - 1; i++) {
if (i > 0) {
stream << " ";
}
stream << "*." << validIconExtensions[i];
}
stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
stream << ')';
return out;
return "(*." + validIconExtensions.join(" *.") + ")";
}
bool isIconSuffix(QString suffix)
{
return validIconExtensions.contains(suffix);
}
} // namespace IconUtils

View File

@ -1,3 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QString>
@ -10,4 +45,5 @@ QString findBestIconIn(const QString& folder, const QString& iconKey);
// Get icon file type filter for file browser dialogs
QString getIconFilter();
bool isIconSuffix(QString suffix);
} // namespace IconUtils

View File

@ -2,6 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* 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
@ -50,8 +51,8 @@ IconType operator--(IconType& t, int)
case IconType::FileBased:
t = IconType::Transient;
break;
default: {
}
default:
break;
}
return temp;
}

View File

@ -1,18 +1,37 @@
/* Copyright 2013-2021 MultiMC Contributors
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (c) 2023 Trial97 <alexandru.tripon97@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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
* the Free Software Foundation, version 3.
*
* http://www.apache.org/licenses/LICENSE-2.0
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* This file incorporates work covered by the following copyright and
* permission notice:
*
* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <QDateTime>
#include <QIcon>

View File

@ -14,7 +14,7 @@ class JavaVersion {
friend class JavaVersionTest;
public:
JavaVersion(){};
JavaVersion() {}
JavaVersion(const QString& rhs);
JavaVersion& operator=(const QString& rhs);

View File

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

View File

@ -16,7 +16,7 @@
#include "BaseEntity.h"
#include "Json.h"
#include "net/Download.h"
#include "net/ApiDownload.h"
#include "net/HttpMetaCache.h"
#include "net/NetJob.h"
@ -32,7 +32,7 @@ class ParsingValidator : public Net::Validator {
bool init(QNetworkRequest&) override { return true; }
bool write(QByteArray& data) override
{
this->data.append(data);
this->m_data.append(data);
return true;
}
bool abort() override { return true; }
@ -40,7 +40,7 @@ class ParsingValidator : public Net::Validator {
{
auto fname = m_entity->localFilename();
try {
auto doc = Json::requireDocument(data, fname);
auto doc = Json::requireDocument(m_data, fname);
auto obj = Json::requireObject(doc, fname);
m_entity->parse(obj);
return true;
@ -51,7 +51,7 @@ class ParsingValidator : public Net::Validator {
}
private: /* data */
QByteArray data;
QByteArray m_data;
Meta::BaseEntity* m_entity;
};
@ -104,7 +104,7 @@ void Meta::BaseEntity::load(Net::Mode loadType)
auto url = this->url();
auto entry = APPLICATION->metacache()->resolveEntry("meta", localFilename());
entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
/*
* The validator parses the file and loads it into the object.
* If that fails, the file is not written to storage.

View File

@ -46,8 +46,8 @@
#include "AssetsUtils.h"
#include "BuildConfig.h"
#include "FileSystem.h"
#include "net/ApiDownload.h"
#include "net/ChecksumValidator.h"
#include "net/Download.h"
#include "Application.h"
@ -279,7 +279,7 @@ NetAction::Ptr AssetObject::getDownloadAction()
{
QFileInfo objectFile(getLocalPath());
if ((!objectFile.isFile()) || (objectFile.size() != size)) {
auto objectDL = Net::Download::makeFile(getUrl(), objectFile.filePath());
auto objectDL = Net::ApiDownload::makeFile(getUrl(), objectFile.filePath());
if (hash.size()) {
auto rawHash = QByteArray::fromHex(hash.toLatin1());
objectDL->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));

View File

@ -25,7 +25,8 @@ class Component : public QObject, public ProblemProvider {
Component(PackProfile* parent, std::shared_ptr<Meta::Version> version);
Component(PackProfile* parent, const QString& uid, std::shared_ptr<VersionFile> file);
virtual ~Component(){};
virtual ~Component() {}
void applyTo(LaunchProfile* profile);
bool isEnabled();

View File

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

View File

@ -41,7 +41,7 @@
class LaunchProfile : public ProblemProvider {
public:
virtual ~LaunchProfile(){};
virtual ~LaunchProfile() {}
public: /* application of profile variables from patches */
void applyMinecraftVersion(const QString& id);

View File

@ -38,8 +38,8 @@
#include <BuildConfig.h>
#include <FileSystem.h>
#include <net/ApiDownload.h>
#include <net/ChecksumValidator.h>
#include <net/Download.h>
void Library::getApplicableFiles(const RuntimeContext& runtimeContext,
QStringList& jar,
@ -115,12 +115,12 @@ QList<NetAction::Ptr> Library::getDownloads(const RuntimeContext& runtimeContext
if (sha1.size()) {
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::Download::makeCached(url, entry, options);
auto dl = Net::ApiDownload::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
} else {
out.append(Net::Download::makeCached(url, entry, options));
out.append(Net::ApiDownload::makeCached(url, entry, options));
qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
}
return true;

View File

@ -3,8 +3,7 @@
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 Jamie Mansfield <jmansfield@cadixdev.org>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* Copyright (c) 2023 seth <getchoo at tuta dot io>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -88,6 +87,10 @@
#include "minecraft/gameoptions/GameOptions.h"
#include "minecraft/update/FoldersTask.h"
#include "tools/BaseProfiler.h"
#include <QActionGroup>
#ifdef Q_OS_LINUX
#include "MangoHud.h"
#endif
@ -166,7 +169,9 @@ void MinecraftInstance::loadSpecificSettings()
// Native library workarounds
auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("CustomOpenALPath"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
m_settings->registerOverride(global_settings->getSetting("CustomGLFWPath"), nativeLibraryWorkaroundsOverride);
// Peformance related options
auto performanceOverride = m_settings->registerSetting("OverridePerformance", false);
@ -179,10 +184,6 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride);
m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride);
// Mod loader specific options
auto modLoaderSettings = m_settings->registerSetting("OverrideModLoaderSettings", false);
m_settings->registerOverride(global_settings->getSetting("DisableQuiltBeacon"), modLoaderSettings);
m_settings->set("InstanceType", "OneSix");
}
@ -194,6 +195,12 @@ void MinecraftInstance::loadSpecificSettings()
m_settings->registerSetting("UseAccountForInstance", false);
m_settings->registerSetting("InstanceAccountId", "");
m_settings->registerSetting("ExportName", "");
m_settings->registerSetting("ExportVersion", "1.0.0");
m_settings->registerSetting("ExportSummary", "");
m_settings->registerSetting("ExportAuthor", "");
m_settings->registerSetting("ExportOptionalFiles", true);
qDebug() << "Instance-type specific settings were loaded!";
setSpecificSettingsLoaded(true);
@ -229,6 +236,50 @@ QSet<QString> MinecraftInstance::traits() const
return profile->getTraits();
}
// FIXME: move UI code out of MinecraftInstance
void MinecraftInstance::populateLaunchMenu(QMenu* menu)
{
QAction* normalLaunch = menu->addAction(tr("&Launch"));
normalLaunch->setShortcut(QKeySequence::Open);
QAction* normalLaunchOffline = menu->addAction(tr("Launch &Offline"));
normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O")));
QAction* normalLaunchDemo = menu->addAction(tr("Launch &Demo"));
normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O")));
normalLaunchDemo->setEnabled(supportsDemo());
connect(normalLaunch, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this()); });
connect(normalLaunchOffline, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, false); });
connect(normalLaunchDemo, &QAction::triggered, [this] { APPLICATION->launch(shared_from_this(), false, true); });
QString profilersTitle = tr("Profilers");
menu->addSeparator()->setText(profilersTitle);
auto profilers = new QActionGroup(menu);
profilers->setExclusive(true);
connect(profilers, &QActionGroup::triggered, [this](QAction* action) {
settings()->set("Profiler", action->data());
emit profilerChanged();
});
QAction* noProfilerAction = menu->addAction(tr("&No Profiler"));
noProfilerAction->setData("");
noProfilerAction->setCheckable(true);
noProfilerAction->setChecked(true);
profilers->addAction(noProfilerAction);
for (auto profiler = APPLICATION->profilers().begin(); profiler != APPLICATION->profilers().end(); profiler++) {
QAction* profilerAction = menu->addAction(profiler.value()->name());
profilers->addAction(profilerAction);
profilerAction->setData(profiler.key());
profilerAction->setCheckable(true);
profilerAction->setChecked(settings()->get("Profiler").toString() == profiler.key());
QString error;
profilerAction->setEnabled(profiler.value()->check(&error));
}
}
QString MinecraftInstance::gameRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
@ -260,7 +311,7 @@ QString MinecraftInstance::getLocalLibraryPath() const
bool MinecraftInstance::supportsDemo() const
{
Version instance_ver{ getPackProfile()->getComponentVersion("net.minecraft") };
// Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History
// Demo mode was introduced in 1.3.1: https://minecraft.wiki/w/Demo_mode#History
// FIXME: Due to Version constraints atm, this can't handle well non-release versions
return instance_ver >= Version("1.3.1");
}
@ -385,10 +436,31 @@ QStringList MinecraftInstance::extraArguments()
}
{
const auto loaders = version->getModLoaders();
if (loaders.has_value() && loaders.value() & ResourceAPI::Quilt && settings()->get("DisableQuiltBeacon").toBool())
list.append("-Dloader.disable_beacon=true");
QString openALPath;
QString glfwPath;
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;
}
@ -868,13 +940,16 @@ QString MinecraftInstance::getStatusbarDescription()
if (m_settings->get("ShowGameTime").toBool()) {
if (lastTimePlayed() > 0) {
QDateTime lastLaunchTime = QDateTime::fromMSecsSinceEpoch(lastLaunch());
description.append(tr(", last played on %1 for %2")
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed())));
description.append(
tr(", last played on %1 for %2")
.arg(QLocale().toString(lastLaunchTime, QLocale::ShortFormat))
.arg(Time::prettifyDuration(lastTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
if (totalTimePlayed() > 0) {
description.append(tr(", total played for %1").arg(Time::prettifyDuration(totalTimePlayed())));
description.append(
tr(", total played for %1")
.arg(Time::prettifyDuration(totalTimePlayed(), APPLICATION->settings()->get("ShowGameTimeWithoutDays").toBool())));
}
}
if (hasCrashed()) {

View File

@ -2,7 +2,7 @@
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
* Copyright (C) 2023 TheKodeToad <TheKodeToad@proton.me>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -70,6 +70,8 @@ class MinecraftInstance : public BaseInstance {
bool canExport() const override { return true; }
void populateLaunchMenu(QMenu* menu) override;
////// Directories and files //////
QString jarModsDir() const;
QString resourcePacksDir() const;

View File

@ -19,8 +19,8 @@ struct MojangDownloadInfo {
};
struct MojangLibraryDownloadInfo {
MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact) : artifact(artifact){};
MojangLibraryDownloadInfo(){};
MojangLibraryDownloadInfo(MojangDownloadInfo::Ptr artifact_) : artifact(artifact_) {}
MojangLibraryDownloadInfo() {}
// types
typedef std::shared_ptr<MojangLibraryDownloadInfo> Ptr;
@ -47,18 +47,18 @@ struct MojangAssetIndexInfo : public MojangDownloadInfo {
// methods
MojangAssetIndexInfo() {}
MojangAssetIndexInfo(QString id)
MojangAssetIndexInfo(QString id_)
{
this->id = id;
this->id = id_;
// HACK: ignore assets from other version files than Minecraft
// workaround for stupid assets issue caused by amazon:
// https://www.theregister.co.uk/2017/02/28/aws_is_awol_as_s3_goes_haywire/
if (id == "legacy") {
if (id_ == "legacy") {
url = "https://piston-meta.mojang.com/mc/assets/legacy/c0fd82e8ce9fbc93119e40d96d5a4e62cfa3f729/legacy.json";
}
// HACK
else {
url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id + ".json";
url = "https://s3.amazonaws.com/Minecraft.Download/indexes/" + id_ + ".json";
}
known = false;
}

View File

@ -350,7 +350,7 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr& patch
}
}
LibraryPtr OneSixVersionFormat::plusJarModFromJson(ProblemContainer& problems,
LibraryPtr OneSixVersionFormat::plusJarModFromJson([[maybe_unused]] ProblemContainer& problems,
const QJsonObject& libObj,
const QString& filename,
const QString& originalName)

View File

@ -58,14 +58,14 @@
#include "ComponentUpdateTask.h"
#include "PackProfile.h"
#include "PackProfile_p.h"
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
#include "Application.h"
#include "modplatform/ResourceAPI.h"
static const QMap<QString, ResourceAPI::ModLoaderType> modloaderMapping{ { "net.minecraftforge", ResourceAPI::Forge },
{ "net.fabricmc.fabric-loader", ResourceAPI::Fabric },
{ "org.quiltmc.quilt-loader", ResourceAPI::Quilt },
{ "com.mumfrey.liteloader", ResourceAPI::LiteLoader } };
static const QMap<QString, ModPlatform::ModLoaderType> modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge },
{ "net.minecraftforge", ModPlatform::Forge },
{ "net.fabricmc.fabric-loader", ModPlatform::Fabric },
{ "org.quiltmc.quilt-loader", ModPlatform::Quilt },
{ "com.mumfrey.liteloader", ModPlatform::LiteLoader } };
PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel()
{
@ -204,10 +204,10 @@ static bool loadPackProfile(PackProfile* parent,
}
auto orderArray = Json::requireArray(obj.value("components"));
for (auto item : orderArray) {
auto obj = Json::requireObject(item, "Component must be an object.");
container.append(componentFromJsonV1(parent, componentJsonPattern, obj));
auto comp_obj = Json::requireObject(item, "Component must be an object.");
container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj));
}
} catch (const JSONValidationError& err) {
} catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
container.clear();
return false;
@ -377,7 +377,7 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component)
qWarning() << "Attempt to add a component that is already present!";
return;
}
beginInsertRows(QModelIndex(), index, index);
beginInsertRows(QModelIndex(), static_cast<int>(index), static_cast<int>(index));
d->components.insert(index, component);
d->componentIndex[id] = component;
endInsertRows();
@ -389,7 +389,7 @@ void PackProfile::componentDataChanged()
{
auto objPtr = qobject_cast<Component*>(sender());
if (!objPtr) {
qWarning() << "PackProfile got dataChenged signal from a non-Component!";
qWarning() << "PackProfile got dataChanged signal from a non-Component!";
return;
}
if (objPtr->getID() == "net.minecraft") {
@ -405,7 +405,7 @@ void PackProfile::componentDataChanged()
}
index++;
}
qWarning() << "PackProfile got dataChenged signal from a Component which does not belong to it!";
qWarning() << "PackProfile got dataChanged signal from a Component which does not belong to it!";
}
bool PackProfile::remove(const int index)
@ -483,9 +483,9 @@ ComponentPtr PackProfile::getComponent(const QString& id)
return (*iter);
}
ComponentPtr PackProfile::getComponent(int index)
ComponentPtr PackProfile::getComponent(size_t index)
{
if (index < 0 || index >= d->components.size()) {
if (index >= static_cast<size_t>(d->components.size())) {
return nullptr;
}
return d->components[index];
@ -547,7 +547,7 @@ QVariant PackProfile::data(const QModelIndex& index, int role) const
return QVariant();
}
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
bool PackProfile::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
{
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent())) {
return false;
@ -989,12 +989,12 @@ void PackProfile::disableInteraction(bool disable)
}
}
std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getModLoaders()
{
ResourceAPI::ModLoaderTypes result;
ModPlatform::ModLoaderTypes result;
bool has_any_loader = false;
QMapIterator<QString, ResourceAPI::ModLoaderType> i(modloaderMapping);
QMapIterator<QString, ModPlatform::ModLoaderType> i(modloaderMapping);
while (i.hasNext()) {
i.next();
@ -1008,3 +1008,18 @@ std::optional<ResourceAPI::ModLoaderTypes> PackProfile::getModLoaders()
return {};
return result;
}
std::optional<ModPlatform::ModLoaderTypes> PackProfile::getSupportedModLoaders()
{
auto loadersOpt = getModLoaders();
if (!loadersOpt.has_value())
return loadersOpt;
auto loaders = loadersOpt.value();
// TODO: remove this or add version condition once Quilt drops official Fabric support
if (loaders & ModPlatform::Quilt)
loaders |= ModPlatform::Fabric;
// TODO: remove this or add version condition once NeoForge drops official Forge support
if (loaders & ModPlatform::NeoForge)
loaders |= ModPlatform::Forge;
return loaders;
}

View File

@ -44,14 +44,11 @@
#include <QList>
#include <QString>
#include <memory>
#include <optional>
#include "BaseVersion.h"
#include "Component.h"
#include "LaunchProfile.h"
#include "Library.h"
#include "MojangDownloadInfo.h"
#include "ProfileUtils.h"
#include "modplatform/ResourceAPI.h"
#include "modplatform/ModIndex.h"
#include "net/Mode.h"
class MinecraftInstance;
@ -140,13 +137,15 @@ class PackProfile : public QAbstractListModel {
ComponentPtr getComponent(const QString& id);
/// get the profile component by index
ComponentPtr getComponent(int index);
ComponentPtr getComponent(size_t index);
/// Add the component to the internal list of patches
// todo(merged): is this the best approach
void appendComponent(ComponentPtr component);
std::optional<ResourceAPI::ModLoaderTypes> getModLoaders();
std::optional<ModPlatform::ModLoaderTypes> getModLoaders();
// this returns aditional loaders(Quilt supports fabric and NeoForge supports Forge)
std::optional<ModPlatform::ModLoaderTypes> getSupportedModLoaders();
private:
void scheduleSave();

View File

@ -82,7 +82,7 @@ bool readOverrideOrders(QString path, PatchOrder& order)
for (auto item : orderArray) {
order.append(Json::requireString(item));
}
} catch (const JSONValidationError& err) {
} catch ([[maybe_unused]] const JSONValidationError& err) {
qCritical() << "Couldn't parse" << orderFile.fileName() << ": bad file format";
qWarning() << "Ignoring overriden order";
order.clear();

View File

@ -55,7 +55,7 @@ class Rule {
public:
Rule(RuleAction result) : m_result(result) {}
virtual ~Rule(){};
virtual ~Rule() {}
virtual QJsonObject toJson() = 0;
RuleAction apply(const Library* parent, const RuntimeContext& runtimeContext)
{

View File

@ -368,11 +368,11 @@ optional<QString> read_string(nbt::value& parent, const char* name)
}
auto& tag_str = namedValue.as<nbt::tag_string>();
return QString::fromStdString(tag_str.get());
} catch (const std::out_of_range& e) {
} catch ([[maybe_unused]] const std::out_of_range& e) {
// fallback for old world formats
qWarning() << "String NBT tag" << name << "could not be found.";
return nullopt;
} catch (const std::bad_cast& e) {
} catch ([[maybe_unused]] const std::bad_cast& e) {
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to string.";
return nullopt;
@ -388,11 +388,11 @@ optional<int64_t> read_long(nbt::value& parent, const char* name)
}
auto& tag_str = namedValue.as<nbt::tag_long>();
return tag_str.get();
} catch (const std::out_of_range& e) {
} catch ([[maybe_unused]] const std::out_of_range& e) {
// fallback for old world formats
qWarning() << "Long NBT tag" << name << "could not be found.";
return nullopt;
} catch (const std::bad_cast& e) {
} catch ([[maybe_unused]] const std::bad_cast& e) {
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to long.";
return nullopt;
@ -408,11 +408,11 @@ optional<int> read_int(nbt::value& parent, const char* name)
}
auto& tag_str = namedValue.as<nbt::tag_int>();
return tag_str.get();
} catch (const std::out_of_range& e) {
} catch ([[maybe_unused]] const std::out_of_range& e) {
// fallback for old world formats
qWarning() << "Int NBT tag" << name << "could not be found.";
return nullopt;
} catch (const std::bad_cast& e) {
} catch ([[maybe_unused]] const std::bad_cast& e) {
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to int.";
return nullopt;

View File

@ -255,7 +255,7 @@ QVariant WorldList::data(const QModelIndex& index, int role) const
}
}
QVariant WorldList::headerData(int section, Qt::Orientation orientation, int role) const
QVariant WorldList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@ -294,7 +294,6 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
default:
return QVariant();
}
return QVariant();
}
QStringList WorldList::mimeTypes() const
@ -339,19 +338,19 @@ QMimeData* WorldList::mimeData(const QModelIndexList& indexes) const
if (indexes.size() == 0)
return new QMimeData();
QList<World> worlds;
QList<World> worlds_;
for (auto idx : indexes) {
if (idx.column() != 0)
continue;
int row = idx.row();
if (row < 0 || row >= this->worlds.size())
continue;
worlds.append(this->worlds[row]);
worlds_.append(this->worlds[row]);
}
if (!worlds.size()) {
if (!worlds_.size()) {
return new QMimeData();
}
return new WorldMimeData(worlds);
return new WorldMimeData(worlds_);
}
Qt::ItemFlags WorldList::flags(const QModelIndex& index) const

View File

@ -311,7 +311,7 @@ bool AccountData::resumeStateFromV2(QJsonObject data)
QJsonObject profileObject = profileVal.toObject();
QString id = profileObject.value("id").toString("");
QString name = profileObject.value("name").toString("");
bool legacy = profileObject.value("legacy").toBool(false);
bool legacy_ = profileObject.value("legacy").toBool(false);
if (id.isEmpty() || name.isEmpty()) {
qWarning() << "Unable to load a profile" << name << "because it was missing an ID or a name.";
continue;
@ -319,7 +319,7 @@ bool AccountData::resumeStateFromV2(QJsonObject data)
if (id == currentProfile) {
currentProfileIndex = index;
}
profiles.append({ id, name, legacy });
profiles.append({ id, name, legacy_ });
}
auto& profile = profiles[currentProfileIndex];

View File

@ -353,7 +353,7 @@ QVariant AccountList::data(const QModelIndex& index, int role) const
}
}
QVariant AccountList::headerData(int section, Qt::Orientation orientation, int role) const
QVariant AccountList::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@ -415,7 +415,7 @@ Qt::ItemFlags AccountList::flags(const QModelIndex& index) const
bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int role)
{
if (idx.row() < 0 || idx.row() >= rowCount(idx) || !idx.isValid()) {
if (idx.row() < 0 || idx.row() >= rowCount(idx.parent()) || !idx.isValid()) {
return false;
}
@ -423,7 +423,8 @@ bool AccountList::setData(const QModelIndex& idx, const QVariant& value, int rol
if (value == Qt::Checked) {
MinecraftAccountPtr account = at(idx.row());
setDefaultAccount(account);
}
} else if (m_defaultAccount == at(idx.row()))
setDefaultAccount(nullptr);
}
emit dataChanged(idx, index(idx.row(), columnCount(QModelIndex()) - 1));

View File

@ -37,6 +37,7 @@
#include "MinecraftAccount.h"
#include <QColor>
#include <QCryptographicHash>
#include <QJsonArray>
#include <QJsonDocument>
@ -126,6 +127,11 @@ QPixmap MinecraftAccount::getFace() const
return QPixmap();
}
QPixmap skin = QPixmap(8, 8);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
skin.fill(QColorConstants::Transparent);
#else
skin.fill(QColor(0, 0, 0, 0));
#endif
QPainter painter(&skin);
painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));

View File

@ -113,16 +113,16 @@ bool parseXTokenResponse(QByteArray& data, Katabasis::Token& output, QString nam
if (!item.isObject()) {
continue;
}
auto obj = item.toObject();
if (obj.contains("uhs")) {
auto obj_ = item.toObject();
if (obj_.contains("uhs")) {
foundUHS = true;
} else {
continue;
}
// consume all 'display claims' ... whatever that means
for (auto iter = obj.begin(); iter != obj.end(); iter++) {
for (auto iter = obj_.begin(); iter != obj_.end(); iter++) {
QString claim;
if (!getString(obj.value(iter.key()), claim)) {
if (!getString(obj_.value(iter.key()), claim)) {
qWarning() << "display claim " << iter.key() << " is not a string...";
return false;
}

View File

@ -36,7 +36,9 @@ void EntitlementsStep::rehydrate()
// NOOP, for now. We only save bools and there's nothing to check.
}
void EntitlementsStep::onRequestDone(QNetworkReply::NetworkError error, QByteArray data, QList<QNetworkReply::RawHeaderPair> headers)
void EntitlementsStep::onRequestDone([[maybe_unused]] QNetworkReply::NetworkError error,
QByteArray data,
[[maybe_unused]] QList<QNetworkReply::RawHeaderPair> headers)
{
auto requestor = qobject_cast<AuthRequest*>(QObject::sender());
requestor->deleteLater();

View File

@ -97,12 +97,11 @@ QVariant GameOptions::data(const QModelIndex& index, int role) const
default:
return QVariant();
}
return QVariant();
}
int GameOptions::rowCount(const QModelIndex&) const
{
return contents.size();
return static_cast<int>(contents.size());
}
int GameOptions::columnCount(const QModelIndex&) const

View File

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

View File

@ -53,7 +53,7 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask* parent) : LaunchStep(parent)
auto instance = parent->instance();
if (instance->settings()->get("CloseAfterLaunch").toBool()) {
std::shared_ptr<QMetaObject::Connection> connection{ new QMetaObject::Connection };
*connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, MessageLevel::Enum level) {
*connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, [[maybe_unused]] MessageLevel::Enum level) {
qDebug() << lines;
if (lines.filter(QRegularExpression(".*Setting user.+", QRegularExpression::CaseInsensitiveOption)).length() != 0) {
APPLICATION->closeAllWindows();

View File

@ -28,7 +28,7 @@
#include "Version.h"
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_data_pack#%22pack_format%22
// https://minecraft.wiki/w/Tutorials/Creating_a_data_pack#%22pack_format%22
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 4, { Version("1.13"), Version("1.14.4") } }, { 5, { Version("1.15"), Version("1.16.1") } },
{ 6, { Version("1.16.2"), Version("1.16.5") } }, { 7, { Version("1.17"), Version("1.17.1") } },

View File

@ -63,7 +63,7 @@ class DataPack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a data pack, as defined in the pack.mcmeta file.
* See https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
* See https://minecraft.wiki/w/Data_pack#pack.mcmeta
*/
int m_pack_format = 0;

View File

@ -132,17 +132,23 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata, bool attempt_trash) -
if (!preserve_metadata) {
qDebug() << QString("Destroying metadata for '%1' on purpose").arg(name());
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(index_dir, n);
}
destroyMetadata(index_dir);
}
return Resource::destroy(attempt_trash);
}
void Mod::destroyMetadata(QDir& index_dir)
{
if (metadata()) {
Metadata::remove(index_dir, metadata()->slug);
} else {
auto n = name();
Metadata::remove(index_dir, n);
}
m_local_details.metadata = nullptr;
}
auto Mod::details() const -> const ModDetails&
{
return m_local_details;
@ -246,7 +252,8 @@ void Mod::setIcon(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
auto pixmap =
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@ -259,7 +266,7 @@ QPixmap Mod::icon(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
return cached_image.scaled(size, mode);
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get

View File

@ -72,7 +72,7 @@ class Mod : public Resource {
auto metaurl() const -> QString;
/** Get the intneral path to the mod's icon file*/
QString iconPath() const { return m_local_details.icon_file; };
QString iconPath() const { return m_local_details.icon_file; }
/** Gets the icon of the mod, converted to a QPixmap for drawing, and scaled to size. */
[[nodiscard]] QPixmap icon(QSize size, Qt::AspectRatioMode mode = Qt::AspectRatioMode::IgnoreAspectRatio) const;
/** Thread-safe. */
@ -93,6 +93,8 @@ class Mod : public Resource {
// Delete all the files of this mod
auto destroy(QDir& index_dir, bool preserve_metadata = false, bool attempt_trash = true) -> bool;
// Delete the metadata only
void destroyMetadata(QDir& index_dir);
void finishResolvingWithDetails(ModDetails&& details);

View File

@ -60,17 +60,17 @@ struct ModLicense {
ModLicense(const QString license)
{
// FIXME: come up with a better license parseing.
// FIXME: come up with a better license parsing.
// handle SPDX identifiers? https://spdx.org/licenses/
auto parts = license.split(' ');
QStringList notNameParts = {};
for (auto part : parts) {
auto url = QUrl(part);
auto _url = QUrl(part);
if (part.startsWith("(") && part.endsWith(")"))
url = QUrl(part.mid(1, part.size() - 2));
_url = QUrl(part.mid(1, part.size() - 2));
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
this->url = url.toString();
if (_url.isValid() && !_url.scheme().isEmpty() && !_url.host().isEmpty()) {
this->url = _url.toString();
notNameParts.append(part);
continue;
}
@ -89,13 +89,9 @@ struct ModLicense {
}
}
ModLicense(const QString name, const QString id, const QString url, const QString description)
{
this->name = name;
this->id = id;
this->url = url;
this->description = description;
}
ModLicense(const QString& name_, const QString& id_, const QString& url_, const QString& description_)
: name(name_), id(id_), url(url_), description(description_)
{}
ModLicense(const ModLicense& other) : name(other.name), id(other.id), url(other.url), description(other.description) {}

View File

@ -51,8 +51,13 @@
#include "Application.h"
#include "Json.h"
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/LocalModUpdateTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
#include "modplatform/flame/FlameAPI.h"
#include "modplatform/flame/FlameModIndex.h"
ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool is_indexed, bool create_dir)
: ResourceFolderModel(QDir(dir), instance, nullptr, create_dir), m_is_indexed(is_indexed)
@ -62,6 +67,7 @@ ModFolderModel::ModFolderModel(const QString& dir, BaseInstance* instance, bool
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::VERSION, SortType::DATE, SortType::PROVIDER };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
QHeaderView::ResizeToContents, QHeaderView::ResizeToContents, QHeaderView::ResizeToContents };
m_columnsHideable = { false, true, false, true, true, true };
}
QVariant ModFolderModel::data(const QModelIndex& index, int role) const
@ -137,7 +143,7 @@ QVariant ModFolderModel::data(const QModelIndex& index, int role) const
}
}
QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
QVariant ModFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@ -227,6 +233,25 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
return true;
}
bool ModFolderModel::deleteModsMetadata(const QModelIndexList& indexes)
{
if (indexes.isEmpty())
return true;
for (auto i : indexes) {
if (i.column() != 0) {
continue;
}
auto m = at(i.row());
auto index_dir = indexDir();
m->destroyMetadata(index_dir);
}
update();
return true;
}
bool ModFolderModel::isValid()
{
return m_dir.exists() && m_dir.isReadable();
@ -308,3 +333,47 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id)
emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
}
static const FlameAPI flameAPI;
bool ModFolderModel::installMod(QString file_path, ModPlatform::IndexedVersion& vers)
{
if (vers.addonId.isValid()) {
ModPlatform::IndexedPack pack{
vers.addonId,
ModPlatform::ResourceProvider::FLAME,
};
QEventLoop loop;
auto response = std::make_shared<QByteArray>();
auto job = flameAPI.getProject(vers.addonId.toString(), response);
QObject::connect(job.get(), &Task::failed, [&loop] { loop.quit(); });
QObject::connect(job.get(), &Task::aborted, &loop, &QEventLoop::quit);
QObject::connect(job.get(), &Task::succeeded, [response, this, &vers, &loop, &pack] {
QJsonParseError parse_error{};
QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
if (parse_error.error != QJsonParseError::NoError) {
qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset
<< " reason: " << parse_error.errorString();
qDebug() << *response;
return;
}
try {
auto obj = Json::requireObject(Json::requireObject(doc), "data");
FlameMod::loadIndexedPack(pack, obj);
} catch (const JSONValidationError& e) {
qDebug() << doc;
qWarning() << "Error while reading mod info: " << e.cause();
}
LocalModUpdateTask update_metadata(indexDir(), pack, vers);
QObject::connect(&update_metadata, &Task::finished, &loop, &QEventLoop::quit);
update_metadata.start();
});
job->start();
loop.exec();
}
return ResourceFolderModel::installResource(file_path);
}

View File

@ -48,6 +48,7 @@
#include "minecraft/mod/tasks/LocalModParseTask.h"
#include "minecraft/mod/tasks/ModFolderLoadTask.h"
#include "modplatform/ModIndex.h"
class LegacyInstance;
class BaseInstance;
@ -75,10 +76,12 @@ class ModFolderModel : public ResourceFolderModel {
[[nodiscard]] Task* createParseTask(Resource&) override;
bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); }
bool installMod(QString file_path, ModPlatform::IndexedVersion& vers);
bool uninstallMod(const QString& filename, bool preserve_metadata = false);
/// Deletes all the selected mods
bool deleteMods(const QModelIndexList& indexes);
bool deleteModsMetadata(const QModelIndexList& indexes);
bool isValid();

View File

@ -447,7 +447,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
}
}
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
bool ResourceFolderModel::setData(const QModelIndex& index, [[maybe_unused]] const QVariant& value, int role)
{
int row = index.row();
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
@ -471,7 +471,7 @@ bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& valu
return false;
}
QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
QVariant ResourceFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@ -551,6 +551,9 @@ QMenu* ResourceFolderModel::createHeaderContextMenu(QTreeView* tree)
menu->addSeparator()->setText(tr("Show / Hide Columns"));
for (int col = 0; col < columnCount(); ++col) {
// Skip creating actions for columns that should not be hidden
if (!m_columnsHideable.at(col))
continue;
auto act = new QAction(menu);
setupHeaderAction(act, col);
@ -584,7 +587,8 @@ SortType ResourceFolderModel::columnToSortKey(size_t column) const
}
/* Standard Proxy Model for createFilterProxyModel */
[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row,
[[maybe_unused]] const QModelIndex& source_parent) const
{
auto* model = qobject_cast<ResourceFolderModel*>(sourceModel());
if (!model)

View File

@ -49,8 +49,8 @@ class ResourceFolderModel : public QAbstractListModel {
bool stopWatching(const QStringList paths);
/* Helper methods for subclasses, using a predetermined list of paths. */
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); };
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); };
virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }
virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }
/** Given a path in the system, install that resource, moving it to its place in the
* instance file hierarchy.
@ -78,7 +78,7 @@ class ResourceFolderModel : public QAbstractListModel {
/** Creates a new parse task, if needed, for 'res' and start it.*/
virtual void resolveResource(Resource* res);
[[nodiscard]] size_t size() const { return m_resources.size(); };
[[nodiscard]] qsizetype size() const { return m_resources.size(); }
[[nodiscard]] bool empty() const { return size() == 0; }
[[nodiscard]] Resource& at(int index) { return *m_resources.at(index); }
[[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); }
@ -97,10 +97,10 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; };
QStringList columnNames(bool translated = true) const { return translated ? m_column_names_translated : m_column_names; }
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; }
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
@ -159,7 +159,7 @@ class ResourceFolderModel : public QAbstractListModel {
* This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed
* in the background, so it slowly updates the UI as tasks get done.
*/
[[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; };
[[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; }
/** Standard implementation of the model update logic.
*
@ -203,6 +203,7 @@ class ResourceFolderModel : public QAbstractListModel {
QStringList m_column_names_translated = { tr("Enable"), tr("Name"), tr("Last Modified") };
QList<QHeaderView::ResizeMode> m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Stretch,
QHeaderView::ResizeToContents };
QList<bool> m_columnsHideable = { false, false, true };
QDir m_dir;
BaseInstance* m_instance;
@ -224,15 +225,15 @@ class ResourceFolderModel : public QAbstractListModel {
/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */
#define RESOURCE_HELPERS(T) \
[[nodiscard]] T* operator[](size_t index) \
[[nodiscard]] T* operator[](int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] T* at(size_t index) \
[[nodiscard]] T* at(int index) \
{ \
return static_cast<T*>(m_resources[index].get()); \
} \
[[nodiscard]] const T* at(size_t index) const \
[[nodiscard]] const T* at(int index) const \
{ \
return static_cast<const T*>(m_resources.at(index).get()); \
} \

View File

@ -11,7 +11,7 @@
#include "minecraft/mod/tasks/LocalResourcePackParseTask.h"
// Values taken from:
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
static const QMap<int, std::pair<Version, Version>> s_pack_format_versions = {
{ 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } },
{ 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } },
@ -50,7 +50,8 @@ void ResourcePack::setImage(QImage new_image) const
PixmapCache::instance().remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
auto pixmap =
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::instance().insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@ -68,7 +69,7 @@ QPixmap ResourcePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::instance().find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
return cached_image.scaled(size, mode);
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get

View File

@ -51,7 +51,7 @@ class ResourcePack : public Resource {
mutable QMutex m_data_lock;
/* The 'version' of a resource pack, as defined in the pack.mcmeta file.
* See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
* See https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
*/
int m_pack_format = 0;

View File

@ -54,6 +54,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir, BaseInstanc
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch, QHeaderView::ResizeToContents,
QHeaderView::ResizeToContents };
m_columnsHideable = { false, true, false, true, true };
}
QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
@ -128,7 +129,7 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const
}
}
QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
QVariant ResourcePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:
@ -165,7 +166,6 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
default:
return {};
}
return {};
}
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const

View File

@ -44,7 +44,8 @@ void TexturePack::setImage(QImage new_image) const
PixmapCache::remove(m_pack_image_cache_key.key);
// scale the image to avoid flooding the pixmapcache
auto pixmap = QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding));
auto pixmap =
QPixmap::fromImage(new_image.scaled({ 64, 64 }, Qt::AspectRatioMode::KeepAspectRatioByExpanding, Qt::SmoothTransformation));
m_pack_image_cache_key.key = PixmapCache::insert(pixmap);
m_pack_image_cache_key.was_ever_used = true;
@ -56,7 +57,7 @@ QPixmap TexturePack::image(QSize size, Qt::AspectRatioMode mode) const
if (PixmapCache::find(m_pack_image_cache_key.key, &cached_image)) {
if (size.isNull())
return cached_image;
return cached_image.scaled(size, mode);
return cached_image.scaled(size, mode, Qt::SmoothTransformation);
}
// No valid image we can get

View File

@ -49,6 +49,7 @@ TexturePackFolderModel::TexturePackFolderModel(const QString& dir, BaseInstance*
m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::NAME, SortType::DATE };
m_column_resize_modes = { QHeaderView::ResizeToContents, QHeaderView::Interactive, QHeaderView::Stretch,
QHeaderView::ResizeToContents };
m_columnsHideable = { false, true, false, true };
}
Task* TexturePackFolderModel::createUpdateTask()
@ -113,7 +114,7 @@ QVariant TexturePackFolderModel::data(const QModelIndex& index, int role) const
}
}
QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
QVariant TexturePackFolderModel::headerData(int section, [[maybe_unused]] Qt::Orientation orientation, int role) const
{
switch (role) {
case Qt::DisplayRole:

View File

@ -39,9 +39,9 @@ static Version mcVersion(BaseInstance* inst)
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getComponent("net.minecraft")->getVersion();
}
static ResourceAPI::ModLoaderTypes mcLoaders(BaseInstance* inst)
static ModPlatform::ModLoaderTypes mcLoaders(BaseInstance* inst)
{
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getModLoaders().value();
return static_cast<MinecraftInstance*>(inst)->getPackProfile()->getSupportedModLoaders().value();
}
GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
@ -61,7 +61,7 @@ GetModDependenciesTask::GetModDependenciesTask(QObject* parent,
if (auto meta = mod->metadata(); meta)
m_mods.append(meta);
prepare();
};
}
void GetModDependenciesTask::prepare()
{
@ -75,7 +75,7 @@ void GetModDependenciesTask::prepare()
ModPlatform::Dependency GetModDependenciesTask::getOverride(const ModPlatform::Dependency& dep,
const ModPlatform::ResourceProvider providerName)
{
if (auto isQuilt = m_loaderType & ResourceAPI::Quilt; isQuilt || m_loaderType & ResourceAPI::Fabric) {
if (auto isQuilt = m_loaderType & ModPlatform::Quilt; isQuilt || m_loaderType & ModPlatform::Fabric) {
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(), [dep, providerName, isQuilt](auto o) {
return o.provider == providerName && dep.addonId == (isQuilt ? o.fabric : o.quilt);
@ -130,7 +130,7 @@ QList<ModPlatform::Dependency> GetModDependenciesTask::getDependenciesForVersion
c_dependencies.append(getOverride(ver_dep, providerName));
}
return c_dependencies;
};
}
Task::Ptr GetModDependenciesTask::getProjectInfoTask(std::shared_ptr<PackDependency> pDep)
{
@ -181,7 +181,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
ResourceAPI::DependencySearchArgs args = { dep, m_version, m_loaderType };
ResourceAPI::DependencySearchCallbacks callbacks;
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, auto& pack) {
callbacks.on_succeed = [dep, provider, pDep, level, this](auto& doc, [[maybe_unused]] auto& pack) {
try {
QJsonArray arr;
if (dep.version.length() != 0 && doc.isObject()) {
@ -191,7 +191,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
}
pDep->version = provider.mod->loadDependencyVersions(dep, arr);
if (!pDep->version.addonId.isValid()) {
if (m_loaderType & ResourceAPI::Quilt) { // falback for quilt
if (m_loaderType & ModPlatform::Quilt) { // falback for quilt
auto overide = ModPlatform::getOverrideDeps();
auto over = std::find_if(overide.cbegin(), overide.cend(),
[dep, provider](auto o) { return o.provider == provider.name && dep.addonId == o.quilt; });
@ -201,6 +201,7 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
}
removePack(dep.addonId);
qWarning() << "Error while reading mod version empty ";
qDebug() << doc;
return;
@ -215,27 +216,27 @@ Task::Ptr GetModDependenciesTask::prepareDependencyTask(const ModPlatform::Depen
return;
}
if (level == 0) {
qWarning() << "Dependency cycle exeeded";
qWarning() << "Dependency cycle exceeded";
return;
}
if (dep.addonId.toString().isEmpty() && !pDep->version.addonId.toString().isEmpty()) {
pDep->pack->addonId = pDep->version.addonId;
auto dep = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
if (dep.addonId != pDep->version.addonId) {
auto dep_ = getOverride({ pDep->version.addonId, pDep->dependency.type }, provider.name);
if (dep_.addonId != pDep->version.addonId) {
removePack(pDep->version.addonId);
addTask(prepareDependencyTask(dep, provider.name, level));
addTask(prepareDependencyTask(dep_, provider.name, level));
} else
addTask(getProjectInfoTask(pDep));
}
for (auto dep : getDependenciesForVersion(pDep->version, provider.name)) {
addTask(prepareDependencyTask(dep, provider.name, level - 1));
for (auto dep_ : getDependenciesForVersion(pDep->version, provider.name)) {
addTask(prepareDependencyTask(dep_, provider.name, level - 1));
}
};
auto version = provider.api->getDependencyVersion(std::move(args), std::move(callbacks));
tasks->addTask(version);
return tasks;
};
}
void GetModDependenciesTask::removePack(const QVariant addonId)
{

View File

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

View File

@ -133,7 +133,7 @@ bool processZIP(DataPack& pack, ProcessingLevel level)
return true;
}
// https://minecraft.fandom.com/wiki/Data_pack#pack.mcmeta
// https://minecraft.wiki/w/Data_pack#pack.mcmeta
bool processMCMeta(DataPack& pack, QByteArray&& raw_data)
{
try {

View File

@ -104,14 +104,15 @@ ModDetails ReadMCModTOML(QByteArray contents)
#if TOML_EXCEPTIONS
try {
tomlData = toml::parse(contents.toStdString());
} catch (const toml::parse_error& err) {
} catch ([[maybe_unused]] const toml::parse_error& err) {
return {};
}
#else
tomlData = toml::parse(contents.toStdString());
if (!tomlData) {
toml::parse_result result = toml::parse(contents.toStdString());
if (!result) {
return {};
}
tomlData = result.table();
#endif
// array defined by [[mods]]
@ -151,8 +152,8 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString authors = "";
if (auto authorsDatum = tomlData["authors"].as_string()) {
authors = QString::fromStdString(authorsDatum->get());
} else if (auto authorsDatum = (*modsTable)["authors"].as_string()) {
authors = QString::fromStdString(authorsDatum->get());
} else if (auto authorsDatumMods = (*modsTable)["authors"].as_string()) {
authors = QString::fromStdString(authorsDatumMods->get());
}
if (!authors.isEmpty()) {
details.authors.append(authors);
@ -161,8 +162,8 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString homeurl = "";
if (auto homeurlDatum = tomlData["displayURL"].as_string()) {
homeurl = QString::fromStdString(homeurlDatum->get());
} else if (auto homeurlDatum = (*modsTable)["displayURL"].as_string()) {
homeurl = QString::fromStdString(homeurlDatum->get());
} else if (auto homeurlDatumMods = (*modsTable)["displayURL"].as_string()) {
homeurl = QString::fromStdString(homeurlDatumMods->get());
}
// fix up url.
if (!homeurl.isEmpty() && !homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) {
@ -173,16 +174,16 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString issueTrackerURL = "";
if (auto issueTrackerURLDatum = tomlData["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
} else if (auto issueTrackerURLDatum = (*modsTable)["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatum->get());
} else if (auto issueTrackerURLDatumMods = (*modsTable)["issueTrackerURL"].as_string()) {
issueTrackerURL = QString::fromStdString(issueTrackerURLDatumMods->get());
}
details.issue_tracker = issueTrackerURL;
QString license = "";
if (auto licenseDatum = tomlData["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
} else if (auto licenseDatum = (*modsTable)["license"].as_string()) {
license = QString::fromStdString(licenseDatum->get());
} else if (auto licenseDatumMods = (*modsTable)["license"].as_string()) {
license = QString::fromStdString(licenseDatumMods->get());
}
if (!license.isEmpty())
details.licenses.append(ModLicense(license));
@ -190,8 +191,8 @@ ModDetails ReadMCModTOML(QByteArray contents)
QString logoFile = "";
if (auto logoFileDatum = tomlData["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
} else if (auto logoFileDatum = (*modsTable)["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatum->get());
} else if (auto logoFileDatumMods = (*modsTable)["logoFile"].as_string()) {
logoFile = QString::fromStdString(logoFileDatumMods->get());
}
details.icon_file = logoFile;
@ -458,7 +459,7 @@ bool process(Mod& mod, ProcessingLevel level)
}
}
bool processZIP(Mod& mod, ProcessingLevel level)
bool processZIP(Mod& mod, [[maybe_unused]] ProcessingLevel level)
{
ModDetails details;
@ -591,7 +592,7 @@ bool processZIP(Mod& mod, ProcessingLevel level)
return false; // no valid mod found in archive
}
bool processFolder(Mod& mod, ProcessingLevel level)
bool processFolder(Mod& mod, [[maybe_unused]] ProcessingLevel level)
{
ModDetails details;
@ -612,7 +613,7 @@ bool processFolder(Mod& mod, ProcessingLevel level)
return false; // no valid mcmod.info file found
}
bool processLitemod(Mod& mod, ProcessingLevel level)
bool processLitemod(Mod& mod, [[maybe_unused]] ProcessingLevel level)
{
ModDetails details;

View File

@ -178,7 +178,7 @@ bool processZIP(ResourcePack& pack, ProcessingLevel level)
return true;
}
// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
// https://minecraft.wiki/w/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta
bool processMCMeta(ResourcePack& pack, QByteArray&& raw_data)
{
try {

View File

@ -42,7 +42,7 @@
CapeChange::CapeChange(QObject* parent, QString token, QString cape) : Task(parent), m_capeId(cape), m_token(token) {}
void CapeChange::setCape(QString& cape)
void CapeChange::setCape([[maybe_unused]] QString& cape)
{
QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/capes/active"));
auto requestString = QString("{\"capeId\":\"%1\"}").arg(m_capeId);

View File

@ -7,6 +7,8 @@
#include "Application.h"
#include "net/ApiDownload.h"
AssetUpdateTask::AssetUpdateTask(MinecraftInstance* inst)
{
m_inst = inst;
@ -29,7 +31,7 @@ void AssetUpdateTask::executeTask()
entry->setStale(true);
auto hexSha1 = assets->sha1.toLatin1();
qDebug() << "Asset index SHA1:" << hexSha1;
auto dl = Net::Download::makeCached(indexUrl, entry);
auto dl = Net::ApiDownload::makeCached(indexUrl, entry);
auto rawSha1 = QByteArray::fromHex(assets->sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
job->addNetAction(dl);

View File

@ -8,6 +8,8 @@
#include "Application.h"
#include "BuildConfig.h"
#include "net/ApiDownload.h"
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance* inst)
{
m_inst = inst;
@ -62,7 +64,7 @@ void FMLLibrariesTask::executeTask()
for (auto& lib : fmlLibsToProcess) {
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options));
dljob->addNetAction(Net::ApiDownload::makeCached(QUrl(urlString), entry, options));
}
connect(dljob.get(), &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished);

View File

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

View File

@ -134,4 +134,25 @@ QString getMetaURL(ResourceProvider provider, QVariant projectID)
projectID.toString();
}
auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
case NeoForge:
return "neoforge";
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
default:
break;
}
return "";
}
} // namespace ModPlatform

View File

@ -31,6 +31,9 @@ class QIODevice;
namespace ModPlatform {
enum ModLoaderType { NeoForge = 1 << 0, Forge = 1 << 1, Cauldron = 1 << 2, LiteLoader = 1 << 3, Fabric = 1 << 4, Quilt = 1 << 5 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
enum class ResourceProvider { MODRINTH, FLAME };
enum class ResourceType { MOD, RESOURCE_PACK, SHADER_PACK };
@ -101,7 +104,7 @@ struct IndexedVersion {
QString downloadUrl;
QString date;
QString fileName;
QStringList loaders = {};
ModLoaderTypes loaders = {};
QString hash_type;
QString hash;
bool is_preferred = true;
@ -144,7 +147,7 @@ struct IndexedPack {
ExtraPackData extraData;
// For internal use, not provided by APIs
[[nodiscard]] bool isVersionSelected(size_t index) const
[[nodiscard]] bool isVersionSelected(int index) const
{
if (!versionsLoaded)
return false;
@ -174,9 +177,18 @@ inline auto getOverrideDeps() -> QList<OverrideDep>
{ "qvIfYCYJ", "P7dR8mSH", "API", ModPlatform::ResourceProvider::MODRINTH },
{ "lwVhp9o5", "Ha28R6CL", "KotlinLibraries", ModPlatform::ResourceProvider::MODRINTH } };
};
}
QString getMetaURL(ResourceProvider provider, QVariant projectID);
auto getModLoaderString(ModLoaderType type) -> const QString;
constexpr bool hasSingleModLoaderSelected(ModLoaderTypes l) noexcept
{
auto x = static_cast<int>(l);
return x && !(x & (x - 1));
}
} // namespace ModPlatform
Q_DECLARE_METATYPE(ModPlatform::IndexedPack)

View File

@ -54,9 +54,6 @@ class ResourceAPI {
public:
virtual ~ResourceAPI() = default;
enum ModLoaderType { Forge = 1 << 0, Cauldron = 1 << 1, LiteLoader = 1 << 2, Fabric = 1 << 3, Quilt = 1 << 4 };
Q_DECLARE_FLAGS(ModLoaderTypes, ModLoaderType)
struct SortingMethod {
// The index of the sorting method. Used to allow for arbitrary ordering in the list of methods.
// Used by Flame in the API request.
@ -74,7 +71,7 @@ class ResourceAPI {
std::optional<QString> search;
std::optional<SortingMethod> sorting;
std::optional<ModLoaderTypes> loaders;
std::optional<ModPlatform::ModLoaderTypes> loaders;
std::optional<std::list<Version> > versions;
};
struct SearchCallbacks {
@ -87,7 +84,7 @@ class ResourceAPI {
ModPlatform::IndexedPack pack;
std::optional<std::list<Version> > mcVersions;
std::optional<ModLoaderTypes> loaders;
std::optional<ModPlatform::ModLoaderTypes> loaders;
VersionSearchArgs(VersionSearchArgs const&) = default;
void operator=(VersionSearchArgs other)
@ -108,13 +105,15 @@ class ResourceAPI {
void operator=(ProjectInfoArgs other) { pack = other.pack; }
};
struct ProjectInfoCallbacks {
std::function<void(QJsonDocument&, ModPlatform::IndexedPack)> on_succeed;
std::function<void(QJsonDocument&, const ModPlatform::IndexedPack&)> on_succeed;
std::function<void(QString const& reason)> on_fail;
std::function<void()> on_abort;
};
struct DependencySearchArgs {
ModPlatform::Dependency dependency;
Version mcVersion;
ModLoaderTypes loader;
ModPlatform::ModLoaderTypes loader;
};
struct DependencySearchCallbacks {
@ -128,28 +127,30 @@ class ResourceAPI {
public slots:
[[nodiscard]] virtual Task::Ptr searchProjects(SearchArgs&&, SearchCallbacks&&) const
{
qWarning() << "TODO";
qWarning() << "TODO: ResourceAPI::searchProjects";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProject(QString addonId, std::shared_ptr<QByteArray> response) const
[[nodiscard]] virtual Task::Ptr getProject([[maybe_unused]] QString addonId,
[[maybe_unused]] std::shared_ptr<QByteArray> response) const
{
qWarning() << "TODO";
qWarning() << "TODO: ResourceAPI::getProject";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjects(QStringList addonIds, std::shared_ptr<QByteArray> response) const
[[nodiscard]] virtual Task::Ptr getProjects([[maybe_unused]] QStringList addonIds,
[[maybe_unused]] std::shared_ptr<QByteArray> response) const
{
qWarning() << "TODO";
qWarning() << "TODO: ResourceAPI::getProjects";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectInfo(ProjectInfoArgs&&, ProjectInfoCallbacks&&) const
{
qWarning() << "TODO";
qWarning() << "TODO: ResourceAPI::getProjectInfo";
return nullptr;
}
[[nodiscard]] virtual Task::Ptr getProjectVersions(VersionSearchArgs&&, VersionSearchCallbacks&&) const
{
qWarning() << "TODO";
qWarning() << "TODO: ResourceAPI::getProjectVersions";
return nullptr;
}
@ -159,25 +160,6 @@ class ResourceAPI {
return nullptr;
}
static auto getModLoaderString(ModLoaderType type) -> const QString
{
switch (type) {
case Forge:
return "forge";
case Cauldron:
return "cauldron";
case LiteLoader:
return "liteloader";
case Fabric:
return "fabric";
case Quilt:
return "quilt";
default:
break;
}
return "";
}
protected:
[[nodiscard]] inline QString debugName() const { return "External resource API"; }

View File

@ -53,6 +53,8 @@
#include "net/ChecksumValidator.h"
#include "settings/INISettingsObject.h"
#include "net/ApiDownload.h"
#include "Application.h"
#include "BuildConfig.h"
@ -83,7 +85,7 @@ void PackInstallTask::executeTask()
NetJob::Ptr netJob{ new NetJob("ATLauncher::VersionFetch", APPLICATION->network()) };
auto searchUrl =
QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json").arg(m_pack_safe_name).arg(m_version_name);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), response));
netJob->addNetAction(Net::ApiDownload::makeByteArray(QUrl(searchUrl), response));
QObject::connect(netJob.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
QObject::connect(netJob.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
@ -631,7 +633,7 @@ void PackInstallTask::installConfigs()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", path);
entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!m_version.configs.sha1.isEmpty()) {
auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
@ -748,7 +750,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToExtract.insert(entry->getFullPath(), mod);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -759,7 +761,7 @@ void PackInstallTask::downloadMods()
entry->setStale(true);
modsToDecomp.insert(entry->getFullPath(), mod);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -773,7 +775,7 @@ void PackInstallTask::downloadMods()
auto entry = APPLICATION->metacache()->resolveEntry("ATLauncherPacks", cacheName);
entry->setStale(true);
auto dl = Net::Download::makeCached(url, entry);
auto dl = Net::ApiDownload::makeCached(url, entry);
if (!mod.md5.isEmpty()) {
auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
@ -1003,15 +1005,30 @@ static Meta::Version::Ptr getComponentVersion(const QString& uid, const QString&
if (!vlist)
return {};
if (!vlist->isLoaded())
vlist->load(Net::Mode::Online);
if (!vlist->isLoaded()) {
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);
if (!ver)
return {};
if (!ver->isLoaded())
if (!ver->isLoaded()) {
QEventLoop loadVersionLoop;
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;
}

View File

@ -1,6 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
#include "modplatform/ModIndex.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.h"
#include "net/Upload.h"
#include "modplatform/modrinth/ModrinthPackIndex.h"
@ -38,7 +41,7 @@ void Flame::FileResolvingTask::executeTask()
return l;
}));
QByteArray data = Json::toText(object);
auto dl = Net::Upload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
auto dl = Net::ApiUpload::makeByteArray(QUrl("https://api.curseforge.com/v1/mods/files"), result, data);
m_dljob->addNetAction(dl);
auto step_progress = std::make_shared<TaskStepProgress>();
@ -93,14 +96,14 @@ void Flame::FileResolvingTask::netJobFinished()
auto& out = m_toProcess.files[fileid];
try {
out.parseFromObject(Json::requireObject(file));
} catch (const JSONValidationError& e) {
} catch ([[maybe_unused]] const JSONValidationError& e) {
qDebug() << "Blocked mod on curseforge" << out.fileName;
auto hash = out.hash;
if (!hash.isEmpty()) {
auto url = QString("https://api.modrinth.com/v2/version_file/%1?algorithm=sha1").arg(hash);
auto output = std::make_shared<QByteArray>();
auto dl = Net::Download::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::Download::succeeded, [&out]() { out.resolved = true; });
auto dl = Net::ApiDownload::makeByteArray(QUrl(url), output);
QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [&out]() { out.resolved = true; });
m_checkJob->addNetAction(dl);
blockedProjects.insert(&out, output);
@ -151,7 +154,7 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
// If there's more than one mod loader for this version, we can't know for sure
// which file is relative to each loader, so it's best to not use any one and
// let the user download it manually.
if (file.loaders.size() <= 1) {
if (!file.loaders || hasSingleModLoaderSelected(file.loaders)) {
out->url = file.downloadUrl;
qDebug() << "Found alternative on modrinth " << out->fileName;
} else {
@ -171,9 +174,9 @@ void Flame::FileResolvingTask::modrinthCheckFinished()
auto projectId = mod->projectId;
auto output = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(projectId);
auto dl = Net::Download::makeByteArray(url, output);
auto dl = Net::ApiDownload::makeByteArray(url, output);
qDebug() << "Fetching url slug for file:" << mod->fileName;
QObject::connect(dl.get(), &Net::Download::succeeded, [block, index, output]() {
QObject::connect(dl.get(), &Net::ApiDownload::succeeded, [block, index, output]() {
auto mod = block->at(index); // use the shared_ptr so it is captured and only freed when we are done
auto json = QJsonDocument::fromJson(*output);
auto base =

View File

@ -6,8 +6,9 @@
#include "FlameModIndex.h"
#include "Application.h"
#include "BuildConfig.h"
#include "Json.h"
#include "net/ApiDownload.h"
#include "net/ApiUpload.h"
#include "net/NetJob.h"
#include "net/Upload.h"
@ -26,7 +27,7 @@ Task::Ptr FlameAPI::matchFingerprints(const QList<uint>& fingerprints, std::shar
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/fingerprints"), response, body_raw));
return netJob;
}
@ -38,7 +39,7 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::FileChangelog"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
netJob->addNetAction(Net::Download::makeByteArray(
netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/files/%2/changelog")
.arg(QString::fromStdString(std::to_string(modId)), QString::fromStdString(std::to_string(fileId))),
response));
@ -73,8 +74,8 @@ auto FlameAPI::getModDescription(int modId) -> QString
auto netJob = makeShared<NetJob>(QString("Flame::ModDescription"), APPLICATION->network());
auto response = std::make_shared<QByteArray>();
netJob->addNetAction(
Net::Download::makeByteArray(QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
netJob->addNetAction(Net::ApiDownload::makeByteArray(
QString("https://api.curseforge.com/v1/mods/%1/description").arg(QString::number(modId)), response));
QObject::connect(netJob.get(), &NetJob::succeeded, [&netJob, response, &description] {
QJsonParseError parse_error{};
@ -113,7 +114,7 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto response = std::make_shared<QByteArray>();
ModPlatform::IndexedVersion ver;
netJob->addNetAction(Net::Download::makeByteArray(versions_url, response));
netJob->addNetAction(Net::ApiDownload::makeByteArray(versions_url, response));
QObject::connect(netJob.get(), &NetJob::succeeded, [response, args, &ver] {
QJsonParseError parse_error{};
@ -129,20 +130,15 @@ auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::Indexe
auto obj = Json::requireObject(doc);
auto arr = Json::requireArray(obj, "data");
QJsonObject latest_file_obj;
ModPlatform::IndexedVersion ver_tmp;
for (auto file : arr) {
auto file_obj = Json::requireObject(file);
auto file_tmp = FlameMod::loadIndexedPackVersion(file_obj);
bool better_release = file_tmp.version_type <= ver_tmp.version_type;
if (file_tmp.date > ver_tmp.date && better_release) {
ver_tmp = file_tmp;
latest_file_obj = file_obj;
}
if (file_tmp.date > ver.date &&
(!args.loaders.has_value() || !file_tmp.loaders || args.loaders.value() & file_tmp.loaders) &&
file_tmp.version_type <= ver.version_type)
ver = file_tmp;
}
ver = FlameMod::loadIndexedPackVersion(latest_file_obj);
} catch (Json::JsonException& e) {
qCritical() << "Failed to parse response from a version request.";
qCritical() << e.what();
@ -174,7 +170,7 @@ Task::Ptr FlameAPI::getProjects(QStringList addonIds, std::shared_ptr<QByteArray
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
@ -196,13 +192,24 @@ Task::Ptr FlameAPI::getFiles(const QStringList& fileIds, std::shared_ptr<QByteAr
QJsonDocument body(body_obj);
auto body_raw = body.toJson();
netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
netJob->addNetAction(Net::ApiUpload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw));
QObject::connect(netJob.get(), &NetJob::failed, [body_raw] { qDebug() << body_raw; });
return netJob;
}
Task::Ptr FlameAPI::getFile(const QString& addonId, const QString& fileId, std::shared_ptr<QByteArray> response) const
{
auto netJob = makeShared<NetJob>(QString("Flame::GetFile"), APPLICATION->network());
netJob->addNetAction(
Net::ApiDownload::makeByteArray(QUrl(QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(addonId, fileId)), response));
QObject::connect(netJob.get(), &NetJob::failed, [addonId, fileId] { qDebug() << "Flame API file failure" << addonId << fileId; });
return netJob;
}
// https://docs.curseforge.com/?python#tocS_ModsSearchSortField
static QList<ResourceAPI::SortingMethod> s_sorts = { { 1, "Featured", QObject::tr("Sort by Featured") },
{ 2, "Popularity", QObject::tr("Sort by Popularity") },

View File

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

View File

@ -5,13 +5,13 @@
#include <MurmurHash2.h>
#include <memory>
#include "FileSystem.h"
#include "Json.h"
#include "ResourceDownloadTask.h"
#include "minecraft/mod/ModFolderModel.h"
#include "minecraft/mod/ResourceFolderModel.h"
#include "net/ApiDownload.h"
static FlameAPI api;
@ -33,7 +33,7 @@ ModPlatform::IndexedPack getProjectInfo(ModPlatform::IndexedVersion& ver_info)
auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1").arg(ver_info.addonId.toString());
auto dl = Net::Download::makeByteArray(url, response);
auto dl = Net::ApiDownload::makeByteArray(url, response);
get_project_job->addNetAction(dl);
QObject::connect(get_project_job, &NetJob::succeeded, [response, &pack]() {
@ -77,7 +77,7 @@ ModPlatform::IndexedVersion getFileInfo(int addonId, int fileId)
auto response = std::make_shared<QByteArray>();
auto url = QString("https://api.curseforge.com/v1/mods/%1/files/%2").arg(QString::number(addonId), QString::number(fileId));
auto dl = Net::Download::makeByteArray(url, response);
auto dl = Net::ApiDownload::makeByteArray(url, response);
get_file_info_job->addNetAction(dl);
QObject::connect(get_file_info_job, &NetJob::succeeded, [response, &ver]() {

View File

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

View File

@ -61,6 +61,7 @@
#include "meta/VersionList.h"
#include "minecraft/World.h"
#include "minecraft/mod/tasks/LocalResourceParse.h"
#include "net/ApiDownload.h"
static const FlameAPI api;
@ -283,7 +284,7 @@ QString FlameCreationTask::getVersionForLoader(QString uid, QString loaderType,
// filter by minecraft version, if the loader depends on a certain version.
// not all mod loaders depend on a given Minecraft version, so we won't do this
// filtering for those loaders.
if (loaderType == "forge") {
if (loaderType == "forge" || loaderType == "neoforge") {
auto iter = std::find_if(reqs.begin(), reqs.end(), [mcVersion](const Meta::Require& req) {
return req.uid == "net.minecraft" && req.equalsVersion == mcVersion;
});
@ -349,7 +350,11 @@ bool FlameCreationTask::createInstance()
for (auto& loader : m_pack.minecraft.modLoaders) {
auto id = loader.id;
if (id.startsWith("forge-")) {
if (id.startsWith("neoforge-")) {
id.remove("neoforge-");
loaderType = "neoforge";
loaderUid = "net.neoforged";
} else if (id.startsWith("forge-")) {
id.remove("forge-");
loaderType = "forge";
loaderUid = "net.minecraftforge";
@ -523,7 +528,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
case Flame::File::Type::Mod: {
if (!result.url.isEmpty()) {
qDebug() << "Will download" << result.url << "to" << path;
auto dl = Net::Download::makeFile(result.url, path);
auto dl = Net::ApiDownload::makeFile(result.url, path);
m_files_job->addNetAction(dl);
}
break;

View File

@ -54,7 +54,7 @@ void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj)
pack.extraDataLoaded = true;
}
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj)
void FlameMod::loadBody(ModPlatform::IndexedPack& pack, [[maybe_unused]] QJsonObject& obj)
{
pack.extraData.body = api.getModDescription(pack.addonId.toInt());
@ -75,12 +75,13 @@ static QString enumToString(int hash_algorithm)
void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
QJsonArray& arr,
const shared_qobject_ptr<QNetworkAccessManager>& network,
[[maybe_unused]] const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst)
{
QVector<ModPlatform::IndexedVersion> unsortedVersions;
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@ -89,7 +90,8 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
if (!file.addonId.isValid())
file.addonId = pack.addonId;
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
if (file.fileId.isValid() &&
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
unsortedVersions.append(file);
}
@ -116,6 +118,19 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
if (str.contains('.'))
file.mcVersion.append(str);
auto loader = str.toLower();
if (loader == "neoforge")
file.loaders |= ModPlatform::NeoForge;
if (loader == "forge")
file.loaders |= ModPlatform::Forge;
if (loader == "cauldron")
file.loaders |= ModPlatform::Cauldron;
if (loader == "liteloader")
file.loaders |= ModPlatform::LiteLoader;
if (loader == "fabric")
file.loaders |= ModPlatform::Fabric;
if (loader == "quilt")
file.loaders |= ModPlatform::Quilt;
}
file.addonId = Json::requireInteger(obj, "modId");
@ -175,8 +190,11 @@ auto FlameMod::loadIndexedPackVersion(QJsonObject& obj, bool load_changelog) ->
return file;
}
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr)
ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst)
{
auto profile = (dynamic_cast<const MinecraftInstance*>(inst))->getPackProfile();
QString mcVersion = profile->getComponentVersion("net.minecraft");
auto loaders = profile->getSupportedModLoaders();
QVector<ModPlatform::IndexedVersion> versions;
for (auto versionIter : arr) {
auto obj = versionIter.toObject();
@ -185,7 +203,8 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
if (!file.addonId.isValid())
file.addonId = m.addonId;
if (file.fileId.isValid()) // Heuristic to check if the returned value is valid
if (file.fileId.isValid() &&
(!loaders.has_value() || !file.loaders || loaders.value() & file.loaders)) // Heuristic to check if the returned value is valid
versions.append(file);
}
@ -194,5 +213,7 @@ ModPlatform::IndexedVersion FlameMod::loadDependencyVersions(const ModPlatform::
return a.date > b.date;
};
std::sort(versions.begin(), versions.end(), orderSortPredicate);
return versions.front();
};
if (versions.size() != 0)
return versions.front();
return {};
}

View File

@ -19,5 +19,5 @@ void loadIndexedPackVersions(ModPlatform::IndexedPack& pack,
const shared_qobject_ptr<QNetworkAccessManager>& network,
const BaseInstance* inst);
auto loadIndexedPackVersion(QJsonObject& obj, bool load_changelog = false) -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr) -> ModPlatform::IndexedVersion;
auto loadDependencyVersions(const ModPlatform::Dependency& m, QJsonArray& arr, const BaseInstance* inst) -> ModPlatform::IndexedVersion;
} // namespace FlameMod

View File

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

View File

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

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