aboutsummaryrefslogtreecommitdiff
path: root/launcher
diff options
context:
space:
mode:
Diffstat (limited to 'launcher')
-rw-r--r--launcher/Application.cpp137
-rw-r--r--launcher/Application.h3
-rw-r--r--launcher/BaseInstance.h2
-rw-r--r--launcher/BaseVersionList.cpp4
-rw-r--r--launcher/CMakeLists.txt23
-rw-r--r--launcher/DataMigrationTask.cpp96
-rw-r--r--launcher/DataMigrationTask.h42
-rw-r--r--launcher/FileSystem.cpp41
-rw-r--r--launcher/FileSystem.h31
-rw-r--r--launcher/InstanceCopyPrefs.cpp135
-rw-r--r--launcher/InstanceCopyPrefs.h41
-rw-r--r--launcher/InstanceCopyTask.cpp16
-rw-r--r--launcher/InstanceCopyTask.h16
-rw-r--r--launcher/JavaCommon.cpp2
-rw-r--r--launcher/MMCStrings.h8
-rw-r--r--launcher/StringUtils.cpp (renamed from launcher/MMCStrings.cpp)42
-rw-r--r--launcher/StringUtils.h32
-rw-r--r--launcher/VersionProxyModel.cpp4
-rw-r--r--launcher/icons/IconList.cpp4
-rw-r--r--launcher/java/JavaInstall.cpp7
-rw-r--r--launcher/java/JavaInstallList.cpp1
-rw-r--r--launcher/java/JavaVersion.cpp7
-rw-r--r--launcher/launch/LaunchTask.cpp1
-rw-r--r--launcher/main.cpp4
-rw-r--r--launcher/meta/Index.cpp4
-rw-r--r--launcher/meta/JsonFormat.h5
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp36
-rw-r--r--launcher/minecraft/MinecraftInstance.h4
-rw-r--r--launcher/minecraft/MojangVersionFormat.cpp17
-rw-r--r--launcher/minecraft/OneSixVersionFormat.cpp29
-rw-r--r--launcher/minecraft/PackProfile.cpp6
-rw-r--r--launcher/minecraft/Rule.h2
-rw-r--r--launcher/minecraft/WorldList.cpp6
-rw-r--r--launcher/minecraft/WorldList.h2
-rw-r--r--launcher/minecraft/auth/AccountList.cpp10
-rw-r--r--launcher/minecraft/mod/ModFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/ResourceFolderModel.h6
-rw-r--r--launcher/minecraft/mod/ResourcePackFolderModel.cpp2
-rw-r--r--launcher/minecraft/mod/tasks/LocalModParseTask.cpp2
-rw-r--r--launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp2
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.cpp59
-rw-r--r--launcher/modplatform/flame/FlameInstanceCreationTask.h3
-rw-r--r--launcher/modplatform/helpers/HashUtils.cpp61
-rw-r--r--launcher/modplatform/helpers/HashUtils.h15
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.cpp63
-rw-r--r--launcher/modplatform/modpacksch/FTBPackInstallTask.h3
-rw-r--r--launcher/modplatform/packwiz/Packwiz.cpp26
-rw-r--r--launcher/modplatform/packwiz/Packwiz.h4
-rw-r--r--launcher/net/HttpMetaCache.cpp72
-rw-r--r--launcher/net/HttpMetaCache.h58
-rw-r--r--launcher/pathmatcher/SimplePrefixMatcher.h25
-rw-r--r--launcher/resources/backgrounds/backgrounds.qrc4
-rw-r--r--launcher/resources/backgrounds/kitteh-spooky.png (renamed from launcher/resources/backgrounds/kitteh-ween.png)bin94677 -> 94677 bytes
-rw-r--r--launcher/resources/backgrounds/rory-flat-spooky.pngbin0 -> 66906 bytes
-rw-r--r--launcher/resources/backgrounds/rory-spooky.pngbin0 -> 95940 bytes
-rw-r--r--launcher/resources/breeze_dark/breeze_dark.qrc43
-rw-r--r--launcher/resources/breeze_dark/index.theme11
-rw-r--r--launcher/resources/breeze_dark/scalable/about.svg12
-rw-r--r--launcher/resources/breeze_dark/scalable/accounts.svg17
-rw-r--r--launcher/resources/breeze_dark/scalable/bug.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/centralmods.svg1
-rw-r--r--launcher/resources/breeze_dark/scalable/checkupdate.svg14
-rw-r--r--launcher/resources/breeze_dark/scalable/copy.svg11
-rw-r--r--launcher/resources/breeze_dark/scalable/coremods.svg1
-rw-r--r--launcher/resources/breeze_dark/scalable/custom-commands.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/delete.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/discord.svg1
-rw-r--r--launcher/resources/breeze_dark/scalable/export.svg11
-rw-r--r--launcher/resources/breeze_dark/scalable/externaltools.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/help.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/instance-settings.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/jarmods.svg1
-rw-r--r--launcher/resources/breeze_dark/scalable/java.svg10
-rw-r--r--launcher/resources/breeze_dark/scalable/language.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/launch.svg8
-rw-r--r--launcher/resources/breeze_dark/scalable/launcher.svg57
-rw-r--r--launcher/resources/breeze_dark/scalable/loadermods.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/log.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/matrix.svg9
-rw-r--r--launcher/resources/breeze_dark/scalable/minecraft.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/new.svg18
-rw-r--r--launcher/resources/breeze_dark/scalable/news.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/notes.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/patreon.svg3
-rw-r--r--launcher/resources/breeze_dark/scalable/proxy.svg14
-rw-r--r--launcher/resources/breeze_dark/scalable/reddit-alien.svg3
-rw-r--r--launcher/resources/breeze_dark/scalable/refresh.svg8
-rw-r--r--launcher/resources/breeze_dark/scalable/rename.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/resourcepacks.svg11
-rw-r--r--launcher/resources/breeze_dark/scalable/screenshots.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/settings.svg17
-rw-r--r--launcher/resources/breeze_dark/scalable/shaderpacks.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/status-bad.svg9
-rw-r--r--launcher/resources/breeze_dark/scalable/status-good.svg10
-rw-r--r--launcher/resources/breeze_dark/scalable/status-yellow.svg9
-rw-r--r--launcher/resources/breeze_dark/scalable/tag.svg17
-rw-r--r--launcher/resources/breeze_dark/scalable/viewfolder.svg13
-rw-r--r--launcher/resources/breeze_dark/scalable/worlds.svg16
-rw-r--r--launcher/resources/breeze_light/breeze_light.qrc43
-rw-r--r--launcher/resources/breeze_light/index.theme11
-rw-r--r--launcher/resources/breeze_light/scalable/about.svg12
-rw-r--r--launcher/resources/breeze_light/scalable/accounts.svg17
-rw-r--r--launcher/resources/breeze_light/scalable/bug.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/centralmods.svg1
-rw-r--r--launcher/resources/breeze_light/scalable/checkupdate.svg14
-rw-r--r--launcher/resources/breeze_light/scalable/copy.svg11
-rw-r--r--launcher/resources/breeze_light/scalable/coremods.svg1
-rw-r--r--launcher/resources/breeze_light/scalable/custom-commands.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/delete.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/discord.svg1
-rw-r--r--launcher/resources/breeze_light/scalable/export.svg11
-rw-r--r--launcher/resources/breeze_light/scalable/externaltools.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/help.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/instance-settings.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/jarmods.svg1
-rw-r--r--launcher/resources/breeze_light/scalable/java.svg10
-rw-r--r--launcher/resources/breeze_light/scalable/language.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/launch.svg8
-rw-r--r--launcher/resources/breeze_light/scalable/loadermods.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/log.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/matrix.svg9
-rw-r--r--launcher/resources/breeze_light/scalable/minecraft.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/new.svg18
-rw-r--r--launcher/resources/breeze_light/scalable/news.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/notes.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/patreon.svg3
-rw-r--r--launcher/resources/breeze_light/scalable/proxy.svg14
-rw-r--r--launcher/resources/breeze_light/scalable/reddit-alien.svg3
-rw-r--r--launcher/resources/breeze_light/scalable/refresh.svg8
-rw-r--r--launcher/resources/breeze_light/scalable/rename.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/resourcepacks.svg11
-rw-r--r--launcher/resources/breeze_light/scalable/screenshots.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/settings.svg17
-rw-r--r--launcher/resources/breeze_light/scalable/shaderpacks.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/status-bad.svg9
-rw-r--r--launcher/resources/breeze_light/scalable/status-good.svg10
-rw-r--r--launcher/resources/breeze_light/scalable/status-yellow.svg9
-rw-r--r--launcher/resources/breeze_light/scalable/tag.svg17
-rw-r--r--launcher/resources/breeze_light/scalable/viewfolder.svg13
-rw-r--r--launcher/resources/breeze_light/scalable/worlds.svg16
-rw-r--r--launcher/translations/TranslationsModel.cpp9
-rw-r--r--launcher/ui/MainWindow.cpp105
-rw-r--r--launcher/ui/MainWindow.h6
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.cpp315
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.h46
-rw-r--r--launcher/ui/dialogs/BlockedModsDialog.ui96
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.cpp100
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.h19
-rw-r--r--launcher/ui/dialogs/CopyInstanceDialog.ui121
-rw-r--r--launcher/ui/dialogs/ExportInstanceDialog.cpp9
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp21
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.h28
-rw-r--r--launcher/ui/dialogs/ProgressDialog.cpp3
-rw-r--r--launcher/ui/pages/global/LauncherPage.cpp29
-rw-r--r--launcher/ui/pages/global/LauncherPage.ui10
-rw-r--r--launcher/ui/pages/instance/ExternalResourcesPage.ui6
-rw-r--r--launcher/ui/pages/instance/ServersPage.cpp4
-rw-r--r--launcher/ui/pages/instance/VersionPage.ui6
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h8
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp91
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h1
-rw-r--r--launcher/ui/pages/modplatform/ModPage.ui4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp5
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp4
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.cpp23
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModPage.h5
-rw-r--r--launcher/ui/pages/modplatform/flame/FlameModel.cpp5
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp5
-rw-r--r--launcher/ui/pages/modplatform/ftb/FtbListModel.cpp4
-rw-r--r--launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp8
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp2
-rw-r--r--launcher/ui/pages/modplatform/modrinth/ModrinthModel.h6
-rw-r--r--launcher/ui/pages/modplatform/technic/TechnicModel.cpp8
175 files changed, 2948 insertions, 440 deletions
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 9f731efa..ea8d2326 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -1,4 +1,7 @@
-// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
+//
+// SPDX-License-Identifier: GPL-3.0-only AND Apache-2.0
+
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
@@ -38,10 +41,14 @@
#include "Application.h"
#include "BuildConfig.h"
+#include "DataMigrationTask.h"
#include "net/PasteUpload.h"
+#include "pathmatcher/MultiMatcher.h"
+#include "pathmatcher/SimplePrefixMatcher.h"
#include "ui/MainWindow.h"
#include "ui/InstanceWindow.h"
+#include "ui/dialogs/ProgressDialog.h"
#include "ui/instanceview/AccessibleInstanceView.h"
#include "ui/pages/BasePageProvider.h"
@@ -226,7 +233,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
setApplicationName(BuildConfig.LAUNCHER_NAME);
setApplicationDisplayName(QString("%1 %2").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()));
- setApplicationVersion(BuildConfig.printableVersionString());
+ setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
setDesktopFileName(BuildConfig.LAUNCHER_DESKTOPFILENAME);
startTime = QDateTime::currentDateTime();
@@ -301,22 +308,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
dataPath = foo.absolutePath();
adjustedBy = "Persistent data path";
- QDir polymcData(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "PolyMC"));
- if (polymcData.exists()) {
- dataPath = polymcData.absolutePath();
- adjustedBy = "PolyMC data path";
- }
-
-#ifdef Q_OS_LINUX
- // TODO: this should be removed in a future version
- // TODO: provide a migration path similar to macOS migration
- QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc"));
- if (bar.exists()) {
- dataPath = bar.absolutePath();
- adjustedBy = "Legacy data path";
- }
-#endif
-
#ifndef Q_OS_MACOS
if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) {
dataPath = m_rootPath;
@@ -440,6 +431,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
}
{
+ bool migrated = false;
+
+ if (!migrated)
+ migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../PolyMC"), "PolyMC", "polymc.cfg");
+ if (!migrated)
+ migrated = handleDataMigration(dataPath, FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "../../multimc"), "MultiMC", "multimc.cfg");
+ }
+
+ {
qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT;
qDebug() << "Version : " << BuildConfig.printableVersionString();
@@ -563,7 +563,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
// Memory
m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
- m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 4096);
+ m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, suitableMaxMem());
m_settings->registerSetting("PermGen", 128);
// Java Settings
@@ -1591,3 +1591,102 @@ QString Application::getUserAgentUncached()
return BuildConfig.USER_AGENT_UNCACHED;
}
+
+int Application::suitableMaxMem()
+{
+ float totalRAM = (float)Sys::getSystemRam() / (float)Sys::mebibyte;
+ int maxMemoryAlloc;
+
+ // If totalRAM < 6GB, use (totalRAM / 1.5), else 4GB
+ if (totalRAM < (4096 * 1.5))
+ maxMemoryAlloc = (int) (totalRAM / 1.5);
+ else
+ maxMemoryAlloc = 4096;
+
+ return maxMemoryAlloc;
+}
+
+bool Application::handleDataMigration(const QString& currentData,
+ const QString& oldData,
+ const QString& name,
+ const QString& configFile) const
+{
+ QString nomigratePath = FS::PathCombine(currentData, name + "_nomigrate.txt");
+ QStringList configPaths = { FS::PathCombine(oldData, configFile), FS::PathCombine(oldData, BuildConfig.LAUNCHER_CONFIGFILE) };
+
+ QLocale locale;
+
+ // Is there a valid config at the old location?
+ bool configExists = false;
+ for (QString configPath : configPaths) {
+ configExists |= QFileInfo::exists(configPath);
+ }
+
+ if (!configExists || QFileInfo::exists(nomigratePath)) {
+ qDebug() << "<> No migration needed from" << name;
+ return false;
+ }
+
+ QString message;
+ bool currentExists = QFileInfo::exists(FS::PathCombine(currentData, BuildConfig.LAUNCHER_CONFIGFILE));
+
+ if (currentExists) {
+ message = tr("Old data from %1 was found, but you already have existing data for %2. Sadly you will need to migrate yourself. Do "
+ "you want to be reminded of the pending data migration next time you start %2?")
+ .arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
+ } else {
+ message = tr("It looks like you used %1 before. Do you want to migrate your data to the new location of %2?")
+ .arg(name, BuildConfig.LAUNCHER_DISPLAYNAME);
+
+ QFileInfo logInfo(FS::PathCombine(oldData, name + "-0.log"));
+ if (logInfo.exists()) {
+ QString lastModified = logInfo.lastModified().toString(locale.dateFormat());
+ message = tr("It looks like you used %1 on %2 before. Do you want to migrate your data to the new location of %3?")
+ .arg(name, lastModified, BuildConfig.LAUNCHER_DISPLAYNAME);
+ }
+ }
+
+ QMessageBox::StandardButton askMoveDialogue =
+ QMessageBox::question(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
+
+ auto setDoNotMigrate = [&nomigratePath] {
+ QFile file(nomigratePath);
+ file.open(QIODevice::WriteOnly);
+ };
+
+ // create no-migrate file if user doesn't want to migrate
+ if (askMoveDialogue != QMessageBox::Yes) {
+ qDebug() << "<> Migration declined for" << name;
+ setDoNotMigrate();
+ return currentExists; // cancel further migrations, if we already have a data directory
+ }
+
+ if (!currentExists) {
+ // Migrate!
+ auto matcher = std::make_shared<MultiMatcher>();
+ matcher->add(std::make_shared<SimplePrefixMatcher>(configFile));
+ matcher->add(std::make_shared<SimplePrefixMatcher>(
+ BuildConfig.LAUNCHER_CONFIGFILE)); // it's possible that we already used that directory before
+ matcher->add(std::make_shared<SimplePrefixMatcher>("accounts.json"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("accounts/"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("assets/"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("icons/"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("instances/"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("libraries/"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("mods/"));
+ matcher->add(std::make_shared<SimplePrefixMatcher>("themes/"));
+
+ ProgressDialog diag;
+ DataMigrationTask task(nullptr, oldData, currentData, matcher);
+ if (diag.execWithTask(&task)) {
+ qDebug() << "<> Migration succeeded";
+ setDoNotMigrate();
+ } else {
+ QString reason = task.failReason();
+ QMessageBox::critical(nullptr, BuildConfig.LAUNCHER_DISPLAYNAME, tr("Migration failed! Reason: %1").arg(reason));
+ }
+ } else {
+ qWarning() << "<> Migration was skipped, due to existing data";
+ }
+ return true;
+}
diff --git a/launcher/Application.h b/launcher/Application.h
index 78ab8fbd..7884227a 100644
--- a/launcher/Application.h
+++ b/launcher/Application.h
@@ -200,6 +200,8 @@ public:
void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
+ int suitableMaxMem();
+
signals:
void updateAllowedChanged(bool status);
void globalSettingsAboutToOpen();
@@ -229,6 +231,7 @@ private slots:
void setupWizardFinished(int status);
private:
+ bool handleDataMigration(const QString & currentData, const QString & oldData, const QString & name, const QString & configFile) const;
bool createSetupWizard();
void performMainStartupAction();
diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h
index 307240e0..a2a4f824 100644
--- a/launcher/BaseInstance.h
+++ b/launcher/BaseInstance.h
@@ -151,7 +151,7 @@ public:
void copyManagedPack(BaseInstance& other);
/// guess log level from a line of game log
- virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
+ virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
{
return level;
};
diff --git a/launcher/BaseVersionList.cpp b/launcher/BaseVersionList.cpp
index 4ed82612..dc95e7ea 100644
--- a/launcher/BaseVersionList.cpp
+++ b/launcher/BaseVersionList.cpp
@@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
int BaseVersionList::rowCount(const QModelIndex &parent) const
{
// Return count
- return count();
+ return parent.isValid() ? 0 : count();
}
int BaseVersionList::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QHash<int, QByteArray> BaseVersionList::roleNames() const
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index bc1f5d5e..e8afa6b8 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -24,13 +24,15 @@ set(CORE_SOURCES
NullInstance.h
MMCZip.h
MMCZip.cpp
- MMCStrings.h
- MMCStrings.cpp
+ StringUtils.h
+ StringUtils.cpp
RuntimeContext.h
# Basic instance manipulation tasks (derived from InstanceTask)
InstanceCreationTask.h
InstanceCreationTask.cpp
+ InstanceCopyPrefs.h
+ InstanceCopyPrefs.cpp
InstanceCopyTask.h
InstanceCopyTask.cpp
InstanceImportTask.h
@@ -95,6 +97,7 @@ set(PATHMATCHER_SOURCES
pathmatcher/IPathMatcher.h
pathmatcher/MultiMatcher.h
pathmatcher/RegexpMatcher.h
+ pathmatcher/SimplePrefixMatcher.h
)
set(NET_SOURCES
@@ -573,6 +576,8 @@ SET(LAUNCHER_SOURCES
# Application base
Application.h
Application.cpp
+ DataMigrationTask.h
+ DataMigrationTask.cpp
UpdateController.cpp
UpdateController.h
ApplicationMessage.h
@@ -596,6 +601,8 @@ SET(LAUNCHER_SOURCES
resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc
+ resources/breeze_dark/breeze_dark.qrc
+ resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc
resources/iOS/iOS.qrc
resources/flat/flat.qrc
@@ -955,6 +962,8 @@ qt_add_resources(LAUNCHER_RESOURCES
resources/pe_light/pe_light.qrc
resources/pe_colored/pe_colored.qrc
resources/pe_blue/pe_blue.qrc
+ resources/breeze_dark/breeze_dark.qrc
+ resources/breeze_light/breeze_light.qrc
resources/OSX/OSX.qrc
resources/iOS/iOS.qrc
resources/flat/flat.qrc
@@ -1064,7 +1073,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
# Image formats
install(
DIRECTORY "${QT_PLUGINS_DIR}/imageformats"
- CONFIGURATIONS Debug RelWithDebInfo
+ CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "tga|tiff|mng" EXCLUDE
@@ -1082,7 +1091,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
# Icon engines
install(
DIRECTORY "${QT_PLUGINS_DIR}/iconengines"
- CONFIGURATIONS Debug RelWithDebInfo
+ CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "fontawesome" EXCLUDE
@@ -1100,7 +1109,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
# Platform plugins
install(
DIRECTORY "${QT_PLUGINS_DIR}/platforms"
- CONFIGURATIONS Debug RelWithDebInfo
+ CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
REGEX "minimal|linuxfb|offscreen" EXCLUDE
@@ -1119,7 +1128,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
if(EXISTS "${QT_PLUGINS_DIR}/styles")
install(
DIRECTORY "${QT_PLUGINS_DIR}/styles"
- CONFIGURATIONS Debug RelWithDebInfo
+ CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
@@ -1137,7 +1146,7 @@ if(INSTALL_BUNDLE STREQUAL "full")
if(EXISTS "${QT_PLUGINS_DIR}/tls")
install(
DIRECTORY "${QT_PLUGINS_DIR}/tls"
- CONFIGURATIONS Debug RelWithDebInfo
+ CONFIGURATIONS Debug RelWithDebInfo ""
DESTINATION ${PLUGIN_DEST_DIR}
COMPONENT Runtime
)
diff --git a/launcher/DataMigrationTask.cpp b/launcher/DataMigrationTask.cpp
new file mode 100644
index 00000000..27ce5f01
--- /dev/null
+++ b/launcher/DataMigrationTask.cpp
@@ -0,0 +1,96 @@
+// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include "DataMigrationTask.h"
+
+#include "FileSystem.h"
+
+#include <QDirIterator>
+#include <QFileInfo>
+#include <QMap>
+
+#include <QtConcurrent>
+
+DataMigrationTask::DataMigrationTask(QObject* parent,
+ const QString& sourcePath,
+ const QString& targetPath,
+ const IPathMatcher::Ptr pathMatcher)
+ : Task(parent), m_sourcePath(sourcePath), m_targetPath(targetPath), m_pathMatcher(pathMatcher), m_copy(sourcePath, targetPath)
+{
+ m_copy.matcher(m_pathMatcher.get()).whitelist(true);
+}
+
+void DataMigrationTask::executeTask()
+{
+ setStatus(tr("Scanning files..."));
+
+ // 1. Scan
+ // Check how many files we gotta copy
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
+ return m_copy(true); // dry run to collect amount of files
+ });
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
+ m_copyFutureWatcher.setFuture(m_copyFuture);
+}
+
+void DataMigrationTask::dryRunFinished()
+{
+ disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::dryRunFinished);
+ disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::dryRunAborted);
+
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
+#else
+ if (!m_copyFuture.result()) {
+#endif
+ emitFailed(tr("Failed to scan source path."));
+ return;
+ }
+
+ // 2. Copy
+ // Actually copy all files now.
+ m_toCopy = m_copy.totalCopied();
+ connect(&m_copy, &FS::copy::fileCopied, [&, this](const QString& relativeName) {
+ QString shortenedName = relativeName;
+ // shorten the filename to hopefully fit into one line
+ if (shortenedName.length() > 50)
+ shortenedName = relativeName.left(20) + "…" + relativeName.right(29);
+ setProgress(m_copy.totalCopied(), m_toCopy);
+ setStatus(tr("Copying %1…").arg(shortenedName));
+ });
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&] {
+ return m_copy(false); // actually copy now
+ });
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
+ m_copyFutureWatcher.setFuture(m_copyFuture);
+}
+
+void DataMigrationTask::dryRunAborted()
+{
+ emitFailed(tr("Aborted"));
+}
+
+void DataMigrationTask::copyFinished()
+{
+ disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &DataMigrationTask::copyFinished);
+ disconnect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &DataMigrationTask::copyAborted);
+
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ if (!m_copyFuture.isValid() || !m_copyFuture.result()) {
+#else
+ if (!m_copyFuture.result()) {
+#endif
+ emitFailed(tr("Some paths could not be copied!"));
+ return;
+ }
+
+ emitSucceeded();
+}
+
+void DataMigrationTask::copyAborted()
+{
+ emitFailed(tr("Aborted"));
+}
diff --git a/launcher/DataMigrationTask.h b/launcher/DataMigrationTask.h
new file mode 100644
index 00000000..6cc23b1a
--- /dev/null
+++ b/launcher/DataMigrationTask.h
@@ -0,0 +1,42 @@
+// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#pragma once
+
+#include "FileSystem.h"
+#include "pathmatcher/IPathMatcher.h"
+#include "tasks/Task.h"
+
+#include <QFuture>
+#include <QFutureWatcher>
+
+/*
+ * Migrate existing data from other MMC-like launchers.
+ */
+
+class DataMigrationTask : public Task {
+ Q_OBJECT
+ public:
+ explicit DataMigrationTask(QObject* parent, const QString& sourcePath, const QString& targetPath, const IPathMatcher::Ptr pathmatcher);
+ ~DataMigrationTask() override = default;
+
+ protected:
+ virtual void executeTask() override;
+
+ protected slots:
+ void dryRunFinished();
+ void dryRunAborted();
+ void copyFinished();
+ void copyAborted();
+
+ private:
+ const QString& m_sourcePath;
+ const QString& m_targetPath;
+ const IPathMatcher::Ptr m_pathMatcher;
+
+ FS::copy m_copy;
+ int m_toCopy = 0;
+ QFuture<bool> m_copyFuture;
+ QFutureWatcher<bool> m_copyFutureWatcher;
+};
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index 4026d6c1..987f4e74 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -44,7 +44,9 @@
#include <QStandardPaths>
#include <QTextStream>
#include <QUrl>
+
#include "DesktopServices.h"
+#include "StringUtils.h"
#if defined Q_OS_WIN32
#include <objbase.h>
@@ -79,22 +81,6 @@ namespace fs = std::filesystem;
namespace fs = ghc::filesystem;
#endif
-#if defined Q_OS_WIN32
-
-std::wstring toStdString(QString s)
-{
- return s.toStdWString();
-}
-
-#else
-
-std::string toStdString(QString s)
-{
- return s.toStdString();
-}
-
-#endif
-
namespace FS {
void ensureExists(const QDir& dir)
@@ -163,9 +149,13 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
-bool copy::operator()(const QString& offset)
+/// @brief Copies a directory and it's contents from src to dest
+/// @param offset subdirectory form src to copy to dest
+/// @return if there was an error during the filecopy
+bool copy::operator()(const QString& offset, bool dryRun)
{
using copy_opts = fs::copy_options;
+ m_copied = 0; // reset counter
// NOTE always deep copy on windows. the alternatives are too messy.
#if defined Q_OS_WIN32
@@ -185,18 +175,21 @@ bool copy::operator()(const QString& offset)
// Function that'll do the actual copying
auto copy_file = [&](QString src_path, QString relative_dst_path) {
- if (m_blacklist && m_blacklist->matches(relative_dst_path))
+ if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
return;
auto dst_path = PathCombine(dst, relative_dst_path);
- ensureFilePathExists(dst_path);
-
- fs::copy(toStdString(src_path), toStdString(dst_path), opt, err);
+ if (!dryRun) {
+ ensureFilePathExists(dst_path);
+ fs::copy(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), opt, err);
+ }
if (err) {
qWarning() << "Failed to copy files:" << QString::fromStdString(err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
}
+ m_copied++;
+ emit fileCopied(relative_dst_path);
};
// We can't use copy_opts::recursive because we need to take into account the
@@ -213,7 +206,7 @@ bool copy::operator()(const QString& offset)
}
// If the root src is not a directory, the previous iterator won't run.
- if (!fs::is_directory(toStdString(src)))
+ if (!fs::is_directory(StringUtils::toStdString(src)))
copy_file(src, "");
return err.value() == 0;
@@ -223,7 +216,7 @@ bool deletePath(QString path)
{
std::error_code err;
- fs::remove_all(toStdString(path), err);
+ fs::remove_all(StringUtils::toStdString(path), err);
if (err) {
qWarning() << "Failed to remove files:" << QString::fromStdString(err.message());
@@ -414,7 +407,7 @@ bool overrideFolder(QString overwritten_path, QString override_path)
fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing;
// FIXME: hello traveller! Apparently std::copy does NOT overwrite existing files on GNU libstdc++ on Windows?
- fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err);
+ fs::copy(StringUtils::toStdString(override_path), StringUtils::toStdString(overwritten_path), opt, err);
if (err) {
qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path);
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index b46f3281..a9a81123 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -40,6 +40,7 @@
#include <QDir>
#include <QFlags>
+#include <QObject>
namespace FS {
@@ -75,9 +76,11 @@ bool ensureFilePathExists(QString filenamepath);
*/
bool ensureFolderPathExists(QString filenamepath);
-class copy {
+/// @brief Copies a directory and it's contents from src to dest
+class copy : public QObject {
+ Q_OBJECT
public:
- copy(const QString& src, const QString& dst)
+ copy(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{
m_src.setPath(src);
m_dst.setPath(dst);
@@ -87,21 +90,35 @@ class copy {
m_followSymlinks = follow;
return *this;
}
- copy& blacklist(const IPathMatcher* filter)
+ copy& matcher(const IPathMatcher* filter)
{
- m_blacklist = filter;
+ m_matcher = filter;
return *this;
}
- bool operator()() { return operator()(QString()); }
+ copy& whitelist(bool whitelist)
+ {
+ m_whitelist = whitelist;
+ return *this;
+ }
+
+ bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+
+ int totalCopied() { return m_copied; }
+
+ signals:
+ void fileCopied(const QString& relativeName);
+ // TODO: maybe add a "shouldCopy" signal in the future?
private:
- bool operator()(const QString& offset);
+ bool operator()(const QString& offset, bool dryRun = false);
private:
bool m_followSymlinks = true;
- const IPathMatcher* m_blacklist = nullptr;
+ const IPathMatcher* m_matcher = nullptr;
+ bool m_whitelist = false;
QDir m_src;
QDir m_dst;
+ int m_copied;
};
/**
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
new file mode 100644
index 00000000..7b93a516
--- /dev/null
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -0,0 +1,135 @@
+//
+// Created by marcelohdez on 10/22/22.
+//
+
+#include "InstanceCopyPrefs.h"
+
+bool InstanceCopyPrefs::allTrue() const
+{
+ return copySaves &&
+ keepPlaytime &&
+ copyGameOptions &&
+ copyResourcePacks &&
+ copyShaderPacks &&
+ copyServers &&
+ copyMods &&
+ copyScreenshots;
+}
+
+// Returns a single RegEx string of the selected folders/files to filter out (ex: ".minecraft/saves|.minecraft/server.dat")
+QString InstanceCopyPrefs::getSelectedFiltersAsRegex() const
+{
+ QStringList filters;
+
+ if(!copySaves)
+ filters << "saves";
+
+ if(!copyGameOptions)
+ filters << "options.txt";
+
+ if(!copyResourcePacks)
+ filters << "resourcepacks" << "texturepacks";
+
+ if(!copyShaderPacks)
+ filters << "shaderpacks";
+
+ if(!copyServers)
+ filters << "servers.dat" << "servers.dat_old" << "server-resource-packs";
+
+ if(!copyMods)
+ filters << "coremods" << "mods" << "config";
+
+ if(!copyScreenshots)
+ filters << "screenshots";
+
+ // If we have any filters to add, join them as a single regex string to return:
+ if (!filters.isEmpty()) {
+ const QString MC_ROOT = "[.]?minecraft/";
+ // Ensure first filter starts with root, then join other filters with OR regex before root (ex: ".minecraft/saves|.minecraft/mods"):
+ return MC_ROOT + filters.join("|" + MC_ROOT);
+ }
+
+ return {};
+}
+
+// ======= Getters =======
+bool InstanceCopyPrefs::isCopySavesEnabled() const
+{
+ return copySaves;
+}
+
+bool InstanceCopyPrefs::isKeepPlaytimeEnabled() const
+{
+ return keepPlaytime;
+}
+
+bool InstanceCopyPrefs::isCopyGameOptionsEnabled() const
+{
+ return copyGameOptions;
+}
+
+bool InstanceCopyPrefs::isCopyResourcePacksEnabled() const
+{
+ return copyResourcePacks;
+}
+
+bool InstanceCopyPrefs::isCopyShaderPacksEnabled() const
+{
+ return copyShaderPacks;
+}
+
+bool InstanceCopyPrefs::isCopyServersEnabled() const
+{
+ return copyServers;
+}
+
+bool InstanceCopyPrefs::isCopyModsEnabled() const
+{
+ return copyMods;
+}
+
+bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
+{
+ return copyScreenshots;
+}
+
+// ======= Setters =======
+void InstanceCopyPrefs::enableCopySaves(bool b)
+{
+ copySaves = b;
+}
+
+void InstanceCopyPrefs::enableKeepPlaytime(bool b)
+{
+ keepPlaytime = b;
+}
+
+void InstanceCopyPrefs::enableCopyGameOptions(bool b)
+{
+ copyGameOptions = b;
+}
+
+void InstanceCopyPrefs::enableCopyResourcePacks(bool b)
+{
+ copyResourcePacks = b;
+}
+
+void InstanceCopyPrefs::enableCopyShaderPacks(bool b)
+{
+ copyShaderPacks = b;
+}
+
+void InstanceCopyPrefs::enableCopyServers(bool b)
+{
+ copyServers = b;
+}
+
+void InstanceCopyPrefs::enableCopyMods(bool b)
+{
+ copyMods = b;
+}
+
+void InstanceCopyPrefs::enableCopyScreenshots(bool b)
+{
+ copyScreenshots = b;
+}
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
new file mode 100644
index 00000000..6988b2df
--- /dev/null
+++ b/launcher/InstanceCopyPrefs.h
@@ -0,0 +1,41 @@
+//
+// Created by marcelohdez on 10/22/22.
+//
+
+#pragma once
+
+#include <QStringList>
+
+struct InstanceCopyPrefs {
+ public:
+ [[nodiscard]] bool allTrue() const;
+ [[nodiscard]] QString getSelectedFiltersAsRegex() const;
+ // Getters
+ [[nodiscard]] bool isCopySavesEnabled() const;
+ [[nodiscard]] bool isKeepPlaytimeEnabled() const;
+ [[nodiscard]] bool isCopyGameOptionsEnabled() const;
+ [[nodiscard]] bool isCopyResourcePacksEnabled() const;
+ [[nodiscard]] bool isCopyShaderPacksEnabled() const;
+ [[nodiscard]] bool isCopyServersEnabled() const;
+ [[nodiscard]] bool isCopyModsEnabled() const;
+ [[nodiscard]] bool isCopyScreenshotsEnabled() const;
+ // Setters
+ void enableCopySaves(bool b);
+ void enableKeepPlaytime(bool b);
+ void enableCopyGameOptions(bool b);
+ void enableCopyResourcePacks(bool b);
+ void enableCopyShaderPacks(bool b);
+ void enableCopyServers(bool b);
+ void enableCopyMods(bool b);
+ void enableCopyScreenshots(bool b);
+
+ protected: // data
+ bool copySaves = true;
+ bool keepPlaytime = true;
+ bool copyGameOptions = true;
+ bool copyResourcePacks = true;
+ bool copyShaderPacks = true;
+ bool copyServers = true;
+ bool copyMods = true;
+ bool copyScreenshots = true;
+};
diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp
index b1e33884..0a83ed9c 100644
--- a/launcher/InstanceCopyTask.cpp
+++ b/launcher/InstanceCopyTask.cpp
@@ -5,15 +5,17 @@
#include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun>
-InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime)
+InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs)
{
m_origInstance = origInstance;
- m_keepPlaytime = keepPlaytime;
+ m_keepPlaytime = prefs.isKeepPlaytimeEnabled();
- if(!copySaves)
+ QString filters = prefs.getSelectedFiltersAsRegex();
+ if (!filters.isEmpty())
{
+ // Set regex filter:
// FIXME: get this from the original instance type...
- auto matcherReal = new RegexpMatcher("[.]?minecraft/saves");
+ auto matcherReal = new RegexpMatcher(filters);
matcherReal->caseSensitive(false);
m_matcher.reset(matcherReal);
}
@@ -24,9 +26,11 @@ void InstanceCopyTask::executeTask()
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
- folderCopy.followSymlinks(false).blacklist(m_matcher.get());
+ folderCopy.followSymlinks(false).matcher(m_matcher.get());
- m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), [&folderCopy]{
+ return folderCopy();
+ });
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &InstanceCopyTask::copyFinished);
connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &InstanceCopyTask::copyAborted);
m_copyFutureWatcher.setFuture(m_copyFuture);
diff --git a/launcher/InstanceCopyTask.h b/launcher/InstanceCopyTask.h
index 82901732..1f29b854 100644
--- a/launcher/InstanceCopyTask.h
+++ b/launcher/InstanceCopyTask.h
@@ -1,20 +1,21 @@
#pragma once
-#include "tasks/Task.h"
-#include "net/NetJob.h"
-#include <QUrl>
#include <QFuture>
#include <QFutureWatcher>
-#include "settings/SettingsObject.h"
-#include "BaseVersion.h"
+#include <QUrl>
#include "BaseInstance.h"
+#include "BaseVersion.h"
+#include "InstanceCopyPrefs.h"
#include "InstanceTask.h"
+#include "net/NetJob.h"
+#include "settings/SettingsObject.h"
+#include "tasks/Task.h"
class InstanceCopyTask : public InstanceTask
{
Q_OBJECT
public:
- explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
+ explicit InstanceCopyTask(InstancePtr origInstance, const InstanceCopyPrefs& prefs);
protected:
//! Entry point for tasks.
@@ -22,7 +23,8 @@ protected:
void copyFinished();
void copyAborted();
-private: /* data */
+private:
+ /* data */
InstancePtr m_origInstance;
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
diff --git a/launcher/JavaCommon.cpp b/launcher/JavaCommon.cpp
index aa4d1123..52cc868a 100644
--- a/launcher/JavaCommon.cpp
+++ b/launcher/JavaCommon.cpp
@@ -36,7 +36,7 @@
#include "JavaCommon.h"
#include "java/JavaUtils.h"
#include "ui/dialogs/CustomMessageBox.h"
-#include <MMCStrings.h>
+
#include <QRegularExpression>
bool JavaCommon::checkJVMArgs(QString jvmargs, QWidget *parent)
diff --git a/launcher/MMCStrings.h b/launcher/MMCStrings.h
deleted file mode 100644
index 48052a00..00000000
--- a/launcher/MMCStrings.h
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-
-#include <QString>
-
-namespace Strings
-{
- int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs);
-}
diff --git a/launcher/MMCStrings.cpp b/launcher/StringUtils.cpp
index dc91c8d6..0f3c3669 100644
--- a/launcher/MMCStrings.cpp
+++ b/launcher/StringUtils.cpp
@@ -1,26 +1,28 @@
-#include "MMCStrings.h"
+#include "StringUtils.h"
+
+/// If you're wondering where these came from exactly, then know you're not the only one =D
/// TAKEN FROM Qt, because it doesn't expose it intelligently
-static inline QChar getNextChar(const QString &s, int location)
+static inline QChar getNextChar(const QString& s, int location)
{
return (location < s.length()) ? s.at(location) : QChar();
}
/// TAKEN FROM Qt, because it doesn't expose it intelligently
-int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs)
+int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs)
{
- for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2)
- {
+ int l1 = 0, l2 = 0;
+ while (l1 <= s1.count() && l2 <= s2.count()) {
// skip spaces, tabs and 0's
QChar c1 = getNextChar(s1, l1);
while (c1.isSpace())
c1 = getNextChar(s1, ++l1);
+
QChar c2 = getNextChar(s2, l2);
while (c2.isSpace())
c2 = getNextChar(s2, ++l2);
- if (c1.isDigit() && c2.isDigit())
- {
+ if (c1.isDigit() && c2.isDigit()) {
while (c1.digitValue() == 0)
c1 = getNextChar(s1, ++l1);
while (c2.digitValue() == 0)
@@ -30,11 +32,8 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
int lookAheadLocation2 = l2;
int currentReturnValue = 0;
// find the last digit, setting currentReturnValue as we go if it isn't equal
- for (QChar lookAhead1 = c1, lookAhead2 = c2;
- (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
- lookAhead1 = getNextChar(s1, ++lookAheadLocation1),
- lookAhead2 = getNextChar(s2, ++lookAheadLocation2))
- {
+ for (QChar lookAhead1 = c1, lookAhead2 = c2; (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length());
+ lookAhead1 = getNextChar(s1, ++lookAheadLocation1), lookAhead2 = getNextChar(s2, ++lookAheadLocation2)) {
bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit();
bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit();
if (!is1ADigit && !is2ADigit)
@@ -43,14 +42,10 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
return -1;
if (!is2ADigit)
return 1;
- if (currentReturnValue == 0)
- {
- if (lookAhead1 < lookAhead2)
- {
+ if (currentReturnValue == 0) {
+ if (lookAhead1 < lookAhead2) {
currentReturnValue = -1;
- }
- else if (lookAhead1 > lookAhead2)
- {
+ } else if (lookAhead1 > lookAhead2) {
currentReturnValue = 1;
}
}
@@ -58,19 +53,24 @@ int Strings::naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensit
if (currentReturnValue != 0)
return currentReturnValue;
}
- if (cs == Qt::CaseInsensitive)
- {
+
+ if (cs == Qt::CaseInsensitive) {
if (!c1.isLower())
c1 = c1.toLower();
if (!c2.isLower())
c2 = c2.toLower();
}
+
int r = QString::localeAwareCompare(c1, c2);
if (r < 0)
return -1;
if (r > 0)
return 1;
+
+ l1 += 1;
+ l2 += 1;
}
+
// The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs);
}
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
new file mode 100644
index 00000000..1799605b
--- /dev/null
+++ b/launcher/StringUtils.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <QString>
+
+namespace StringUtils {
+
+#if defined Q_OS_WIN32
+using string = std::wstring;
+
+inline string toStdString(QString s)
+{
+ return s.toStdWString();
+}
+inline QString fromStdString(string s)
+{
+ return QString::fromStdWString(s);
+}
+#else
+using string = std::string;
+
+inline string toStdString(QString s)
+{
+ return s.toStdString();
+}
+inline QString fromStdString(string s)
+{
+ return QString::fromStdString(s);
+}
+#endif
+
+int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
+} // namespace StringUtils
diff --git a/launcher/VersionProxyModel.cpp b/launcher/VersionProxyModel.cpp
index 032f21f9..6aba268d 100644
--- a/launcher/VersionProxyModel.cpp
+++ b/launcher/VersionProxyModel.cpp
@@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
int VersionProxyModel::columnCount(const QModelIndex &parent) const
{
- return m_columns.size();
+ return parent.isValid() ? 0 : m_columns.size();
}
int VersionProxyModel::rowCount(const QModelIndex &parent) const
{
if(sourceModel())
{
- return sourceModel()->rowCount();
+ return sourceModel()->rowCount(parent);
}
return 0;
}
diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp
index 3a223d1b..01043ad2 100644
--- a/launcher/icons/IconList.cpp
+++ b/launcher/icons/IconList.cpp
@@ -242,7 +242,7 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction;
}
-bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
+bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
@@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
int IconList::rowCount(const QModelIndex &parent) const
{
- return icons.size();
+ return parent.isValid() ? 0 : icons.size();
}
void IconList::installIcons(const QStringList &iconFiles)
diff --git a/launcher/java/JavaInstall.cpp b/launcher/java/JavaInstall.cpp
index 5bcf7bcb..d5932bcb 100644
--- a/launcher/java/JavaInstall.cpp
+++ b/launcher/java/JavaInstall.cpp
@@ -1,9 +1,10 @@
#include "JavaInstall.h"
-#include <MMCStrings.h>
+
+#include "StringUtils.h"
bool JavaInstall::operator<(const JavaInstall &rhs)
{
- auto archCompare = Strings::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
+ auto archCompare = StringUtils::naturalCompare(arch, rhs.arch, Qt::CaseInsensitive);
if(archCompare != 0)
return archCompare < 0;
if(id < rhs.id)
@@ -14,7 +15,7 @@ bool JavaInstall::operator<(const JavaInstall &rhs)
{
return false;
}
- return Strings::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
+ return StringUtils::naturalCompare(path, rhs.path, Qt::CaseInsensitive) < 0;
}
bool JavaInstall::operator==(const JavaInstall &rhs)
diff --git a/launcher/java/JavaInstallList.cpp b/launcher/java/JavaInstallList.cpp
index 155c956a..e2f0aa00 100644
--- a/launcher/java/JavaInstallList.cpp
+++ b/launcher/java/JavaInstallList.cpp
@@ -41,7 +41,6 @@
#include "java/JavaInstallList.h"
#include "java/JavaCheckerJob.h"
#include "java/JavaUtils.h"
-#include "MMCStrings.h"
#include "minecraft/VersionFilterData.h"
JavaInstallList::JavaInstallList(QObject *parent) : BaseVersionList(parent)
diff --git a/launcher/java/JavaVersion.cpp b/launcher/java/JavaVersion.cpp
index 179ccd8d..0e4fc1d3 100644
--- a/launcher/java/JavaVersion.cpp
+++ b/launcher/java/JavaVersion.cpp
@@ -1,5 +1,6 @@
#include "JavaVersion.h"
-#include <MMCStrings.h>
+
+#include "StringUtils.h"
#include <QRegularExpression>
#include <QString>
@@ -98,12 +99,12 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
else if(thisPre && rhsPre)
{
// both are prereleases - use natural compare...
- return Strings::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
+ return StringUtils::naturalCompare(m_prerelease, rhs.m_prerelease, Qt::CaseSensitive) < 0;
}
// neither is prerelease, so they are the same -> this cannot be less than rhs
return false;
}
- else return Strings::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
+ else return StringUtils::naturalCompare(m_string, rhs.m_string, Qt::CaseSensitive) < 0;
}
bool JavaVersion::operator==(const JavaVersion &rhs)
diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp
index 28fcc4f4..9e1794b3 100644
--- a/launcher/launch/LaunchTask.cpp
+++ b/launcher/launch/LaunchTask.cpp
@@ -37,7 +37,6 @@
#include "launch/LaunchTask.h"
#include "MessageLevel.h"
-#include "MMCStrings.h"
#include "java/JavaChecker.h"
#include "tasks/Task.h"
#include <QDebug>
diff --git a/launcher/main.cpp b/launcher/main.cpp
index e2116f38..b63f8bfd 100644
--- a/launcher/main.cpp
+++ b/launcher/main.cpp
@@ -81,6 +81,8 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(pe_light);
Q_INIT_RESOURCE(pe_blue);
Q_INIT_RESOURCE(pe_colored);
+ Q_INIT_RESOURCE(breeze_dark);
+ Q_INIT_RESOURCE(breeze_light);
Q_INIT_RESOURCE(OSX);
Q_INIT_RESOURCE(iOS);
Q_INIT_RESOURCE(flat);
@@ -91,5 +93,7 @@ int main(int argc, char *argv[])
return 1;
case Application::Succeeded:
return 0;
+ default:
+ return -1;
}
}
diff --git a/launcher/meta/Index.cpp b/launcher/meta/Index.cpp
index eec1b329..242aad9f 100644
--- a/launcher/meta/Index.cpp
+++ b/launcher/meta/Index.cpp
@@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
}
int Index::rowCount(const QModelIndex &parent) const
{
- return m_lists.size();
+ return parent.isValid() ? 0 : m_lists.size();
}
int Index::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
{
diff --git a/launcher/meta/JsonFormat.h b/launcher/meta/JsonFormat.h
index 93217b7e..63128a4e 100644
--- a/launcher/meta/JsonFormat.h
+++ b/launcher/meta/JsonFormat.h
@@ -60,11 +60,6 @@ struct Require
QString suggests;
};
-inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
-{
- return qHash(key.uid, seed);
-}
-
using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);
diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 3a820951..70d0b949 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * 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>
*
* 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
@@ -43,7 +44,6 @@
#include "settings/SettingsObject.h"
#include "Application.h"
-#include "MMCStrings.h"
#include "pathmatcher/RegexpMatcher.h"
#include "pathmatcher/MultiMatcher.h"
#include "FileSystem.h"
@@ -437,6 +437,17 @@ QStringList MinecraftInstance::javaArguments()
return args;
}
+QString MinecraftInstance::getLauncher()
+{
+ auto profile = m_components->getProfile();
+
+ // use legacy launcher if the traits are set
+ if (profile->getTraits().contains("legacyLaunch") || profile->getTraits().contains("alphaLaunch"))
+ return "legacy";
+
+ return "standard";
+}
+
QMap<QString, QString> MinecraftInstance::getVariables()
{
QMap<QString, QString> out;
@@ -628,26 +639,13 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS
launchScript += "sessionId " + session->session + "\n";
}
- // libraries and class path.
- {
- QStringList jars, nativeJars;
- profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot());
- for(auto file: jars)
- {
- launchScript += "cp " + file + "\n";
- }
- for(auto file: nativeJars)
- {
- launchScript += "ext " + file + "\n";
- }
- launchScript += "natives " + getNativePath() + "\n";
- }
-
for (auto trait : profile->getTraits())
{
launchScript += "traits " + trait + "\n";
}
- launchScript += "launcher onesix\n";
+
+ launchScript += "launcher " + getLauncher() + "\n";
+
// qDebug() << "Generated launch script:" << launchScript;
return launchScript;
}
@@ -783,6 +781,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
}
out << "";
+ out << "Launcher: " + getLauncher();
+ out << "";
return out;
}
diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h
index 1895d187..1bbd7b83 100644
--- a/launcher/minecraft/MinecraftInstance.h
+++ b/launcher/minecraft/MinecraftInstance.h
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 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
@@ -130,6 +131,7 @@ public:
QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java
QStringList javaArguments();
+ QString getLauncher();
/// get variables for launch command variable substitution/environment
QMap<QString, QString> getVariables() override;
diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp
index 9bbb4ada..623dcdfa 100644
--- a/launcher/minecraft/MojangVersionFormat.cpp
+++ b/launcher/minecraft/MojangVersionFormat.cpp
@@ -135,7 +135,7 @@ QJsonObject libDownloadInfoToJson(MojangLibraryDownloadInfo::Ptr libinfo)
{
out.insert("artifact", downloadInfoToJson(libinfo->artifact));
}
- if(libinfo->classifiers.size())
+ if(!libinfo->classifiers.isEmpty())
{
QJsonObject classifiersOut;
for(auto iter = libinfo->classifiers.begin(); iter != libinfo->classifiers.end(); iter++)
@@ -297,7 +297,7 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
{
out.insert("assetIndex", assetIndexToJson(in->mojangAssetIndex));
}
- if(in->mojangDownloads.size())
+ if(!in->mojangDownloads.isEmpty())
{
QJsonObject downloadsOut;
for(auto iter = in->mojangDownloads.begin(); iter != in->mojangDownloads.end(); iter++)
@@ -306,6 +306,15 @@ void MojangVersionFormat::writeVersionProperties(const VersionFile* in, QJsonObj
}
out.insert("downloads", downloadsOut);
}
+ if(!in->compatibleJavaMajors.isEmpty())
+ {
+ QJsonArray compatibleJavaMajorsOut;
+ for(auto compatibleJavaMajor : in->compatibleJavaMajors)
+ {
+ compatibleJavaMajorsOut.append(compatibleJavaMajor);
+ }
+ out.insert("compatibleJavaMajors", compatibleJavaMajorsOut);
+ }
}
QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch)
@@ -396,7 +405,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
iter++;
}
libRoot.insert("natives", nativeList);
- if (library->m_extractExcludes.size())
+ if (!library->m_extractExcludes.isEmpty())
{
QJsonArray excludes;
QJsonObject extract;
@@ -408,7 +417,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library)
libRoot.insert("extract", extract);
}
}
- if (library->m_rules.size())
+ if (!library->m_rules.isEmpty())
{
QJsonArray allRules;
for (auto &rule : library->m_rules)
diff --git a/launcher/minecraft/OneSixVersionFormat.cpp b/launcher/minecraft/OneSixVersionFormat.cpp
index cec4a55b..280f6b26 100644
--- a/launcher/minecraft/OneSixVersionFormat.cpp
+++ b/launcher/minecraft/OneSixVersionFormat.cpp
@@ -63,13 +63,13 @@ LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, con
QJsonObject OneSixVersionFormat::libraryToJson(Library *library)
{
QJsonObject libRoot = MojangVersionFormat::libraryToJson(library);
- if (library->m_absoluteURL.size())
+ if (!library->m_absoluteURL.isEmpty())
libRoot.insert("MMC-absoluteUrl", library->m_absoluteURL);
- if (library->m_hint.size())
+ if (!library->m_hint.isEmpty())
libRoot.insert("MMC-hint", library->m_hint);
- if (library->m_filename.size())
+ if (!library->m_filename.isEmpty())
libRoot.insert("MMC-filename", library->m_filename);
- if (library->m_displayname.size())
+ if (!library->m_displayname.isEmpty())
libRoot.insert("MMC-displayname", library->m_displayname);
return libRoot;
}
@@ -225,11 +225,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
QJsonObject agentObj = requireObject(agentVal);
auto lib = libraryFromJson(*out, agentObj, filename);
+
QString arg = "";
- if (agentObj.contains("argument"))
- {
- readString(agentObj, "argument", arg);
- }
+ readString(agentObj, "argument", arg);
+
AgentPtr agent(new Agent(lib, arg));
out->agents.append(agent);
}
@@ -332,6 +331,20 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
writeString(root, "appletClass", patch->appletClass);
writeStringList(root, "+tweakers", patch->addTweakers);
writeStringList(root, "+traits", patch->traits.values());
+ writeStringList(root, "+jvmArgs", patch->addnJvmArguments);
+ if (!patch->agents.isEmpty())
+ {
+ QJsonArray array;
+ for (auto value: patch->agents)
+ {
+ QJsonObject agentOut = OneSixVersionFormat::libraryToJson(value->library().get());
+ if (!value->argument().isEmpty())
+ agentOut.insert("argument", value->argument());
+
+ array.append(agentOut);
+ }
+ root.insert("+agents", array);
+ }
if (!patch->libraries.isEmpty())
{
QJsonArray array;
diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp
index 1618458f..6ce525eb 100644
--- a/launcher/minecraft/PackProfile.cpp
+++ b/launcher/minecraft/PackProfile.cpp
@@ -613,7 +613,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
{
- if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
+ if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
{
return false;
}
@@ -675,12 +675,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
int PackProfile::rowCount(const QModelIndex &parent) const
{
- return d->components.size();
+ return parent.isValid() ? 0 : d->components.size();
}
int PackProfile::columnCount(const QModelIndex &parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
void PackProfile::move(const int index, const MoveDirection direction)
diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h
index 236f9a87..846e8e42 100644
--- a/launcher/minecraft/Rule.h
+++ b/launcher/minecraft/Rule.h
@@ -104,7 +104,7 @@ public:
class ImplicitRule : public Rule
{
protected:
- virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
+ virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
{
return true;
}
diff --git a/launcher/minecraft/WorldList.cpp b/launcher/minecraft/WorldList.cpp
index aee7be35..ae29a972 100644
--- a/launcher/minecraft/WorldList.cpp
+++ b/launcher/minecraft/WorldList.cpp
@@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row)
int WorldList::columnCount(const QModelIndex &parent) const
{
- return 4;
+ return parent.isValid()? 0 : 4;
}
QVariant WorldList::data(const QModelIndex &index, int role) const
@@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename)
w.install(m_dir.absolutePath());
}
-bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
- const QModelIndex &parent)
+bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
+ [[maybe_unused]] const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
diff --git a/launcher/minecraft/WorldList.h b/launcher/minecraft/WorldList.h
index 5138e583..08294755 100644
--- a/launcher/minecraft/WorldList.h
+++ b/launcher/minecraft/WorldList.h
@@ -54,7 +54,7 @@ public:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
{
- return size();
+ return parent.isValid() ? 0 : static_cast<int>(size());
};
virtual QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp
index b3b57c74..9e2fd111 100644
--- a/launcher/minecraft/auth/AccountList.cpp
+++ b/launcher/minecraft/auth/AccountList.cpp
@@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
}
}
-int AccountList::rowCount(const QModelIndex &) const
+int AccountList::rowCount(const QModelIndex &parent) const
{
// Return count
- return count();
+ return parent.isValid() ? 0 : count();
}
-int AccountList::columnCount(const QModelIndex &) const
+int AccountList::columnCount(const QModelIndex &parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
{
- if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
+ if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
{
return Qt::NoItemFlags;
}
diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp
index 66e80f4a..4ccc5d4d 100644
--- a/launcher/minecraft/mod/ModFolderModel.cpp
+++ b/launcher/minecraft/mod/ModFolderModel.cpp
@@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
int ModFolderModel::columnCount(const QModelIndex &parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ModFolderModel::createUpdateTask()
diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp
index b2356309..0310c8f6 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourceFolderModel.cpp
@@ -426,7 +426,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
int row = index.row();
- if (row < 0 || row >= rowCount(index) || !index.isValid())
+ if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
return false;
if (role == Qt::CheckStateRole)
diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h
index 25095a45..fe283b04 100644
--- a/launcher/minecraft/mod/ResourceFolderModel.h
+++ b/launcher/minecraft/mod/ResourceFolderModel.h
@@ -90,8 +90,8 @@ class ResourceFolderModel : public QAbstractListModel {
/* Basic columns */
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
- [[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
- [[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
+ [[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]] Qt::DropActions supportedDropActions() const override;
@@ -176,7 +176,7 @@ class ResourceFolderModel : public QAbstractListModel {
* if the resource is complex and has more stuff to parse.
*/
virtual void onParseSucceeded(int ticket, QString resource_id);
- virtual void onParseFailed(int ticket, QString resource_id) {}
+ virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
protected:
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
index f8a6c1cf..ebac707d 100644
--- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp
+++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp
@@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
{
- return NUM_COLUMNS;
+ return parent.isValid() ? 0 : NUM_COLUMNS;
}
Task* ResourcePackFolderModel::createUpdateTask()
diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
index a694e7b2..774f6114 100644
--- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp
@@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents)
return {};
}
auto modsTable = tomlModsTable0->as_table();
- if (!tomlModsTable0) {
+ if (!modsTable) {
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
return {};
}
diff --git a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
index 4b878918..cc4e252c 100644
--- a/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
+++ b/launcher/minecraft/mod/tasks/LocalModUpdateTask.cpp
@@ -36,7 +36,7 @@ LocalModUpdateTask::LocalModUpdateTask(QDir index_dir, ModPlatform::IndexedPack&
}
#ifdef Q_OS_WIN32
- SetFileAttributesA(index_dir.path().toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
+ SetFileAttributesW(index_dir.path().toStdWString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
#endif
}
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
index 48ac02e0..91554b58 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp
@@ -372,13 +372,20 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
auto results = m_mod_id_resolver->getResults();
// first check for blocked mods
- QString text;
- QList<QUrl> urls;
+ QList<BlockedMod> blocked_mods;
auto anyBlocked = false;
for (const auto& result : results.files.values()) {
if (!result.resolved || result.url.isEmpty()) {
- text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
- urls.append(QUrl(result.websiteUrl));
+
+ BlockedMod blocked_mod;
+ blocked_mod.name = result.fileName;
+ blocked_mod.websiteUrl = result.websiteUrl;
+ blocked_mod.hash = result.hash;
+ blocked_mod.matched = false;
+ blocked_mod.localPath = "";
+
+ blocked_mods.append(blocked_mod);
+
anyBlocked = true;
}
}
@@ -386,13 +393,15 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
qWarning() << "Blocked mods found, displaying mod list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"),
- tr("The following mods were blocked on third party launchers.<br/>"
- "You will need to manually download them and add them to the modpack"),
- text,
- urls);
+ tr("The following files are not available for download in third party launchers.<br/>"
+ "You will need to manually download them and add them to the instance."),
+ blocked_mods);
+
message_dialog->setModal(true);
if (message_dialog->exec()) {
+ qDebug() << "Post dialog blocked mods list: " << blocked_mods;
+ copyBlockedMods(blocked_mods);
setupDownloadJob(loop);
} else {
m_mod_id_resolver.reset();
@@ -404,6 +413,38 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
}
}
+/// @brief copy the matched blocked mods to the instance staging area
+/// @param blocked_mods list of the blocked mods and their matched paths
+void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
+{
+ setStatus(tr("Copying Blocked Mods..."));
+ setAbortable(false);
+ int i = 0;
+ int total = blocked_mods.length();
+ setProgress(i, total);
+ for (auto const& mod : blocked_mods) {
+ if (!mod.matched) {
+ qDebug() << mod.name << "was not matched to a local file, skipping copy";
+ continue;
+ }
+
+ auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name);
+
+ setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
+
+ qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
+
+ if (!FS::copy(mod.localPath, dest_path)()) {
+ qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
+ }
+
+ i++;
+ setProgress(i, total);
+ }
+
+ setAbortable(true);
+}
+
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
{
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
@@ -449,7 +490,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
m_files_job.reset();
setError(reason);
});
- connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); });
+ connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress);
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
setStatus(tr("Downloading mods..."));
diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h
index ded0e2ce..fbc7d5bf 100644
--- a/launcher/modplatform/flame/FlameInstanceCreationTask.h
+++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h
@@ -10,6 +10,8 @@
#include "net/NetJob.h"
+#include "ui/dialogs/BlockedModsDialog.h"
+
class FlameCreationTask final : public InstanceCreationTask {
Q_OBJECT
@@ -29,6 +31,7 @@ class FlameCreationTask final : public InstanceCreationTask {
private slots:
void idResolverSucceeded(QEventLoop&);
void setupDownloadJob(QEventLoop&);
+ void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
private:
QWidget* m_parent = nullptr;
diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp
index a7bbaba5..f1e4759e 100644
--- a/launcher/modplatform/helpers/HashUtils.cpp
+++ b/launcher/modplatform/helpers/HashUtils.cpp
@@ -4,6 +4,7 @@
#include <QFile>
#include "FileSystem.h"
+#include "StringUtils.h"
#include <MurmurHash2.h>
@@ -35,6 +36,18 @@ Hasher::Ptr createFlameHasher(QString file_path)
return new FlameHasher(file_path);
}
+Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider)
+{
+ return new BlockedModHasher(file_path, provider);
+}
+
+Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type)
+{
+ auto hasher = new BlockedModHasher(file_path, provider);
+ hasher->useHashType(type);
+ return hasher;
+}
+
void ModrinthHasher::executeTask()
{
QFile file(m_path);
@@ -66,7 +79,7 @@ void FlameHasher::executeTask()
// CF-specific
auto should_filter_out = [](char c) { return (c == 9 || c == 10 || c == 13 || c == 32); };
- std::ifstream file_stream(m_path.toStdString(), std::ifstream::binary);
+ std::ifstream file_stream(StringUtils::toStdString(m_path), std::ifstream::binary);
// TODO: This is very heavy work, but apparently QtConcurrent can't use move semantics, so we can't boop this to another thread.
// How do we make this non-blocking then?
m_hash = QString::number(MurmurHash2(std::move(file_stream), 4 * MiB, should_filter_out));
@@ -78,4 +91,50 @@ void FlameHasher::executeTask()
}
}
+
+BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider)
+ : Hasher(file_path), provider(provider) {
+ setObjectName(QString("BlockedModHasher: %1").arg(file_path));
+ hash_type = ProviderCaps.hashType(provider).first();
+}
+
+void BlockedModHasher::executeTask()
+{
+ QFile file(m_path);
+
+ try {
+ file.open(QFile::ReadOnly);
+ } catch (FS::FileSystemException& e) {
+ qCritical() << QString("Failed to open JAR file in %1").arg(m_path);
+ qCritical() << QString("Reason: ") << e.cause();
+
+ emitFailed("Failed to open file for hashing.");
+ return;
+ }
+
+ m_hash = ProviderCaps.hash(provider, &file, hash_type);
+
+ file.close();
+
+ if (m_hash.isEmpty()) {
+ emitFailed("Empty hash!");
+ } else {
+ emitSucceeded();
+ }
+}
+
+QStringList BlockedModHasher::getHashTypes() {
+ return ProviderCaps.hashType(provider);
+}
+
+bool BlockedModHasher::useHashType(QString type) {
+ auto types = ProviderCaps.hashType(provider);
+ if (types.contains(type)) {
+ hash_type = type;
+ return true;
+ }
+ qDebug() << "Bad hash type " << type << " for provider";
+ return false;
+}
+
} // namespace Hashing
diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h
index 38fddf03..fa3244f6 100644
--- a/launcher/modplatform/helpers/HashUtils.h
+++ b/launcher/modplatform/helpers/HashUtils.h
@@ -40,8 +40,23 @@ class ModrinthHasher : public Hasher {
void executeTask() override;
};
+class BlockedModHasher : public Hasher {
+ public:
+ BlockedModHasher(QString file_path, ModPlatform::Provider provider);
+
+ void executeTask() override;
+
+ QStringList getHashTypes();
+ bool useHashType(QString type);
+ private:
+ ModPlatform::Provider provider;
+ QString hash_type;
+};
+
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider);
Hasher::Ptr createFlameHasher(QString file_path);
Hasher::Ptr createModrinthHasher(QString file_path);
+Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider);
+Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type);
} // namespace Hashing
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
index 7b112d8f..4c7b7a4f 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -176,8 +176,6 @@ void PackInstallTask::resolveMods()
void PackInstallTask::onResolveModsSucceeded()
{
- QString text;
- QList<QUrl> urls;
auto anyBlocked = false;
Flame::Manifest results = m_mod_id_resolver_task->getResults();
@@ -191,11 +189,16 @@ void PackInstallTask::onResolveModsSucceeded()
// First check for blocked mods
if (!results_file.resolved || results_file.url.isEmpty()) {
- QString type(local_file.type);
- type[0] = type[0].toUpper();
- text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
- urls.append(QUrl(results_file.websiteUrl));
+ BlockedMod blocked_mod;
+ blocked_mod.name = local_file.name;
+ blocked_mod.websiteUrl = results_file.websiteUrl;
+ blocked_mod.hash = results_file.hash;
+ blocked_mod.matched = false;
+ blocked_mod.localPath = "";
+
+ m_blocked_mods.append(blocked_mod);
+
anyBlocked = true;
} else {
local_file.url = results_file.url.toString();
@@ -208,15 +211,17 @@ void PackInstallTask::onResolveModsSucceeded()
qDebug() << "Blocked files found, displaying file list";
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
- tr("The following files are not available for download in third party launchers.<br/>"
- "You will need to manually download them and add them to the instance."),
- text,
- urls);
+ tr("The following files are not available for download in third party launchers.<br/>"
+ "You will need to manually download them and add them to the instance."),
+ m_blocked_mods);
- if (message_dialog->exec() == QDialog::Accepted)
+ if (message_dialog->exec() == QDialog::Accepted) {
+ qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
createInstance();
- else
+ } else {
abort();
+ }
+
} else {
createInstance();
}
@@ -320,6 +325,9 @@ void PackInstallTask::downloadPack()
void PackInstallTask::onModDownloadSucceeded()
{
m_net_job.reset();
+ if (!m_blocked_mods.isEmpty()) {
+ copyBlockedMods();
+ }
emitSucceeded();
}
@@ -343,4 +351,35 @@ void PackInstallTask::onModDownloadFailed(QString reason)
emitFailed(reason);
}
+/// @brief copy the matched blocked mods to the instance staging area
+void PackInstallTask::copyBlockedMods()
+{
+ setStatus(tr("Copying Blocked Mods..."));
+ setAbortable(false);
+ int i = 0;
+ int total = m_blocked_mods.length();
+ setProgress(i, total);
+ for (auto const& mod : m_blocked_mods) {
+ if (!mod.matched) {
+ qDebug() << mod.name << "was not matched to a local file, skipping copy";
+ continue;
+ }
+
+ auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name);
+
+ setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
+
+ qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
+
+ if (!FS::copy(mod.localPath, dest_path)()) {
+ qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
+ }
+
+ i++;
+ setProgress(i, total);
+ }
+
+ setAbortable(true);
+}
+
} // namespace ModpacksCH
diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h
index 7c6fbeb9..2cd4d729 100644
--- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h
+++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h
@@ -43,6 +43,7 @@
#include "QObjectPtr.h"
#include "modplatform/flame/FileResolvingTask.h"
#include "net/NetJob.h"
+#include "ui/dialogs/BlockedModsDialog.h"
#include <QWidget>
@@ -76,6 +77,7 @@ private:
void resolveMods();
void createInstance();
void downloadPack();
+ void copyBlockedMods();
private:
NetJob::Ptr m_net_job = nullptr;
@@ -90,6 +92,7 @@ private:
Version m_version;
QMap<QString, QString> m_files_to_copy;
+ QList<BlockedMod> m_blocked_mods;
//FIXME: nuke
QWidget* m_parent;
diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp
index b1fe963e..0ed29311 100644
--- a/launcher/modplatform/packwiz/Packwiz.cpp
+++ b/launcher/modplatform/packwiz/Packwiz.cpp
@@ -22,10 +22,14 @@
#include <QDir>
#include <QObject>
-#include <toml++/toml.h>
+#include "FileSystem.h"
+#include "StringUtils.h"
+
#include "minecraft/mod/Mod.h"
#include "modplatform/ModIndex.h"
+#include <toml++/toml.h>
+
namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_find_match) -> QString
@@ -63,22 +67,22 @@ static inline auto indexFileName(QString const& mod_slug) -> QString
static ModPlatform::ProviderCapabilities ProviderCaps;
// Helper functions for extracting data from the TOML file
-auto stringEntry(toml::table table, const std::string entry_name) -> QString
+auto stringEntry(toml::table table, QString entry_name) -> QString
{
- auto node = table[entry_name];
+ auto node = table[StringUtils::toStdString(entry_name)];
if (!node) {
- qCritical() << QString::fromStdString("Failed to read str property '" + entry_name + "' in mod metadata.");
+ qCritical() << "Failed to read str property '" + entry_name + "' in mod metadata.";
return {};
}
- return QString::fromStdString(node.value_or(""));
+ return node.value_or("");
}
-auto intEntry(toml::table table, const std::string entry_name) -> int
+auto intEntry(toml::table table, QString entry_name) -> int
{
- auto node = table[entry_name];
+ auto node = table[StringUtils::toStdString(entry_name)];
if (!node) {
- qCritical() << QString::fromStdString("Failed to read int property '" + entry_name + "' in mod metadata.");
+ qCritical() << "Failed to read int property '" + entry_name + "' in mod metadata.";
return {};
}
@@ -145,6 +149,8 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod)
// they want to do!
if (index_file.exists()) {
index_file.remove();
+ } else {
+ FS::ensureFilePathExists(index_file.fileName());
}
if (!index_file.open(QIODevice::ReadWrite)) {
@@ -228,14 +234,14 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod
toml::table table;
#if TOML_EXCEPTIONS
try {
- table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString());
+ table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname)));
} catch (const toml::parse_error& err) {
qWarning() << QString("Could not open file %1!").arg(normalized_fname);
qWarning() << "Reason: " << QString(err.what());
return {};
}
#else
- table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString());
+ table = toml::parse_file(StringUtils::toStdString(index_dir.absoluteFilePath(real_fname)));
if (!table) {
qWarning() << QString("Could not open file %1!").arg(normalized_fname);
qWarning() << "Reason: " << QString(table.error().what());
diff --git a/launcher/modplatform/packwiz/Packwiz.h b/launcher/modplatform/packwiz/Packwiz.h
index 3ec80377..9754e5c4 100644
--- a/launcher/modplatform/packwiz/Packwiz.h
+++ b/launcher/modplatform/packwiz/Packwiz.h
@@ -24,7 +24,6 @@
#include <QUrl>
#include <QVariant>
-struct toml_table_t;
class QDir;
// Mod from launcher/minecraft/mod/Mod.h
@@ -34,9 +33,6 @@ namespace Packwiz {
auto getRealIndexName(QDir& index_dir, QString normalized_index_name, bool should_match = false) -> QString;
-auto stringEntry(toml_table_t* parent, const char* entry_name) -> QString;
-auto intEntry(toml_table_t* parent, const char* entry_name) -> int;
-
class V1 {
public:
struct Mod {
diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp
index e242dcf4..0d7ca769 100644
--- a/launcher/net/HttpMetaCache.cpp
+++ b/launcher/net/HttpMetaCache.cpp
@@ -47,7 +47,7 @@
auto MetaEntry::getFullPath() -> QString
{
// FIXME: make local?
- return FS::PathCombine(basePath, relativePath);
+ return FS::PathCombine(m_basePath, m_relativePath);
}
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
@@ -99,7 +99,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
return staleEntry(base, resource_path);
}
- if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
+ if (!expected_etag.isEmpty() && expected_etag != entry->m_etag) {
// if the etag doesn't match expected, we disown the entry
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
@@ -107,17 +107,17 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
// if the file changed, check md5sum
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
- if (file_last_changed != entry->local_changed_timestamp) {
+ if (file_last_changed != entry->m_local_changed_timestamp) {
QFile input(real_path);
input.open(QIODevice::ReadOnly);
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
- if (entry->md5sum != md5sum) {
+ if (entry->m_md5sum != md5sum) {
selected_base.entry_list.remove(resource_path);
return staleEntry(base, resource_path);
}
// md5sums matched... keep entry and save the new state to file
- entry->local_changed_timestamp = file_last_changed;
+ entry->m_local_changed_timestamp = file_last_changed;
SaveEventually();
}
@@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
}
// entry passed all the checks we cared about.
- entry->basePath = getBasePath(base);
+ entry->m_basePath = getBasePath(base);
return entry;
}
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
{
- if (!m_entries.contains(stale_entry->baseId)) {
- qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
+ if (!m_entries.contains(stale_entry->m_baseId)) {
+ qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
return false;
}
- if (stale_entry->stale) {
+ if (stale_entry->m_stale) {
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
return false;
}
- m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
+ m_entries[stale_entry->m_baseId].entry_list[stale_entry->m_relativePath] = stale_entry;
SaveEventually();
return true;
@@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
if (!entry)
return false;
- entry->stale = true;
+ entry->m_stale = true;
SaveEventually();
return true;
}
@@ -169,7 +169,7 @@ void HttpMetaCache::evictAll()
qDebug() << "Evicting base" << base;
for (MetaEntryPtr entry : map.entry_list) {
if (!evictEntry(entry))
- qWarning() << "Unexpected missing cache entry" << entry->basePath;
+ qWarning() << "Unexpected missing cache entry" << entry->m_basePath;
}
}
}
@@ -177,10 +177,10 @@ void HttpMetaCache::evictAll()
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
{
auto foo = new MetaEntry();
- foo->baseId = base;
- foo->basePath = getBasePath(base);
- foo->relativePath = resource_path;
- foo->stale = true;
+ foo->m_baseId = base;
+ foo->m_basePath = getBasePath(base);
+ foo->m_relativePath = resource_path;
+ foo->m_stale = true;
return MetaEntryPtr(foo);
}
@@ -235,23 +235,23 @@ void HttpMetaCache::Load()
auto& entrymap = m_entries[base];
auto foo = new MetaEntry();
- foo->baseId = base;
- foo->relativePath = Json::ensureString(element_obj, "path");
- foo->md5sum = Json::ensureString(element_obj, "md5sum");
- foo->etag = Json::ensureString(element_obj, "etag");
- foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
- foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
+ foo->m_baseId = base;
+ foo->m_relativePath = Json::ensureString(element_obj, "path");
+ foo->m_md5sum = Json::ensureString(element_obj, "md5sum");
+ foo->m_etag = Json::ensureString(element_obj, "etag");
+ foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
+ foo->m_remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false));
if (!foo->isEternal()) {
- foo->current_age = Json::ensureDouble(element_obj, "current_age");
- foo->max_age = Json::ensureDouble(element_obj, "max_age");
+ foo->m_current_age = Json::ensureDouble(element_obj, "current_age");
+ foo->m_max_age = Json::ensureDouble(element_obj, "max_age");
}
// presumed innocent until closer examination
- foo->stale = false;
+ foo->m_stale = false;
- entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
+ entrymap.entry_list[foo->m_relativePath] = MetaEntryPtr(foo);
}
}
@@ -276,23 +276,23 @@ void HttpMetaCache::SaveNow()
for (auto group : m_entries) {
for (auto entry : group.entry_list) {
// do not save stale entries. they are dead.
- if (entry->stale) {
+ if (entry->m_stale) {
continue;
}
QJsonObject entryObj;
- Json::writeString(entryObj, "base", entry->baseId);
- Json::writeString(entryObj, "path", entry->relativePath);
- Json::writeString(entryObj, "md5sum", entry->md5sum);
- Json::writeString(entryObj, "etag", entry->etag);
- entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
- if (!entry->remote_changed_timestamp.isEmpty())
- entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
+ Json::writeString(entryObj, "base", entry->m_baseId);
+ Json::writeString(entryObj, "path", entry->m_relativePath);
+ Json::writeString(entryObj, "md5sum", entry->m_md5sum);
+ Json::writeString(entryObj, "etag", entry->m_etag);
+ entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp)));
+ if (!entry->m_remote_changed_timestamp.isEmpty())
+ entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp));
if (entry->isEternal()) {
entryObj.insert("eternal", true);
} else {
- entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
- entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
+ entryObj.insert("current_age", QJsonValue(double(entry->m_current_age)));
+ entryObj.insert("max_age", QJsonValue(double(entry->m_max_age)));
}
entriesArr.append(entryObj);
}
diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h
index 2a07d65a..37f4b49a 100644
--- a/launcher/net/HttpMetaCache.h
+++ b/launcher/net/HttpMetaCache.h
@@ -49,47 +49,47 @@ class MetaEntry {
MetaEntry() = default;
public:
- auto isStale() -> bool { return stale; }
- void setStale(bool stale) { this->stale = stale; }
+ auto isStale() -> bool { return m_stale; }
+ void setStale(bool stale) { m_stale = stale; }
auto getFullPath() -> QString;
- auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
- void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
- void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
+ auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; }
+ void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; }
+ void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; }
- auto getETag() -> QString { return etag; }
- void setETag(QString etag) { this->etag = etag; }
+ auto getETag() -> QString { return m_etag; }
+ void setETag(QString etag) { m_etag = etag; }
- auto getMD5Sum() -> QString { return md5sum; }
- void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
+ auto getMD5Sum() -> QString { return m_md5sum; }
+ void setMD5Sum(QString md5sum) { m_md5sum = md5sum; }
/* Whether the entry expires after some time (false) or not (true). */
- void makeEternal(bool eternal) { is_eternal = eternal; }
- [[nodiscard]] bool isEternal() const { return is_eternal; }
+ void makeEternal(bool eternal) { m_is_eternal = eternal; }
+ [[nodiscard]] bool isEternal() const { return m_is_eternal; }
- auto getCurrentAge() -> qint64 { return current_age; }
- void setCurrentAge(qint64 age) { current_age = age; }
+ auto getCurrentAge() -> qint64 { return m_current_age; }
+ void setCurrentAge(qint64 age) { m_current_age = age; }
- auto getMaximumAge() -> qint64 { return max_age; }
- void setMaximumAge(qint64 age) { max_age = age; }
+ auto getMaximumAge() -> qint64 { return m_max_age; }
+ void setMaximumAge(qint64 age) { m_max_age = age; }
- bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); };
+ bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); };
protected:
- QString baseId;
- QString basePath;
- QString relativePath;
- QString md5sum;
- QString etag;
-
- qint64 local_changed_timestamp = 0;
- QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
- qint64 current_age = 0;
- qint64 max_age = 0;
- bool is_eternal = false;
-
- bool stale = true;
+ QString m_baseId;
+ QString m_basePath;
+ QString m_relativePath;
+ QString m_md5sum;
+ QString m_etag;
+
+ qint64 m_local_changed_timestamp = 0;
+ QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time
+ qint64 m_current_age = 0;
+ qint64 m_max_age = 0;
+ bool m_is_eternal = false;
+
+ bool m_stale = true;
};
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
diff --git a/launcher/pathmatcher/SimplePrefixMatcher.h b/launcher/pathmatcher/SimplePrefixMatcher.h
new file mode 100644
index 00000000..fc1f5ced
--- /dev/null
+++ b/launcher/pathmatcher/SimplePrefixMatcher.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: 2022 Sefa Eyeoglu <contact@scrumplex.net>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+#include <QRegularExpression>
+#include "IPathMatcher.h"
+
+class SimplePrefixMatcher : public IPathMatcher {
+ public:
+ virtual ~SimplePrefixMatcher(){};
+ SimplePrefixMatcher(const QString& prefix)
+ {
+ m_prefix = prefix;
+ m_isPrefix = prefix.endsWith('/');
+ }
+
+ virtual bool matches(const QString& string) const override
+ {
+ if (m_isPrefix)
+ return string.startsWith(m_prefix);
+ return string == m_prefix;
+ }
+ QString m_prefix;
+ bool m_isPrefix = false;
+};
diff --git a/launcher/resources/backgrounds/backgrounds.qrc b/launcher/resources/backgrounds/backgrounds.qrc
index 652e7084..e55faf15 100644
--- a/launcher/resources/backgrounds/backgrounds.qrc
+++ b/launcher/resources/backgrounds/backgrounds.qrc
@@ -4,12 +4,14 @@
<file alias="kitteh">kitteh.png</file>
<file alias="kitteh-xmas">kitteh-xmas.png</file>
<file alias="kitteh-bday">kitteh-bday.png</file>
- <file alias="kitteh-ween">kitteh-ween.png</file>
+ <file alias="kitteh-spooky">kitteh-spooky.png</file>
<file alias="rory">rory.png</file>
<file alias="rory-xmas">rory-xmas.png</file>
<file alias="rory-bday">rory-bday.png</file>
+ <file alias="rory-spooky">rory-spooky.png</file>
<file alias="rory-flat">rory-flat.png</file>
<file alias="rory-flat-xmas">rory-flat-xmas.png</file>
<file alias="rory-flat-bday">rory-flat-bday.png</file>
+ <file alias="rory-flat-spooky">rory-flat-spooky.png</file>
</qresource>
</RCC>
diff --git a/launcher/resources/backgrounds/kitteh-ween.png b/launcher/resources/backgrounds/kitteh-spooky.png
index deb0bebb..deb0bebb 100644
--- a/launcher/resources/backgrounds/kitteh-ween.png
+++ b/launcher/resources/backgrounds/kitteh-spooky.png
Binary files differ
diff --git a/launcher/resources/backgrounds/rory-flat-spooky.png b/launcher/resources/backgrounds/rory-flat-spooky.png
new file mode 100644
index 00000000..6360c612
--- /dev/null
+++ b/launcher/resources/backgrounds/rory-flat-spooky.png
Binary files differ
diff --git a/launcher/resources/backgrounds/rory-spooky.png b/launcher/resources/backgrounds/rory-spooky.png
new file mode 100644
index 00000000..a727619b
--- /dev/null
+++ b/launcher/resources/backgrounds/rory-spooky.png
Binary files differ
diff --git a/launcher/resources/breeze_dark/breeze_dark.qrc b/launcher/resources/breeze_dark/breeze_dark.qrc
new file mode 100644
index 00000000..4d7a69b2
--- /dev/null
+++ b/launcher/resources/breeze_dark/breeze_dark.qrc
@@ -0,0 +1,43 @@
+<RCC>
+ <qresource prefix="/icons/breeze_dark">
+ <file>index.theme</file>
+ <file>scalable/about.svg</file>
+ <file>scalable/accounts.svg</file>
+ <file>scalable/bug.svg</file>
+ <file>scalable/centralmods.svg</file>
+ <file>scalable/checkupdate.svg</file>
+ <file>scalable/copy.svg</file>
+ <file>scalable/coremods.svg</file>
+ <file>scalable/custom-commands.svg</file>
+ <file>scalable/discord.svg</file>
+ <file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
+ <file>scalable/instance-settings.svg</file>
+ <file>scalable/jarmods.svg</file>
+ <file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
+ <file>scalable/loadermods.svg</file>
+ <file>scalable/log.svg</file>
+ <file>scalable/minecraft.svg</file>
+ <file>scalable/new.svg</file>
+ <file>scalable/news.svg</file>
+ <file>scalable/notes.svg</file>
+ <file>scalable/proxy.svg</file>
+ <file>scalable/reddit-alien.svg</file>
+ <file>scalable/refresh.svg</file>
+ <file>scalable/resourcepacks.svg</file>
+ <file>scalable/shaderpacks.svg</file>
+ <file>scalable/screenshots.svg</file>
+ <file>scalable/settings.svg</file>
+ <file>scalable/status-bad.svg</file>
+ <file>scalable/status-good.svg</file>
+ <file>scalable/status-yellow.svg</file>
+ <file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
+ <file>scalable/delete.svg</file>
+ <file>scalable/tag.svg</file>
+ <file>scalable/export.svg</file>
+ <file>scalable/rename.svg</file>
+ <file>scalable/launch.svg</file>
+ </qresource>
+</RCC>
diff --git a/launcher/resources/breeze_dark/index.theme b/launcher/resources/breeze_dark/index.theme
new file mode 100644
index 00000000..f9f6f4dc
--- /dev/null
+++ b/launcher/resources/breeze_dark/index.theme
@@ -0,0 +1,11 @@
+[Icon Theme]
+Name=Breeze Dark
+Comment=Breeze Dark Icons
+Inherits=multimc
+Directories=scalable
+
+[scalable]
+Size=48
+Type=Scalable
+MinSize=16
+MaxSize=256
diff --git a/launcher/resources/breeze_dark/scalable/about.svg b/launcher/resources/breeze_dark/scalable/about.svg
new file mode 100644
index 00000000..856d1b2b
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/about.svg
@@ -0,0 +1,12 @@
+<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ <g class="ColorScheme-Text" fill="currentColor" fill-rule="evenodd">
+ <path d="m8 2a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 -6-6zm0 1a5 5 0 0 1 5 5 5 5 0 0 1 -5 5 5 5 0 0 1 -5-5 5 5 0 0 1 5-5z"/>
+ <path d="m7 4h2v2h-2z"/>
+ <path d="m7 7h2v5h-2z"/>
+ </g>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/accounts.svg b/launcher/resources/breeze_dark/scalable/accounts.svg
new file mode 100644
index 00000000..fbb51959
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/accounts.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 20.5,4 A 4.5,4.5 0 0 0 16,8.5 4.5,4.5 0 0 0 20.5,13 4.5,4.5 0 0 0 25,8.5 4.5,4.5 0 0 0 20.5,4 Z m 0,1 A 3.5,3.5 0 0 1 24,8.5 3.5,3.5 0 0 1 20.5,12 3.5,3.5 0 0 1 17,8.5 3.5,3.5 0 0 1 20.5,5 Z m -9,4 C 9.014719,9 7,11.01472 7,13.5 7,15.98528 9.014719,18 11.5,18 13.985281,18 16,15.98528 16,13.5 16,11.01472 13.985281,9 11.5,9 Z m 0,1 A 3.5,3.5 0 0 1 15,13.5 3.5,3.5 0 0 1 11.5,17 3.5,3.5 0 0 1 8,13.5 3.5,3.5 0 0 1 11.5,10 Z m 9,4 c -0.88285,0.003 -1.758266,0.17228 -2.585938,0.5 -0.06618,0.42368 -0.174132,0.83977 -0.322265,1.24219 C 18.494507,15.25488 19.490227,15.00077 20.5,15 c 3.589851,0 6.5,3.13401 6.5,7 l -8.341797,0 c 0.170323,0.32329 0.325499,0.65711 0.464844,1 L 28,23 28,22 c 0,-4.41828 -3.357864,-8 -7.5,-8 z m -9,5 C 7.357864,19 4,22.58172 4,27 l 0,1 15,0 0,-1 c 0,-4.41828 -3.357864,-8 -7.5,-8 z m 0,1 c 3.589851,0 6.5,3.13401 6.5,7 L 5,27 c 0,-3.86599 2.910149,-7 6.5,-7 z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/bug.svg b/launcher/resources/breeze_dark/scalable/bug.svg
new file mode 100644
index 00000000..6ddf482f
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/bug.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m2 2v12h12v-12zm1 1h10v10h-10zm1 2v2h2v-2zm6 0v2h2v-2zm-4 4v1h4v-1zm4 1v1h1v-1zm1 1v1h1v-1zm-5-1h-1v1h1zm-1 1h-1v1h1z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/centralmods.svg b/launcher/resources/breeze_dark/scalable/centralmods.svg
new file mode 100644
index 00000000..4035e51c
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/centralmods.svg
@@ -0,0 +1 @@
+<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3.5v.25h-.5V6h3V3.75H5V3.5h-.75v.25h-.5V3.5H3Zm-.25.5h2.5v1.75h-2.5V4Z" fill="#EFF0F1"/><path d="M1 1v6h6l-.25-.25h-5.5V3.5H2.5l.75-.75h3.5v4L7 7V1.75H4.5L3.75 1H1Z" fill="#EFF0F1"/></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_dark/scalable/checkupdate.svg b/launcher/resources/breeze_dark/scalable/checkupdate.svg
new file mode 100644
index 00000000..cc5dfc16
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/checkupdate.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor"
+ d="M 6 2 L 6 3 L 6 6 L 7 6 L 7 3 L 9 3 L 9 6 L 10 6 L 10 3 L 10 2 L 6 2 z M 3.7 6 L 3 6.7 L 6.3 10 L 8 11.7 L 9.7 10 L 13 6.7 L 12.3 6 L 9 9.3 L 8 10.3 L 7 9.3 L 3.7 6 z M 2 12 L 2 14 L 3 14 L 14 14 L 14 13 L 14 12 L 13 12 L 13 13 L 3 13 L 3 12 L 2 12 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/copy.svg b/launcher/resources/breeze_dark/scalable/copy.svg
new file mode 100644
index 00000000..fe4a36ac
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/copy.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 3 L 3 17 L 7 17 L 7 19 L 17 19 L 17 10 L 13 6 L 12 6 L 9 3 L 3 3 Z M 4 4 L 8 4 L 8 6 L 7 6 L 7 16 L 4 16 L 4 4 Z M 8 7 L 12 7 L 12 11 L 16 11 L 16 18 L 8 18 L 8 7 Z"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/coremods.svg b/launcher/resources/breeze_dark/scalable/coremods.svg
new file mode 100644
index 00000000..ec4ecea8
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/coremods.svg
@@ -0,0 +1 @@
+<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.167 2.46v.208H2.75v1.875h2.5V2.668h-.417V2.46h-.625v.208h-.416V2.46h-.625Zm-.209.417h2.084v1.458H2.958V2.877Z" fill="#EFF0F1"/><path d="M1.5 1v6h5V1h-5Zm1.5.5h2a1 1 0 0 1 1 1V3a.5.5 0 1 0 0 1v1l-.5.5h-3L2 5V4a.5.5 0 1 0 0-1v-.5a1 1 0 0 1 1-1ZM2 6h2v.5H2V6Zm3 0h1v.5H5V6Z" fill="#EFF0F1"/></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_dark/scalable/custom-commands.svg b/launcher/resources/breeze_dark/scalable/custom-commands.svg
new file mode 100644
index 00000000..44efd39e
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/custom-commands.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 14 L 14 14 L 14 2 L 2 2 z M 3 3 L 13 3 L 13 13 L 3 13 L 3 3 z M 4.71875 4 L 4 4.6875 L 6.625 7.5 L 4.03125 10.3125 L 4.71875 11 L 7.6875 7.84375 L 8 7.5 L 7.6875 7.15625 L 4.71875 4 z M 8 11 L 8 12 L 12 12 L 12 11 L 8 11 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/delete.svg b/launcher/resources/breeze_dark/scalable/delete.svg
new file mode 100644
index 00000000..c7074585
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/delete.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 6 2 L 6 3 L 2 3 L 2 4 L 3 4 L 3 14 L 4 14 L 13 14 L 13 13 L 13 4 L 14 4 L 14 3 L 10 3 L 10 2 L 6 2 z M 7 3 L 9 3 L 9 4 L 10 4 L 12 4 L 12 13 L 4 13 L 4 4 L 7 4 L 7 3 z M 6 6 L 6 11 L 7 11 L 7 6 L 6 6 z M 9 6 L 9 11 L 10 11 L 10 6 L 9 6 z "
+ class="ColorScheme-Text"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/discord.svg b/launcher/resources/breeze_dark/scalable/discord.svg
new file mode 100644
index 00000000..22ee27ba
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/discord.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#fff;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_dark/scalable/export.svg b/launcher/resources/breeze_dark/scalable/export.svg
new file mode 100644
index 00000000..b1fe39d1
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/export.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 7 12 L 11.0859 12 L 9.46484 13.6211 L 10.1719 14.3281 L 13 11.5 L 10.1719 8.67187 L 9.46484 9.37891 L 11.0859 11 L 7 11 L 7 12 Z M 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 9 L 13 9 L 13 5 L 10 2 L 3 2 L 3 14 L 8 14 L 8 13 L 4 13 Z"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/externaltools.svg b/launcher/resources/breeze_dark/scalable/externaltools.svg
new file mode 100644
index 00000000..dd19fb90
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/externaltools.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 4 5 L 4 7 L 2 7 L 2 8 L 4 8 L 4 10 L 5 10 L 7 10 L 7 9 L 5 9 L 5 6 L 7 6 L 7 5 L 5 5 L 4 5 z M 9 5 L 9 6 L 11 6 L 11 9 L 9 9 L 9 10 L 11 10 L 12 10 L 12 9 L 12 8 L 14 8 L 14 7 L 12 7 L 12 6 L 12 5 L 11 5 L 9 5 z M 7 7 L 7 8 L 9 8 L 9 7 L 7 7 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/help.svg b/launcher/resources/breeze_dark/scalable/help.svg
new file mode 100644
index 00000000..b273a8bc
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/help.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 3 4 L 3 16 L 6 20 L 6 17 L 6 16 L 19 16 L 19 4 L 3 4 z M 4 5 L 18 5 L 18 15 L 4 15 L 4 5 z M 10.5 6 A 2.5 2.5 0 0 0 8 8.5 L 8 9 L 9 9 L 9 8.5 A 1.5 1.5 0 0 1 10.5 7 A 1.5 1.5 0 0 1 12 8.5 A 1.5 1.5 0 0 1 10.5 10 L 10 10 L 10 12 L 11 12 L 11 10.949219 A 2.5 2.5 0 0 0 13 8.5 A 2.5 2.5 0 0 0 10.5 6 z M 10 13 L 10 14 L 11 14 L 11 13 L 10 13 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/instance-settings.svg b/launcher/resources/breeze_dark/scalable/instance-settings.svg
new file mode 100644
index 00000000..c5f0504b
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/instance-settings.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 5 L 2 14 L 3 14 L 14 14 L 14 13 L 14 2 L 3 2 L 2 2 z M 3 5 L 13 5 L 13 13 L 3 13 L 3 5 z M 4 6 L 4 12 L 6 12 L 6 6 L 4 6 z M 7 7 L 7 8 L 12 8 L 12 7 L 7 7 z M 7 10 L 7 11 L 12 11 L 12 10 L 7 10 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/jarmods.svg b/launcher/resources/breeze_dark/scalable/jarmods.svg
new file mode 100644
index 00000000..49a45d36
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/jarmods.svg
@@ -0,0 +1 @@
+<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 1.5V2H1v4.5h6V2H6v-.5H4.5V2h-1v-.5H2Zm-.5 1h5V6h-5V2.5Z" fill="#EFF0F1"/><path d="M3.93 4.37s-.19-.364-.183-.63c.005-.19.434-.378.603-.651.168-.273-.022-.54-.022-.54s.043.197-.07.4c-.112.203-.525.322-.686.672-.16.35.357.75.357.75Z" fill="#EFF0F1"/><path d="M4.637 3.264s-.645.246-.645.525c0 .28.175.372.203.463.028.091-.049.245-.049.245s.252-.175.21-.378c-.042-.203-.238-.267-.126-.47.075-.136.407-.385.407-.385Z" fill="#EFF0F1"/><path d="M3.859 4.741c.595-.021.812-.209.812-.209-.385.105-1.407.098-1.415.021-.006-.077.316-.14.316-.14s-.505 0-.546.126c-.043.126.238.223.833.202ZM4.72 5.036s.583-.124.526-.44c-.07-.386-.477-.169-.477-.169s.288 0 .316.175c.028.175-.364.434-.364.434ZM4.434 4.868s-.147.038-.364.063c-.292.033-.645.007-.673-.042-.028-.05.05-.077.05-.077-.35.084-.16.23.251.26.352.023.876-.106.876-.106l-.14-.098ZM3.53 5.174s-.159.004-.168.088c-.01.084.098.159.49.182.392.024.668-.107.668-.107l-.178-.107s-.112.023-.285.046c-.173.024-.527-.018-.541-.051-.014-.032.014-.051.014-.051Z" fill="#EFF0F1"/><path d="M5.057 5.552c.06-.065-.019-.117-.019-.117s.028.033-.009.07c-.037.037-.378.13-.924.158-.546.028-1.14-.05-1.159-.121-.018-.07.304-.126.304-.126-.037.005-.485.014-.5.136-.013.12.197.22 1.037.22.84 0 1.21-.155 1.27-.22Z" fill="#EFF0F1"/><path d="M4.73 5.828c-.368.074-1.489.027-1.489.027s.728.173 1.56.029c.397-.07.42-.262.42-.262s-.122.13-.49.206Z" fill="#EFF0F1"/></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_dark/scalable/java.svg b/launcher/resources/breeze_dark/scalable/java.svg
new file mode 100644
index 00000000..7149981c
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/java.svg
@@ -0,0 +1,10 @@
+<svg width="1000" height="1000" viewBox="0 0 1000 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M479.6 536.2C479.6 536.2 425 430.9 427 354C428.5 299 552.4 244.7 601.1 165.8C649.7 86.9 595 10 595 10C595 10 607.2 66.7 574.8 125.3C542.4 184 422.9 218.5 376.4 319.6C329.9 420.9 479.6 536.2 479.6 536.2Z" fill="#eff0f1"/>
+<path d="M684.099 216.4C684.099 216.4 497.899 287.3 497.899 368.2C497.899 449.2 548.499 475.5 556.599 501.8C564.699 528.2 542.399 572.7 542.399 572.7C542.399 572.7 615.199 522.1 602.999 463.4C590.799 404.7 534.199 386.4 566.599 327.8C588.399 288.4 684.099 216.4 684.099 216.4Z" fill="#eff0f1"/>
+<path d="M459.399 643.2C631.499 637.1 694.2 582.8 694.2 582.8C582.9 613.1 287.399 611.1 285.299 588.9C283.299 566.6 376.399 548.4 376.399 548.4C376.399 548.4 230.7 548.4 218.6 584.8C206.4 621.2 287.499 649.2 459.399 643.2Z" fill="#eff0f1"/>
+<path d="M708.399 728.5C708.399 728.5 876.799 692.6 860.099 601.1C839.899 489.7 722.499 552.5 722.499 552.5C722.499 552.5 805.599 552.5 813.599 603.1C821.699 653.6 708.399 728.5 708.399 728.5Z" fill="#eff0f1"/>
+<path d="M625.4 679.9C625.4 679.9 583 691 520.1 698.1C435.8 707.6 333.9 700.1 325.8 685.9C317.8 671.7 340 663.7 340 663.7C238.8 688 294.2 730.4 412.8 738.6C514.5 745.5 665.9 708.2 665.9 708.2L625.4 679.9Z" fill="#eff0f1"/>
+<path d="M364.299 768.3C364.299 768.3 318.399 769.6 315.699 793.9C313.099 818 343.999 839.7 457.299 846.5C570.599 853.2 650.299 815.5 650.299 815.5L598.999 784.4C598.999 784.4 566.599 791.2 516.699 797.9C466.699 804.7 364.299 792.5 360.199 783.1C356.199 773.7 364.299 768.3 364.299 768.3Z" fill="#eff0f1"/>
+<path d="M805.5 877.6C823 858.7 800.099 843.8 800.099 843.8C800.099 843.8 808.2 853.3 797.5 864C786.7 874.8 688.199 901.7 530.299 909.8C372.499 917.9 201.1 895 195.6 874.7C190.3 854.5 283.4 838.3 283.4 838.3C272.6 839.6 143.1 842.3 139 877.5C135 912.5 195.7 940.9 438.6 940.9C681.4 941 788 896.4 805.5 877.6Z" fill="#eff0f1"/>
+<path d="M711.099 957.2C604.499 978.7 280.699 965.2 280.699 965.2C280.699 965.2 491.099 1015.2 731.299 973.4C846.099 953.4 852.799 897.8 852.799 897.8C852.799 897.8 817.699 935.6 711.099 957.2Z" fill="#eff0f1"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/language.svg b/launcher/resources/breeze_dark/scalable/language.svg
new file mode 100644
index 00000000..239cdf94
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/language.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 3 L 2 8 L 3 8 L 6 8 L 6 10 L 6 11 L 9 14 L 9 11 L 13 11 L 14 11 L 14 6 L 14 5 L 13 5 L 10 5 L 10 2 L 3 2 L 2 2 z M 3 3 L 9 3 L 9 5 L 9 6 L 9 7 L 7 7 L 6 7 L 3 7 L 3 3 z M 10 6 L 13 6 L 13 10 L 8 10 L 7 10 L 7 8 L 8 8 L 8 10 L 10 8 L 10 7 L 10 6 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/launch.svg b/launcher/resources/breeze_dark/scalable/launch.svg
new file mode 100644
index 00000000..25c5fabc
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/launch.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ <path d="m2 2v12l12-6z" class="ColorScheme-Text" fill="currentColor"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/launcher.svg b/launcher/resources/breeze_dark/scalable/launcher.svg
new file mode 100644
index 00000000..aeee8433
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/launcher.svg
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <title>Prism Launcher Logo</title>
+ <g stroke-width=".26458">
+ <path d="m6.35 6.35" fill="#99cd61"/>
+ <path d="m6.35 0.52917-2.5208 4.3656 2.5208 1.4552 2.5203-1.4552 0.10955-3.0996c-1.1511-0.66459-2.3388-1.2661-2.6298-1.2661z" fill="#df6277"/>
+ <path d="m8.9798 1.7952-2.6298 4.5548 2.5203 1.4552 2.5208-4.3656c-0.14552-0.25205-1.2601-0.97975-2.4112-1.6443z" fill="#fb9168"/>
+ <path d="m11.391 3.4396-5.041 2.9104 2.5203 1.4552 2.7389-1.4552c0-1.3292-0.072554-2.6584-0.21808-2.9104z" fill="#f3db6c"/>
+ <path d="m6.35 6.35v2.9104h5.041c0.14552-0.25205 0.21807-1.5812 0.21808-2.9104h-5.2591z" fill="#7ab392"/>
+ <path d="m6.35 6.35v2.9104l2.6298 1.6443c1.1511-0.66459 2.2657-1.3923 2.4112-1.6443l-5.041-2.9104z" fill="#4b7cbc"/>
+ <path d="m6.35 6.35-2.5208 1.4552 2.5208 4.3656c0.29104 0 1.4787-0.60148 2.6298-1.2661l-2.6298-4.5548z" fill="#6f488c"/>
+ <path d="m3.8292 4.8948-2.5203 4.3656c0.29104 0.5041 4.459 2.9104 5.041 2.9104v-5.8208l-2.5208-1.4552z" fill="#4d3f33"/>
+ <path d="m1.309 3.4396c-0.29104 0.5041-0.29104 5.3167 0 5.8208l5.041-2.9104v-2.9104h-5.041z" fill="#7a573b"/>
+ <path d="m6.35 0.52917c-0.58208-2e-8 -4.75 2.4063-5.041 2.9104l5.041 2.9104v-5.8208z" fill="#99cd61"/>
+ </g>
+ <g transform="matrix(.88 0 0 .88 -10.906 -1.2421)">
+ <g transform="translate(13.26 2.2776)">
+ <path transform="matrix(.96975 0 0 .96975 .1921 .1921)" d="m6.3498 2.9393c-0.34105 0-2.7827 1.4099-2.9532 1.7052l2.9532 5.1157 2.9538-5.1157c-0.17052-0.29535-2.6127-1.7052-2.9538-1.7052z" fill="#fff" stroke-width=".26458"/>
+ </g>
+ <path d="m16.746 6.9737 2.8639 4.9609c0.33073 0 2.6991-1.3672 2.8644-1.6536 0.16536-0.28642 0.16536-3.0209 0-3.3073l-2.8644 1.6536z" fill="#dfdfdf" stroke-width=".26458"/>
+ </g>
+ <path d="m3.8299 4.8948c-0.14551 0.25205-0.14553 2.6584 0 2.9104 0.14553 0.25204 2.2292 1.4552 2.5203 1.4552v-2.9104z" fill="#d6d2d2" stroke-width=".26458"/>
+ <metadata>
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:title>Prism Launcher Logo</dc:title>
+ <dc:date>19/10/2022</dc:date>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Prism Launcher</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>AutiOne, Boba, ely, Fulmine, gon sawa, Pankakes, tobimori, Zeke</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <dc:source>https://github.com/PrismLauncher/PrismLauncher</dc:source>
+ <dc:publisher>
+ <cc:Agent>
+ <dc:title>Prism Launcher</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/"/>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:requires rdf:resource="http://creativecommons.org/ns#Notice"/>
+ <cc:requires rdf:resource="http://creativecommons.org/ns#Attribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ <cc:requires rdf:resource="http://creativecommons.org/ns#ShareAlike"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/loadermods.svg b/launcher/resources/breeze_dark/scalable/loadermods.svg
new file mode 100644
index 00000000..7bd87188
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/loadermods.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m4 3v1h-2v9h12v-9h-2v-1h-3v1h-2v-1zm-1 2h10v7h-10z"
+ class="ColorScheme-Text"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/log.svg b/launcher/resources/breeze_dark/scalable/log.svg
new file mode 100644
index 00000000..fcd83c4d
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/log.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 11 2 L 11 14 L 14 14 L 14 2 L 11 2 z M 6 5 L 6 14 L 9 14 L 9 5 L 6 5 z M 1 8 L 1 14 L 4 14 L 4 8 L 1 8 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/matrix.svg b/launcher/resources/breeze_dark/scalable/matrix.svg
new file mode 100644
index 00000000..214f5708
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/matrix.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 27.9 32" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <title>Matrix (protocol) logo</title>
+ <g transform="translate(-.095 .005)" fill="#eff0f1">
+ <path d="m27.1 31.2v-30.5h-2.19v-0.732h3.04v32h-3.04v-0.732z"/>
+ <path d="m8.23 10.4v1.54h0.044c0.385-0.564 0.893-1.03 1.49-1.37 0.58-0.323 1.25-0.485 1.99-0.485 0.72 0 1.38 0.14 1.97 0.42 0.595 0.279 1.05 0.771 1.36 1.48 0.338-0.5 0.796-0.941 1.38-1.32 0.58-0.383 1.27-0.574 2.06-0.574 0.602 0 1.16 0.074 1.67 0.22 0.514 0.148 0.954 0.383 1.32 0.707 0.366 0.323 0.653 0.746 0.859 1.27 0.205 0.522 0.308 1.15 0.308 1.89v7.63h-3.13v-6.46c0-0.383-0.015-0.743-0.044-1.08-0.0209-0.307-0.103-0.607-0.242-0.882-0.133-0.251-0.336-0.458-0.584-0.596-0.257-0.146-0.606-0.22-1.05-0.22-0.44 0-0.796 0.085-1.07 0.253-0.272 0.17-0.485 0.39-0.639 0.662-0.159 0.287-0.264 0.602-0.308 0.927-0.052 0.347-0.078 0.697-0.078 1.05v6.35h-3.13v-6.4c0-0.338-7e-3 -0.673-0.021-1-0.0114-0.314-0.0749-0.623-0.188-0.916-0.108-0.277-0.3-0.512-0.55-0.673-0.258-0.168-0.636-0.253-1.14-0.253-0.198 0.0083-0.394 0.042-0.584 0.1-0.258 0.0745-0.498 0.202-0.705 0.374-0.228 0.184-0.422 0.449-0.584 0.794-0.161 0.346-0.242 0.798-0.242 1.36v6.62h-3.13v-11.4z"/>
+ <path d="m0.936 0.732v30.5h2.19v0.732h-3.04v-32h3.03v0.732z"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_dark/scalable/minecraft.svg b/launcher/resources/breeze_dark/scalable/minecraft.svg
new file mode 100644
index 00000000..1d8d0167
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/minecraft.svg
@@ -0,0 +1,13 @@
+<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">.ColorScheme-Text {
+ color:#eff0f1;
+ }</style>
+ </defs>
+ <g fill="currentColor">
+ <path class="ColorScheme-Text" d="m8.2746408 13.953029c0.00833 0 1.1494866-0.753825 2.5359342-1.674795l2.520534-1.674794v-2.6001033c0-1.4307351-0.0074-2.6013858-0.01668-2.6013858-0.0093 0-1.150413 0.7546842-2.535934 1.6773611l-2.5192543 1.6773619v2.5988191c0 1.429027 0.00706 2.597536 0.015402 2.597536z" fill="currentColor" fill-opacity=".2"/>
+ <path class="ColorScheme-Text" d="m7.9409649 8.3549799c0.0753873-0.0030883 5.0483591-3.3585525 5.0192511-3.3868071-0.014404-0.0138645-1.1312-0.7652159-2.482034-1.6683781-1.3508332-0.9031623-2.4637176-1.6377305-2.4730489-1.6311596-0.4205404 0.2725585-4.9953997 3.3678436-4.9999993 3.3829565-0.00988 0.032723 4.8804389 3.3033883 4.9358311 3.3033883z" fill="currentColor" fill-opacity=".4"/>
+ <path class="ColorScheme-Text" d="m7.7189427 13.994097c0.00828 0 0.015875-1.150637 0.015402-2.556468l-0.0013141-2.5551853-2.517967-1.6850615c-1.3845486-0.9264887-2.5204415-1.6863448-2.524384-1.6863448-0.00421-0.00112-0.0077 1.1516957-0.0077 2.5616013v2.5628853l2.5102968 1.679928c1.3808361 0.923532 2.5173881 1.678645 2.5256673 1.678645z" fill="currentColor" fill-opacity=".6"/>
+ <path class="ColorScheme-Text" d="m 8,15 c 5.76994,-3.758469 4.07772,-2.720917 6,-4 V 5 C 12.707222,4.143927 10.030643,2.3424577 8,1 5,3 4.3571975,3.3856408 2,5 v 6 z M 8.508315,13.412447 8.4963301,9 C 10.411258,7.652439 11.772087,6.8337528 13,6 v 4.594435 z M 7.5156547,13.400462 3,10.570466 V 6 L 7.5276396,9 Z M 8,8 3.4485124,5 8,2 12.477901,5 C 10.956071,6.0867591 9.5367568,6.9353406 8,8 Z" fill="currentColor"/>
+ </g>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/new.svg b/launcher/resources/breeze_dark/scalable/new.svg
new file mode 100644
index 00000000..9ee910e7
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/new.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 4 4 L 4 28 L 17 28 L 17 27 L 5 27 L 5 14 L 10 14 L 13 11 L 27 11 L 27 17 L 28 17 L 28 7 L 18 7 L 15 4 L 4 4 z M 22 17 L 22 22 L 17 22 L 17 23 L 22 23 L 22 28 L 23 28 L 23 23 L 28 23 L 28 22 L 23 22 L 23 17 L 22 17 z "
+ id="path99"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/news.svg b/launcher/resources/breeze_dark/scalable/news.svg
new file mode 100644
index 00000000..a2ff0c8d
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/news.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m2 2v12h12v-12zm1 1h10v10h-10zm1 1v3h3v-3zm4 0v1h4v-1zm0 2v1h4v-1zm-4 2v4h8v-4z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/notes.svg b/launcher/resources/breeze_dark/scalable/notes.svg
new file mode 100644
index 00000000..6452d3c8
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/notes.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 3 2 L 3 3 L 2 3 L 2 14 L 14 14 L 14 3 L 13 3 L 13 2 L 12 2 L 12 3 L 11 3 L 11 2 L 10 2 L 10 3 L 6 3 L 6 2 L 5 2 L 5 3 L 4 3 L 4 2 L 3 2 z M 3 4 L 13 4 L 13 13 L 3 13 L 3 4 z M 4 5 L 4 6 L 12 6 L 12 5 L 4 5 z M 4 7 L 4 8 L 8 8 L 8 7 L 4 7 z M 4 9 L 4 10 L 10 10 L 10 9 L 4 9 z M 10 11 L 10 12 L 12 12 L 12 11 L 10 11 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/patreon.svg b/launcher/resources/breeze_dark/scalable/patreon.svg
new file mode 100644
index 00000000..7f98dd13
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/patreon.svg
@@ -0,0 +1,3 @@
+<svg fill="#eff0f1" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,3h0a9,9,0,0,0-9,9v9H5.09V12a6.91,6.91,0,1,1,7.23,6.9,5.9,5.9,0,0,1-2.59-.47v-3A4.13,4.13,0,1,0,7.85,12v9H12A9,9,0,1,0,12,3Zm0,15.91h0Z"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/proxy.svg b/launcher/resources/breeze_dark/scalable/proxy.svg
new file mode 100644
index 00000000..c6efb171
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/proxy.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 7 2 L 7 3 L 13 3 L 13 10 L 7 10 L 7 12 L 7 13 L 6 13 C 5.4459904 13 5 12.55401 5 12 L 5 10 L 5 8 L 5 7 L 6 7 L 6 3 L 3 3 L 3 7 L 4 7 L 4 8 L 4 10 L 4 12 C 4 13.108 4.892 14 6 14 L 7 14 L 9 14 L 11 14 L 11 13 L 9 13 L 9 12 L 14 12 L 14 11 L 14 10 L 14 2 L 7 2 z M 4 5 L 5 5 L 5 6 L 4 6 L 4 5 z M 2 8 L 2 12 L 3 12 L 3 10 L 3 8 L 2 8 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/reddit-alien.svg b/launcher/resources/breeze_dark/scalable/reddit-alien.svg
new file mode 100644
index 00000000..00f82bb3
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/reddit-alien.svg
@@ -0,0 +1,3 @@
+<svg fill="#eff0f1" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20,11.86a1.76,1.76,0,0,0-1.75-1.75,1.73,1.73,0,0,0-1.22.51,9,9,0,0,0-4.67-1.38l1-3.16L16,6.71s0,0,0,0a1.46,1.46,0,1,0,.1-.53l-2.89-.68a.25.25,0,0,0-.29.17L11.83,9.23a9.16,9.16,0,0,0-4.88,1.36,1.75,1.75,0,1,0-2.07,2.79,3,3,0,0,0-.06.58C4.82,16.58,8,18.7,12,18.7s7.14-2.13,7.14-4.74a2.94,2.94,0,0,0-.05-.55A1.74,1.74,0,0,0,20,11.86ZM8.51,13.08a1.06,1.06,0,1,1,1.06,1.06A1.06,1.06,0,0,1,8.51,13.08Zm6.06,3.14A3.48,3.48,0,0,1,12,17h0a3.48,3.48,0,0,1-2.56-.79.25.25,0,0,1,.35-.35,3,3,0,0,0,2.2.65h0a3,3,0,0,0,2.2-.65.25.25,0,1,1,.35.35Zm-.13-2.08a1.06,1.06,0,1,1,1.06-1.06A1.06,1.06,0,0,1,14.44,14.14Z"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/refresh.svg b/launcher/resources/breeze_dark/scalable/refresh.svg
new file mode 100644
index 00000000..7b486463
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/refresh.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">.ColorScheme-Text {
+ color:#eff0f1;
+ }</style>
+ </defs>
+ <path class="ColorScheme-Text" fill="currentColor" d="m14 8c0 1.1088173-0.319333 2.140071-0.84375 3.03125l-2.6875-2.6875 0.71875-0.71875 1.625 1.625c0.05109-0.19534 0.097716-0.3898623 0.125-0.59375 0.007789-0.0524063 0.025184-0.1032787 0.03125-0.15625 0.01707-0.1680854 0.03125-0.327411 0.03125-0.5 0-2.761424-2.238576-5-5-5-0.243024 0-0.4855082 0.02845-0.71875 0.0625-0.1362493 0.019955-0.2738032 0.031786-0.40625 0.0625-0.2865815 0.06695-0.547624 0.166647-0.8125 0.28125-0.030675 0.013272-0.063399 0.017376-0.09375 0.03125-0.09166 0.040961-0.163333 0.108255-0.25 0.15625-8e-3 0.00445-0.02305-0.00433-0.03125 0l-0.71875-0.75c0.891179-0.524417 1.922432-0.84375 3.03125-0.84375 3.313709 0 6 2.686293 6 6zm-2.96875 5.15625c-0.891179 0.524417-1.922432 0.84375-3.03125 0.84375-3.313709 0-6-2.686293-6-6 0-1.1088173 0.319333-2.140071 0.84375-3.03125l0.75 0.75 1.90625 1.9375-0.6875 0.6875-1.625-1.59375c-0.05109 0.19534-0.09772 0.3898623-0.125 0.59375-0.0078 0.052406-0.02518 0.1032787-0.03125 0.15625-0.01707 0.1680854-0.03125 0.327411-0.03125 0.5s0.01418 0.3319146 0.03125 0.5c0.01707 0.1680853 0.029198 0.337256 0.0625 0.5 0.466231 2.278415 2.490004 4 4.90625 4 0.2482626 0 0.4801862-0.02756 0.71875-0.0625 0.1362493-0.01995 0.2738032-0.03179 0.40625-0.0625 0.2865815-0.06695 0.547624-0.166647 0.8125-0.28125 0.030718-0.01299 0.063349-0.01766 0.09375-0.03125 0.08886-0.04062 0.164735-0.108612 0.25-0.15625h0.03125z"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/rename.svg b/launcher/resources/breeze_dark/scalable/rename.svg
new file mode 100644
index 00000000..6a844965
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/rename.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/resourcepacks.svg b/launcher/resources/breeze_dark/scalable/resourcepacks.svg
new file mode 100644
index 00000000..0986c216
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/resourcepacks.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8 3 L 13 3 L 13 13 L 8 13 L 8 3 Z M 3 3 L 13 3 L 13 9 L 11 7 L 7.65625 10.3438 L 6.3125 9 L 6.2813 9 L 3 12.2813 L 3 3 Z M 2 2 L 2 13.2813 L 2 14 L 14 14 L 14 13 L 14 12 L 14 11 L 14 10 L 14 2 L 2 2 Z M 6 4 C 4.89543 4 4 4.89543 4 6 C 4 7.10457 4.89543 8 6 8 C 7.10457 8 8 7.10457 8 6 C 8 4.89543 7.10457 4 6 4 Z"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/screenshots.svg b/launcher/resources/breeze_dark/scalable/screenshots.svg
new file mode 100644
index 00000000..a10ed713
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/screenshots.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 13.28125 L 2 14 L 14 14 L 14 13 L 14 12 L 14 11 L 14 10 L 14 2 L 2 2 z M 3 3 L 13 3 L 13 9 L 11 7 L 7.65625 10.34375 L 6.3125 9 L 6.28125 9 L 3 12.28125 L 3 3 z M 6 4 C 4.8954305 4 4 4.8954305 4 6 C 4 7.1045695 4.8954305 8 6 8 C 7.1045695 8 8 7.1045695 8 6 C 8 4.8954305 7.1045695 4 6 4 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/settings.svg b/launcher/resources/breeze_dark/scalable/settings.svg
new file mode 100644
index 00000000..009d8154
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/settings.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M18.5 6A3.5 3.5 0 0 0 15.04102 9H4V10H15.04A3.5 3.5 0 0 0 18.5 13 3.5 3.5 0 0 0 21.958984 10H28V9H21.961A3.5 3.5 0 0 0 18.5 6M7.5 19A3.5 3.5 0 0 0 4 22.5 3.5 3.5 0 0 0 7.5 26 3.5 3.5 0 0 0 10.960938 23H28V22H10.959A3.5 3.5 0 0 0 7.5 19m0 1A2.5 2.5 0 0 1 10 22.5 2.5 2.5 0 0 1 7.5 25 2.5 2.5 0 0 1 5 22.5 2.5 2.5 0 0 1 7.5 20"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/shaderpacks.svg b/launcher/resources/breeze_dark/scalable/shaderpacks.svg
new file mode 100644
index 00000000..b2887947
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/shaderpacks.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 14 L 5.15625 10.84375 C 4.43239 10.11989 4 9.10457 4 8 C 4 5.79086 5.79086 4 8 4 C 9.10457 4 10.11989 4.43239 10.84375 5.15625 L 14 2 L 2 2 z M 10.84375 5.15625 L 5.15625 10.84375 C 5.88011 11.56761 6.89543 12 8 12 C 10.20914 12 12 10.20914 12 8 C 12 6.89543 11.56761 5.88011 10.84375 5.15625 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/status-bad.svg b/launcher/resources/breeze_dark/scalable/status-bad.svg
new file mode 100644
index 00000000..6fc3137e
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/status-bad.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-NegativeText {
+ color:#da4453;
+ }
+ </style>
+ <rect class="ColorScheme-NegativeText" x="3" y="3" width="16" height="16" rx="2" fill="currentColor"/>
+ <path d="M 6.414,5 5,6.414 9.586,11 5,15.586 6.414,17 11,12.414 15.586,17 17,15.586 12.414,11 17,6.414 15.586,5 11,9.586 Z" fill="#fff"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/status-good.svg b/launcher/resources/breeze_dark/scalable/status-good.svg
new file mode 100644
index 00000000..eb8bc03b
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/status-good.svg
@@ -0,0 +1,10 @@
+<svg id="svg9" version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style id="current-color-scheme" type="text/css">.ColorScheme-PositiveText {
+ color:#27ae60;
+ }
+ .ColorScheme-Text {
+ color:#232629;
+ }</style>
+ <rect id="rect3" class="ColorScheme-PositiveText" x="3" y="3" width="16" height="16" rx="1.4545455" fill="currentColor"/>
+ <path id="path5" d="M 18.99323,4.3805651 9,14 5.7332785,10.623339 4.3852425,12.059327 9,16.828 l 10,-10 z" fill="#fff"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/status-yellow.svg b/launcher/resources/breeze_dark/scalable/status-yellow.svg
new file mode 100644
index 00000000..1dc4d0f5
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/status-yellow.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-NeutralText {
+ color:#f67400;
+ }
+ </style>
+ <path class="ColorScheme-NeutralText" d="m11.006318 3.0000261a0.72728737 0.72727154 0 0 0-0.65674 0.4021811l-7.2728738 14.545431a0.72728737 0.72727154 0 0 0 0.6509222 1.052362h14.545748a0.72728737 0.72727154 0 0 0 0.650922-1.052362l-7.272874-14.545431a0.72728737 0.72727154 0 0 0-0.645104-0.4021811z" fill="currentColor"/>
+ <path d="m10 7v6h2v-6zm0 8v2h2v-2z" fill="#fff"/>
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/tag.svg b/launcher/resources/breeze_dark/scalable/tag.svg
new file mode 100644
index 00000000..b54b515f
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/tag.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 9 3 L 3 5 L 5 11 L 15 16 L 19 8 L 9 3 z M 3 5 L 3 11 L 11 19 L 13.705078 16.294922 L 4 11 L 3 5 z M 6.5 5 C 7.331 5 8 5.669 8 6.5 C 8 7.331 7.331 8 6.5 8 C 5.669 8 5 7.331 5 6.5 C 5 5.669 5.669 5 6.5 5 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/viewfolder.svg b/launcher/resources/breeze_dark/scalable/viewfolder.svg
new file mode 100644
index 00000000..0189b954
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/viewfolder.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m4 4v24h24l-1-1h-22v-13h5l3-3h14v16l1 1v-21h-10l-3-3z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_dark/scalable/worlds.svg b/launcher/resources/breeze_dark/scalable/worlds.svg
new file mode 100644
index 00000000..0cff8266
--- /dev/null
+++ b/launcher/resources/breeze_dark/scalable/worlds.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#eff0f1;
+ }
+ </style>
+ </defs>
+ <g transform="translate(0,-1036.3622)">
+ <path
+ style="fill:currentColor;fill-opacity:1;fill-rule:nonzero"
+ d="M 8 2 C 4.6862915 2 2 4.6862868 2 8 C 2 11.313713 4.6862915 14 8 14 C 11.313708 14 14 11.313713 14 8 C 14 4.6862868 11.313708 2 8 2 z M 8 3 C 8.172589 3 8.3319146 3.01418 8.5 3.03125 C 8.5403398 3.0352163 8.5849592 3.0263276 8.625 3.03125 C 8.6776214 3.0379183 8.7291519 3.0542091 8.78125 3.0625 C 8.9249022 3.0850796 9.0482753 3.1217241 9.1875 3.15625 C 9.4440966 3.2195469 9.6982233 3.3050445 9.9375 3.40625 C 10.069426 3.4620498 10.186649 3.5272356 10.3125 3.59375 C 10.312425 3.60275 10.3133 3.614 10.3125 3.625 C 10.34148 3.638 10.35728 3.69175 10.40625 3.71875 C 10.42423 3.72875 10.42056 3.741 10.4375 3.75 C 10.46648 3.766 10.50599 3.7835 10.5 3.8125 C 10.492 3.8525 10.43425 3.844 10.40625 3.875 C 10.41005 3.933 10.35871 3.92975 10.34375 3.96875 C 10.35275 3.99375 10.40229 4.00125 10.40625 4.03125 C 10.40135 4.06325 10.33173 4.06075 10.34375 4.09375 C 10.39776 4.13575 10.449004 4.10375 10.5 4.09375 C 10.611991 4.07975 10.712253 4.08825 10.78125 4.03125 C 10.77225 3.97825 10.88099 3.9856 10.875 3.9375 C 10.90366 3.9583822 10.9403 3.9788168 10.96875 4 C 10.957987 4.01008 10.949578 4.02585 10.9375 4.03125 C 10.91649 4.04125 10.89401 4.0505 10.875 4.0625 C 10.847 4.0805 10.81328 4.104 10.78125 4.125 C 10.75826 4.141 10.711504 4.21875 10.6875 4.21875 C 10.65851 4.21875 10.61475 4.1815 10.59375 4.1875 C 10.56372 4.1975 10.56519 4.238 10.53125 4.25 C 10.46526 4.274 10.335495 4.2135 10.3125 4.3125 C 10.3405 4.3695 10.450005 4.343 10.5 4.375 C 10.53102 4.395 10.58575 4.466 10.59375 4.5 C 10.60175 4.528 10.60574 4.6575 10.59375 4.6875 C 10.56273 4.7675 10.466988 4.751 10.375 4.75 L 10.3125 4.75 C 10.157516 4.74 10.007499 4.67025 9.9375 4.78125 C 9.94245 4.86625 9.95653 4.926 9.9375 5 C 9.92353 5.054 9.840752 5.10965 9.84375 5.15625 C 9.84485 5.18025 9.900255 5.216 9.90625 5.25 C 9.9101 5.268 9.89818 5.2955 9.90625 5.3125 C 9.932265 5.3615 9.9852535 5.3025 10.03125 5.3125 C 10.07525 5.3225 10.125 5.3883 10.125 5.4375 C 10.125 5.4725 10.11575 5.5286 10.09375 5.5625 C 10.04975 5.6285 9.969247 5.62025 9.90625 5.65625 C 9.854275 5.68625 9.843502 5.7609 9.8125 5.8125 C 9.7795 5.8685 9.736983 5.88865 9.75 5.96875 C 9.70501 6.05775 9.655488 6.11325 9.5625 6.15625 C 9.535495 6.16825 9.493747 6.1695 9.46875 6.1875 C 9.44873 6.2015 9.434272 6.24925 9.40625 6.28125 C 9.37127 6.32025 9.331497 6.345 9.3125 6.375 C 9.298475 6.398 9.29621 6.43675 9.28125 6.46875 C 9.25925 6.51075 9.215494 6.5203 9.1875 6.5625 C 9.17452 6.5825 9.17022 6.63025 9.15625 6.65625 C 9.12424 6.71425 9.087749 6.7615 9.09375 6.8125 C 9.0987 6.8605 9.153252 6.86225 9.15625 6.90625 C 9.159 6.93025 9.12401 6.94175 9.125 6.96875 C 9.1261 7.00675 9.15328 7.0325 9.15625 7.0625 C 9.16324 7.1365 9.10772 7.16815 9.09375 7.21875 C 9.08676 7.24275 9.09975 7.26125 9.09375 7.28125 C 9.08176 7.31825 9.032251 7.36205 9.03125 7.40625 C 9.03015 7.43725 9.09078 7.456 9.09375 7.5 C 9.09595 7.529 9.056505 7.56575 9.0625 7.59375 C 9.0735 7.64775 9.1767575 7.708 9.21875 7.75 C 9.275747 7.807 9.350008 7.84625 9.375 7.90625 C 9.39601 7.95725 9.398533 8.05565 9.4375 8.09375 C 9.466485 8.12175 9.525507 8.12625 9.5625 8.15625 C 9.59748 8.18325 9.619253 8.18875 9.65625 8.21875 C 9.717245 8.26675 9.809252 8.382 9.90625 8.375 C 9.957235 8.365 10.005504 8.3225 10.0625 8.3125 C 10.1215 8.3025 10.190006 8.27625 10.25 8.28125 C 10.29598 8.29125 10.358253 8.3155 10.40625 8.3125 C 10.45624 8.3025 10.505509 8.30625 10.5625 8.28125 C 10.6615 8.23825 10.824766 8.1668 10.96875 8.1875 C 11.078739 8.2035 11.079255 8.326 11.15625 8.375 C 11.24424 8.385 11.305007 8.358 11.375 8.375 C 11.42802 8.388 11.495001 8.4584 11.5 8.5 C 11.505 8.543 11.44349 8.5978 11.4375 8.625 C 11.42353 8.683 11.44746 8.74425 11.4375 8.78125 C 11.4295 8.81025 11.40526 8.846 11.40625 8.875 C 11.40625 8.901 11.44173 8.9658 11.46875 9 C 11.50175 9.042 11.55375 9.0798 11.59375 9.125 C 11.66674 9.209 11.699004 9.29525 11.75 9.40625 C 11.76298 9.43425 11.73801 9.464 11.75 9.5 C 11.728 9.6679 11.672491 9.78285 11.5625 9.96875 C 11.51052 10.02875 11.436499 10.08135 11.4375 10.15625 C 11.4337 10.33625 11.53925 10.451 11.53125 10.625 C 11.51327 10.8589 11.54929 10.8751 11.53125 11 C 11.61025 11.04 11.55424 11.1669 11.53125 11.25 C 11.49726 11.349 11.4565 11.3715 11.4375 11.4375 C 11.490583 11.460643 11.566021 11.451092 11.625 11.4375 C 11.595204 11.468874 11.561843 11.500657 11.53125 11.53125 C 11.305044 11.757456 11.047252 11.976543 10.78125 12.15625 C 10.265207 12.504882 9.6912413 12.769698 9.0625 12.90625 C 8.718961 12.980861 8.3658887 13 8 13 C 7.8096669 13 7.6218651 12.989959 7.4375 12.96875 C 7.2906389 12.951989 7.1427139 12.935453 7 12.90625 C 6.7168255 12.848304 6.4510281 12.728616 6.1875 12.625 C 6.1781304 12.60785 6.1541244 12.582618 6.15625 12.5625 C 6.208225 12.5405 6.305756 12.6365 6.34375 12.5625 C 6.36278 12.5275 6.318725 12.44125 6.34375 12.40625 C 6.361735 12.38225 6.463007 12.382 6.5 12.375 C 6.55302 12.365 6.626253 12.35775 6.65625 12.34375 C 6.68925 12.32275 6.724751 12.2491 6.71875 12.1875 C 6.716 12.1585 6.679185 12.151 6.65625 12.125 C 6.637275 12.103 6.66923 12.0835 6.65625 12.0625 C 6.63227 12.0275 6.572499 12.01975 6.5625 11.96875 C 6.634495 11.95475 6.7397615 12.029 6.84375 12 C 6.892755 11.986 6.976999 11.88985 7 11.84375 C 7.00699 11.82975 6.99202 11.79925 7 11.78125 C 7.00902 11.76325 7.028225 11.72875 7.03125 11.71875 C 7.03823 11.69175 7.02525 11.7055 7.03125 11.6875 C 7.049235 11.6365 7.094997 11.5988 7.125 11.5625 C 7.14799 11.5345 7.197723 11.49775 7.21875 11.46875 C 7.24273 11.43775 7.23405 11.43425 7.25 11.40625 C 7.265015 11.31525 7.19675 11.23615 7.21875 11.15625 C 7.24273 11.06625 7.381762 11.00975 7.46875 10.96875 C 7.498725 10.95475 7.537502 10.9465 7.5625 10.9375 C 7.622494 10.9165 7.6940045 10.895 7.75 10.875 C 7.843995 10.841 7.860248 10.7734 7.90625 10.6875 C 7.92825 10.6475 7.964735 10.5918 7.96875 10.5625 C 7.9726 10.5365 7.9638 10.531 7.96875 10.5 C 7.9737 10.471 7.960775 10.44025 7.96875 10.40625 C 7.97975 10.36325 8 10.26665 8 10.21875 C 8 10.17875 7.96974 10.161 7.96875 10.125 C 7.96176 9.995 8.014512 10.0085 8.0625 9.9375 C 8.08648 9.9025 8.10806 9.8376 8.125 9.8125 C 8.205993 9.6925 8.353749 9.65075 8.34375 9.46875 C 8.3399 9.40875 8.293978 9.269 8.25 9.25 C 8.224975 9.239 8.1932485 9.23175 8.15625 9.21875 C 8.0382585 9.17475 7.9354855 9.04125 7.8125 9.03125 L 7.75 9.03125 C 7.701985 9.03125 7.6477435 9.007 7.59375 9 C 7.55976 8.99 7.4887485 9.01 7.46875 9 C 7.43773 8.985 7.43122 8.92325 7.40625 8.90625 C 7.38623 8.89225 7.343493 8.886 7.3125 8.875 C 7.263495 8.857 7.2444965 8.8165 7.1875 8.8125 C 7.169515 8.8125 7.144982 8.8225 7.125 8.8125 C 7.09398 8.8025 7.060246 8.79925 7.03125 8.78125 C 6.99528 8.75925 6.952498 8.74975 6.9375 8.71875 C 6.9705 8.61085 6.86575 8.59425 6.84375 8.53125 C 6.83374 8.50225 6.85376 8.43225 6.84375 8.40625 C 6.83077 8.36925 6.791002 8.3435 6.75 8.3125 C 6.678005 8.2565 6.634243 8.23165 6.53125 8.21875 C 6.48527 8.20875 6.4299945 8.22875 6.375 8.21875 C 6.317008 8.20875 6.2314945 8.175 6.1875 8.125 C 6.14548 8.076 6.130744 8.0231 6.09375 8 C 6.05877 7.978 6.0239965 7.9605 6 7.9375 C 5.989 7.9275 5.97876 7.92925 5.96875 7.90625 C 5.950765 7.86925 5.9155 7.8575 5.9375 7.8125 C 5.897515 7.7925 5.917993 7.85075 5.875 7.84375 C 5.836005 7.78875 5.8067385 7.734 5.71875 7.75 C 5.678765 7.76 5.640747 7.84275 5.59375 7.84375 C 5.554755 7.84375 5.519746 7.758 5.46875 7.75 C 5.429755 7.74 5.3707495 7.78625 5.34375 7.78125 C 5.30074 7.77125 5.288973 7.74475 5.25 7.71875 C 5.221015 7.69975 5.17225 7.65525 5.15625 7.65625 C 5.106255 7.65625 5.0689915 7.75515 5 7.71875 C 4.96601 7.67575 5.071268 7.665 5.03125 7.625 C 5.001275 7.596 4.992763 7.64225 4.96875 7.65625 C 4.940755 7.67425 4.905993 7.6765 4.875 7.6875 C 4.806002 7.7135 4.7414935 7.721 4.6875 7.75 C 4.637505 7.776 4.622747 7.79245 4.59375 7.84375 C 4.57076 7.88575 4.534997 7.96675 4.5 7.96875 C 4.45798 7.96875 4.4442495 7.92325 4.40625 7.90625 C 4.2912615 7.85625 4.2199905 7.9355 4.125 7.9375 C 4.037006 7.9375 3.9022515 7.80175 3.90625 7.71875 C 3.909 7.66775 3.931505 7.59425 3.9375 7.53125 C 3.94245 7.48125 3.999065 7.4206 4 7.375 C 4.0011 7.313 3.883746 7.28835 3.84375 7.28125 C 3.749755 7.26425 3.6382415 7.32325 3.53125 7.28125 C 3.51123 7.24725 3.55051 7.2215 3.5625 7.1875 C 3.56948 7.1695 3.55551 7.146 3.5625 7.125 C 3.57449 7.093 3.641257 7.06525 3.65625 7.03125 C 3.66725 7.00525 3.64525 6.9705 3.65625 6.9375 C 3.66923 6.9015 3.715725 6.86975 3.71875 6.84375 C 3.7226 6.80975 3.6832 6.772 3.65625 6.75 C 3.573255 6.76 3.5167485 6.75725 3.46875 6.78125 C 3.360763 6.83025 3.3902435 6.966 3.28125 7 C 3.244235 7.012 3.198248 7.02425 3.15625 7.03125 C 3.137935 7.04125 3.11427 7.02925 3.09375 7.03125 C 3.0956963 7.0211336 3.0917443 7.0100944 3.09375 7 C 3.0984123 6.9772158 3.1200289 6.9601701 3.125 6.9375 C 3.1962653 6.6125035 3.3063949 6.2979344 3.4375 6 L 3.46875 6 C 3.506755 6.01 3.529505 6.0595 3.5625 6.0625 C 3.654493 6.0725 3.674001 5.9777 3.75 5.9375 C 3.827996 5.9485 3.866506 5.9275 3.9375 5.9375 C 3.985515 5.9475 4.055756 5.996 4.09375 6 C 4.12576 6 4.126248 5.96475 4.15625 5.96875 C 4.186225 5.97875 4.245 6.0265 4.25 6.0625 C 4.25495 6.1075 4.205715 6.16915 4.21875 6.21875 C 4.265775 6.26675 4.3705045 6.2845 4.4375 6.3125 C 4.4815 6.2755 4.44448 6.20825 4.4375 6.15625 C 4.4364 6.13325 4.44025 6.0835 4.4375 6.0625 C 4.43255 6.0265 4.40625 5.99975 4.40625 5.96875 C 4.40625 5.82575 4.5290085 5.77465 4.625 5.71875 C 4.665975 5.69475 4.7140025 5.643 4.75 5.625 C 4.800985 5.6 4.8330025 5.61775 4.875 5.59375 C 4.950988 5.55075 5.0035015 5.4862 5.0625 5.4375 C 5.089505 5.3725 5.05975 5.28275 5.0625 5.21875 C 5.089505 5.20175 5.127282 5.21875 5.15625 5.21875 C 5.20223 5.20875 5.223996 5.151 5.25 5.125 C 5.265015 5.11 5.294482 5.10875 5.3125 5.09375 C 5.35848 5.05775 5.366237 5.016 5.40625 5 C 5.420275 4.99 5.446723 4.97575 5.46875 4.96875 C 5.49878 4.95875 5.545757 4.9555 5.59375 4.9375 C 5.62477 4.9265 5.706749 4.91925 5.71875 4.90625 C 5.733765 4.89125 5.7149 4.8355 5.71875 4.8125 C 5.74273 4.6996 5.899009 4.69435 6 4.65625 C 6.069993 4.63025 6.1407545 4.5485 6.21875 4.5625 C 6.203735 4.6165 6.143249 4.6224 6.15625 4.6875 C 6.173245 4.7785 6.280501 4.66925 6.3125 4.65625 C 6.373495 4.63025 6.4825025 4.5925 6.5625 4.5625 C 6.632493 4.5375 6.753489 4.5181 6.6875 4.4375 C 6.6325 4.4275 6.6104935 4.48 6.5625 4.5 C 6.5295 4.51 6.527978 4.47175 6.5 4.46875 C 6.472995 4.46875 6.464527 4.505 6.4375 4.5 C 6.410495 4.49 6.378998 4.43925 6.375 4.40625 C 6.364 4.32225 6.43476 4.3119 6.46875 4.25 C 6.44576 4.174 6.3452425 4.2175 6.28125 4.1875 C 6.28824 4.1425 6.322007 4.10375 6.375 4.09375 C 6.41801 4.08375 6.5365015 4.102 6.5625 4.125 C 6.577515 4.138 6.5395595 4.1665 6.5625 4.1875 C 6.588515 4.2105 6.622254 4.1975 6.65625 4.1875 C 6.66824 4.1305 6.583751 4.162 6.59375 4.125 C 6.661741 4.081 6.792005 4.10475 6.875 4.09375 C 6.923015 4.08375 6.988251 4.05425 7.03125 4.03125 C 7.071235 4.00825 7.0830015 3.962 7.125 4 C 7.13699 4.041 7.116685 4.07175 7.09375 4.09375 C 7.016756 4.16175 6.934994 4.2336 6.875 4.3125 C 6.912015 4.3485 6.969254 4.33675 7.03125 4.34375 C 7.05523 4.35375 7.099997 4.33875 7.125 4.34375 C 7.15503 4.35375 7.194748 4.33375 7.21875 4.34375 C 7.26077 4.35375 7.270507 4.4375 7.3125 4.4375 C 7.380496 4.4375 7.351523 4.3577 7.3125 4.3125 C 7.32449 4.2655 7.3608 4.2207 7.34375 4.1875 C 7.31273 4.1255 7.1935005 4.206 7.1875 4.125 C 7.18365 4.075 7.231993 4.06925 7.25 4.03125 C 7.24202 3.93525 7.322499 3.93075 7.3125 3.84375 C 7.30755 3.80775 7.275008 3.81525 7.25 3.78125 C 7.23702 3.76325 7.23371 3.7015 7.21875 3.6875 C 7.166775 3.6435 7.0685005 3.67795 7.0625 3.59375 C 7.05975 3.54375 7.06948 3.4911 7.0625 3.4375 C 7.05551 3.3865 7.012728 3.22875 6.96875 3.21875 C 6.912755 3.20675 6.8794955 3.31245 6.8125 3.34375 C 6.77653 3.36075 6.693243 3.381 6.65625 3.375 C 6.63227 3.365 6.596753 3.3595 6.59375 3.3125 C 6.58979 3.2701 6.6405966 3.2512172 6.65625 3.21875 C 6.6601634 3.2106332 6.654259 3.19872 6.65625 3.1875 C 6.7288164 3.1672517 6.8012164 3.1422715 6.875 3.125 C 6.9169167 3.1152798 6.9576866 3.1024086 7 3.09375 C 7.0298771 3.0875279 7.0636852 3.0994569 7.09375 3.09375 C 7.2273044 3.0689879 7.3629458 3.0451686 7.5 3.03125 C 7.6662387 3.0137812 7.8289018 3 8 3 z M 5.96875 3.46875 C 5.9369162 3.6513239 5.7660026 3.7240206 5.5625 3.71875 C 5.51652 3.76275 5.55622 3.84525 5.53125 3.90625 C 5.51024 3.95725 5.425991 4.004 5.375 4 C 5.337985 4 5.28125 3.94125 5.28125 3.90625 C 5.28125 3.86325 5.349521 3.8617 5.3125 3.8125 C 5.31415 3.8025 5.333355 3.79125 5.34375 3.78125 C 5.3639901 3.7685049 5.3858533 3.762493 5.40625 3.75 C 5.5874525 3.6384834 5.7732114 3.5568279 5.96875 3.46875 z M 10.15625 3.65625 C 10.13782 3.66625 10.14122 3.6775 10.125 3.6875 C 10.08699 3.7105 10.06026 3.739 10.03125 3.75 C 9.99028 3.765 9.943504 3.7755 9.9375 3.8125 C 9.93255 3.8435 9.969795 3.846 9.96875 3.875 C 9.953735 3.912 9.94553 3.92375 9.9375 3.96875 C 9.988485 4.05175 10.093258 3.9505 10.15625 3.9375 C 10.18425 3.9275 10.20375 3.93025 10.21875 3.90625 C 10.23574 3.87925 10.23706 3.8507 10.25 3.8125 C 10.26403 3.7705 10.31751 3.75575 10.3125 3.71875 C 10.3087 3.68175 10.218244 3.65125 10.15625 3.65625 z M 4.6875 4.28125 C 4.7349118 4.319347 4.8015588 4.40185 4.71875 4.4375 C 4.70874 4.4475 4.5864965 4.47275 4.5625 4.46875 C 4.55282 4.46875 4.5397337 4.44475 4.53125 4.4375 C 4.5829129 4.3871923 4.6337069 4.3293033 4.6875 4.28125 z M 4.875 4.46875 C 4.89898 4.46875 4.909483 4.52925 4.9375 4.53125 C 4.964505 4.53125 4.983973 4.496 5 4.5 C 5.04202 4.511 5.071268 4.5938 5.03125 4.625 C 4.99825 4.635 4.992713 4.58975 4.96875 4.59375 C 4.900754 4.60575 4.8634955 4.754 4.8125 4.75 C 4.764485 4.74 4.732228 4.62875 4.78125 4.59375 C 4.78235 4.56375 4.779215 4.56225 4.78125 4.53125 C 4.796265 4.51325 4.834982 4.46575 4.875 4.46875 z M 4.625 4.5 C 4.639025 4.5 4.651245 4.49 4.65625 4.5 C 4.64927 4.553 4.634708 4.56875 4.59375 4.59375 C 4.522756 4.63675 4.460501 4.68975 4.4375 4.78125 C 4.43051 4.80825 4.3464955 4.8817 4.3125 4.875 C 4.233504 4.858 4.357015 4.7104 4.375 4.6875 C 4.39502 4.6625 4.414526 4.654 4.4375 4.625 C 4.46049 4.597 4.478055 4.54725 4.5 4.53125 C 4.52398 4.51425 4.5830075 4.498 4.625 4.5 z M 11.40625 4.65625 C 11.48524 4.64625 11.526502 4.73365 11.5625 4.78125 C 11.59248 4.82025 11.669746 4.87905 11.71875 4.90625 C 11.74675 4.92125 11.7815 4.95475 11.8125 4.96875 C 11.87749 4.99775 11.996996 5.09225 12 5.15625 C 12.0011 5.18825 11.96374 5.22 11.96875 5.25 C 12.01776 5.26 12.0615 5.1742 12.0625 5.125 C 12.15441 5.2550064 12.233102 5.3926596 12.3125 5.53125 C 12.35 5.5967067 12.402944 5.6512957 12.4375 5.71875 C 12.39339 5.73175 12.38209 5.73825 12.375 5.78125 C 12.36499 5.83725 12.39649 5.9276 12.3125 5.9375 C 12.25953 5.9475 12.19799 5.892 12.125 5.875 C 12.09502 5.865 12.05525 5.885 12.03125 5.875 C 11.97625 5.857 11.9845 5.7833 11.9375 5.75 C 11.91049 5.731 11.824253 5.6925 11.78125 5.6875 C 11.75424 5.6875 11.74673 5.6975 11.71875 5.6875 C 11.68476 5.6775 11.621749 5.7015 11.59375 5.6875 C 11.56674 5.6705 11.50701 5.61675 11.5 5.59375 C 11.489 5.55675 11.59175 5.5313 11.59375 5.5 C 11.59595 5.476 11.5675 5.44325 11.5625 5.40625 C 11.5587 5.38425 11.5674 5.35875 11.5625 5.34375 C 11.5555 5.31775 11.50974 5.3135 11.46875 5.3125 C 11.43272 5.3125 11.40599 5.3125 11.375 5.3125 C 11.331 5.3125 11.242494 5.3085 11.1875 5.3125 C 11.1545 5.3125 11.12475 5.3095 11.09375 5.3125 C 11.00676 5.3225 10.942997 5.3025 10.875 5.3125 C 10.82599 5.3125 10.733496 5.354 10.6875 5.375 C 10.66451 5.386 10.65501 5.39125 10.625 5.40625 C 10.61103 5.41625 10.58054 5.4295 10.5625 5.4375 C 10.53451 5.4505 10.49575 5.492 10.46875 5.5 C 10.42976 5.51 10.38349 5.49 10.3125 5.5 C 10.26152 5.5 10.188501 5.46725 10.1875 5.40625 C 10.1864 5.31325 10.349511 5.36075 10.4375 5.34375 C 10.4705 5.33375 10.49123 5.30525 10.53125 5.28125 C 10.55028 5.27025 10.54451 5.262 10.5625 5.25 C 10.59247 5.229 10.65125 5.18025 10.65625 5.15625 C 10.66005 5.13425 10.65323 5.11075 10.65625 5.09375 C 10.66005 5.07275 10.69175 5.05925 10.71875 5.03125 C 10.76077 4.98925 10.791747 4.93345 10.84375 4.90625 C 10.87873 4.88825 10.9215 4.87275 10.9375 4.84375 C 10.9413 4.81175 10.9364 4.81125 10.9375 4.78125 C 10.9595 4.76125 10.98202 4.754 11 4.75 C 11.05401 4.738 11.093259 4.753 11.15625 4.75 C 11.18325 4.75 11.221 4.763 11.25 4.75 C 11.27398 4.734 11.2885 4.6995 11.3125 4.6875 C 11.33851 4.6735 11.37422 4.65825 11.40625 4.65625 z M 11.96875 5.25 L 11.84375 5.25 C 11.79975 5.25 11.744749 5.227 11.71875 5.25 C 11.75577 5.31 11.823255 5.32875 11.90625 5.34375 C 11.93623 5.32175 11.95478 5.2883 11.96875 5.25 z M 5.125 4.6875 L 5.21875 4.6875 C 5.243775 4.6875 5.271251 4.6775 5.28125 4.6875 C 5.265245 4.7535 5.114497 4.74275 5.0625 4.71875 C 5.06525 4.69375 5.092968 4.6915 5.125 4.6875 z M 4.90625 4.8125 C 4.8625672 4.8442816 4.8193312 4.8845833 4.75 4.90625 C 4.711995 4.91825 4.663252 4.917 4.65625 4.875 C 4.64822 4.823 4.715988 4.84875 4.75 4.84375 C 4.794 4.83375 4.855254 4.8145 4.90625 4.8125 z M 11.40625 4.8125 C 11.37325 4.8235 11.35724 4.89625 11.40625 4.90625 C 11.42924 4.89225 11.44355 4.8555 11.4375 4.8125 C 11.42452 4.7985 11.41621 4.8025 11.40625 4.8125 z M 11.34375 4.96875 C 11.34485 5.02475 11.35475 5.0764 11.34375 5.125 C 11.37576 5.187 11.452756 5.13575 11.46875 5.09375 C 11.48778 4.98975 11.45425 4.95575 11.40625 4.96875 C 11.39025 4.97875 11.36069 4.95575 11.34375 4.96875 z M 3.0625 7.3125 C 3.118864 7.3635 3.1512925 7.43535 3.21875 7.46875 C 3.243775 7.48175 3.280501 7.492 3.3125 7.5 C 3.338515 7.51 3.350013 7.492 3.375 7.5 C 3.42802 7.518 3.4892575 7.5435 3.53125 7.5625 C 3.595248 7.5905 3.66375 7.68675 3.71875 7.71875 C 3.7215 7.75075 3.70874 7.7885 3.71875 7.8125 C 3.744765 7.8475 3.7704965 7.88225 3.8125 7.90625 C 3.863485 7.93425 3.8895065 7.9778 3.9375 8 C 3.954495 8.01 3.973963 7.99 4 8 C 4.065994 8.02 4.099253 8.11055 4.15625 8.09375 C 4.193265 8.08275 4.181505 8.036 4.1875 8 C 4.212525 7.973 4.242222 7.9375 4.28125 7.9375 C 4.34324 7.9375 4.351999 8.03315 4.375 8.09375 C 4.38798 8.12775 4.428535 8.1515 4.4375 8.1875 C 4.452515 8.2495 4.45048 8.43175 4.4375 8.46875 C 4.421495 8.51475 4.3504995 8.5122 4.3125 8.5625 C 4.294515 8.5865 4.29522 8.63925 4.28125 8.65625 C 4.25727 8.68525 4.236773 8.69075 4.21875 8.71875 C 4.20874 8.73375 4.196465 8.76025 4.1875 8.78125 C 4.15747 8.84525 4.099986 8.89175 4.125 8.96875 C 4.13501 8.99975 4.1875 9.0255 4.1875 9.0625 C 4.1875 9.1065 4.100751 9.1393 4.09375 9.1875 C 4.0888 9.2225 4.114055 9.2658 4.125 9.3125 C 4.13298 9.3475 4.197751 9.38325 4.21875 9.40625 C 4.25373 9.44525 4.280501 9.513 4.3125 9.5625 C 4.375491 9.6595 4.406765 9.78 4.46875 9.875 C 4.50175 9.924 4.57175 9.9484 4.59375 10 C 4.60475 10.026 4.58374 10.06975 4.59375 10.09375 C 4.62972 10.17475 4.794007 10.2726 4.875 10.3125 C 4.913005 10.3315 4.965009 10.32375 5 10.34375 C 5.05302 10.37375 5.160506 10.4534 5.1875 10.5 C 5.21148 10.542 5.20676 10.65185 5.21875 10.71875 C 5.233765 10.80675 5.2149 10.87365 5.21875 10.96875 C 5.2215 11.02075 5.25 11.04215 5.25 11.09375 C 5.25 11.11175 5.2511 11.1685 5.25 11.1875 C 5.2489 11.2495 5.25495 11.312 5.25 11.375 C 5.24725 11.409 5.2478 11.43975 5.25 11.46875 C 5.25275 11.49975 5.27927 11.50325 5.28125 11.53125 C 5.28345 11.56025 5.246975 11.602 5.25 11.625 C 5.25699 11.688 5.300499 11.72315 5.3125 11.78125 C 5.32251 11.82725 5.349745 11.945 5.34375 12 C 5.3399 12.029 5.316515 12.06075 5.3125 12.09375 C 5.30975 12.11975 5.31635 12.13225 5.3125 12.15625 C 5.30975 12.17925 5.31844 12.19975 5.3125 12.21875 C 5.2806543 12.198271 5.2500951 12.177426 5.21875 12.15625 C 4.9527483 11.976543 4.6949555 11.757456 4.46875 11.53125 C 4.4381566 11.500657 4.4047959 11.468874 4.375 11.4375 C 4.1944191 11.247353 4.0241375 11.029182 3.875 10.8125 C 3.8614865 10.792866 3.8569888 10.769835 3.84375 10.75 C 3.6707036 10.490733 3.5290643 10.227866 3.40625 9.9375 C 3.3973945 9.9165633 3.3835772 9.8960813 3.375 9.875 C 3.2696226 9.6160011 3.1860736 9.3410185 3.125 9.0625 C 3.1134736 9.0099352 3.1036081 8.9594198 3.09375 8.90625 C 3.0689879 8.7726956 3.0451686 8.6370542 3.03125 8.5 C 3.01418 8.3319146 3 8.172589 3 8 C 3 7.7623582 3.030436 7.5413039 3.0625 7.3125 z M 5 7.84375 C 5.048015 7.82175 5.038995 7.89925 5 7.90625 C 4.962985 7.91625 4.96403 7.86175 5 7.84375 z " transform="translate(0,1036.3622)"
+ id="path26"
+ class="ColorScheme-Text"/>
+ </g>
+</svg>
diff --git a/launcher/resources/breeze_light/breeze_light.qrc b/launcher/resources/breeze_light/breeze_light.qrc
new file mode 100644
index 00000000..7d9d99f5
--- /dev/null
+++ b/launcher/resources/breeze_light/breeze_light.qrc
@@ -0,0 +1,43 @@
+<RCC>
+ <qresource prefix="/icons/breeze_light">
+ <file>index.theme</file>
+ <file>scalable/about.svg</file>
+ <file>scalable/accounts.svg</file>
+ <file>scalable/bug.svg</file>
+ <file>scalable/centralmods.svg</file>
+ <file>scalable/checkupdate.svg</file>
+ <file>scalable/copy.svg</file>
+ <file>scalable/coremods.svg</file>
+ <file>scalable/custom-commands.svg</file>
+ <file>scalable/discord.svg</file>
+ <file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
+ <file>scalable/instance-settings.svg</file>
+ <file>scalable/jarmods.svg</file>
+ <file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
+ <file>scalable/loadermods.svg</file>
+ <file>scalable/log.svg</file>
+ <file>scalable/minecraft.svg</file>
+ <file>scalable/new.svg</file>
+ <file>scalable/news.svg</file>
+ <file>scalable/notes.svg</file>
+ <file>scalable/proxy.svg</file>
+ <file>scalable/reddit-alien.svg</file>
+ <file>scalable/refresh.svg</file>
+ <file>scalable/resourcepacks.svg</file>
+ <file>scalable/shaderpacks.svg</file>
+ <file>scalable/screenshots.svg</file>
+ <file>scalable/settings.svg</file>
+ <file>scalable/status-bad.svg</file>
+ <file>scalable/status-good.svg</file>
+ <file>scalable/status-yellow.svg</file>
+ <file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
+ <file>scalable/delete.svg</file>
+ <file>scalable/tag.svg</file>
+ <file>scalable/export.svg</file>
+ <file>scalable/rename.svg</file>
+ <file>scalable/launch.svg</file>
+ </qresource>
+</RCC>
diff --git a/launcher/resources/breeze_light/index.theme b/launcher/resources/breeze_light/index.theme
new file mode 100644
index 00000000..126d42d7
--- /dev/null
+++ b/launcher/resources/breeze_light/index.theme
@@ -0,0 +1,11 @@
+[Icon Theme]
+Name=Breeze Light
+Comment=Breeze Light Icons
+Inherits=multimc
+Directories=scalable
+
+[scalable]
+Size=48
+Type=Scalable
+MinSize=16
+MaxSize=256
diff --git a/launcher/resources/breeze_light/scalable/about.svg b/launcher/resources/breeze_light/scalable/about.svg
new file mode 100644
index 00000000..ea1dc02c
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/about.svg
@@ -0,0 +1,12 @@
+<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ <g class="ColorScheme-Text" fill="currentColor" fill-rule="evenodd">
+ <path d="m8 2a6 6 0 0 0 -6 6 6 6 0 0 0 6 6 6 6 0 0 0 6-6 6 6 0 0 0 -6-6zm0 1a5 5 0 0 1 5 5 5 5 0 0 1 -5 5 5 5 0 0 1 -5-5 5 5 0 0 1 5-5z"/>
+ <path d="m7 4h2v2h-2z"/>
+ <path d="m7 7h2v5h-2z"/>
+ </g>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/accounts.svg b/launcher/resources/breeze_light/scalable/accounts.svg
new file mode 100644
index 00000000..8a542f36
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/accounts.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 20.5,4 A 4.5,4.5 0 0 0 16,8.5 4.5,4.5 0 0 0 20.5,13 4.5,4.5 0 0 0 25,8.5 4.5,4.5 0 0 0 20.5,4 Z m 0,1 A 3.5,3.5 0 0 1 24,8.5 3.5,3.5 0 0 1 20.5,12 3.5,3.5 0 0 1 17,8.5 3.5,3.5 0 0 1 20.5,5 Z m -9,4 C 9.014719,9 7,11.01472 7,13.5 7,15.98528 9.014719,18 11.5,18 13.985281,18 16,15.98528 16,13.5 16,11.01472 13.985281,9 11.5,9 Z m 0,1 A 3.5,3.5 0 0 1 15,13.5 3.5,3.5 0 0 1 11.5,17 3.5,3.5 0 0 1 8,13.5 3.5,3.5 0 0 1 11.5,10 Z m 9,4 c -0.88285,0.003 -1.758266,0.17228 -2.585938,0.5 -0.06618,0.42368 -0.174132,0.83977 -0.322265,1.24219 C 18.494507,15.25488 19.490227,15.00077 20.5,15 c 3.589851,0 6.5,3.13401 6.5,7 l -8.341797,0 c 0.170323,0.32329 0.325499,0.65711 0.464844,1 L 28,23 28,22 c 0,-4.41828 -3.357864,-8 -7.5,-8 z m -9,5 C 7.357864,19 4,22.58172 4,27 l 0,1 15,0 0,-1 c 0,-4.41828 -3.357864,-8 -7.5,-8 z m 0,1 c 3.589851,0 6.5,3.13401 6.5,7 L 5,27 c 0,-3.86599 2.910149,-7 6.5,-7 z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/bug.svg b/launcher/resources/breeze_light/scalable/bug.svg
new file mode 100644
index 00000000..4f41ad6b
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/bug.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m2 2v12h12v-12zm1 1h10v10h-10zm1 2v2h2v-2zm6 0v2h2v-2zm-4 4v1h4v-1zm4 1v1h1v-1zm1 1v1h1v-1zm-5-1h-1v1h1zm-1 1h-1v1h1z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/centralmods.svg b/launcher/resources/breeze_light/scalable/centralmods.svg
new file mode 100644
index 00000000..4035e51c
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/centralmods.svg
@@ -0,0 +1 @@
+<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 3.5v.25h-.5V6h3V3.75H5V3.5h-.75v.25h-.5V3.5H3Zm-.25.5h2.5v1.75h-2.5V4Z" fill="#EFF0F1"/><path d="M1 1v6h6l-.25-.25h-5.5V3.5H2.5l.75-.75h3.5v4L7 7V1.75H4.5L3.75 1H1Z" fill="#EFF0F1"/></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_light/scalable/checkupdate.svg b/launcher/resources/breeze_light/scalable/checkupdate.svg
new file mode 100644
index 00000000..06b31827
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/checkupdate.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor"
+ d="M 6 2 L 6 3 L 6 6 L 7 6 L 7 3 L 9 3 L 9 6 L 10 6 L 10 3 L 10 2 L 6 2 z M 3.7 6 L 3 6.7 L 6.3 10 L 8 11.7 L 9.7 10 L 13 6.7 L 12.3 6 L 9 9.3 L 8 10.3 L 7 9.3 L 3.7 6 z M 2 12 L 2 14 L 3 14 L 14 14 L 14 13 L 14 12 L 13 12 L 13 13 L 3 13 L 3 12 L 2 12 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/copy.svg b/launcher/resources/breeze_light/scalable/copy.svg
new file mode 100644
index 00000000..2557953b
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/copy.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 3 3 L 3 17 L 7 17 L 7 19 L 17 19 L 17 10 L 13 6 L 12 6 L 9 3 L 3 3 Z M 4 4 L 8 4 L 8 6 L 7 6 L 7 16 L 4 16 L 4 4 Z M 8 7 L 12 7 L 12 11 L 16 11 L 16 18 L 8 18 L 8 7 Z"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/coremods.svg b/launcher/resources/breeze_light/scalable/coremods.svg
new file mode 100644
index 00000000..ec4ecea8
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/coremods.svg
@@ -0,0 +1 @@
+<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.167 2.46v.208H2.75v1.875h2.5V2.668h-.417V2.46h-.625v.208h-.416V2.46h-.625Zm-.209.417h2.084v1.458H2.958V2.877Z" fill="#EFF0F1"/><path d="M1.5 1v6h5V1h-5Zm1.5.5h2a1 1 0 0 1 1 1V3a.5.5 0 1 0 0 1v1l-.5.5h-3L2 5V4a.5.5 0 1 0 0-1v-.5a1 1 0 0 1 1-1ZM2 6h2v.5H2V6Zm3 0h1v.5H5V6Z" fill="#EFF0F1"/></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_light/scalable/custom-commands.svg b/launcher/resources/breeze_light/scalable/custom-commands.svg
new file mode 100644
index 00000000..b2ac78c5
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/custom-commands.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 14 L 14 14 L 14 2 L 2 2 z M 3 3 L 13 3 L 13 13 L 3 13 L 3 3 z M 4.71875 4 L 4 4.6875 L 6.625 7.5 L 4.03125 10.3125 L 4.71875 11 L 7.6875 7.84375 L 8 7.5 L 7.6875 7.15625 L 4.71875 4 z M 8 11 L 8 12 L 12 12 L 12 11 L 8 11 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/delete.svg b/launcher/resources/breeze_light/scalable/delete.svg
new file mode 100644
index 00000000..f2aea6e8
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/delete.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 6 2 L 6 3 L 2 3 L 2 4 L 3 4 L 3 14 L 4 14 L 13 14 L 13 13 L 13 4 L 14 4 L 14 3 L 10 3 L 10 2 L 6 2 z M 7 3 L 9 3 L 9 4 L 10 4 L 12 4 L 12 13 L 4 13 L 4 4 L 7 4 L 7 3 z M 6 6 L 6 11 L 7 11 L 7 6 L 6 6 z M 9 6 L 9 11 L 10 11 L 10 6 L 9 6 z "
+ class="ColorScheme-Text"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/discord.svg b/launcher/resources/breeze_light/scalable/discord.svg
new file mode 100644
index 00000000..22ee27ba
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/discord.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><defs><style>.cls-1{fill:#fff;}</style></defs><g id="图层_2" data-name="图层 2"><g id="Discord_Logos" data-name="Discord Logos"><g id="Discord_Logo_-_Large_-_White" data-name="Discord Logo - Large - White"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z"/></g></g></g></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_light/scalable/export.svg b/launcher/resources/breeze_light/scalable/export.svg
new file mode 100644
index 00000000..d6314bd7
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/export.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 7 12 L 11.0859 12 L 9.46484 13.6211 L 10.1719 14.3281 L 13 11.5 L 10.1719 8.67187 L 9.46484 9.37891 L 11.0859 11 L 7 11 L 7 12 Z M 4 13 L 4 3 L 9 3 L 9 6 L 12 6 L 12 9 L 13 9 L 13 5 L 10 2 L 3 2 L 3 14 L 8 14 L 8 13 L 4 13 Z"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/externaltools.svg b/launcher/resources/breeze_light/scalable/externaltools.svg
new file mode 100644
index 00000000..c965b6c3
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/externaltools.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 4 5 L 4 7 L 2 7 L 2 8 L 4 8 L 4 10 L 5 10 L 7 10 L 7 9 L 5 9 L 5 6 L 7 6 L 7 5 L 5 5 L 4 5 z M 9 5 L 9 6 L 11 6 L 11 9 L 9 9 L 9 10 L 11 10 L 12 10 L 12 9 L 12 8 L 14 8 L 14 7 L 12 7 L 12 6 L 12 5 L 11 5 L 9 5 z M 7 7 L 7 8 L 9 8 L 9 7 L 7 7 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/help.svg b/launcher/resources/breeze_light/scalable/help.svg
new file mode 100644
index 00000000..bcd14e05
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/help.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 3 4 L 3 16 L 6 20 L 6 17 L 6 16 L 19 16 L 19 4 L 3 4 z M 4 5 L 18 5 L 18 15 L 4 15 L 4 5 z M 10.5 6 A 2.5 2.5 0 0 0 8 8.5 L 8 9 L 9 9 L 9 8.5 A 1.5 1.5 0 0 1 10.5 7 A 1.5 1.5 0 0 1 12 8.5 A 1.5 1.5 0 0 1 10.5 10 L 10 10 L 10 12 L 11 12 L 11 10.949219 A 2.5 2.5 0 0 0 13 8.5 A 2.5 2.5 0 0 0 10.5 6 z M 10 13 L 10 14 L 11 14 L 11 13 L 10 13 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/instance-settings.svg b/launcher/resources/breeze_light/scalable/instance-settings.svg
new file mode 100644
index 00000000..69854d73
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/instance-settings.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 5 L 2 14 L 3 14 L 14 14 L 14 13 L 14 2 L 3 2 L 2 2 z M 3 5 L 13 5 L 13 13 L 3 13 L 3 5 z M 4 6 L 4 12 L 6 12 L 6 6 L 4 6 z M 7 7 L 7 8 L 12 8 L 12 7 L 7 7 z M 7 10 L 7 11 L 12 11 L 12 10 L 7 10 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/jarmods.svg b/launcher/resources/breeze_light/scalable/jarmods.svg
new file mode 100644
index 00000000..49a45d36
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/jarmods.svg
@@ -0,0 +1 @@
+<svg width="8" height="8" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 1.5V2H1v4.5h6V2H6v-.5H4.5V2h-1v-.5H2Zm-.5 1h5V6h-5V2.5Z" fill="#EFF0F1"/><path d="M3.93 4.37s-.19-.364-.183-.63c.005-.19.434-.378.603-.651.168-.273-.022-.54-.022-.54s.043.197-.07.4c-.112.203-.525.322-.686.672-.16.35.357.75.357.75Z" fill="#EFF0F1"/><path d="M4.637 3.264s-.645.246-.645.525c0 .28.175.372.203.463.028.091-.049.245-.049.245s.252-.175.21-.378c-.042-.203-.238-.267-.126-.47.075-.136.407-.385.407-.385Z" fill="#EFF0F1"/><path d="M3.859 4.741c.595-.021.812-.209.812-.209-.385.105-1.407.098-1.415.021-.006-.077.316-.14.316-.14s-.505 0-.546.126c-.043.126.238.223.833.202ZM4.72 5.036s.583-.124.526-.44c-.07-.386-.477-.169-.477-.169s.288 0 .316.175c.028.175-.364.434-.364.434ZM4.434 4.868s-.147.038-.364.063c-.292.033-.645.007-.673-.042-.028-.05.05-.077.05-.077-.35.084-.16.23.251.26.352.023.876-.106.876-.106l-.14-.098ZM3.53 5.174s-.159.004-.168.088c-.01.084.098.159.49.182.392.024.668-.107.668-.107l-.178-.107s-.112.023-.285.046c-.173.024-.527-.018-.541-.051-.014-.032.014-.051.014-.051Z" fill="#EFF0F1"/><path d="M5.057 5.552c.06-.065-.019-.117-.019-.117s.028.033-.009.07c-.037.037-.378.13-.924.158-.546.028-1.14-.05-1.159-.121-.018-.07.304-.126.304-.126-.037.005-.485.014-.5.136-.013.12.197.22 1.037.22.84 0 1.21-.155 1.27-.22Z" fill="#EFF0F1"/><path d="M4.73 5.828c-.368.074-1.489.027-1.489.027s.728.173 1.56.029c.397-.07.42-.262.42-.262s-.122.13-.49.206Z" fill="#EFF0F1"/></svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_light/scalable/java.svg b/launcher/resources/breeze_light/scalable/java.svg
new file mode 100644
index 00000000..ff86c9cc
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/java.svg
@@ -0,0 +1,10 @@
+<svg width="1000" height="1000" viewBox="0 0 1000 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M479.6 536.2C479.6 536.2 425 430.9 427 354C428.5 299 552.4 244.7 601.1 165.8C649.7 86.9 595 10 595 10C595 10 607.2 66.7 574.8 125.3C542.4 184 422.9 218.5 376.4 319.6C329.9 420.9 479.6 536.2 479.6 536.2Z" fill="#232629"/>
+<path d="M684.099 216.4C684.099 216.4 497.899 287.3 497.899 368.2C497.899 449.2 548.499 475.5 556.599 501.8C564.699 528.2 542.399 572.7 542.399 572.7C542.399 572.7 615.199 522.1 602.999 463.4C590.799 404.7 534.199 386.4 566.599 327.8C588.399 288.4 684.099 216.4 684.099 216.4Z" fill="#232629"/>
+<path d="M459.399 643.2C631.499 637.1 694.2 582.8 694.2 582.8C582.9 613.1 287.399 611.1 285.299 588.9C283.299 566.6 376.399 548.4 376.399 548.4C376.399 548.4 230.7 548.4 218.6 584.8C206.4 621.2 287.499 649.2 459.399 643.2Z" fill="#232629"/>
+<path d="M708.399 728.5C708.399 728.5 876.799 692.6 860.099 601.1C839.899 489.7 722.499 552.5 722.499 552.5C722.499 552.5 805.599 552.5 813.599 603.1C821.699 653.6 708.399 728.5 708.399 728.5Z" fill="#232629"/>
+<path d="M625.4 679.9C625.4 679.9 583 691 520.1 698.1C435.8 707.6 333.9 700.1 325.8 685.9C317.8 671.7 340 663.7 340 663.7C238.8 688 294.2 730.4 412.8 738.6C514.5 745.5 665.9 708.2 665.9 708.2L625.4 679.9Z" fill="#232629"/>
+<path d="M364.299 768.3C364.299 768.3 318.399 769.6 315.699 793.9C313.099 818 343.999 839.7 457.299 846.5C570.599 853.2 650.299 815.5 650.299 815.5L598.999 784.4C598.999 784.4 566.599 791.2 516.699 797.9C466.699 804.7 364.299 792.5 360.199 783.1C356.199 773.7 364.299 768.3 364.299 768.3Z" fill="#232629"/>
+<path d="M805.5 877.6C823 858.7 800.099 843.8 800.099 843.8C800.099 843.8 808.2 853.3 797.5 864C786.7 874.8 688.199 901.7 530.299 909.8C372.499 917.9 201.1 895 195.6 874.7C190.3 854.5 283.4 838.3 283.4 838.3C272.6 839.6 143.1 842.3 139 877.5C135 912.5 195.7 940.9 438.6 940.9C681.4 941 788 896.4 805.5 877.6Z" fill="#232629"/>
+<path d="M711.099 957.2C604.499 978.7 280.699 965.2 280.699 965.2C280.699 965.2 491.099 1015.2 731.299 973.4C846.099 953.4 852.799 897.8 852.799 897.8C852.799 897.8 817.699 935.6 711.099 957.2Z" fill="#232629"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/language.svg b/launcher/resources/breeze_light/scalable/language.svg
new file mode 100644
index 00000000..3d56d33e
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/language.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 3 L 2 8 L 3 8 L 6 8 L 6 10 L 6 11 L 9 14 L 9 11 L 13 11 L 14 11 L 14 6 L 14 5 L 13 5 L 10 5 L 10 2 L 3 2 L 2 2 z M 3 3 L 9 3 L 9 5 L 9 6 L 9 7 L 7 7 L 6 7 L 3 7 L 3 3 z M 10 6 L 13 6 L 13 10 L 8 10 L 7 10 L 7 8 L 8 8 L 8 10 L 10 8 L 10 7 L 10 6 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/launch.svg b/launcher/resources/breeze_light/scalable/launch.svg
new file mode 100644
index 00000000..678fd098
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/launch.svg
@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ <path d="m2 2v12l12-6z" class="ColorScheme-Text" fill="currentColor"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/loadermods.svg b/launcher/resources/breeze_light/scalable/loadermods.svg
new file mode 100644
index 00000000..4fb0f96d
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/loadermods.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m4 3v1h-2v9h12v-9h-2v-1h-3v1h-2v-1zm-1 2h10v7h-10z"
+ class="ColorScheme-Text"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/log.svg b/launcher/resources/breeze_light/scalable/log.svg
new file mode 100644
index 00000000..cf9c9b22
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/log.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 11 2 L 11 14 L 14 14 L 14 2 L 11 2 z M 6 5 L 6 14 L 9 14 L 9 5 L 6 5 z M 1 8 L 1 14 L 4 14 L 4 8 L 1 8 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/matrix.svg b/launcher/resources/breeze_light/scalable/matrix.svg
new file mode 100644
index 00000000..4745efc1
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/matrix.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg version="1.1" viewBox="0 0 27.9 32" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <title>Matrix (protocol) logo</title>
+ <g transform="translate(-.095 .005)" fill="#232629">
+ <path d="m27.1 31.2v-30.5h-2.19v-0.732h3.04v32h-3.04v-0.732z"/>
+ <path d="m8.23 10.4v1.54h0.044c0.385-0.564 0.893-1.03 1.49-1.37 0.58-0.323 1.25-0.485 1.99-0.485 0.72 0 1.38 0.14 1.97 0.42 0.595 0.279 1.05 0.771 1.36 1.48 0.338-0.5 0.796-0.941 1.38-1.32 0.58-0.383 1.27-0.574 2.06-0.574 0.602 0 1.16 0.074 1.67 0.22 0.514 0.148 0.954 0.383 1.32 0.707 0.366 0.323 0.653 0.746 0.859 1.27 0.205 0.522 0.308 1.15 0.308 1.89v7.63h-3.13v-6.46c0-0.383-0.015-0.743-0.044-1.08-0.0209-0.307-0.103-0.607-0.242-0.882-0.133-0.251-0.336-0.458-0.584-0.596-0.257-0.146-0.606-0.22-1.05-0.22-0.44 0-0.796 0.085-1.07 0.253-0.272 0.17-0.485 0.39-0.639 0.662-0.159 0.287-0.264 0.602-0.308 0.927-0.052 0.347-0.078 0.697-0.078 1.05v6.35h-3.13v-6.4c0-0.338-7e-3 -0.673-0.021-1-0.0114-0.314-0.0749-0.623-0.188-0.916-0.108-0.277-0.3-0.512-0.55-0.673-0.258-0.168-0.636-0.253-1.14-0.253-0.198 0.0083-0.394 0.042-0.584 0.1-0.258 0.0745-0.498 0.202-0.705 0.374-0.228 0.184-0.422 0.449-0.584 0.794-0.161 0.346-0.242 0.798-0.242 1.36v6.62h-3.13v-11.4z"/>
+ <path d="m0.936 0.732v30.5h2.19v0.732h-3.04v-32h3.03v0.732z"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/launcher/resources/breeze_light/scalable/minecraft.svg b/launcher/resources/breeze_light/scalable/minecraft.svg
new file mode 100644
index 00000000..1ffb4565
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/minecraft.svg
@@ -0,0 +1,13 @@
+<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">.ColorScheme-Text {
+ color:#232629;
+ }</style>
+ </defs>
+ <g fill="currentColor">
+ <path class="ColorScheme-Text" d="m8.2746408 13.953029c0.00833 0 1.1494866-0.753825 2.5359342-1.674795l2.520534-1.674794v-2.6001033c0-1.4307351-0.0074-2.6013858-0.01668-2.6013858-0.0093 0-1.150413 0.7546842-2.535934 1.6773611l-2.5192543 1.6773619v2.5988191c0 1.429027 0.00706 2.597536 0.015402 2.597536z" fill="currentColor" fill-opacity=".2"/>
+ <path class="ColorScheme-Text" d="m7.9409649 8.3549799c0.0753873-0.0030883 5.0483591-3.3585525 5.0192511-3.3868071-0.014404-0.0138645-1.1312-0.7652159-2.482034-1.6683781-1.3508332-0.9031623-2.4637176-1.6377305-2.4730489-1.6311596-0.4205404 0.2725585-4.9953997 3.3678436-4.9999993 3.3829565-0.00988 0.032723 4.8804389 3.3033883 4.9358311 3.3033883z" fill="currentColor" fill-opacity=".4"/>
+ <path class="ColorScheme-Text" d="m7.7189427 13.994097c0.00828 0 0.015875-1.150637 0.015402-2.556468l-0.0013141-2.5551853-2.517967-1.6850615c-1.3845486-0.9264887-2.5204415-1.6863448-2.524384-1.6863448-0.00421-0.00112-0.0077 1.1516957-0.0077 2.5616013v2.5628853l2.5102968 1.679928c1.3808361 0.923532 2.5173881 1.678645 2.5256673 1.678645z" fill="currentColor" fill-opacity=".6"/>
+ <path class="ColorScheme-Text" d="m 8,15 c 5.76994,-3.758469 4.07772,-2.720917 6,-4 V 5 C 12.707222,4.143927 10.030643,2.3424577 8,1 5,3 4.3571975,3.3856408 2,5 v 6 z M 8.508315,13.412447 8.4963301,9 C 10.411258,7.652439 11.772087,6.8337528 13,6 v 4.594435 z M 7.5156547,13.400462 3,10.570466 V 6 L 7.5276396,9 Z M 8,8 3.4485124,5 8,2 12.477901,5 C 10.956071,6.0867591 9.5367568,6.9353406 8,8 Z" fill="currentColor"/>
+ </g>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/new.svg b/launcher/resources/breeze_light/scalable/new.svg
new file mode 100644
index 00000000..51babd76
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/new.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs
+ id="defs3051">
+ <style
+ type="text/css"
+ id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 4 4 L 4 28 L 17 28 L 17 27 L 5 27 L 5 14 L 10 14 L 13 11 L 27 11 L 27 17 L 28 17 L 28 7 L 18 7 L 15 4 L 4 4 z M 22 17 L 22 22 L 17 22 L 17 23 L 22 23 L 22 28 L 23 28 L 23 23 L 28 23 L 28 22 L 23 22 L 23 17 L 22 17 z "
+ id="path99"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/news.svg b/launcher/resources/breeze_light/scalable/news.svg
new file mode 100644
index 00000000..3e3ebe95
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/news.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m2 2v12h12v-12zm1 1h10v10h-10zm1 1v3h3v-3zm4 0v1h4v-1zm0 2v1h4v-1zm-4 2v4h8v-4z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/notes.svg b/launcher/resources/breeze_light/scalable/notes.svg
new file mode 100644
index 00000000..a8eaf279
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/notes.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 3 2 L 3 3 L 2 3 L 2 14 L 14 14 L 14 3 L 13 3 L 13 2 L 12 2 L 12 3 L 11 3 L 11 2 L 10 2 L 10 3 L 6 3 L 6 2 L 5 2 L 5 3 L 4 3 L 4 2 L 3 2 z M 3 4 L 13 4 L 13 13 L 3 13 L 3 4 z M 4 5 L 4 6 L 12 6 L 12 5 L 4 5 z M 4 7 L 4 8 L 8 8 L 8 7 L 4 7 z M 4 9 L 4 10 L 10 10 L 10 9 L 4 9 z M 10 11 L 10 12 L 12 12 L 12 11 L 10 11 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/patreon.svg b/launcher/resources/breeze_light/scalable/patreon.svg
new file mode 100644
index 00000000..e12f1f8d
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/patreon.svg
@@ -0,0 +1,3 @@
+<svg fill="#232629" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,3h0a9,9,0,0,0-9,9v9H5.09V12a6.91,6.91,0,1,1,7.23,6.9,5.9,5.9,0,0,1-2.59-.47v-3A4.13,4.13,0,1,0,7.85,12v9H12A9,9,0,1,0,12,3Zm0,15.91h0Z"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/proxy.svg b/launcher/resources/breeze_light/scalable/proxy.svg
new file mode 100644
index 00000000..2e67ff6c
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/proxy.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 7 2 L 7 3 L 13 3 L 13 10 L 7 10 L 7 12 L 7 13 L 6 13 C 5.4459904 13 5 12.55401 5 12 L 5 10 L 5 8 L 5 7 L 6 7 L 6 3 L 3 3 L 3 7 L 4 7 L 4 8 L 4 10 L 4 12 C 4 13.108 4.892 14 6 14 L 7 14 L 9 14 L 11 14 L 11 13 L 9 13 L 9 12 L 14 12 L 14 11 L 14 10 L 14 2 L 7 2 z M 4 5 L 5 5 L 5 6 L 4 6 L 4 5 z M 2 8 L 2 12 L 3 12 L 3 10 L 3 8 L 2 8 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/reddit-alien.svg b/launcher/resources/breeze_light/scalable/reddit-alien.svg
new file mode 100644
index 00000000..93b8eedc
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/reddit-alien.svg
@@ -0,0 +1,3 @@
+<svg fill="#232629" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20,11.86a1.76,1.76,0,0,0-1.75-1.75,1.73,1.73,0,0,0-1.22.51,9,9,0,0,0-4.67-1.38l1-3.16L16,6.71s0,0,0,0a1.46,1.46,0,1,0,.1-.53l-2.89-.68a.25.25,0,0,0-.29.17L11.83,9.23a9.16,9.16,0,0,0-4.88,1.36,1.75,1.75,0,1,0-2.07,2.79,3,3,0,0,0-.06.58C4.82,16.58,8,18.7,12,18.7s7.14-2.13,7.14-4.74a2.94,2.94,0,0,0-.05-.55A1.74,1.74,0,0,0,20,11.86ZM8.51,13.08a1.06,1.06,0,1,1,1.06,1.06A1.06,1.06,0,0,1,8.51,13.08Zm6.06,3.14A3.48,3.48,0,0,1,12,17h0a3.48,3.48,0,0,1-2.56-.79.25.25,0,0,1,.35-.35,3,3,0,0,0,2.2.65h0a3,3,0,0,0,2.2-.65.25.25,0,1,1,.35.35Zm-.13-2.08a1.06,1.06,0,1,1,1.06-1.06A1.06,1.06,0,0,1,14.44,14.14Z"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/refresh.svg b/launcher/resources/breeze_light/scalable/refresh.svg
new file mode 100644
index 00000000..ecd2b394
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/refresh.svg
@@ -0,0 +1,8 @@
+<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style type="text/css" id="current-color-scheme">.ColorScheme-Text {
+ color:#232629;
+ }</style>
+ </defs>
+ <path class="ColorScheme-Text" fill="currentColor" d="m14 8c0 1.1088173-0.319333 2.140071-0.84375 3.03125l-2.6875-2.6875 0.71875-0.71875 1.625 1.625c0.05109-0.19534 0.097716-0.3898623 0.125-0.59375 0.007789-0.0524063 0.025184-0.1032787 0.03125-0.15625 0.01707-0.1680854 0.03125-0.327411 0.03125-0.5 0-2.761424-2.238576-5-5-5-0.243024 0-0.4855082 0.02845-0.71875 0.0625-0.1362493 0.019955-0.2738032 0.031786-0.40625 0.0625-0.2865815 0.06695-0.547624 0.166647-0.8125 0.28125-0.030675 0.013272-0.063399 0.017376-0.09375 0.03125-0.09166 0.040961-0.163333 0.108255-0.25 0.15625-8e-3 0.00445-0.02305-0.00433-0.03125 0l-0.71875-0.75c0.891179-0.524417 1.922432-0.84375 3.03125-0.84375 3.313709 0 6 2.686293 6 6zm-2.96875 5.15625c-0.891179 0.524417-1.922432 0.84375-3.03125 0.84375-3.313709 0-6-2.686293-6-6 0-1.1088173 0.319333-2.140071 0.84375-3.03125l0.75 0.75 1.90625 1.9375-0.6875 0.6875-1.625-1.59375c-0.05109 0.19534-0.09772 0.3898623-0.125 0.59375-0.0078 0.052406-0.02518 0.1032787-0.03125 0.15625-0.01707 0.1680854-0.03125 0.327411-0.03125 0.5s0.01418 0.3319146 0.03125 0.5c0.01707 0.1680853 0.029198 0.337256 0.0625 0.5 0.466231 2.278415 2.490004 4 4.90625 4 0.2482626 0 0.4801862-0.02756 0.71875-0.0625 0.1362493-0.01995 0.2738032-0.03179 0.40625-0.0625 0.2865815-0.06695 0.547624-0.166647 0.8125-0.28125 0.030718-0.01299 0.063349-0.01766 0.09375-0.03125 0.08886-0.04062 0.164735-0.108612 0.25-0.15625h0.03125z"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/rename.svg b/launcher/resources/breeze_light/scalable/rename.svg
new file mode 100644
index 00000000..18ccc58a
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/rename.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 10.398438 2 L 5.2871094 7.1113281 L 2 10.398438 L 2 14 L 5.6015625 14 L 14 5.6015625 L 10.398438 2 z M 8.3496094 5.4902344 L 10.509766 7.6503906 L 7.3359375 10.826172 L 7.3359375 10.150391 L 6.3222656 10.171875 L 5.2871094 10.171875 L 5.2871094 9.1367188 L 5.2871094 8.5507812 L 6.7285156 7.1113281 L 8.3496094 5.4902344 z M 4.2734375 9.5644531 L 4.2734375 11.185547 L 5.3085938 11.185547 L 6.3007812 11.185547 L 6.3222656 11.837891 L 5.2421875 12.919922 L 3.8007812 12.919922 L 3.0800781 12.199219 L 3.0800781 10.757812 L 4.2734375 9.5644531 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/resourcepacks.svg b/launcher/resources/breeze_light/scalable/resourcepacks.svg
new file mode 100644
index 00000000..913d3c1f
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/resourcepacks.svg
@@ -0,0 +1,11 @@
+<!DOCTYPE svg>
+<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <style id="current-color-scheme" type="text/css">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path class="ColorScheme-Text" style="fill:currentColor; fill-opacity:1; stroke:none" d="M 8 3 L 13 3 L 13 13 L 8 13 L 8 3 Z M 3 3 L 13 3 L 13 9 L 11 7 L 7.65625 10.3438 L 6.3125 9 L 6.2813 9 L 3 12.2813 L 3 3 Z M 2 2 L 2 13.2813 L 2 14 L 14 14 L 14 13 L 14 12 L 14 11 L 14 10 L 14 2 L 2 2 Z M 6 4 C 4.89543 4 4 4.89543 4 6 C 4 7.10457 4.89543 8 6 8 C 7.10457 8 8 7.10457 8 6 C 8 4.89543 7.10457 4 6 4 Z"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/screenshots.svg b/launcher/resources/breeze_light/scalable/screenshots.svg
new file mode 100644
index 00000000..d984b330
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/screenshots.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 13.28125 L 2 14 L 14 14 L 14 13 L 14 12 L 14 11 L 14 10 L 14 2 L 2 2 z M 3 3 L 13 3 L 13 9 L 11 7 L 7.65625 10.34375 L 6.3125 9 L 6.28125 9 L 3 12.28125 L 3 3 z M 6 4 C 4.8954305 4 4 4.8954305 4 6 C 4 7.1045695 4.8954305 8 6 8 C 7.1045695 8 8 7.1045695 8 6 C 8 4.8954305 7.1045695 4 6 4 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/settings.svg b/launcher/resources/breeze_light/scalable/settings.svg
new file mode 100644
index 00000000..19e86e26
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/settings.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M18.5 6A3.5 3.5 0 0 0 15.04102 9H4V10H15.04A3.5 3.5 0 0 0 18.5 13 3.5 3.5 0 0 0 21.958984 10H28V9H21.961A3.5 3.5 0 0 0 18.5 6M7.5 19A3.5 3.5 0 0 0 4 22.5 3.5 3.5 0 0 0 7.5 26 3.5 3.5 0 0 0 10.960938 23H28V22H10.959A3.5 3.5 0 0 0 7.5 19m0 1A2.5 2.5 0 0 1 10 22.5 2.5 2.5 0 0 1 7.5 25 2.5 2.5 0 0 1 5 22.5 2.5 2.5 0 0 1 7.5 20"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/shaderpacks.svg b/launcher/resources/breeze_light/scalable/shaderpacks.svg
new file mode 100644
index 00000000..591c6af5
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/shaderpacks.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 2 2 L 2 14 L 5.15625 10.84375 C 4.43239 10.11989 4 9.10457 4 8 C 4 5.79086 5.79086 4 8 4 C 9.10457 4 10.11989 4.43239 10.84375 5.15625 L 14 2 L 2 2 z M 10.84375 5.15625 L 5.15625 10.84375 C 5.88011 11.56761 6.89543 12 8 12 C 10.20914 12 12 10.20914 12 8 C 12 6.89543 11.56761 5.88011 10.84375 5.15625 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/status-bad.svg b/launcher/resources/breeze_light/scalable/status-bad.svg
new file mode 100644
index 00000000..6fc3137e
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/status-bad.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-NegativeText {
+ color:#da4453;
+ }
+ </style>
+ <rect class="ColorScheme-NegativeText" x="3" y="3" width="16" height="16" rx="2" fill="currentColor"/>
+ <path d="M 6.414,5 5,6.414 9.586,11 5,15.586 6.414,17 11,12.414 15.586,17 17,15.586 12.414,11 17,6.414 15.586,5 11,9.586 Z" fill="#fff"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/status-good.svg b/launcher/resources/breeze_light/scalable/status-good.svg
new file mode 100644
index 00000000..eb8bc03b
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/status-good.svg
@@ -0,0 +1,10 @@
+<svg id="svg9" version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style id="current-color-scheme" type="text/css">.ColorScheme-PositiveText {
+ color:#27ae60;
+ }
+ .ColorScheme-Text {
+ color:#232629;
+ }</style>
+ <rect id="rect3" class="ColorScheme-PositiveText" x="3" y="3" width="16" height="16" rx="1.4545455" fill="currentColor"/>
+ <path id="path5" d="M 18.99323,4.3805651 9,14 5.7332785,10.623339 4.3852425,12.059327 9,16.828 l 10,-10 z" fill="#fff"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/status-yellow.svg b/launcher/resources/breeze_light/scalable/status-yellow.svg
new file mode 100644
index 00000000..1dc4d0f5
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/status-yellow.svg
@@ -0,0 +1,9 @@
+<svg version="1.1" viewBox="0 0 22 22" xmlns="http://www.w3.org/2000/svg">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-NeutralText {
+ color:#f67400;
+ }
+ </style>
+ <path class="ColorScheme-NeutralText" d="m11.006318 3.0000261a0.72728737 0.72727154 0 0 0-0.65674 0.4021811l-7.2728738 14.545431a0.72728737 0.72727154 0 0 0 0.6509222 1.052362h14.545748a0.72728737 0.72727154 0 0 0 0.650922-1.052362l-7.272874-14.545431a0.72728737 0.72727154 0 0 0-0.645104-0.4021811z" fill="currentColor"/>
+ <path d="m10 7v6h2v-6zm0 8v2h2v-2z" fill="#fff"/>
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/tag.svg b/launcher/resources/breeze_light/scalable/tag.svg
new file mode 100644
index 00000000..4887d126
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/tag.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ .ColorScheme-Highlight {
+ color:#3daee9;
+ }
+ </style>
+ </defs>
+ <path
+ style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="M 9 3 L 3 5 L 5 11 L 15 16 L 19 8 L 9 3 z M 3 5 L 3 11 L 11 19 L 13.705078 16.294922 L 4 11 L 3 5 z M 6.5 5 C 7.331 5 8 5.669 8 6.5 C 8 7.331 7.331 8 6.5 8 C 5.669 8 5 7.331 5 6.5 C 5 5.669 5.669 5 6.5 5 z "
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/viewfolder.svg b/launcher/resources/breeze_light/scalable/viewfolder.svg
new file mode 100644
index 00000000..4a8498ce
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/viewfolder.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <path style="fill:currentColor;fill-opacity:1;stroke:none"
+ d="m4 4v24h24l-1-1h-22v-13h5l3-3h14v16l1 1v-21h-10l-3-3z"
+ class="ColorScheme-Text"
+ />
+</svg>
diff --git a/launcher/resources/breeze_light/scalable/worlds.svg b/launcher/resources/breeze_light/scalable/worlds.svg
new file mode 100644
index 00000000..543cc55e
--- /dev/null
+++ b/launcher/resources/breeze_light/scalable/worlds.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <defs id="defs3051">
+ <style type="text/css" id="current-color-scheme">
+ .ColorScheme-Text {
+ color:#232629;
+ }
+ </style>
+ </defs>
+ <g transform="translate(0,-1036.3622)">
+ <path
+ style="fill:currentColor;fill-opacity:1;fill-rule:nonzero"
+ d="M 8 2 C 4.6862915 2 2 4.6862868 2 8 C 2 11.313713 4.6862915 14 8 14 C 11.313708 14 14 11.313713 14 8 C 14 4.6862868 11.313708 2 8 2 z M 8 3 C 8.172589 3 8.3319146 3.01418 8.5 3.03125 C 8.5403398 3.0352163 8.5849592 3.0263276 8.625 3.03125 C 8.6776214 3.0379183 8.7291519 3.0542091 8.78125 3.0625 C 8.9249022 3.0850796 9.0482753 3.1217241 9.1875 3.15625 C 9.4440966 3.2195469 9.6982233 3.3050445 9.9375 3.40625 C 10.069426 3.4620498 10.186649 3.5272356 10.3125 3.59375 C 10.312425 3.60275 10.3133 3.614 10.3125 3.625 C 10.34148 3.638 10.35728 3.69175 10.40625 3.71875 C 10.42423 3.72875 10.42056 3.741 10.4375 3.75 C 10.46648 3.766 10.50599 3.7835 10.5 3.8125 C 10.492 3.8525 10.43425 3.844 10.40625 3.875 C 10.41005 3.933 10.35871 3.92975 10.34375 3.96875 C 10.35275 3.99375 10.40229 4.00125 10.40625 4.03125 C 10.40135 4.06325 10.33173 4.06075 10.34375 4.09375 C 10.39776 4.13575 10.449004 4.10375 10.5 4.09375 C 10.611991 4.07975 10.712253 4.08825 10.78125 4.03125 C 10.77225 3.97825 10.88099 3.9856 10.875 3.9375 C 10.90366 3.9583822 10.9403 3.9788168 10.96875 4 C 10.957987 4.01008 10.949578 4.02585 10.9375 4.03125 C 10.91649 4.04125 10.89401 4.0505 10.875 4.0625 C 10.847 4.0805 10.81328 4.104 10.78125 4.125 C 10.75826 4.141 10.711504 4.21875 10.6875 4.21875 C 10.65851 4.21875 10.61475 4.1815 10.59375 4.1875 C 10.56372 4.1975 10.56519 4.238 10.53125 4.25 C 10.46526 4.274 10.335495 4.2135 10.3125 4.3125 C 10.3405 4.3695 10.450005 4.343 10.5 4.375 C 10.53102 4.395 10.58575 4.466 10.59375 4.5 C 10.60175 4.528 10.60574 4.6575 10.59375 4.6875 C 10.56273 4.7675 10.466988 4.751 10.375 4.75 L 10.3125 4.75 C 10.157516 4.74 10.007499 4.67025 9.9375 4.78125 C 9.94245 4.86625 9.95653 4.926 9.9375 5 C 9.92353 5.054 9.840752 5.10965 9.84375 5.15625 C 9.84485 5.18025 9.900255 5.216 9.90625 5.25 C 9.9101 5.268 9.89818 5.2955 9.90625 5.3125 C 9.932265 5.3615 9.9852535 5.3025 10.03125 5.3125 C 10.07525 5.3225 10.125 5.3883 10.125 5.4375 C 10.125 5.4725 10.11575 5.5286 10.09375 5.5625 C 10.04975 5.6285 9.969247 5.62025 9.90625 5.65625 C 9.854275 5.68625 9.843502 5.7609 9.8125 5.8125 C 9.7795 5.8685 9.736983 5.88865 9.75 5.96875 C 9.70501 6.05775 9.655488 6.11325 9.5625 6.15625 C 9.535495 6.16825 9.493747 6.1695 9.46875 6.1875 C 9.44873 6.2015 9.434272 6.24925 9.40625 6.28125 C 9.37127 6.32025 9.331497 6.345 9.3125 6.375 C 9.298475 6.398 9.29621 6.43675 9.28125 6.46875 C 9.25925 6.51075 9.215494 6.5203 9.1875 6.5625 C 9.17452 6.5825 9.17022 6.63025 9.15625 6.65625 C 9.12424 6.71425 9.087749 6.7615 9.09375 6.8125 C 9.0987 6.8605 9.153252 6.86225 9.15625 6.90625 C 9.159 6.93025 9.12401 6.94175 9.125 6.96875 C 9.1261 7.00675 9.15328 7.0325 9.15625 7.0625 C 9.16324 7.1365 9.10772 7.16815 9.09375 7.21875 C 9.08676 7.24275 9.09975 7.26125 9.09375 7.28125 C 9.08176 7.31825 9.032251 7.36205 9.03125 7.40625 C 9.03015 7.43725 9.09078 7.456 9.09375 7.5 C 9.09595 7.529 9.056505 7.56575 9.0625 7.59375 C 9.0735 7.64775 9.1767575 7.708 9.21875 7.75 C 9.275747 7.807 9.350008 7.84625 9.375 7.90625 C 9.39601 7.95725 9.398533 8.05565 9.4375 8.09375 C 9.466485 8.12175 9.525507 8.12625 9.5625 8.15625 C 9.59748 8.18325 9.619253 8.18875 9.65625 8.21875 C 9.717245 8.26675 9.809252 8.382 9.90625 8.375 C 9.957235 8.365 10.005504 8.3225 10.0625 8.3125 C 10.1215 8.3025 10.190006 8.27625 10.25 8.28125 C 10.29598 8.29125 10.358253 8.3155 10.40625 8.3125 C 10.45624 8.3025 10.505509 8.30625 10.5625 8.28125 C 10.6615 8.23825 10.824766 8.1668 10.96875 8.1875 C 11.078739 8.2035 11.079255 8.326 11.15625 8.375 C 11.24424 8.385 11.305007 8.358 11.375 8.375 C 11.42802 8.388 11.495001 8.4584 11.5 8.5 C 11.505 8.543 11.44349 8.5978 11.4375 8.625 C 11.42353 8.683 11.44746 8.74425 11.4375 8.78125 C 11.4295 8.81025 11.40526 8.846 11.40625 8.875 C 11.40625 8.901 11.44173 8.9658 11.46875 9 C 11.50175 9.042 11.55375 9.0798 11.59375 9.125 C 11.66674 9.209 11.699004 9.29525 11.75 9.40625 C 11.76298 9.43425 11.73801 9.464 11.75 9.5 C 11.728 9.6679 11.672491 9.78285 11.5625 9.96875 C 11.51052 10.02875 11.436499 10.08135 11.4375 10.15625 C 11.4337 10.33625 11.53925 10.451 11.53125 10.625 C 11.51327 10.8589 11.54929 10.8751 11.53125 11 C 11.61025 11.04 11.55424 11.1669 11.53125 11.25 C 11.49726 11.349 11.4565 11.3715 11.4375 11.4375 C 11.490583 11.460643 11.566021 11.451092 11.625 11.4375 C 11.595204 11.468874 11.561843 11.500657 11.53125 11.53125 C 11.305044 11.757456 11.047252 11.976543 10.78125 12.15625 C 10.265207 12.504882 9.6912413 12.769698 9.0625 12.90625 C 8.718961 12.980861 8.3658887 13 8 13 C 7.8096669 13 7.6218651 12.989959 7.4375 12.96875 C 7.2906389 12.951989 7.1427139 12.935453 7 12.90625 C 6.7168255 12.848304 6.4510281 12.728616 6.1875 12.625 C 6.1781304 12.60785 6.1541244 12.582618 6.15625 12.5625 C 6.208225 12.5405 6.305756 12.6365 6.34375 12.5625 C 6.36278 12.5275 6.318725 12.44125 6.34375 12.40625 C 6.361735 12.38225 6.463007 12.382 6.5 12.375 C 6.55302 12.365 6.626253 12.35775 6.65625 12.34375 C 6.68925 12.32275 6.724751 12.2491 6.71875 12.1875 C 6.716 12.1585 6.679185 12.151 6.65625 12.125 C 6.637275 12.103 6.66923 12.0835 6.65625 12.0625 C 6.63227 12.0275 6.572499 12.01975 6.5625 11.96875 C 6.634495 11.95475 6.7397615 12.029 6.84375 12 C 6.892755 11.986 6.976999 11.88985 7 11.84375 C 7.00699 11.82975 6.99202 11.79925 7 11.78125 C 7.00902 11.76325 7.028225 11.72875 7.03125 11.71875 C 7.03823 11.69175 7.02525 11.7055 7.03125 11.6875 C 7.049235 11.6365 7.094997 11.5988 7.125 11.5625 C 7.14799 11.5345 7.197723 11.49775 7.21875 11.46875 C 7.24273 11.43775 7.23405 11.43425 7.25 11.40625 C 7.265015 11.31525 7.19675 11.23615 7.21875 11.15625 C 7.24273 11.06625 7.381762 11.00975 7.46875 10.96875 C 7.498725 10.95475 7.537502 10.9465 7.5625 10.9375 C 7.622494 10.9165 7.6940045 10.895 7.75 10.875 C 7.843995 10.841 7.860248 10.7734 7.90625 10.6875 C 7.92825 10.6475 7.964735 10.5918 7.96875 10.5625 C 7.9726 10.5365 7.9638 10.531 7.96875 10.5 C 7.9737 10.471 7.960775 10.44025 7.96875 10.40625 C 7.97975 10.36325 8 10.26665 8 10.21875 C 8 10.17875 7.96974 10.161 7.96875 10.125 C 7.96176 9.995 8.014512 10.0085 8.0625 9.9375 C 8.08648 9.9025 8.10806 9.8376 8.125 9.8125 C 8.205993 9.6925 8.353749 9.65075 8.34375 9.46875 C 8.3399 9.40875 8.293978 9.269 8.25 9.25 C 8.224975 9.239 8.1932485 9.23175 8.15625 9.21875 C 8.0382585 9.17475 7.9354855 9.04125 7.8125 9.03125 L 7.75 9.03125 C 7.701985 9.03125 7.6477435 9.007 7.59375 9 C 7.55976 8.99 7.4887485 9.01 7.46875 9 C 7.43773 8.985 7.43122 8.92325 7.40625 8.90625 C 7.38623 8.89225 7.343493 8.886 7.3125 8.875 C 7.263495 8.857 7.2444965 8.8165 7.1875 8.8125 C 7.169515 8.8125 7.144982 8.8225 7.125 8.8125 C 7.09398 8.8025 7.060246 8.79925 7.03125 8.78125 C 6.99528 8.75925 6.952498 8.74975 6.9375 8.71875 C 6.9705 8.61085 6.86575 8.59425 6.84375 8.53125 C 6.83374 8.50225 6.85376 8.43225 6.84375 8.40625 C 6.83077 8.36925 6.791002 8.3435 6.75 8.3125 C 6.678005 8.2565 6.634243 8.23165 6.53125 8.21875 C 6.48527 8.20875 6.4299945 8.22875 6.375 8.21875 C 6.317008 8.20875 6.2314945 8.175 6.1875 8.125 C 6.14548 8.076 6.130744 8.0231 6.09375 8 C 6.05877 7.978 6.0239965 7.9605 6 7.9375 C 5.989 7.9275 5.97876 7.92925 5.96875 7.90625 C 5.950765 7.86925 5.9155 7.8575 5.9375 7.8125 C 5.897515 7.7925 5.917993 7.85075 5.875 7.84375 C 5.836005 7.78875 5.8067385 7.734 5.71875 7.75 C 5.678765 7.76 5.640747 7.84275 5.59375 7.84375 C 5.554755 7.84375 5.519746 7.758 5.46875 7.75 C 5.429755 7.74 5.3707495 7.78625 5.34375 7.78125 C 5.30074 7.77125 5.288973 7.74475 5.25 7.71875 C 5.221015 7.69975 5.17225 7.65525 5.15625 7.65625 C 5.106255 7.65625 5.0689915 7.75515 5 7.71875 C 4.96601 7.67575 5.071268 7.665 5.03125 7.625 C 5.001275 7.596 4.992763 7.64225 4.96875 7.65625 C 4.940755 7.67425 4.905993 7.6765 4.875 7.6875 C 4.806002 7.7135 4.7414935 7.721 4.6875 7.75 C 4.637505 7.776 4.622747 7.79245 4.59375 7.84375 C 4.57076 7.88575 4.534997 7.96675 4.5 7.96875 C 4.45798 7.96875 4.4442495 7.92325 4.40625 7.90625 C 4.2912615 7.85625 4.2199905 7.9355 4.125 7.9375 C 4.037006 7.9375 3.9022515 7.80175 3.90625 7.71875 C 3.909 7.66775 3.931505 7.59425 3.9375 7.53125 C 3.94245 7.48125 3.999065 7.4206 4 7.375 C 4.0011 7.313 3.883746 7.28835 3.84375 7.28125 C 3.749755 7.26425 3.6382415 7.32325 3.53125 7.28125 C 3.51123 7.24725 3.55051 7.2215 3.5625 7.1875 C 3.56948 7.1695 3.55551 7.146 3.5625 7.125 C 3.57449 7.093 3.641257 7.06525 3.65625 7.03125 C 3.66725 7.00525 3.64525 6.9705 3.65625 6.9375 C 3.66923 6.9015 3.715725 6.86975 3.71875 6.84375 C 3.7226 6.80975 3.6832 6.772 3.65625 6.75 C 3.573255 6.76 3.5167485 6.75725 3.46875 6.78125 C 3.360763 6.83025 3.3902435 6.966 3.28125 7 C 3.244235 7.012 3.198248 7.02425 3.15625 7.03125 C 3.137935 7.04125 3.11427 7.02925 3.09375 7.03125 C 3.0956963 7.0211336 3.0917443 7.0100944 3.09375 7 C 3.0984123 6.9772158 3.1200289 6.9601701 3.125 6.9375 C 3.1962653 6.6125035 3.3063949 6.2979344 3.4375 6 L 3.46875 6 C 3.506755 6.01 3.529505 6.0595 3.5625 6.0625 C 3.654493 6.0725 3.674001 5.9777 3.75 5.9375 C 3.827996 5.9485 3.866506 5.9275 3.9375 5.9375 C 3.985515 5.9475 4.055756 5.996 4.09375 6 C 4.12576 6 4.126248 5.96475 4.15625 5.96875 C 4.186225 5.97875 4.245 6.0265 4.25 6.0625 C 4.25495 6.1075 4.205715 6.16915 4.21875 6.21875 C 4.265775 6.26675 4.3705045 6.2845 4.4375 6.3125 C 4.4815 6.2755 4.44448 6.20825 4.4375 6.15625 C 4.4364 6.13325 4.44025 6.0835 4.4375 6.0625 C 4.43255 6.0265 4.40625 5.99975 4.40625 5.96875 C 4.40625 5.82575 4.5290085 5.77465 4.625 5.71875 C 4.665975 5.69475 4.7140025 5.643 4.75 5.625 C 4.800985 5.6 4.8330025 5.61775 4.875 5.59375 C 4.950988 5.55075 5.0035015 5.4862 5.0625 5.4375 C 5.089505 5.3725 5.05975 5.28275 5.0625 5.21875 C 5.089505 5.20175 5.127282 5.21875 5.15625 5.21875 C 5.20223 5.20875 5.223996 5.151 5.25 5.125 C 5.265015 5.11 5.294482 5.10875 5.3125 5.09375 C 5.35848 5.05775 5.366237 5.016 5.40625 5 C 5.420275 4.99 5.446723 4.97575 5.46875 4.96875 C 5.49878 4.95875 5.545757 4.9555 5.59375 4.9375 C 5.62477 4.9265 5.706749 4.91925 5.71875 4.90625 C 5.733765 4.89125 5.7149 4.8355 5.71875 4.8125 C 5.74273 4.6996 5.899009 4.69435 6 4.65625 C 6.069993 4.63025 6.1407545 4.5485 6.21875 4.5625 C 6.203735 4.6165 6.143249 4.6224 6.15625 4.6875 C 6.173245 4.7785 6.280501 4.66925 6.3125 4.65625 C 6.373495 4.63025 6.4825025 4.5925 6.5625 4.5625 C 6.632493 4.5375 6.753489 4.5181 6.6875 4.4375 C 6.6325 4.4275 6.6104935 4.48 6.5625 4.5 C 6.5295 4.51 6.527978 4.47175 6.5 4.46875 C 6.472995 4.46875 6.464527 4.505 6.4375 4.5 C 6.410495 4.49 6.378998 4.43925 6.375 4.40625 C 6.364 4.32225 6.43476 4.3119 6.46875 4.25 C 6.44576 4.174 6.3452425 4.2175 6.28125 4.1875 C 6.28824 4.1425 6.322007 4.10375 6.375 4.09375 C 6.41801 4.08375 6.5365015 4.102 6.5625 4.125 C 6.577515 4.138 6.5395595 4.1665 6.5625 4.1875 C 6.588515 4.2105 6.622254 4.1975 6.65625 4.1875 C 6.66824 4.1305 6.583751 4.162 6.59375 4.125 C 6.661741 4.081 6.792005 4.10475 6.875 4.09375 C 6.923015 4.08375 6.988251 4.05425 7.03125 4.03125 C 7.071235 4.00825 7.0830015 3.962 7.125 4 C 7.13699 4.041 7.116685 4.07175 7.09375 4.09375 C 7.016756 4.16175 6.934994 4.2336 6.875 4.3125 C 6.912015 4.3485 6.969254 4.33675 7.03125 4.34375 C 7.05523 4.35375 7.099997 4.33875 7.125 4.34375 C 7.15503 4.35375 7.194748 4.33375 7.21875 4.34375 C 7.26077 4.35375 7.270507 4.4375 7.3125 4.4375 C 7.380496 4.4375 7.351523 4.3577 7.3125 4.3125 C 7.32449 4.2655 7.3608 4.2207 7.34375 4.1875 C 7.31273 4.1255 7.1935005 4.206 7.1875 4.125 C 7.18365 4.075 7.231993 4.06925 7.25 4.03125 C 7.24202 3.93525 7.322499 3.93075 7.3125 3.84375 C 7.30755 3.80775 7.275008 3.81525 7.25 3.78125 C 7.23702 3.76325 7.23371 3.7015 7.21875 3.6875 C 7.166775 3.6435 7.0685005 3.67795 7.0625 3.59375 C 7.05975 3.54375 7.06948 3.4911 7.0625 3.4375 C 7.05551 3.3865 7.012728 3.22875 6.96875 3.21875 C 6.912755 3.20675 6.8794955 3.31245 6.8125 3.34375 C 6.77653 3.36075 6.693243 3.381 6.65625 3.375 C 6.63227 3.365 6.596753 3.3595 6.59375 3.3125 C 6.58979 3.2701 6.6405966 3.2512172 6.65625 3.21875 C 6.6601634 3.2106332 6.654259 3.19872 6.65625 3.1875 C 6.7288164 3.1672517 6.8012164 3.1422715 6.875 3.125 C 6.9169167 3.1152798 6.9576866 3.1024086 7 3.09375 C 7.0298771 3.0875279 7.0636852 3.0994569 7.09375 3.09375 C 7.2273044 3.0689879 7.3629458 3.0451686 7.5 3.03125 C 7.6662387 3.0137812 7.8289018 3 8 3 z M 5.96875 3.46875 C 5.9369162 3.6513239 5.7660026 3.7240206 5.5625 3.71875 C 5.51652 3.76275 5.55622 3.84525 5.53125 3.90625 C 5.51024 3.95725 5.425991 4.004 5.375 4 C 5.337985 4 5.28125 3.94125 5.28125 3.90625 C 5.28125 3.86325 5.349521 3.8617 5.3125 3.8125 C 5.31415 3.8025 5.333355 3.79125 5.34375 3.78125 C 5.3639901 3.7685049 5.3858533 3.762493 5.40625 3.75 C 5.5874525 3.6384834 5.7732114 3.5568279 5.96875 3.46875 z M 10.15625 3.65625 C 10.13782 3.66625 10.14122 3.6775 10.125 3.6875 C 10.08699 3.7105 10.06026 3.739 10.03125 3.75 C 9.99028 3.765 9.943504 3.7755 9.9375 3.8125 C 9.93255 3.8435 9.969795 3.846 9.96875 3.875 C 9.953735 3.912 9.94553 3.92375 9.9375 3.96875 C 9.988485 4.05175 10.093258 3.9505 10.15625 3.9375 C 10.18425 3.9275 10.20375 3.93025 10.21875 3.90625 C 10.23574 3.87925 10.23706 3.8507 10.25 3.8125 C 10.26403 3.7705 10.31751 3.75575 10.3125 3.71875 C 10.3087 3.68175 10.218244 3.65125 10.15625 3.65625 z M 4.6875 4.28125 C 4.7349118 4.319347 4.8015588 4.40185 4.71875 4.4375 C 4.70874 4.4475 4.5864965 4.47275 4.5625 4.46875 C 4.55282 4.46875 4.5397337 4.44475 4.53125 4.4375 C 4.5829129 4.3871923 4.6337069 4.3293033 4.6875 4.28125 z M 4.875 4.46875 C 4.89898 4.46875 4.909483 4.52925 4.9375 4.53125 C 4.964505 4.53125 4.983973 4.496 5 4.5 C 5.04202 4.511 5.071268 4.5938 5.03125 4.625 C 4.99825 4.635 4.992713 4.58975 4.96875 4.59375 C 4.900754 4.60575 4.8634955 4.754 4.8125 4.75 C 4.764485 4.74 4.732228 4.62875 4.78125 4.59375 C 4.78235 4.56375 4.779215 4.56225 4.78125 4.53125 C 4.796265 4.51325 4.834982 4.46575 4.875 4.46875 z M 4.625 4.5 C 4.639025 4.5 4.651245 4.49 4.65625 4.5 C 4.64927 4.553 4.634708 4.56875 4.59375 4.59375 C 4.522756 4.63675 4.460501 4.68975 4.4375 4.78125 C 4.43051 4.80825 4.3464955 4.8817 4.3125 4.875 C 4.233504 4.858 4.357015 4.7104 4.375 4.6875 C 4.39502 4.6625 4.414526 4.654 4.4375 4.625 C 4.46049 4.597 4.478055 4.54725 4.5 4.53125 C 4.52398 4.51425 4.5830075 4.498 4.625 4.5 z M 11.40625 4.65625 C 11.48524 4.64625 11.526502 4.73365 11.5625 4.78125 C 11.59248 4.82025 11.669746 4.87905 11.71875 4.90625 C 11.74675 4.92125 11.7815 4.95475 11.8125 4.96875 C 11.87749 4.99775 11.996996 5.09225 12 5.15625 C 12.0011 5.18825 11.96374 5.22 11.96875 5.25 C 12.01776 5.26 12.0615 5.1742 12.0625 5.125 C 12.15441 5.2550064 12.233102 5.3926596 12.3125 5.53125 C 12.35 5.5967067 12.402944 5.6512957 12.4375 5.71875 C 12.39339 5.73175 12.38209 5.73825 12.375 5.78125 C 12.36499 5.83725 12.39649 5.9276 12.3125 5.9375 C 12.25953 5.9475 12.19799 5.892 12.125 5.875 C 12.09502 5.865 12.05525 5.885 12.03125 5.875 C 11.97625 5.857 11.9845 5.7833 11.9375 5.75 C 11.91049 5.731 11.824253 5.6925 11.78125 5.6875 C 11.75424 5.6875 11.74673 5.6975 11.71875 5.6875 C 11.68476 5.6775 11.621749 5.7015 11.59375 5.6875 C 11.56674 5.6705 11.50701 5.61675 11.5 5.59375 C 11.489 5.55675 11.59175 5.5313 11.59375 5.5 C 11.59595 5.476 11.5675 5.44325 11.5625 5.40625 C 11.5587 5.38425 11.5674 5.35875 11.5625 5.34375 C 11.5555 5.31775 11.50974 5.3135 11.46875 5.3125 C 11.43272 5.3125 11.40599 5.3125 11.375 5.3125 C 11.331 5.3125 11.242494 5.3085 11.1875 5.3125 C 11.1545 5.3125 11.12475 5.3095 11.09375 5.3125 C 11.00676 5.3225 10.942997 5.3025 10.875 5.3125 C 10.82599 5.3125 10.733496 5.354 10.6875 5.375 C 10.66451 5.386 10.65501 5.39125 10.625 5.40625 C 10.61103 5.41625 10.58054 5.4295 10.5625 5.4375 C 10.53451 5.4505 10.49575 5.492 10.46875 5.5 C 10.42976 5.51 10.38349 5.49 10.3125 5.5 C 10.26152 5.5 10.188501 5.46725 10.1875 5.40625 C 10.1864 5.31325 10.349511 5.36075 10.4375 5.34375 C 10.4705 5.33375 10.49123 5.30525 10.53125 5.28125 C 10.55028 5.27025 10.54451 5.262 10.5625 5.25 C 10.59247 5.229 10.65125 5.18025 10.65625 5.15625 C 10.66005 5.13425 10.65323 5.11075 10.65625 5.09375 C 10.66005 5.07275 10.69175 5.05925 10.71875 5.03125 C 10.76077 4.98925 10.791747 4.93345 10.84375 4.90625 C 10.87873 4.88825 10.9215 4.87275 10.9375 4.84375 C 10.9413 4.81175 10.9364 4.81125 10.9375 4.78125 C 10.9595 4.76125 10.98202 4.754 11 4.75 C 11.05401 4.738 11.093259 4.753 11.15625 4.75 C 11.18325 4.75 11.221 4.763 11.25 4.75 C 11.27398 4.734 11.2885 4.6995 11.3125 4.6875 C 11.33851 4.6735 11.37422 4.65825 11.40625 4.65625 z M 11.96875 5.25 L 11.84375 5.25 C 11.79975 5.25 11.744749 5.227 11.71875 5.25 C 11.75577 5.31 11.823255 5.32875 11.90625 5.34375 C 11.93623 5.32175 11.95478 5.2883 11.96875 5.25 z M 5.125 4.6875 L 5.21875 4.6875 C 5.243775 4.6875 5.271251 4.6775 5.28125 4.6875 C 5.265245 4.7535 5.114497 4.74275 5.0625 4.71875 C 5.06525 4.69375 5.092968 4.6915 5.125 4.6875 z M 4.90625 4.8125 C 4.8625672 4.8442816 4.8193312 4.8845833 4.75 4.90625 C 4.711995 4.91825 4.663252 4.917 4.65625 4.875 C 4.64822 4.823 4.715988 4.84875 4.75 4.84375 C 4.794 4.83375 4.855254 4.8145 4.90625 4.8125 z M 11.40625 4.8125 C 11.37325 4.8235 11.35724 4.89625 11.40625 4.90625 C 11.42924 4.89225 11.44355 4.8555 11.4375 4.8125 C 11.42452 4.7985 11.41621 4.8025 11.40625 4.8125 z M 11.34375 4.96875 C 11.34485 5.02475 11.35475 5.0764 11.34375 5.125 C 11.37576 5.187 11.452756 5.13575 11.46875 5.09375 C 11.48778 4.98975 11.45425 4.95575 11.40625 4.96875 C 11.39025 4.97875 11.36069 4.95575 11.34375 4.96875 z M 3.0625 7.3125 C 3.118864 7.3635 3.1512925 7.43535 3.21875 7.46875 C 3.243775 7.48175 3.280501 7.492 3.3125 7.5 C 3.338515 7.51 3.350013 7.492 3.375 7.5 C 3.42802 7.518 3.4892575 7.5435 3.53125 7.5625 C 3.595248 7.5905 3.66375 7.68675 3.71875 7.71875 C 3.7215 7.75075 3.70874 7.7885 3.71875 7.8125 C 3.744765 7.8475 3.7704965 7.88225 3.8125 7.90625 C 3.863485 7.93425 3.8895065 7.9778 3.9375 8 C 3.954495 8.01 3.973963 7.99 4 8 C 4.065994 8.02 4.099253 8.11055 4.15625 8.09375 C 4.193265 8.08275 4.181505 8.036 4.1875 8 C 4.212525 7.973 4.242222 7.9375 4.28125 7.9375 C 4.34324 7.9375 4.351999 8.03315 4.375 8.09375 C 4.38798 8.12775 4.428535 8.1515 4.4375 8.1875 C 4.452515 8.2495 4.45048 8.43175 4.4375 8.46875 C 4.421495 8.51475 4.3504995 8.5122 4.3125 8.5625 C 4.294515 8.5865 4.29522 8.63925 4.28125 8.65625 C 4.25727 8.68525 4.236773 8.69075 4.21875 8.71875 C 4.20874 8.73375 4.196465 8.76025 4.1875 8.78125 C 4.15747 8.84525 4.099986 8.89175 4.125 8.96875 C 4.13501 8.99975 4.1875 9.0255 4.1875 9.0625 C 4.1875 9.1065 4.100751 9.1393 4.09375 9.1875 C 4.0888 9.2225 4.114055 9.2658 4.125 9.3125 C 4.13298 9.3475 4.197751 9.38325 4.21875 9.40625 C 4.25373 9.44525 4.280501 9.513 4.3125 9.5625 C 4.375491 9.6595 4.406765 9.78 4.46875 9.875 C 4.50175 9.924 4.57175 9.9484 4.59375 10 C 4.60475 10.026 4.58374 10.06975 4.59375 10.09375 C 4.62972 10.17475 4.794007 10.2726 4.875 10.3125 C 4.913005 10.3315 4.965009 10.32375 5 10.34375 C 5.05302 10.37375 5.160506 10.4534 5.1875 10.5 C 5.21148 10.542 5.20676 10.65185 5.21875 10.71875 C 5.233765 10.80675 5.2149 10.87365 5.21875 10.96875 C 5.2215 11.02075 5.25 11.04215 5.25 11.09375 C 5.25 11.11175 5.2511 11.1685 5.25 11.1875 C 5.2489 11.2495 5.25495 11.312 5.25 11.375 C 5.24725 11.409 5.2478 11.43975 5.25 11.46875 C 5.25275 11.49975 5.27927 11.50325 5.28125 11.53125 C 5.28345 11.56025 5.246975 11.602 5.25 11.625 C 5.25699 11.688 5.300499 11.72315 5.3125 11.78125 C 5.32251 11.82725 5.349745 11.945 5.34375 12 C 5.3399 12.029 5.316515 12.06075 5.3125 12.09375 C 5.30975 12.11975 5.31635 12.13225 5.3125 12.15625 C 5.30975 12.17925 5.31844 12.19975 5.3125 12.21875 C 5.2806543 12.198271 5.2500951 12.177426 5.21875 12.15625 C 4.9527483 11.976543 4.6949555 11.757456 4.46875 11.53125 C 4.4381566 11.500657 4.4047959 11.468874 4.375 11.4375 C 4.1944191 11.247353 4.0241375 11.029182 3.875 10.8125 C 3.8614865 10.792866 3.8569888 10.769835 3.84375 10.75 C 3.6707036 10.490733 3.5290643 10.227866 3.40625 9.9375 C 3.3973945 9.9165633 3.3835772 9.8960813 3.375 9.875 C 3.2696226 9.6160011 3.1860736 9.3410185 3.125 9.0625 C 3.1134736 9.0099352 3.1036081 8.9594198 3.09375 8.90625 C 3.0689879 8.7726956 3.0451686 8.6370542 3.03125 8.5 C 3.01418 8.3319146 3 8.172589 3 8 C 3 7.7623582 3.030436 7.5413039 3.0625 7.3125 z M 5 7.84375 C 5.048015 7.82175 5.038995 7.89925 5 7.90625 C 4.962985 7.91625 4.96403 7.86175 5 7.84375 z " transform="translate(0,1036.3622)"
+ id="path26"
+ class="ColorScheme-Text"/>
+ </g>
+</svg>
diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp
index 20aa6d04..38f48296 100644
--- a/launcher/translations/TranslationsModel.cpp
+++ b/launcher/translations/TranslationsModel.cpp
@@ -83,12 +83,21 @@ struct Language
else if(key == "es_UY") {
result = u8"español de Latinoamérica";
}
+ else if(key == "en_NZ") {
+ result = u8"New Zealand English"; // No idea why qt translates this to just english and not to New Zealand English
+ }
else if(key == "en@pirate") {
result = u8"Tongue of the High Seas";
}
else if(key == "en@uwu") {
result = u8"Cute Engwish";
}
+ else if(key == "tok") {
+ result = u8"toki pona";
+ }
+ else if(key == "nan") {
+ result = u8"閩南語"; // Using traditional Chinese script. Not sure if we should use simplified instead?
+ }
else {
result = locale.nativeLanguageName();
}
diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp
index 834bfd04..929f2a85 100644
--- a/launcher/ui/MainWindow.cpp
+++ b/launcher/ui/MainWindow.cpp
@@ -49,7 +49,7 @@
#include <QKeyEvent>
#include <QAction>
-
+#include <QActionGroup>
#include <QApplication>
#include <QButtonGroup>
#include <QHBoxLayout>
@@ -61,6 +61,7 @@
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
+#include <QFileDialog>
#include <QInputDialog>
#include <QLabel>
#include <QToolButton>
@@ -105,6 +106,7 @@
#include "ui/dialogs/UpdateDialog.h"
#include "ui/dialogs/EditAccountDialog.h"
#include "ui/dialogs/ExportInstanceDialog.h"
+#include "ui/themes/ITheme.h"
#include "UpdateController.h"
#include "KonamiCode.h"
@@ -253,6 +255,9 @@ public:
QMenu * helpMenu = nullptr;
TranslatedToolButton helpMenuButton;
TranslatedAction actionClearMetadata;
+ #ifdef Q_OS_MAC
+ TranslatedAction actionAddToPATH;
+ #endif
TranslatedAction actionReportBug;
TranslatedAction actionDISCORD;
TranslatedAction actionMATRIX;
@@ -264,6 +269,8 @@ public:
TranslatedAction actionLockToolbars;
+ TranslatedAction actionChangeTheme;
+
QVector<TranslatedToolButton *> all_toolbuttons;
QWidget *centralWidget = nullptr;
@@ -290,7 +297,6 @@ public:
actionAddInstance = TranslatedAction(MainWindow);
actionAddInstance->setObjectName(QStringLiteral("actionAddInstance"));
actionAddInstance->setIcon(APPLICATION->getThemedIcon("new"));
- actionAddInstance->setIconVisibleInMenu(false);
actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instanc&e..."));
actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance."));
actionAddInstance->setShortcut(QKeySequence::New);
@@ -350,6 +356,14 @@ public:
actionClearMetadata.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Clear cached metadata"));
all_actions.append(&actionClearMetadata);
+ #ifdef Q_OS_MAC
+ actionAddToPATH = TranslatedAction(MainWindow);
+ actionAddToPATH->setObjectName(QStringLiteral("actionAddToPATH"));
+ actionAddToPATH.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Install to &PATH"));
+ actionAddToPATH.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Install a prismlauncher symlink to /usr/local/bin"));
+ all_actions.append(&actionAddToPATH);
+ #endif
+
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
actionReportBug = TranslatedAction(MainWindow);
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
@@ -428,6 +442,11 @@ public:
actionLockToolbars.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Lock Toolbars"));
actionLockToolbars->setCheckable(true);
all_actions.append(&actionLockToolbars);
+
+ actionChangeTheme = TranslatedAction(MainWindow);
+ actionChangeTheme->setObjectName(QStringLiteral("actionChangeTheme"));
+ actionChangeTheme.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Themes"));
+ all_actions.append(&actionChangeTheme);
}
void createMainToolbar(QMainWindow *MainWindow)
@@ -455,6 +474,10 @@ public:
helpMenu->addAction(actionClearMetadata);
+ #ifdef Q_OS_MAC
+ helpMenu->addAction(actionAddToPATH);
+ #endif
+
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
helpMenu->addAction(actionReportBug);
}
@@ -509,8 +532,6 @@ public:
fileMenu->setSeparatorsCollapsible(false);
fileMenu->addAction(actionAddInstance);
fileMenu->addAction(actionLaunchInstance);
- fileMenu->addAction(actionLaunchInstanceOffline);
- fileMenu->addAction(actionLaunchInstanceDemo);
fileMenu->addAction(actionKillInstance);
fileMenu->addAction(actionCloseWindow);
fileMenu->addSeparator();
@@ -528,6 +549,8 @@ public:
viewMenu = menuBar->addMenu(tr("&View"));
viewMenu->setSeparatorsCollapsible(false);
+ viewMenu->addAction(actionChangeTheme);
+ viewMenu->addSeparator();
viewMenu->addAction(actionCAT);
viewMenu->addSeparator();
@@ -542,6 +565,9 @@ public:
helpMenu = menuBar->addMenu(tr("&Help"));
helpMenu->setSeparatorsCollapsible(false);
helpMenu->addAction(actionClearMetadata);
+ #ifdef Q_OS_MAC
+ helpMenu->addAction(actionAddToPATH);
+ #endif
helpMenu->addSeparator();
helpMenu->addAction(actionAbout);
helpMenu->addAction(actionOpenWiki);
@@ -555,10 +581,11 @@ public:
helpMenu->addAction(actionDISCORD);
if (!BuildConfig.SUBREDDIT_URL.isEmpty())
helpMenu->addAction(actionREDDIT);
- helpMenu->addSeparator();
if(BuildConfig.UPDATER_ENABLED)
+ {
+ helpMenu->addSeparator();
helpMenu->addAction(actionCheckUpdate);
-
+ }
MainWindow->setMenuBar(menuBar);
}
@@ -576,6 +603,7 @@ public:
actionOpenWiki->setObjectName(QStringLiteral("actionOpenWiki"));
actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &Help"));
actionOpenWiki.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki"));
+ actionOpenWiki->setIcon(APPLICATION->getThemedIcon("help"));
connect(actionOpenWiki, &QAction::triggered, MainWindow, &MainWindow::on_actionOpenWiki_triggered);
all_actions.append(&actionOpenWiki);
@@ -583,6 +611,7 @@ public:
actionNewsMenuBar->setObjectName(QStringLiteral("actionNewsMenuBar"));
actionNewsMenuBar.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &News"));
actionNewsMenuBar.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki"));
+ actionNewsMenuBar->setIcon(APPLICATION->getThemedIcon("news"));
connect(actionNewsMenuBar, &QAction::triggered, MainWindow, &MainWindow::on_actionMoreNews_triggered);
all_actions.append(&actionNewsMenuBar);
}
@@ -822,6 +851,7 @@ public:
createInstanceToolbar(MainWindow);
MainWindow->updateToolsMenu();
+ MainWindow->updateThemeMenu();
retranslateUi(MainWindow);
@@ -1271,6 +1301,38 @@ void MainWindow::updateToolsMenu()
ui->actionLaunchInstance->setMenu(launchMenu);
}
+void MainWindow::updateThemeMenu()
+{
+ QMenu *themeMenu = ui->actionChangeTheme->menu();
+
+ if (themeMenu) {
+ themeMenu->clear();
+ } else {
+ themeMenu = new QMenu(this);
+ }
+
+ auto themes = APPLICATION->getValidApplicationThemes();
+
+ QActionGroup* themesGroup = new QActionGroup( this );
+
+ for (auto* theme : themes) {
+ QAction * themeAction = themeMenu->addAction(theme->name());
+
+ themeAction->setCheckable(true);
+ if (APPLICATION->settings()->get("ApplicationTheme").toString() == theme->id()) {
+ themeAction->setChecked(true);
+ }
+ themeAction->setActionGroup(themesGroup);
+
+ connect(themeAction, &QAction::triggered, [theme]() {
+ APPLICATION->setApplicationTheme(theme->id(),false);
+ APPLICATION->settings()->set("ApplicationTheme", theme->id());
+ });
+ }
+
+ ui->actionChangeTheme->setMenu(themeMenu);
+}
+
void MainWindow::repopulateAccountsMenu()
{
accountMenu->clear();
@@ -1590,8 +1652,8 @@ void MainWindow::setCatBackground(bool enabled)
QString cat = APPLICATION->settings()->get("BackgroundCat").toString();
if (non_stupid_abs(now.daysTo(xmas)) <= 4) {
cat += "-xmas";
- } else if (cat == "kitteh" && non_stupid_abs(now.daysTo(halloween)) <= 4) {
- cat += "-ween";
+ } else if (non_stupid_abs(now.daysTo(halloween)) <= 4) {
+ cat += "-spooky";
} else if (non_stupid_abs(now.daysTo(birthday)) <= 12) {
cat += "-bday";
}
@@ -1651,7 +1713,7 @@ void MainWindow::on_actionCopyInstance_triggered()
if (!copyInstDlg.exec())
return;
- auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime());
+ auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.getChosenOptions());
copyTask->setName(copyInstDlg.instName());
copyTask->setGroup(copyInstDlg.instGroup());
copyTask->setIcon(copyInstDlg.iconKey());
@@ -1901,6 +1963,7 @@ void MainWindow::globalSettingsClosed()
proxymodel->sort(0);
updateMainToolBar();
updateToolsMenu();
+ updateThemeMenu();
updateStatusCenter();
// This needs to be done to prevent UI elements disappearing in the event the config is changed
// but Prism Launcher exits abnormally, causing the window state to never be saved:
@@ -1926,7 +1989,31 @@ void MainWindow::on_actionReportBug_triggered()
void MainWindow::on_actionClearMetadata_triggered()
{
APPLICATION->metacache()->evictAll();
+ APPLICATION->metacache()->SaveNow();
+}
+
+#ifdef Q_OS_MAC
+void MainWindow::on_actionAddToPATH_triggered()
+{
+ auto binaryPath = APPLICATION->applicationFilePath();
+ auto targetPath = QString("/usr/local/bin/%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
+ qDebug() << "Symlinking" << binaryPath << "to" << targetPath;
+
+ QStringList args;
+ args << "-e";
+ args << QString("do shell script \"mkdir -p /usr/local/bin && ln -sf '%1' '%2'\" with administrator privileges")
+ .arg(binaryPath, targetPath);
+ auto outcome = QProcess::execute("/usr/bin/osascript", args);
+ if (!outcome) {
+ QMessageBox::information(this, tr("Successfully added %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME),
+ tr("%1 was successfully added to your PATH. You can now start it by running `%2`.")
+ .arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.LAUNCHER_APP_BINARY_NAME));
+ } else {
+ QMessageBox::critical(this, tr("Failed to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME),
+ tr("An error occurred while trying to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME));
+ }
}
+#endif
void MainWindow::on_actionOpenWiki_triggered()
{
diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h
index f9d1f1c7..0aa01ee2 100644
--- a/launcher/ui/MainWindow.h
+++ b/launcher/ui/MainWindow.h
@@ -128,6 +128,10 @@ private slots:
void on_actionClearMetadata_triggered();
+ #ifdef Q_OS_MAC
+ void on_actionAddToPATH_triggered();
+ #endif
+
void on_actionOpenWiki_triggered();
void on_actionMoreNews_triggered();
@@ -170,6 +174,8 @@ private slots:
void updateToolsMenu();
+ void updateThemeMenu();
+
void instanceActivated(QModelIndex);
void instanceChanged(const QModelIndex &current, const QModelIndex &previous);
diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp
index fe87b517..edb4ff7d 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.cpp
+++ b/launcher/ui/dialogs/BlockedModsDialog.cpp
@@ -1,28 +1,321 @@
#include "BlockedModsDialog.h"
-#include "ui_BlockedModsDialog.h"
-#include <QPushButton>
-#include <QDialogButtonBox>
#include <QDesktopServices>
+#include <QDialogButtonBox>
+#include <QPushButton>
+#include "Application.h"
+#include "ui_BlockedModsDialog.h"
+#include <QDebug>
+#include <QDragEnterEvent>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QStandardPaths>
+
+BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
+ : QDialog(parent), ui(new Ui::BlockedModsDialog), m_mods(mods)
+{
+ m_hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10));
+ connect(m_hashing_task.get(), &Task::finished, this, &BlockedModsDialog::hashTaskFinished);
-BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls) :
- QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) {
ui->setupUi(this);
auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole);
connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll);
+ auto downloadFolderButton = ui->buttonBox->addButton(tr("Add Download Folder"), QDialogButtonBox::ActionRole);
+ connect(downloadFolderButton, &QPushButton::clicked, this, &BlockedModsDialog::addDownloadFolder);
+
+ connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged);
+
+ qDebug() << "[Blocked Mods Dialog] Mods List: " << mods;
+
+ setupWatch();
+ scanPaths();
+
this->setWindowTitle(title);
- ui->label->setText(text);
- ui->textBrowser->setText(body);
+ ui->labelDescription->setText(text);
+ ui->labelExplain->setText(
+ QString(tr("Your configured global mods folder and default downloads folder "
+ "are automatically checked for the downloaded mods and they will be copied to the instance if found.<br/>"
+ "Optionally, you may drag and drop the downloaded mods onto this dialog or add a folder to watch "
+ "if you did not download the mods to a default location."))
+ .arg(APPLICATION->settings()->get("CentralModsDir").toString(),
+ QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)));
+
+ // force all URL handeling as external
+ connect(ui->textBrowserWatched, &QTextBrowser::anchorClicked, this, [](const QUrl url) { QDesktopServices::openUrl(url); });
+
+ setAcceptDrops(true);
+
+ update();
}
-BlockedModsDialog::~BlockedModsDialog() {
+BlockedModsDialog::~BlockedModsDialog()
+{
delete ui;
}
-void BlockedModsDialog::openAll() {
- for(auto &url : urls) {
- QDesktopServices::openUrl(url);
+void BlockedModsDialog::dragEnterEvent(QDragEnterEvent* e)
+{
+ if (e->mimeData()->hasUrls()) {
+ e->acceptProposedAction();
+ }
+}
+
+void BlockedModsDialog::dropEvent(QDropEvent* e)
+{
+ for (const QUrl& url : e->mimeData()->urls()) {
+ QString filePath = url.toLocalFile();
+ qDebug() << "[Blocked Mods Dialog] Dropped file:" << filePath;
+ addHashTask(filePath);
+
+ // watch for changes
+ QFileInfo file = QFileInfo(filePath);
+ QString path = file.dir().absolutePath();
+ qDebug() << "[Blocked Mods Dialog] Adding watch path:" << path;
+ m_watcher.addPath(path);
+ }
+ scanPaths();
+ update();
+}
+
+void BlockedModsDialog::openAll()
+{
+ for (auto& mod : m_mods) {
+ QDesktopServices::openUrl(mod.websiteUrl);
+ }
+}
+
+void BlockedModsDialog::addDownloadFolder()
+{
+ QString dir =
+ QFileDialog::getExistingDirectory(this, tr("Select directory where you downloaded the mods"),
+ QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), QFileDialog::ShowDirsOnly);
+ qDebug() << "[Blocked Mods Dialog] Adding watch path:" << dir;
+ m_watcher.addPath(dir);
+ scanPath(dir, true);
+ update();
+}
+
+/// @brief update UI with current status of the blocked mod detection
+void BlockedModsDialog::update()
+{
+ QString text;
+ QString span;
+
+ for (auto& mod : m_mods) {
+ if (mod.matched) {
+ // &#x2714; -> html for HEAVY CHECK MARK : ✔
+ span = QString(tr("<span style=\"color:green\"> &#x2714; Found at %1 </span>")).arg(mod.localPath);
+ } else {
+ // &#x2718; -> html for HEAVY BALLOT X : ✘
+ span = QString(tr("<span style=\"color:red\"> &#x2718; Not Found </span>"));
+ }
+ text += QString(tr("%1: <a href='%2'>%2</a> <p>Hash: %3 %4</p> <br/>")).arg(mod.name, mod.websiteUrl, mod.hash, span);
+ }
+
+ ui->textBrowserModsListing->setText(text);
+
+ QString watching;
+ for (auto& dir : m_watcher.directories()) {
+ watching += QString("<a href=\"%1\">%1</a><br/>").arg(dir);
+ }
+
+ ui->textBrowserWatched->setText(watching);
+
+ if (allModsMatched()) {
+ ui->labelModsFound->setText("<span style=\"color:green\">✔</span>" + tr("All mods found"));
+ } else {
+ ui->labelModsFound->setText(tr("Please download the missing mods."));
+ }
+}
+
+/// @brief Signal fired when a watched direcotry has changed
+/// @param path the path to the changed directory
+void BlockedModsDialog::directoryChanged(QString path)
+{
+ qDebug() << "[Blocked Mods Dialog] Directory changed: " << path;
+ validateMatchedMods();
+ scanPath(path, true);
+}
+
+/// @brief add the user downloads folder and the global mods folder to the filesystem watcher
+void BlockedModsDialog::setupWatch()
+{
+ const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
+ const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString();
+ m_watcher.addPath(downloadsFolder);
+ m_watcher.addPath(modsFolder);
+}
+
+/// @brief scan all watched folder
+void BlockedModsDialog::scanPaths()
+{
+ for (auto& dir : m_watcher.directories()) {
+ scanPath(dir, false);
}
+ runHashTask();
+}
+
+/// @brief Scan the directory at path, skip paths that do not contain a file name
+/// of a blocked mod we are looking for
+/// @param path the directory to scan
+void BlockedModsDialog::scanPath(QString path, bool start_task)
+{
+ QDir scan_dir(path);
+ QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags);
+ while (scan_it.hasNext()) {
+ QString file = scan_it.next();
+
+ if (!checkValidPath(file)) {
+ continue;
+ }
+
+ addHashTask(file);
+ }
+
+ if (start_task) {
+ runHashTask();
+ }
+}
+
+/// @brief add a hashing task for the file located at path, add the path to the pending set if the hasing task is already running
+/// @param path the path to the local file being hashed
+void BlockedModsDialog::addHashTask(QString path)
+{
+ qDebug() << "[Blocked Mods Dialog] adding a Hash task for" << path << "to the pending set.";
+ m_pending_hash_paths.insert(path);
+}
+
+/// @brief add a hashing task for the file located at path and connect it to check that hash against
+/// our blocked mods list
+/// @param path the path to the local file being hashed
+void BlockedModsDialog::buildHashTask(QString path)
+{
+ auto hash_task = Hashing::createBlockedModHasher(path, ModPlatform::Provider::FLAME, "sha1");
+
+ qDebug() << "[Blocked Mods Dialog] Creating Hash task for path: " << path;
+
+ connect(hash_task.get(), &Task::succeeded, this, [this, hash_task, path] { checkMatchHash(hash_task->getResult(), path); });
+ connect(hash_task.get(), &Task::failed, this, [path] { qDebug() << "Failed to hash path: " << path; });
+
+ m_hashing_task->addTask(hash_task);
+}
+
+/// @brief check if the computed hash for the provided path matches a blocked
+/// mod we are looking for
+/// @param hash the computed hash for the provided path
+/// @param path the path to the local file being compared
+void BlockedModsDialog::checkMatchHash(QString hash, QString path)
+{
+ bool match = false;
+
+ qDebug() << "[Blocked Mods Dialog] Checking for match on hash: " << hash << "| From path:" << path;
+
+ for (auto& mod : m_mods) {
+ if (mod.matched) {
+ continue;
+ }
+ if (mod.hash.compare(hash, Qt::CaseInsensitive) == 0) {
+ mod.matched = true;
+ mod.localPath = path;
+ match = true;
+
+ qDebug() << "[Blocked Mods Dialog] Hash match found:" << mod.name << hash << "| From path:" << path;
+
+ break;
+ }
+ }
+
+ if (match) {
+ update();
+ }
+}
+
+/// @brief Check if the name of the file at path matches the name of a blocked mod we are searching for
+/// @param path the path to check
+/// @return boolean: did the path match the name of a blocked mod?
+bool BlockedModsDialog::checkValidPath(QString path)
+{
+ QFileInfo file = QFileInfo(path);
+ QString filename = file.fileName();
+
+ for (auto& mod : m_mods) {
+ if (mod.name.compare(filename, Qt::CaseInsensitive) == 0) {
+ qDebug() << "[Blocked Mods Dialog] Name match found:" << mod.name << "| From path:" << path;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool BlockedModsDialog::allModsMatched()
+{
+ return std::all_of(m_mods.begin(), m_mods.end(), [](auto const& mod) { return mod.matched; });
+}
+
+/// @brief ensure matched file paths still exist
+void BlockedModsDialog::validateMatchedMods()
+{
+ bool changed = false;
+ for (auto& mod : m_mods) {
+ if (mod.matched) {
+ QFileInfo file = QFileInfo(mod.localPath);
+ if (!file.exists() || !file.isFile()) {
+ qDebug() << "[Blocked Mods Dialog] File" << mod.localPath << "for mod" << mod.name
+ << "has vanshed! marking as not matched.";
+ mod.localPath = "";
+ mod.matched = false;
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ update();
+ }
+}
+
+/// @brief run hash task or mark a pending run if it is already runing
+void BlockedModsDialog::runHashTask()
+{
+ if (!m_hashing_task->isRunning()) {
+ m_rehash_pending = false;
+
+ if (!m_pending_hash_paths.isEmpty()) {
+ qDebug() << "[Blocked Mods Dialog] there are pending hash tasks, building and running tasks";
+
+ auto path = m_pending_hash_paths.begin();
+ while (path != m_pending_hash_paths.end()) {
+ buildHashTask(*path);
+ path = m_pending_hash_paths.erase(path);
+ }
+
+ m_hashing_task->start();
+ }
+ } else {
+ qDebug() << "[Blocked Mods Dialog] queueing another run of the hashing task";
+ qDebug() << "[Blocked Mods Dialog] pending hash tasks:" << m_pending_hash_paths;
+ m_rehash_pending = true;
+ }
+}
+
+void BlockedModsDialog::hashTaskFinished()
+{
+ qDebug() << "[Blocked Mods Dialog] All hash tasks finished";
+ if (m_rehash_pending) {
+ qDebug() << "[Blocked Mods Dialog] task finished with a rehash pending, rerunning";
+ runHashTask();
+ }
+}
+
+/// qDebug print support for the BlockedMod struct
+QDebug operator<<(QDebug debug, const BlockedMod& m)
+{
+ QDebugStateSaver saver(debug);
+
+ debug.nospace() << "{ name: " << m.name << ", websiteUrl: " << m.websiteUrl << ", hash: " << m.hash << ", matched: " << m.matched
+ << ", localPath: " << m.localPath << "}";
+
+ return debug;
}
diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h
index 5f5bd61b..dac43cba 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.h
+++ b/launcher/ui/dialogs/BlockedModsDialog.h
@@ -1,7 +1,23 @@
#pragma once
#include <QDialog>
+#include <QString>
+#include <QList>
+#include <QFileSystemWatcher>
+
+#include "modplatform/helpers/HashUtils.h"
+
+#include "tasks/ConcurrentTask.h"
+
+struct BlockedMod {
+ QString name;
+ QString websiteUrl;
+ QString hash;
+ bool matched;
+ QString localPath;
+
+};
QT_BEGIN_NAMESPACE
namespace Ui { class BlockedModsDialog; }
@@ -11,12 +27,38 @@ class BlockedModsDialog : public QDialog {
Q_OBJECT
public:
- BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls);
+ BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, QList<BlockedMod> &mods);
~BlockedModsDialog() override;
+protected:
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void dropEvent(QDropEvent *event) override;
+
private:
Ui::BlockedModsDialog *ui;
- const QList<QUrl> &urls;
+ QList<BlockedMod> &m_mods;
+ QFileSystemWatcher m_watcher;
+ shared_qobject_ptr<ConcurrentTask> m_hashing_task;
+ QSet<QString> m_pending_hash_paths;
+ bool m_rehash_pending;
+
void openAll();
+ void addDownloadFolder();
+ void update();
+ void directoryChanged(QString path);
+ void setupWatch();
+ void scanPaths();
+ void scanPath(QString path, bool start_task);
+ void addHashTask(QString path);
+ void buildHashTask(QString path);
+ void checkMatchHash(QString hash, QString path);
+ void validateMatchedMods();
+ void runHashTask();
+ void hashTaskFinished();
+
+ bool checkValidPath(QString path);
+ bool allModsMatched();
};
+
+QDebug operator<<(QDebug debug, const BlockedMod &m);
diff --git a/launcher/ui/dialogs/BlockedModsDialog.ui b/launcher/ui/dialogs/BlockedModsDialog.ui
index f4ae95b6..88105178 100644
--- a/launcher/ui/dialogs/BlockedModsDialog.ui
+++ b/launcher/ui/dialogs/BlockedModsDialog.ui
@@ -13,29 +13,41 @@
<property name="windowTitle">
<string notr="true">BlockedModsDialog</string>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="label">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="labelDescription">
<property name="text">
<string notr="true"/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
+ <item>
+ <widget class="QLabel" name="labelExplain">
+ <property name="text">
+ <string/>
</property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QTextBrowser" name="textBrowser">
+ <item>
+ <widget class="QTextBrowser" name="textBrowserModsListing">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>165</height>
+ </size>
+ </property>
<property name="acceptRichText">
<bool>true</bool>
</property>
@@ -44,6 +56,68 @@
</property>
</widget>
</item>
+ <item>
+ <widget class="QLabel" name="labelWatched">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>1</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Watched Folders:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTextBrowser" name="textBrowserWatched">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>16</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>12</height>
+ </size>
+ </property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="bottomBoxH">
+ <item>
+ <widget class="QLabel" name="labelModsFound">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
<resources/>
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 9ec341bc..3f5122f6 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -44,7 +44,6 @@
#include "BaseVersion.h"
#include "icons/IconList.h"
-#include "tasks/Task.h"
#include "BaseInstance.h"
#include "InstanceList.h"
@@ -78,8 +77,14 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
}
ui->groupBox->setCurrentIndex(index);
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
- ui->copySavesCheckbox->setChecked(m_copySaves);
- ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime);
+ ui->copySavesCheckbox->setChecked(m_selectedOptions.isCopySavesEnabled());
+ ui->keepPlaytimeCheckbox->setChecked(m_selectedOptions.isKeepPlaytimeEnabled());
+ ui->copyGameOptionsCheckbox->setChecked(m_selectedOptions.isCopyGameOptionsEnabled());
+ ui->copyResPacksCheckbox->setChecked(m_selectedOptions.isCopyResourcePacksEnabled());
+ ui->copyShaderPacksCheckbox->setChecked(m_selectedOptions.isCopyShaderPacksEnabled());
+ ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
+ ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
+ ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -117,6 +122,31 @@ QString CopyInstanceDialog::instGroup() const
return ui->groupBox->currentText();
}
+const InstanceCopyPrefs& CopyInstanceDialog::getChosenOptions() const
+{
+ return m_selectedOptions;
+}
+
+void CopyInstanceDialog::checkAllCheckboxes(const bool& b)
+{
+ ui->keepPlaytimeCheckbox->setChecked(b);
+ ui->copySavesCheckbox->setChecked(b);
+ ui->copyGameOptionsCheckbox->setChecked(b);
+ ui->copyResPacksCheckbox->setChecked(b);
+ ui->copyShaderPacksCheckbox->setChecked(b);
+ ui->copyServersCheckbox->setChecked(b);
+ ui->copyModsCheckbox->setChecked(b);
+ ui->copyScreenshotsCheckbox->setChecked(b);
+}
+
+// Check the "Select all" checkbox if all options are already selected:
+void CopyInstanceDialog::updateSelectAllCheckbox()
+{
+ ui->selectAllCheckbox->blockSignals(true);
+ ui->selectAllCheckbox->setChecked(m_selectedOptions.allTrue());
+ ui->selectAllCheckbox->blockSignals(false);
+}
+
void CopyInstanceDialog::on_iconButton_clicked()
{
IconPickerDialog dlg(this);
@@ -129,42 +159,64 @@ void CopyInstanceDialog::on_iconButton_clicked()
}
}
+
void CopyInstanceDialog::on_instNameTextBox_textChanged(const QString &arg1)
{
updateDialogState();
}
-bool CopyInstanceDialog::shouldCopySaves() const
+void CopyInstanceDialog::on_selectAllCheckbox_stateChanged(int state)
{
- return m_copySaves;
+ bool checked;
+ checked = (state == Qt::Checked);
+ checkAllCheckboxes(checked);
}
void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
{
- if(state == Qt::Unchecked)
- {
- m_copySaves = false;
- }
- else if(state == Qt::Checked)
- {
- m_copySaves = true;
- }
+ m_selectedOptions.enableCopySaves(state == Qt::Checked);
+ updateSelectAllCheckbox();
}
-bool CopyInstanceDialog::shouldKeepPlaytime() const
+
+void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
{
- return m_keepPlaytime;
+ m_selectedOptions.enableKeepPlaytime(state == Qt::Checked);
+ updateSelectAllCheckbox();
}
+void CopyInstanceDialog::on_copyGameOptionsCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableCopyGameOptions(state == Qt::Checked);
+ updateSelectAllCheckbox();
+}
-void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
+void CopyInstanceDialog::on_copyResPacksCheckbox_stateChanged(int state)
{
- if(state == Qt::Unchecked)
- {
- m_keepPlaytime = false;
- }
- else if(state == Qt::Checked)
- {
- m_keepPlaytime = true;
- }
+ m_selectedOptions.enableCopyResourcePacks(state == Qt::Checked);
+ updateSelectAllCheckbox();
+}
+
+void CopyInstanceDialog::on_copyShaderPacksCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableCopyShaderPacks(state == Qt::Checked);
+ updateSelectAllCheckbox();
+}
+
+void CopyInstanceDialog::on_copyServersCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableCopyServers(state == Qt::Checked);
+ updateSelectAllCheckbox();
+}
+
+void CopyInstanceDialog::on_copyModsCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableCopyMods(state == Qt::Checked);
+ updateSelectAllCheckbox();
+}
+
+void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableCopyScreenshots(state == Qt::Checked);
+ updateSelectAllCheckbox();
}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index bf3cd920..884501d1 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -17,7 +17,7 @@
#include <QDialog>
#include "BaseVersion.h"
-#include <BaseInstance.h>
+#include "InstanceCopyPrefs.h"
class BaseInstance;
@@ -39,20 +39,29 @@ public:
QString instName() const;
QString instGroup() const;
QString iconKey() const;
- bool shouldCopySaves() const;
- bool shouldKeepPlaytime() const;
+ const InstanceCopyPrefs& getChosenOptions() const;
private
slots:
void on_iconButton_clicked();
void on_instNameTextBox_textChanged(const QString &arg1);
+ // Checkboxes
+ void on_selectAllCheckbox_stateChanged(int state);
void on_copySavesCheckbox_stateChanged(int state);
void on_keepPlaytimeCheckbox_stateChanged(int state);
+ void on_copyGameOptionsCheckbox_stateChanged(int state);
+ void on_copyResPacksCheckbox_stateChanged(int state);
+ void on_copyShaderPacksCheckbox_stateChanged(int state);
+ void on_copyServersCheckbox_stateChanged(int state);
+ void on_copyModsCheckbox_stateChanged(int state);
+ void on_copyScreenshotsCheckbox_stateChanged(int state);
private:
+ void checkAllCheckboxes(const bool& b);
+ void updateSelectAllCheckbox();
+ /* data */
Ui::CopyInstanceDialog *ui;
QString InstIconKey;
InstancePtr m_original;
- bool m_copySaves = true;
- bool m_keepPlaytime = true;
+ InstanceCopyPrefs m_selectedOptions;
};
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index f4b191e2..b7828fe3 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>345</width>
- <height>323</height>
+ <width>341</width>
+ <height>399</height>
</rect>
</property>
<property name="windowTitle">
@@ -33,7 +33,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
+ <width>60</width>
<height>20</height>
</size>
</property>
@@ -60,7 +60,7 @@
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>40</width>
+ <width>60</width>
<height>20</height>
</size>
</property>
@@ -83,7 +83,10 @@
</widget>
</item>
<item>
- <layout class="QGridLayout" name="gridLayout">
+ <layout class="QGridLayout" name="groupDropdownLayout">
+ <property name="verticalSpacing">
+ <number>6</number>
+ </property>
<item row="0" column="0">
<widget class="QLabel" name="labelVersion_3">
<property name="text">
@@ -110,18 +113,96 @@
</layout>
</item>
<item>
- <widget class="QCheckBox" name="copySavesCheckbox">
- <property name="text">
- <string>Copy saves</string>
- </property>
- </widget>
+ <layout class="QHBoxLayout" name="selectAllButtonLayout">
+ <item>
+ <widget class="QCheckBox" name="selectAllCheckbox">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Select all</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
- <widget class="QCheckBox" name="keepPlaytimeCheckbox">
- <property name="text">
- <string>Keep play time</string>
- </property>
- </widget>
+ <layout class="QGridLayout" name="copyOptionsLayout">
+ <item row="6" column="1">
+ <widget class="QCheckBox" name="copyModsCheckbox">
+ <property name="toolTip">
+ <string>Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.</string>
+ </property>
+ <property name="text">
+ <string>Copy mods</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QCheckBox" name="copyGameOptionsCheckbox">
+ <property name="toolTip">
+ <string>Copy the in-game options like FOV, max framerate, etc.</string>
+ </property>
+ <property name="text">
+ <string>Copy game options</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QCheckBox" name="copySavesCheckbox">
+ <property name="text">
+ <string>Copy saves</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QCheckBox" name="copyShaderPacksCheckbox">
+ <property name="text">
+ <string>Copy shader packs</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QCheckBox" name="copyServersCheckbox">
+ <property name="text">
+ <string>Copy servers</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QCheckBox" name="copyResPacksCheckbox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Copy resource packs</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="keepPlaytimeCheckbox">
+ <property name="text">
+ <string>Keep play time</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QCheckBox" name="copyScreenshotsCheckbox">
+ <property name="text">
+ <string>Copy screenshots</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
@@ -139,8 +220,6 @@
<tabstop>iconButton</tabstop>
<tabstop>instNameTextBox</tabstop>
<tabstop>groupBox</tabstop>
- <tabstop>copySavesCheckbox</tabstop>
- <tabstop>keepPlaytimeCheckbox</tabstop>
</tabstops>
<resources>
<include location="../../graphics.qrc"/>
@@ -153,8 +232,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
- <x>248</x>
- <y>254</y>
+ <x>254</x>
+ <y>316</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@@ -169,8 +248,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
- <x>316</x>
- <y>260</y>
+ <x>322</x>
+ <y>316</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
diff --git a/launcher/ui/dialogs/ExportInstanceDialog.cpp b/launcher/ui/dialogs/ExportInstanceDialog.cpp
index 9f32dd8e..88552b23 100644
--- a/launcher/ui/dialogs/ExportInstanceDialog.cpp
+++ b/launcher/ui/dialogs/ExportInstanceDialog.cpp
@@ -39,13 +39,12 @@
#include <MMCZip.h>
#include <QFileDialog>
#include <QMessageBox>
-#include <qfilesystemmodel.h>
+#include <QFileSystemModel>
#include <QSortFilterProxyModel>
#include <QDebug>
-#include <qstack.h>
#include <QSaveFile>
-#include "MMCStrings.h"
+#include "StringUtils.h"
#include "SeparatorPrefixTree.h"
#include "Application.h"
#include <icons/IconList.h>
@@ -85,7 +84,7 @@ public:
// sort and proxy model breaks the original model...
if (sortColumn() == 0)
{
- return Strings::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(),
+ return StringUtils::naturalCompare(leftFileInfo.fileName(), rightFileInfo.fileName(),
Qt::CaseInsensitive) < 0;
}
if (sortColumn() == 1)
@@ -94,7 +93,7 @@ public:
auto rightSize = rightFileInfo.size();
if ((leftSize == rightSize) || (leftFileInfo.isDir() && rightFileInfo.isDir()))
{
- return Strings::naturalCompare(leftFileInfo.fileName(),
+ return StringUtils::naturalCompare(leftFileInfo.fileName(),
rightFileInfo.fileName(),
Qt::CaseInsensitive) < 0
? asc
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index d740c8cb..24d23ba9 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 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
@@ -131,6 +132,8 @@ QList<BasePage*> ModDownloadDialog::getPages()
if (APPLICATION->capabilities() & Application::SupportsFlame)
pages.append(FlameModPage::create(this, m_instance));
+ m_selectedPage = dynamic_cast<ModPage*>(pages[0]);
+
return pages;
}
@@ -178,12 +181,22 @@ void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* select
return;
}
- auto* selected_page = dynamic_cast<ModPage*>(selected);
- if (!selected_page) {
+ m_selectedPage = dynamic_cast<ModPage*>(selected);
+ if (!m_selectedPage) {
qCritical() << "Page '" << selected->displayName() << "' in ModDownloadDialog is not a ModPage!";
return;
}
// Same effect as having a global search bar
- selected_page->setSearchTerm(prev_page->getSearchTerm());
+ m_selectedPage->setSearchTerm(prev_page->getSearchTerm());
+}
+
+bool ModDownloadDialog::selectPage(QString pageId)
+{
+ return m_container->selectPage(pageId);
+}
+
+ModPage* ModDownloadDialog::getSelectedPage()
+{
+ return m_selectedPage;
}
diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h
index 18a5f0f3..fcf6f4fc 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.h
+++ b/launcher/ui/dialogs/ModDownloadDialog.h
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 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
@@ -32,13 +33,14 @@ class ModDownloadDialog;
class PageContainer;
class QDialogButtonBox;
+class ModPage;
class ModrinthModPage;
class ModDownloadDialog final : public QDialog, public BasePageProvider
{
Q_OBJECT
-public:
+ public:
explicit ModDownloadDialog(const std::shared_ptr<ModFolderModel>& mods, QWidget* parent, BaseInstance* instance);
~ModDownloadDialog() override = default;
@@ -51,22 +53,26 @@ public:
bool isModSelected(QString name) const;
const QList<ModDownloadTask*> getTasks();
- const std::shared_ptr<ModFolderModel> &mods;
+ const std::shared_ptr<ModFolderModel>& mods;
-public slots:
+ bool selectPage(QString pageId);
+ ModPage* getSelectedPage();
+
+ public slots:
void confirm();
void accept() override;
void reject() override;
-private slots:
+ private slots:
void selectedPageChanged(BasePage* previous, BasePage* selected);
-private:
- Ui::ModDownloadDialog *ui = nullptr;
- PageContainer * m_container = nullptr;
- QDialogButtonBox * m_buttons = nullptr;
- QVBoxLayout *m_verticalLayout = nullptr;
+ private:
+ Ui::ModDownloadDialog* ui = nullptr;
+ PageContainer* m_container = nullptr;
+ QDialogButtonBox* m_buttons = nullptr;
+ QVBoxLayout* m_verticalLayout = nullptr;
+ ModPage* m_selectedPage = nullptr;
QHash<QString, ModDownloadTask*> modTask;
- BaseInstance *m_instance;
+ BaseInstance* m_instance;
};
diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp
index 05269f62..da73a449 100644
--- a/launcher/ui/dialogs/ProgressDialog.cpp
+++ b/launcher/ui/dialogs/ProgressDialog.cpp
@@ -44,7 +44,8 @@ void ProgressDialog::setSkipButton(bool present, QString label)
void ProgressDialog::on_skipButton_clicked(bool checked)
{
Q_UNUSED(checked);
- task->abort();
+ if (ui->skipButton->isEnabled()) // prevent other triggers from aborting
+ task->abort();
}
ProgressDialog::~ProgressDialog()
diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp
index 6661bf0f..cae0635f 100644
--- a/launcher/ui/pages/global/LauncherPage.cpp
+++ b/launcher/ui/pages/global/LauncherPage.cpp
@@ -303,21 +303,27 @@ void LauncherPage::applySettings()
s->set("IconTheme", "pe_blue");
break;
case 4:
- s->set("IconTheme", "OSX");
+ s->set("IconTheme", "breeze_light");
break;
case 5:
- s->set("IconTheme", "iOS");
+ s->set("IconTheme", "breeze_dark");
break;
case 6:
- s->set("IconTheme", "flat");
+ s->set("IconTheme", "OSX");
break;
case 7:
- s->set("IconTheme", "flat_white");
+ s->set("IconTheme", "iOS");
break;
case 8:
- s->set("IconTheme", "multimc");
+ s->set("IconTheme", "flat");
break;
case 9:
+ s->set("IconTheme", "flat_white");
+ break;
+ case 10:
+ s->set("IconTheme", "multimc");
+ break;
+ case 11:
s->set("IconTheme", "custom");
break;
}
@@ -397,7 +403,18 @@ void LauncherPage::loadSettings()
m_currentUpdateChannel = s->get("UpdateChannel").toString();
//FIXME: make generic
auto theme = s->get("IconTheme").toString();
- QStringList iconThemeOptions{"pe_colored", "pe_light", "pe_dark", "pe_blue", "OSX", "iOS", "flat", "flat_white", "multimc", "custom"};
+ QStringList iconThemeOptions{"pe_colored",
+ "pe_light",
+ "pe_dark",
+ "pe_blue",
+ "breeze_light",
+ "breeze_dark",
+ "OSX",
+ "iOS",
+ "flat",
+ "flat_white",
+ "multimc",
+ "custom"};
ui->themeComboBox->setCurrentIndex(iconThemeOptions.indexOf(theme));
auto cat = s->get("BackgroundCat").toString();
diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui
index 6de644ee..c44718a1 100644
--- a/launcher/ui/pages/global/LauncherPage.ui
+++ b/launcher/ui/pages/global/LauncherPage.ui
@@ -287,6 +287,16 @@
</item>
<item>
<property name="text">
+ <string>Breeze Light</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Breeze Dark</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string notr="true">OSX</string>
</property>
</item>
diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui
index 76f8ec18..33a03336 100644
--- a/launcher/ui/pages/instance/ExternalResourcesPage.ui
+++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui
@@ -27,11 +27,7 @@
<item row="4" column="1" colspan="3">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
- <widget class="QLineEdit" name="filterEdit">
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
+ <widget class="QLineEdit" name="filterEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="filterLabel">
diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp
index 5e8bd7cc..d64bcb76 100644
--- a/launcher/ui/pages/instance/ServersPage.cpp
+++ b/launcher/ui/pages/instance/ServersPage.cpp
@@ -400,11 +400,11 @@ public:
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
- return m_servers.size();
+ return parent.isValid() ? 0 : m_servers.size();
}
int columnCount(const QModelIndex & parent) const override
{
- return COLUMN_COUNT;
+ return parent.isValid() ? 0 : COLUMN_COUNT;
}
Server * at(int index)
diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui
index fcba5598..14b7cd9f 100644
--- a/launcher/ui/pages/instance/VersionPage.ui
+++ b/launcher/ui/pages/instance/VersionPage.ui
@@ -48,11 +48,7 @@
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
- <widget class="QLineEdit" name="filterEdit">
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
+ <widget class="QLineEdit" name="filterEdit"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="filterLabel">
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index d2636d87..36840649 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -20,8 +20,8 @@ class ListModel : public QAbstractListModel {
ListModel(ModPage* parent);
~ListModel() override;
- inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
- inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
+ inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
+ inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
auto debugName() const -> QString;
@@ -41,12 +41,12 @@ class ListModel : public QAbstractListModel {
void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index);
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
- virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};
+ virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
- inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
+ inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
public slots:
void searchRequestFinished(QJsonDocument& doc);
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index f2c1746f..677bc4d6 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 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
@@ -37,7 +38,9 @@
#include "Application.h"
#include "ui_ModPage.h"
+#include <QDesktopServices>
#include <QKeyEvent>
+#include <QRegularExpression>
#include <memory>
#include <HoeDown.h>
@@ -80,6 +83,8 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
ui->packView->setItemDelegate(new ProjectItemDelegate(this));
ui->packView->installEventFilter(this);
+
+ connect(ui->packDescription, &QTextBrowser::anchorClicked, this, &ModPage::openUrl);
}
ModPage::~ModPage()
@@ -158,8 +163,8 @@ void ModPage::triggerSearch()
{
auto changed = m_filter_widget->changed();
m_filter = m_filter_widget->getFilter();
-
- if(changed){
+
+ if (changed) {
ui->packView->clearSelection();
ui->packDescription->clear();
ui->versionSelectionBox->clear();
@@ -241,6 +246,79 @@ void ModPage::onModSelected()
ui->packView->adjustSize();
}
+static const QRegularExpression modrinth(QRegularExpression::anchoredPattern("(?:www\\.)?modrinth\\.com\\/mod\\/([^\\/]+)\\/?"));
+static const QRegularExpression curseForge(QRegularExpression::anchoredPattern("(?:www\\.)?curseforge\\.com\\/minecraft\\/mc-mods\\/([^\\/]+)\\/?"));
+static const QRegularExpression curseForgeOld(QRegularExpression::anchoredPattern("minecraft\\.curseforge\\.com\\/projects\\/([^\\/]+)\\/?"));
+
+void ModPage::openUrl(const QUrl& url)
+{
+ // do not allow other url schemes for security reasons
+ if (!(url.scheme() == "http" || url.scheme() == "https")) {
+ qWarning() << "Unsupported scheme" << url.scheme();
+ return;
+ }
+
+ // detect mod URLs and search instead
+
+ const QString address = url.host() + url.path();
+ QRegularExpressionMatch match;
+ QString page;
+
+ match = modrinth.match(address);
+ if (match.hasMatch())
+ page = "modrinth";
+ else if (APPLICATION->capabilities() & Application::SupportsFlame) {
+ match = curseForge.match(address);
+ if (!match.hasMatch())
+ match = curseForgeOld.match(address);
+
+ if (match.hasMatch())
+ page = "curseforge";
+ }
+
+ if (!page.isNull()) {
+ const QString slug = match.captured(1);
+
+ // ensure the user isn't opening the same mod
+ if (slug != current.slug) {
+ dialog->selectPage(page);
+
+ ModPage* newPage = dialog->getSelectedPage();
+
+ QLineEdit* searchEdit = newPage->ui->searchEdit;
+ ModPlatform::ListModel* model = newPage->listModel;
+ QListView* view = newPage->ui->packView;
+
+ auto jump = [url, slug, model, view] {
+ for (int row = 0; row < model->rowCount({}); row++) {
+ const QModelIndex index = model->index(row);
+ const auto pack = model->data(index, Qt::UserRole).value<ModPlatform::IndexedPack>();
+
+ if (pack.slug == slug) {
+ view->setCurrentIndex(index);
+ return;
+ }
+ }
+
+ // The final fallback.
+ QDesktopServices::openUrl(url);
+ };
+
+ searchEdit->setText(slug);
+ newPage->triggerSearch();
+
+ if (model->activeJob())
+ connect(model->activeJob(), &Task::finished, jump);
+ else
+ jump();
+
+ return;
+ }
+ }
+
+ // open in the user's web browser
+ QDesktopServices::openUrl(url);
+}
/******** Make changes to the UI ********/
@@ -270,8 +348,8 @@ void ModPage::updateModVersions(int prev_count)
if ((valid || m_filter->versions.empty()) && !optedOut(version))
ui->versionSelectionBox->addItem(version.version, QVariant(i));
}
- if (ui->versionSelectionBox->count() == 0 && prev_count != 0) {
- ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
+ if (ui->versionSelectionBox->count() == 0 && prev_count != 0) {
+ ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
}
@@ -317,8 +395,7 @@ void ModPage::updateUi()
text += "<br>" + tr(" by ") + authorStrs.join(", ");
}
-
- if(current.extraDataLoaded) {
+ if (current.extraDataLoaded) {
if (!current.extraData.donate.isEmpty()) {
text += "<br><br>" + tr("Donate information: ");
auto donateToStr = [](ModPlatform::DonationData& donate) -> QString {
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index ae3d7e77..c9ccbaf2 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -82,6 +82,7 @@ class ModPage : public QWidget, public BasePage {
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
void onModSelected();
+ virtual void openUrl(const QUrl& url);
protected:
Ui::ModPage* ui = nullptr;
diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui
index 943f02aa..94365aa5 100644
--- a/launcher/ui/pages/modplatform/ModPage.ui
+++ b/launcher/ui/pages/modplatform/ModPage.ui
@@ -16,10 +16,10 @@
<item row="1" column="2">
<widget class="ProjectDescriptionPage" name="packDescription">
<property name="openExternalLinks">
- <bool>true</bool>
+ <bool>false</bool>
</property>
<property name="openLinks">
- <bool>true</bool>
+ <bool>false</bool>
</property>
</widget>
</item>
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
index c1ab166b..0887ebee 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -20,7 +20,8 @@
#include <modplatform/atlauncher/ATLPackIndex.h>
#include <Version.h>
-#include <MMCStrings.h>
+
+#include "StringUtils.h"
namespace Atl {
@@ -86,7 +87,7 @@ bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) co
return lv < rv;
}
else if (currentSorting == ByName) {
- return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
}
// Invalid sorting set, somehow...
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
index ef9a9268..2ce04068 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -32,12 +32,12 @@ ListModel::~ListModel()
int ListModel::rowCount(const QModelIndex &parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
index 9138dcbb..cdb4532c 100644
--- a/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+++ b/launcher/ui/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -75,12 +75,12 @@ QVector<QString> AtlOptionalModListModel::getResult() {
}
int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const {
- return m_mods.size();
+ return parent.isValid() ? 0 : m_mods.size();
}
int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const {
// Enabled, Name, Description
- return 3;
+ return parent.isValid() ? 0 : 3;
}
QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const {
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
index fd6e32ff..bad78c97 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 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
@@ -39,7 +40,7 @@
#include "FlameModModel.h"
#include "ui/dialogs/ModDownloadDialog.h"
-FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
+FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
: ModPage(dialog, instance, new FlameAPI())
{
listModel = new FlameMod::ListModel(this);
@@ -53,7 +54,7 @@ FlameModPage::FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance)
ui->sortByBox->addItem(tr("Sort by Author"));
ui->sortByBox->addItem(tr("Sort by Downloads"));
- // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
+ // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
// so it's best not to connect them in the parent's contructor...
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlameModPage::onSelectionChanged);
@@ -78,3 +79,19 @@ bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const
// other mod providers start loading before being selected, at least with
// my Qt, so we need to implement this in every derived class...
auto FlameModPage::shouldDisplay() const -> bool { return true; }
+
+void FlameModPage::openUrl(const QUrl& url)
+{
+ if (url.scheme().isEmpty()) {
+ QString query = url.query(QUrl::FullyDecoded);
+
+ if (query.startsWith("remoteUrl=")) {
+ // attempt to resolve url from warning page
+ query.remove(0, 10);
+ ModPage::openUrl({QUrl::fromPercentEncoding(query.toUtf8())}); // double decoding is necessary
+ return;
+ }
+ }
+
+ ModPage::openUrl(url);
+}
diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h
index 50dedd6f..58479ab9 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModPage.h
+++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h
@@ -1,7 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
- * PolyMC - Minecraft Launcher
+ * Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 Sefa Eyeoglu <contact@scrumplex.net>
+ * Copyright (C) 2022 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
@@ -64,4 +65,6 @@ class FlameModPage : public ModPage {
bool optedOut(ModPlatform::IndexedVersion& ver) const override;
auto shouldDisplay() const -> bool override;
+
+ void openUrl(const QUrl& url) override;
};
diff --git a/launcher/ui/pages/modplatform/flame/FlameModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
index 9f8605eb..127c3de5 100644
--- a/launcher/ui/pages/modplatform/flame/FlameModel.cpp
+++ b/launcher/ui/pages/modplatform/flame/FlameModel.cpp
@@ -3,7 +3,6 @@
#include "Application.h"
#include "ui/widgets/ProjectItem.h"
-#include <MMCStrings.h>
#include <Version.h>
#include <QtMath>
@@ -16,12 +15,12 @@ ListModel::~ListModel() {}
int ListModel::rowCount(const QModelIndex& parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex& parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex& index, int role) const
diff --git a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
index cbf347fc..e2b548f2 100644
--- a/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
+++ b/launcher/ui/pages/modplatform/ftb/FtbFilterModel.cpp
@@ -19,7 +19,8 @@
#include <QDebug>
#include "modplatform/modpacksch/FTBPackManifest.h"
-#include <MMCStrings.h>
+
+#include "StringUtils.h"
namespace Ftb {
@@ -81,7 +82,7 @@ bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) co
return leftPack.installs < rightPack.installs;
}
else if (currentSorting == ByName) {
- return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
}
// Invalid sorting set, somehow...
diff --git a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
index 3a149944..ce2b2b18 100644
--- a/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
+++ b/launcher/ui/pages/modplatform/ftb/FtbListModel.cpp
@@ -34,12 +34,12 @@ ListModel::~ListModel()
int ListModel::rowCount(const QModelIndex &parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
index 2d135e59..6b1f6b89 100644
--- a/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
+++ b/launcher/ui/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -36,7 +36,7 @@
#include "ListModel.h"
#include "Application.h"
-#include <MMCStrings.h>
+#include "StringUtils.h"
#include <Version.h>
#include <QtMath>
@@ -66,7 +66,7 @@ bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) co
return lv < rv;
} else if(currentSorting == Sorting::ByName) {
- return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ return StringUtils::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
}
//UHM, some inavlid value set?!
@@ -125,12 +125,12 @@ QString ListModel::translatePackType(PackType type) const
int ListModel::rowCount(const QModelIndex &parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
int ListModel::columnCount(const QModelIndex &parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
index 62e417c8..c531ea90 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp
@@ -53,7 +53,7 @@ ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instan
ui->sortByBox->addItem(tr("Sort by Last Updated"));
ui->sortByBox->addItem(tr("Sort by Newest"));
- // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
+ // sometimes Qt just ignores virtual slots and doesn't work as intended it seems,
// so it's best not to connect them in the parent's constructor...
connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged);
diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
index 6f33e11e..3be377a1 100644
--- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
+++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h
@@ -55,8 +55,8 @@ class ModpackListModel : public QAbstractListModel {
ModpackListModel(ModrinthPage* parent);
~ModpackListModel() override = default;
- inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
- inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
+ inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
+ inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
auto debugName() const -> QString;
@@ -74,7 +74,7 @@ class ModpackListModel : public QAbstractListModel {
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
- inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
+ inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
public slots:
void searchRequestFinished(QJsonDocument& doc_all);
diff --git a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
index 742f4f2a..b2af1ac0 100644
--- a/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
+++ b/launcher/ui/pages/modplatform/technic/TechnicModel.cpp
@@ -80,14 +80,14 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
return QVariant();
}
-int Technic::ListModel::columnCount(const QModelIndex&) const
+int Technic::ListModel::columnCount(const QModelIndex& parent) const
{
- return 1;
+ return parent.isValid() ? 0 : 1;
}
-int Technic::ListModel::rowCount(const QModelIndex&) const
+int Technic::ListModel::rowCount(const QModelIndex& parent) const
{
- return modpacks.size();
+ return parent.isValid() ? 0 : modpacks.size();
}
void Technic::ListModel::searchWithTerm(const QString& term)